用过 MyBatis 再用 JPA,可能会觉得 JPA 很别扭——SQL 都不用写了,框架自动搞定。
但用久了会发现,JPA 写起来其实很爽,尤其单表操作,几乎不需要写 SQL。
基础配置
依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency>配置
spring:datasource:url:jdbc:mysql://localhost:3306/test?useSSL=falseusername:rootpassword:123456jpa:hibernate:ddl-auto:update# 开发用 update,生产用 validateshow-sql:true# 打印 SQLproperties:hibernate:format_sql:trueddl-auto几个选项:
update:自动更新表结构(不会删数据)create:每次启动删表重建validate:只验证,不改表none:什么都不做
实体映射
基本映射
@Entity@Table(name="sys_user")@DatapublicclassUser{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;@Column(name="user_name",length=50,nullable=false)privateStringuserName;privateIntegerage;@Column(columnDefinition="varchar(20) default 'ACTIVE'")privateStringstatus;@Column(name="create_time")privateLocalDateTimecreateTime;@Transient// 不映射到数据库privateStringtempField;}主键生成策略
@Id@GeneratedValue(strategy=GenerationType.IDENTITY)// MySQL 自增// 或@GeneratedValue(strategy=GenerationType.SEQUENCE)// Oracle、序列// 或@GeneratedValue(strategy=GenerationType.UUID)// UUIDprivateStringid;Repository 基础 CRUD
继承JpaRepository就有基本的增删改查:
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{// 继承来的方法:// save(entity) - 保存或更新// findById(id) - 查询// deleteById(id) - 删除// count() - 计数// existsById(id) - 是否存在}@Service@RequiredArgsConstructorpublicclassUserService{privatefinalUserRepositoryuserRepository;publicUsercreateUser(Stringname,Integerage){Useruser=newUser();user.setUserName(name);user.setAge(age);returnuserRepository.save(user);// 自动 insert}publicOptional<User>getUser(Longid){returnuserRepository.findById(id);// 自动 select}publicvoiddeleteUser(Longid){userRepository.deleteById(id);// 自动 delete}publicUserupdateUser(Longid,StringnewName){Useruser=userRepository.findById(id).orElseThrow(()->newRuntimeException("用户不存在"));user.setUserName(newName);returnuserRepository.save(user);// 自动 update}}方法名查询(单表神器)
这是 JPA 最爽的地方——不用写 SQL,方法名就是查询。
基础规则
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{// 等值查询:findBy + 字段名List<User>findByUserName(StringuserName);// → SELECT * FROM sys_user WHERE user_name = ?// 模糊查询:findBy + 字段名 + LikeList<User>findByUserNameLike(StringuserName);// → SELECT * FROM sys_user WHERE user_name LIKE ?// 多条件:findBy + 字段1 + And + 字段2List<User>findByUserNameAndAge(StringuserName,Integerage);// → SELECT * FROM sys_user WHERE user_name = ? AND age = ?// Or 条件List<User>findByUserNameOrAge(StringuserName,Integerage);// → SELECT * FROM sys_user WHERE user_name = ? OR age = ?}常用关键词
// 大于/小于/等于List<User>findByAgeGreaterThan(Integerage);// age > ?List<User>findByAgeLessThan(Integerage);// age < ?List<User>findByAgeGreaterThanEqual(Integerage);// age >= ?List<User>findByStatusEquals(Stringstatus);// status = ?// BETWEEN 范围List<User>findByAgeBetween(Integermin,Integermax);// → WHERE age BETWEEN ? AND ?// IN 查询List<User>findByUserNameIn(List<String>names);// → WHERE user_name IN (?, ?, ?)// NULL 判断List<User>findByEmailIsNull();List<User>findByEmailIsNotNull();// True/FalseList<User>findByActiveTrue();// WHERE active = trueList<User>findByActiveFalse();// WHERE active = false// 排序List<User>findByStatusOrderByAgeDesc(Stringstatus);// → WHERE status = ? ORDER BY age DESC分页和排序
// 分页:参数加 PageablePage<User>findByStatus(Stringstatus,Pageablepageable);// 排序:SortList<User>findByStatus(Stringstatus,Sortsort);// 使用@ServicepublicclassUserService{publicPage<User>getUserPage(intpage,intsize){Pageablepageable=PageRequest.of(page,size,Sort.by("age").descending());returnuserRepository.findByStatus("ACTIVE",pageable);}}@Query 自定义查询
方法名解决不了的,用@Query。
JPQL 查询
@Query("SELECT u FROM User u WHERE u.userName = ?1")UserfindByName(StringuserName);// 占位符 ?1、?2 按参数顺序@Query("SELECT u FROM User u WHERE u.userName = ?1 AND u.age > ?2")List<User>findByNameAndAge(Stringname,Integerage);// 命名参数(更清晰)@Query("SELECT u FROM User u WHERE u.userName = :name AND u.age > :age")List<User>findByNameAndAgeV2(@Param("name")Stringname,@Param("age")Integerage);多表联查
假设 User 关联 Department:
@EntitypublicclassUser{@ManyToOne(fetch=FetchType.LAZY)@JoinColumn(name="dept_id")privateDepartmentdepartment;}// 查询用户并带出部门名称@Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")UserfindByIdWithDepartment(@Param("id")Longid);原生 SQL
@Query(value="SELECT * FROM sys_user WHERE user_name = :name",nativeQuery=true)UserfindByNameNative(@Param("name")Stringname);// 原生 SQL 分页(注意 countQuery)@Query(value="SELECT * FROM sys_user WHERE status = :status ORDER BY age DESC",countQuery="SELECT count(*) FROM sys_user WHERE status = :status",nativeQuery=true)Page<User>findByStatusPage(@Param("status")Stringstatus,Pageablepageable);@Modifying 修改操作
@Modifying@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")intupdateStatus(@Param("id")Longid,@Param("status")Stringstatus);@Modifying@Query("DELETE FROM User u WHERE u.status = 'INACTIVE'")voiddeleteInactiveUsers();记得在 Service 层加事务:
@TransactionalpublicvoidupdateStatus(Longid,Stringstatus){userRepository.updateStatus(id,status);}一对多 / 多对一
实体定义
@Entity@Table(name="department")@DatapublicclassDepartment{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;privateStringname;@OneToMany(mappedBy="department",cascade=CascadeType.ALL,fetch=FetchType.LAZY)privateList<User>users;}@Entity@Table(name="sys_user")@DatapublicclassUser{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;privateStringuserName;@ManyToOne(fetch=FetchType.LAZY)@JoinColumn(name="dept_id")privateDepartmentdepartment;}查询
// 根据部门查用户List<User>findByDepartmentId(LongdeptId);// 根据用户查部门(直接访问属性就行)Useruser=userRepository.findById(id).orElseThrow(...);StringdeptName=user.getDepartment().getName();// 懒加载,会再查一次// 一次性查出来(解决 N+1)@Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")UserfindByIdWithDept(@Param("id")Longid);常见问题
1. 懒加载异常
// 报错:LazyInitializationExceptionUseruser=userRepository.findById(1L).orElseThrow(...);StringdeptName=user.getDepartment().getName();// session 已关闭解决:在 Service 层加@Transactional,或者用JOIN FETCH一次性加载。
2. N+1 问题
// N+1:查 1 个用户,再查 N 次部门List<User>users=userRepository.findAll();// SELECT * FROM sys_user// SELECT * FROM department WHERE id = ?// SELECT * FROM department WHERE id = ?// ...解决:用@EntityGraph或JOIN FETCH:
@EntityGraph(attributePaths={"department"})List<User>findAllWithDept();// 或@Query("SELECT u FROM User u JOIN FETCH u.department")List<User>findAllWithDept();3. save 和 update 的区别
// save() - 如果 id 已存在就是 update,不存在就是 insertUseruser=newUser();user.setId(1L);// id 存在,变成 updateuser.setUserName("newName");userRepository.save(user);// UPDATE总结
| 方式 | 适用场景 |
|---|---|
继承JpaRepository | 基本 CRUD |
| 方法名查询 | 单表简单查询 |
@Query+ JPQL | 多表关联、复杂查询 |
@Query+ 原生 SQL | 特定数据库语法 |
JOIN FETCH | 解决懒加载和 N+1 |
JPA 最大的好处是不用写 SQL,单表操作特别爽。但多表关联和复杂查询,还是得用@Query。用久了会发现,JPA +@Query配合起来,效率比 MyBatis 高多了。