单元1 · JPA 快速入门
JPA 规范、Hibernate 实现、实体与持久化
- JPA:Java 持久化规范,Hibernate 是其主流实现。
- 配置:spring.datasource + spring.jpa 自动装配。
- EntityManager:核心 API,persist/find/remove 管理实体生命周期。
实训1.1 引入 JPA 依赖
在 Spring Boot 项目引入 spring-boot-starter-data-jpa 依赖。
spring-boot-starter-data-jpa 默认集成 Hibernate 作为 JPA 实现。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
实训1.2 JPA 配置
在 application.yml 配置 JPA 的数据库方言、建表策略与 SQL 显示。
ddl-auto 常用 update/none/create;show-sql 打印 SQL 便于调试。
spring:
datasource:
url: jdbc:mysql://localhost:3306/helpme
username: root
password: 123456
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
实训1.3 EntityManager 基础操作
使用 EntityManager 完成实体的 persist、find、remove 操作。
EntityManager 是 JPA 核心 API,persist 持久化、find 查询、remove 删除。
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
@PersistenceContext
private EntityManager em;
public void save(User user) {
em.persist(user);
}
public User find(Long id) {
return em.find(User.class, id);
}
public void delete(Long id) {
User user = em.find(User.class, id);
if (user != null) {
em.remove(user);
}
}
}
单元2 · 实体映射
@Entity、@Table、@Column、主键生成策略
- 实体映射:@Entity/@Table/@Column 映射表与列。
- 主键:@Id + @GeneratedValue,IDENTITY/SEQUENCE 策略。
- 字段细节:unique、precision、@Transient 控制列属性。
实训2.1 实体与表映射
定义 User 实体映射 user 表,配置主键自增。
@Entity 标注实体;@Table(name) 指定表名;@Id + @GeneratedValue 配置主键。
import jakarta.persistence.*;
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name", nullable = false, length = 50)
private String name;
private Integer age;
// getter / setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
实训2.2 主键生成策略
对比 IDENTITY 与 SEQUENCE 主键策略的适用场景。
IDENTITY 依赖数据库自增(MySQL);SEQUENCE 依赖序列(Oracle/PostgreSQL)。
import jakarta.persistence.*;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "seq", sequenceName = "product_seq", allocationSize = 1)
private Long seqNo;
private String name;
}
实训2.3 字段映射细节
使用 @Column 配置唯一约束、精度与可空性,并说明 @Transient。
@Column unique 唯一约束、precision/scale 精确小数;@Transient 忽略非表字段。
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, length = 32)
private String orderNo;
@Column(precision = 10, scale = 2)
private BigDecimal amount;
@Transient
private String tempField;
}
单元3 · 关联映射
一对多、多对一、多对多、级联
- 多对一:@ManyToOne + @JoinColumn 定义外键。
- 一对多:@OneToMany(mappedBy) 维护反向关系。
- 多对多:@ManyToMany + @JoinTable 中间表。
- 级联:CascadeType.ALL/REMOVE,orphanRemoval 孤儿删除。
实训3.1 多对一与一对多
实现 Order 多对一 User、User 一对多 Order 的双向映射。
@ManyToOne + @JoinColumn 定义外键;@OneToMany(mappedBy) 维护反向关系。
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();
}
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderNo;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
实训3.2 多对多映射
实现 Student 与 Course 的多对多映射,通过中间表关联。
@ManyToMany + @JoinTable 配置中间表;双向用 mappedBy。
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private Set<Course> courses = new HashSet<>();
}
实训3.3 级联操作
配置级联保存并说明 cascade 的常用取值。
CascadeType.ALL/PERSIST/MERGE/REMOVE 控制关联实体生命周期操作联动。
import jakarta.persistence.*;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<Order> orders;
// orphanRemoval=true 移除集合元素时同步删除子记录
}
单元4 · Repository 接口
JpaRepository、CrudRepository、PagingAndSortingRepository
- JpaRepository:继承即得完整 CRUD 能力。
- 常用方法:save/findAll/deleteById/existsById/count。
实训4.1 JpaRepository 继承
创建继承 JpaRepository 的 UserRepository 接口。
JpaRepository 组合 CrudRepository 与 PagingAndSortingRepository,提供完整 CRUD。
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
实训4.2 通用方法使用
在 Service 中使用 Repository 的 save、findAll、deleteById。
save 既新增也更新;findAll 返回全部;deleteById 按主键删除。
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User save(User user) {
return userRepository.save(user);
}
public List<User> findAll() {
return userRepository.findAll();
}
public void delete(Long id) {
userRepository.deleteById(id);
}
}
实训4.3 exists 与 count
使用 existsById 判断记录是否存在,count 统计数量。
existsById 返回 boolean 优化查询;count 返回记录总数。
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public boolean exists(Long id) {
return userRepository.existsById(id);
}
public long count() {
return userRepository.count();
}
}
单元5 · 派生查询
方法名解析、关键字组合
- 派生查询:方法名解析自动生成 JPQL。
- 关键字:And/Or/GreaterThan/Like/Containing/OrderBy/Top。
实训5.1 等值查询方法
在 Repository 中声明按姓名、按邮箱查询的方法。
findByXxx 自动解析;And/Or 组合多个条件。
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
User findByEmail(String email);
List<User> findByNameAndAge(String name, int age);
List<User> findByNameOrEmail(String name, String email);
}
实训5.2 比较与模糊查询
声明大于、小于、Between、Like 查询方法。
GreaterThan/LessThan/Between/Like 关键字对应 SQL 比较与模糊条件。
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByAgeGreaterThan(int age);
List<User> findByAgeBetween(int min, int max);
List<User> findByNameContaining(String keyword);
List<User> findByNameStartingWith(String prefix);
}
实训5.3 排序与数量限制
声明按年龄降序、取前 3 条的查询方法。
OrderBy 后缀控制排序;Top/First 限制返回条数。
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByAgeGreaterThanOrderByAgeDesc(int age);
List<User> findTop3ByOrderByAgeDesc();
User findFirstByOrderByAgeAsc();
}
单元6 · JPQL 与原生 SQL
@Query、命名参数、分页查询
- @Query:JPQL 面向实体编写查询。
- @Modifying:更新/删除语句需标注并加事务。
- nativeQuery:直接使用数据库 SQL。
实训6.1 @Query JPQL 查询
使用 JPQL 按姓名模糊查询并忽略大小写。
@Query 编写面向实体的 JPQL;LOWER 函数忽略大小写。
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE LOWER(u.name) LIKE LOWER(CONCAT('%', :kw, '%'))")
List<User> searchByName(@Param("kw") String keyword);
}
实训6.2 @Query 更新操作
使用 @Query + @Modifying 实现批量更新年龄。
@Modifying 标记更新/删除语句,需配合事务使用。
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Transactional
@Query("UPDATE User u SET u.age = u.age + :delta WHERE u.id = :id")
int increaseAge(@Param("id") Long id, @Param("delta") int delta);
}
实训6.3 原生 SQL 查询
使用 nativeQuery 执行数据库特定 SQL 查询。
nativeQuery = true 直接写数据库 SQL,返回 Object[] 或 DTO。
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "SELECT name, COUNT(*) FROM user GROUP BY name HAVING COUNT(*) > :min",
nativeQuery = true)
List<Object[]> countByName(@Param("min") long min);
}
单元7 · 分页与排序
Pageable、Page、Sort
- Pageable:PageRequest.of 构建分页参数。
- Page:返回分页结果含 total/records。
- Sort:Sort.by(DESC, 属性) 组合排序。
实训7.1 分页查询
使用 Pageable 实现用户分页查询。
PageRequest.of(current, size) 构建分页参数,返回 Page 对象。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Page<User> pageUsers(int page, int size) {
return userRepository.findAll(PageRequest.of(page, size));
}
}
实训7.2 分页 + 排序
按年龄降序分页查询用户。
Sort.by(DESC, "age") 与 PageRequest 组合;注意多条件用 then。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
public Page<User> pageUsersSorted(int page, int size) {
Sort sort = Sort.by(Sort.Direction.DESC, "age");
return userRepository.findAll(PageRequest.of(page, size, sort));
}
实训7.3 JPQL 分页
为自定义 @Query 查询添加分页支持。
@Query 方法接收 Pageable 参数,Spring Data 自动追加分页 SQL。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.age > :minAge")
Page<User> findOlderThan(@Param("minAge") int minAge, Pageable pageable);
}
单元8 · 事务管理
@Transactional、传播、只读事务
- 声明式事务:@Transactional 管理事务边界。
- 只读事务:readOnly = true 优化查询路径。
- 传播行为:REQUIRED 复用、REQUIRES_NEW 新建。
实训8.1 声明式事务
为转账方法添加事务,确保原子性。
@Transactional 由 Spring 管理事务边界,异常自动回滚。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
private final AccountRepository accountRepository;
public AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Transactional
public void transfer(Long fromId, Long toId, double amount) {
Account from = accountRepository.findById(fromId).orElseThrow();
Account to = accountRepository.findById(toId).orElseThrow();
from.setBalance(from.getBalance() - amount);
to.setBalance(to.getBalance() + amount);
accountRepository.save(from);
accountRepository.save(to);
}
}
实训8.2 只读事务优化
为查询方法配置只读事务提升性能。
readOnly = true 提示数据库与 JPA 优化查询路径。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional(readOnly = true)
public List<User> findAll() {
return userRepository.findAll();
}
}
实训8.3 传播行为
解释 REQUIRED 与 REQUIRES_NEW 在嵌套事务中的应用。
REQUIRED 复用外层事务;REQUIRES_NEW 挂起外层开启新事务,互不影响。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
private final AuditLogRepository auditLogRepository;
public OrderService(AuditLogRepository auditLogRepository) {
this.auditLogRepository = auditLogRepository;
}
@Transactional
public void createOrder() {
// 主业务
saveAudit("订单创建");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAudit(String message) {
auditLogRepository.save(new AuditLog(message));
}
}
单元9 · 审计与乐观锁
@CreatedDate、@Version
- 审计:@EnableJpaAuditing + @CreatedDate 自动填充。
- 乐观锁:@Version 防止并发更新丢失。
实训9.1 自动审计字段
为实体配置创建时间、更新时间自动填充。
@EnableJpaAuditing + @CreatedDate/@LastModifiedDate 自动填充审计字段。
import jakarta.persistence.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
实训9.2 乐观锁
为实体添加 @Version 版本字段防止并发更新丢失。
@Version 乐观锁:更新时校验版本号,冲突抛 OptimisticLockException。
import jakarta.persistence.*;
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer stock;
@Version
private Long version;
}
实训9.3 开启 JPA 审计
在启动类或配置类开启审计功能。
@EnableJpaAuditing 开启审计,配合 AuditingEntityListener 生效。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication
@EnableJpaAuditing
public class HelpmeApplication {
public static void main(String[] args) {
SpringApplication.run(HelpmeApplication.class, args);
}
}
单元10 · 复杂查询与 Specification
Specification、Criteria API
- Specification:Criteria API 构建动态条件。
- JpaSpecificationExecutor:接口提供 findAll(Specification)。
- 聚合:CriteriaQuery groupBy + count。
实训10.1 Specification 动态查询
使用 Specification 实现动态条件查询(姓名 + 年龄范围)。
Specification 的 toPredicate 通过 CriteriaBuilder 构建条件。
import org.springframework.data.jpa.domain.Specification;
import jakarta.persistence.criteria.*;
public class UserSpecs {
public static Specification<User> byNameAndAge(String name, Integer minAge) {
return (root, query, cb) -> {
Predicate p = cb.conjunction();
if (name != null && !name.isEmpty()) {
p = cb.and(p, cb.like(root.get("name"), "%" + name + "%"));
}
if (minAge != null) {
p = cb.and(p, cb.greaterThanOrEqualTo(root.get("age"), minAge));
}
return p;
};
}
}
实训10.2 Specification 组合使用
在 Service 中用 Specification 执行动态查询。
JpaSpecificationExecutor 的 findAll(Specification) 执行条件查询。
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {
}
实训10.3 Criteria 聚合查询
使用 CriteriaBuilder 实现按部门统计人数。
CriteriaQuery 分组聚合,groupBy + count 构建统计查询。
import jakarta.persistence.*;
import jakarta.persistence.criteria.*;
import java.util.List;
@Repository
public class StatsRepository {
@PersistenceContext
private EntityManager em;
public List<Object[]> countByDepartment() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
Root<User> root = query.from(User.class);
query.multiselect(root.get("department"), cb.count(root))
.groupBy(root.get("department"));
return em.createQuery(query).getResultList();
}
}
单元11 · 多数据源与性能
多数据源配置、N+1 问题、批量操作
- N+1:懒加载循环访问导致;@EntityGraph 预抓取。
- 批量:batch_size + order_inserts 优化写入。
- 多数据源:手动配置 DataSource 与 EntityManagerFactory。
实训11.1 N+1 查询问题
说明关联查询中的 N+1 问题并给出解决方案。
循环访问懒加载关联触发 N+1;用 JOIN FETCH 或 @EntityGraph 一次查完。
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = {"orders"})
@Override
java.util.List<User> findAll();
}
实训11.2 批量插入优化
配置 JPA 批量插入参数提升性能。
hibernate.jdbc.batch_size 与 order_inserts 开启批量写入。
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 50
order_inserts: true
order_updates: true
实训11.3 多数据源配置
简述 Spring Boot + JPA 多数据源的核心配置思路。
多 DataSource 时需手动定义两个 EntityManagerFactory 与事务管理器。
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
单元12 · 综合项目实训
综合运用所学知识完成项目
- 综合应用:实体设计 + 关联 + 分页 + 事务组合。
实训12.1 博客系统 - 实体设计
设计 Article(文章)与 Comment(评论)实体,一对多关联。
综合运用实体映射与级联配置,构建核心数据模型。
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
@OneToMany(mappedBy = "article", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
@CreatedDate
private LocalDateTime createdAt;
public void addComment(Comment comment) {
comments.add(comment);
comment.setArticle(this);
}
}
实训12.2 博客系统 - 分页检索
实现文章按标题模糊分页查询。
Pageable + 派生查询/JPQL 组合实现检索。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ArticleRepository extends JpaRepository<Article, Long> {
Page<Article> findByTitleContaining(String keyword, PageRequest pageRequest);
}
实训12.3 博客系统 - 事务发布
实现发布文章事务方法:保存文章并初始化统计记录。
@Transactional 保证文章与统计表写入原子性。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ArticleService {
private final ArticleRepository articleRepository;
private final ArticleStatsRepository statsRepository;
public ArticleService(ArticleRepository articleRepository,
ArticleStatsRepository statsRepository) {
this.articleRepository = articleRepository;
this.statsRepository = statsRepository;
}
@Transactional
public Article publish(Article article) {
Article saved = articleRepository.save(article);
statsRepository.createFor(saved.getId());
return saved;
}
}