spring-data-jpa 是做什么的?
提供使用 Spring Data JPA 实现持久化层的模式。适用于创建仓库、配置实体关系、编写查询(派生查询和 @Query 注解)、设置分页、数据库审计、事务、UUID 主键、多数据库以及数据库索引。
快速开始
1. 添加 Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
2. 第一个实体
package com.example.demo.entity;
import javax.persistence.*;
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
// 构造函数、getter 和 setter
}
3. 第一个仓库
package com.example.demo.repository;
import com.example.demo.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
public interface StudentRepository extends JpaRepository<Student, Long> {
}
现在,您可以直接在服务类中注入并使用 StudentRepository。
仓库方法
Spring Data JPA 会自动提供以下方法,无需编写任何实现:
save(S entity):保存实体findById(ID id):按主键查找findAll():查找所有实体findAllById(Iterable<ID> ids):按多个主键查找count():返回实体数量deleteById(ID id):按主键删除delete(S entity):删除实体existsById(ID id):检查实体是否存在
自定义查询
派生查询
根据方法名称自动生成查询:
public interface StudentRepository extends JpaRepository<Student, Long> {
List<Student> findByName(String name);
Optional<Student> findByEmail(String email);
List<Student> findByNameContainingIgnoreCase(String namePart);
List<Student> findByAgeBetween(int start, int end);
List<Student> findByDepartmentName(String departmentName);
}
使用 @Query 注解
public interface StudentRepository extends JpaRepository<Student, Long> {
@Query("SELECT s FROM Student s WHERE s.email = ?1")
Optional<Student> findByEmail(String email);
@Query("SELECT s FROM Student s WHERE s.name LIKE %:name%")
List<Student> findByNameContaining(@Param("name") String name);
@Query(value = "SELECT * FROM students WHERE year = :year", nativeQuery = true)
List<Student> findByYear(@Param("year") int year);
}
分页和排序
public interface StudentRepository extends JpaRepository<Student, Long> {
Page<Student> findAll(Pageable pageable);
List<Student> findByNameContaining(String name, Sort sort);
@Query("SELECT s FROM Student s WHERE s.age > :age")
Page<Student> findByAgeGreaterThan(@Param("age") int age, Pageable pageable);
}
使用示例:
Pageable firstPageWithThreeElements = PageRequest.of(0, 3, Sort.by("name").ascending());
Page<Student> studentPage = studentRepository.findAll(firstPageWithThreeElements);
List<Student> students = studentPage.getContent();
int totalPages = studentPage.getTotalPages();
long totalElements = studentPage.getTotalElements();
实体关系
一对一
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Profile profile;
}
@Entity
public class Profile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "user_id")
private User user;
private String bio;
}
一对多
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees = new ArrayList<>();
}
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
}
多对多
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany
@JoinTable(
name = "enrollments",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}
数据库审计
通过 Spring Data JPA 自动填充 createdDate 和 lastModifiedDate。
1. 启用审计
@SpringBootApplication
@EnableJpaAuditing
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2. 使用注解
@Entity
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
}
3. 实现 AuditorAware
@Component
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
// 从 SecurityContext 获取当前用户
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(ctx -> ctx.getAuthentication())
.map(auth -> auth.getName());
}
}
事务管理
@Service
@Transactional
public class StudentService {
@Autowired
private StudentRepository studentRepository;
@Autowired
private EmailService emailService;
public void enrollStudent(Student student) {
studentRepository.save(student);
emailService.sendEnrollmentConfirmation(student.getEmail());
}
@Transactional(readOnly = true)
public List<Student> getAllActiveStudents() {
return studentRepository.findByActiveTrue();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateAuditLog(String action) {
// 此方法在独立事务中执行
}
}
UUID 主键
@Entity
public class Product {
@Id
@GeneratedValue(generator = "UUID")
@GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
@Column(updatable = false, nullable = false)
private UUID id;
private String name;
}
多数据库配置
1. application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/primarydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
datasource:
url: jdbc:postgresql://localhost:5432/secondarydb
username: postgres
password: password
driver-class-name: org.postgresql.Driver
2. 配置类
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
basePackages = "com.example.demo.primary.repository",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
public class PrimaryDataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "primaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("primaryDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.example.demo.primary.entity")
.persistenceUnit("primary")
.build();
}
@Primary
@Bean(name = "primaryTransactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
第二数据源的配置类似,只需将 primary 替换为 secondary 并移除 @Primary 注解。
数据库索引
@Entity
@Table(name = "employees", indexes = {
@Index(name = "idx_employee_email", columnList = "email", unique = true),
@Index(name = "idx_employee_department_id", columnList = "department_id"),
@Index(name = "idx_employee_name_email", columnList = "name, email")
})
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
}
最佳实践
- 懒加载:始终在
@ManyToOne和@OneToOne关联中使用FetchType.LAZY以避免 N+1 问题。 - 分页:对于返回大量数据的查询,始终使用分页。
- 事务:在服务层使用
@Transactional管理事务边界。 - 索引:为频繁查询的列添加数据库索引以提高性能。
- 只读查询:为只读查询设置
@Transactional(readOnly = true)以优化性能。 - 避免复杂关联:将大型实体分解为更小、更集中的实体。
此技能由 giuseppe-trisciuoglio 创建。