🗄️ 第9章 SpringBoot 数据库集成

掌握SpringBoot数据库操作 - 从JPA到多数据源配置

学习进度:9/18 章节 (50%)

💻 查看完整代码 - 在线IDE体验

🎯 本章学习目标

  • 掌握SpringBoot数据库集成方式
  • 学会使用Spring Data JPA
  • 理解实体映射和关系配置
  • 掌握Repository接口使用
  • 学会事务管理和查询优化

⏰ 预计学习时间

3小时(理论学习 + 数据库操作实践)

🏗️ SpringBoot数据库支持

核心特性:

  • 自动配置:零配置启动
  • 多数据源:支持多个数据库
  • 连接池:HikariCP高性能连接池
  • 事务管理:声明式事务支持

技术栈:

SpringBoot + Spring Data JPA + Hibernate + HikariCP ↓ ↓ ↓ ↓ 框架核心 数据访问层 ORM框架 连接池

💡 开箱即用

SpringBoot提供了数据库操作的完整解决方案,只需添加依赖即可使用

🗃️ 支持的数据库类型

SpringBoot支持多种关系型和非关系型数据库

MySQL
特点:开源、高性能、易用
适用场景:Web应用、中小型项目
驱动:mysql-connector-java
# application.yml spring: datasource: url: jdbc:mysql://localhost:3306/mydb username: root password: password driver-class-name: com.mysql.cj.jdbc.Driver
PostgreSQL
特点:功能强大、标准兼容
适用场景:企业级应用、复杂查询
驱动:postgresql
# application.yml spring: datasource: url: jdbc:postgresql://localhost:5432/mydb username: postgres password: password driver-class-name: org.postgresql.Driver
H2 Database
特点:内存数据库、轻量级
适用场景:测试、原型开发
驱动:h2
# application.yml spring: datasource: url: jdbc:h2:mem:testdb driver-class-name: org.h2.Driver h2: console: enabled: true
MongoDB
特点:文档数据库、NoSQL
适用场景:大数据、灵活模式
驱动:spring-boot-starter-data-mongodb
# application.yml spring: data: mongodb: uri: mongodb://localhost:27017/mydb

📚 Spring Data JPA核心概念

实体映射
使用注解将Java类映射到数据库表
Repository接口
提供CRUD操作的标准接口
查询方法
通过方法名自动生成查询语句
事务管理
声明式事务处理和回滚机制

🏗️ 实体类定义

基础实体类:

@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "username", unique = true, nullable = false, length = 50) private String username; @Column(name = "email", unique = true, nullable = false) private String email; @Column(name = "password", nullable = false) private String password; @Column(name = "age") private Integer age; @Column(name = "created_at") @CreationTimestamp private LocalDateTime createdAt; @Column(name = "updated_at") @UpdateTimestamp private LocalDateTime updatedAt; // 构造函数 public User() {} public User(String username, String email, String password, Integer age) { this.username = username; this.email = email; this.password = password; this.age = age; } // getter和setter方法 // toString、equals、hashCode方法 }

关系映射示例:

// 一对多关系:用户和订单 @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Order> orders = new ArrayList<>(); // getter和setter } @Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String orderNumber; private BigDecimal amount; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; // getter和setter }

🔧 Repository接口

基础Repository:

public interface UserRepository extends JpaRepository<User, Long> { // 根据用户名查找 Optional<User> findByUsername(String username); // 根据邮箱查找 Optional<User> findByEmail(String email); // 根据年龄范围查找 List<User> findByAgeBetween(int minAge, int maxAge); // 根据用户名模糊查询 List<User> findByUsernameContaining(String keyword); // 根据创建时间排序 List<User> findAllByOrderByCreatedAtDesc(); // 分页查询 Page<User> findByAgeGreaterThan(int age, Pageable pageable); // 统计查询 long countByAgeGreaterThan(int age); // 存在性检查 boolean existsByUsername(String username); // 删除操作 void deleteByUsername(String username); }

Repository层次结构:

  • Repository:标记接口
  • CrudRepository:基本CRUD操作
  • PagingAndSortingRepository:分页和排序
  • JpaRepository:JPA特定功能

🔍 自定义查询

@Query注解:

public interface UserRepository extends JpaRepository<User, Long> { // JPQL查询 @Query("SELECT u FROM User u WHERE u.age > :age") List<User> findUsersOlderThan(@Param("age") int age); // 原生SQL查询 @Query(value = "SELECT * FROM users WHERE email LIKE %:domain%", nativeQuery = true) List<User> findByEmailDomain(@Param("domain") String domain); // 更新查询 @Modifying @Query("UPDATE User u SET u.age = :age WHERE u.id = :id") int updateUserAge(@Param("id") Long id, @Param("age") int age); // 删除查询 @Modifying @Query("DELETE FROM User u WHERE u.age < :age") int deleteUsersYoungerThan(@Param("age") int age); // 投影查询 @Query("SELECT u.username, u.email FROM User u WHERE u.age > :age") List<UserProjection> findUserProjections(@Param("age") int age); } // 投影接口 public interface UserProjection { String getUsername(); String getEmail(); }

💡 查询方法命名规则

Spring Data JPA支持通过方法名自动生成查询,如findBy、countBy、deleteBy等

🔄 事务管理

声明式事务:

@Service @Transactional public class UserService { private final UserRepository userRepository; private final OrderRepository orderRepository; public UserService(UserRepository userRepository, OrderRepository orderRepository) { this.userRepository = userRepository; this.orderRepository = orderRepository; } // 只读事务 @Transactional(readOnly = true) public List<User> findAllUsers() { return userRepository.findAll(); } // 写事务 @Transactional public User createUser(User user) { return userRepository.save(user); } // 复杂事务操作 @Transactional(rollbackFor = Exception.class) public void createUserWithOrder(User user, Order order) { // 保存用户 User savedUser = userRepository.save(user); // 设置订单关联 order.setUser(savedUser); orderRepository.save(order); // 如果这里抛出异常,整个事务会回滚 if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) { throw new IllegalArgumentException("订单金额必须大于0"); } } // 事务传播行为 @Transactional(propagation = Propagation.REQUIRES_NEW) public void logUserAction(Long userId, String action) { // 这个方法会在新事务中执行 // 即使外层事务回滚,这个操作也会提交 } }

事务属性:

  • propagation:事务传播行为
  • isolation:事务隔离级别
  • timeout:事务超时时间
  • readOnly:只读事务
  • rollbackFor:回滚异常类型

⚙️ 数据库配置

基础配置:

# application.yml spring: datasource: url: jdbc:mysql://localhost:3306/springboot_demo username: root password: password driver-class-name: com.mysql.cj.jdbc.Driver # HikariCP连接池配置 hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 jpa: # Hibernate配置 hibernate: ddl-auto: update # create, create-drop, update, validate, none show-sql: true format-sql: true properties: hibernate: dialect: org.hibernate.dialect.MySQL8Dialect jdbc: batch_size: 20 order_inserts: true order_updates: true # 日志配置 logging: level: org.hibernate.SQL: DEBUG org.hibernate.type.descriptor.sql.BasicBinder: TRACE

多数据源配置:

@Configuration public class DatabaseConfig { @Primary @Bean @ConfigurationProperties("spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } @Primary @Bean public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("primaryDataSource") DataSource dataSource ) { return builder .dataSource(dataSource) .packages("com.example.primary.entity") .persistenceUnit("primary") .build(); } }

🚀 性能优化

查询优化:

  • 使用分页查询避免大量数据加载
  • 合理使用懒加载和急加载
  • 使用投影查询减少数据传输
  • 添加适当的数据库索引

批量操作:

@Service public class UserBatchService { @Autowired private EntityManager entityManager; @Transactional public void batchInsertUsers(List<User> users) { int batchSize = 20; for (int i = 0; i < users.size(); i++) { entityManager.persist(users.get(i)); if (i % batchSize == 0 && i > 0) { entityManager.flush(); entityManager.clear(); } } } }

⚠️ 性能注意事项

避免N+1查询问题,合理使用@EntityGraph或JOIN FETCH

🧪 测试支持

Repository测试:

@DataJpaTest class UserRepositoryTest { @Autowired private TestEntityManager entityManager; @Autowired private UserRepository userRepository; @Test void testFindByUsername() { // 准备测试数据 User user = new User("testuser", "test@example.com", "password", 25); entityManager.persistAndFlush(user); // 执行查询 Optional<User> found = userRepository.findByUsername("testuser"); // 验证结果 assertThat(found).isPresent(); assertThat(found.get().getEmail()).isEqualTo("test@example.com"); } @Test void testFindByAgeBetween() { // 准备多个测试用户 entityManager.persistAndFlush(new User("user1", "user1@example.com", "pass", 20)); entityManager.persistAndFlush(new User("user2", "user2@example.com", "pass", 30)); entityManager.persistAndFlush(new User("user3", "user3@example.com", "pass", 40)); // 执行范围查询 List<User> users = userRepository.findByAgeBetween(25, 35); // 验证结果 assertThat(users).hasSize(1); assertThat(users.get(0).getUsername()).isEqualTo("user2"); } }

💡 测试最佳实践

使用@DataJpaTest进行Repository层测试,它会自动配置内存数据库

🎉 恭喜完成第9章学习!

你已经掌握了SpringBoot数据库集成的核心技能,接下来让我们学习缓存机制。

📚 进入第10章:SpringBoot 打包 ⬅️ 返回第8章 🏠 返回课程首页