🎯 本章学习目标
- 掌握SpringBoot数据库集成方式
- 学会使用Spring Data JPA
- 理解实体映射和关系配置
- 掌握Repository接口使用
- 学会事务管理和查询优化
⏰ 预计学习时间
3小时(理论学习 + 数据库操作实践)
🏗️ SpringBoot数据库支持
核心特性:
- 自动配置:零配置启动
- 多数据源:支持多个数据库
- 连接池:HikariCP高性能连接池
- 事务管理:声明式事务支持
技术栈:
SpringBoot + Spring Data JPA + Hibernate + HikariCP
↓ ↓ ↓ ↓
框架核心 数据访问层 ORM框架 连接池
💡 开箱即用
SpringBoot提供了数据库操作的完整解决方案,只需添加依赖即可使用
🏗️ 实体类定义
基础实体类:
@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;
}
}
关系映射示例:
@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<>();
}
@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;
}
🔧 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> {
@Query("SELECT u FROM User u WHERE u.age > :age")
List<User> findUsersOlderThan(@Param("age") int age);
@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:回滚异常类型
⚙️ 数据库配置
基础配置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/springboot_demo
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: update
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层测试,它会自动配置内存数据库