用过 MyBatis 再用 JPA,可能会觉得 JPA 很别扭------SQL 都不用写了,框架自动搞定。
但用久了会发现,JPA 写起来其实很爽,尤其单表操作,几乎不需要写 SQL。
基础配置
依赖
xml
<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>
配置
yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useSSL=false
username: root
password: 123456
jpa:
hibernate:
ddl-auto: update # 开发用 update,生产用 validate
show-sql: true # 打印 SQL
properties:
hibernate:
format_sql: true
ddl-auto 几个选项:
update:自动更新表结构(不会删数据)create:每次启动删表重建validate:只验证,不改表none:什么都不做
实体映射
基本映射
java
@Entity
@Table(name = "sys_user")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_name", length = 50, nullable = false)
private String userName;
private Integer age;
@Column(columnDefinition = "varchar(20) default 'ACTIVE'")
private String status;
@Column(name = "create_time")
private LocalDateTime createTime;
@Transient // 不映射到数据库
private String tempField;
}
主键生成策略
java
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // MySQL 自增
// 或
@GeneratedValue(strategy = GenerationType.SEQUENCE) // Oracle、序列
// 或
@GeneratedValue(strategy = GenerationType.UUID) // UUID
private String id;
Repository 基础 CRUD
继承 JpaRepository 就有基本的增删改查:
java
public interface UserRepository extends JpaRepository<User, Long> {
// 继承来的方法:
// save(entity) - 保存或更新
// findById(id) - 查询
// deleteById(id) - 删除
// count() - 计数
// existsById(id) - 是否存在
}
java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public User createUser(String name, Integer age) {
User user = new User();
user.setUserName(name);
user.setAge(age);
return userRepository.save(user); // 自动 insert
}
public Optional<User> getUser(Long id) {
return userRepository.findById(id); // 自动 select
}
public void deleteUser(Long id) {
userRepository.deleteById(id); // 自动 delete
}
public User updateUser(Long id, String newName) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
user.setUserName(newName);
return userRepository.save(user); // 自动 update
}
}
方法名查询(单表神器)
这是 JPA 最爽的地方------不用写 SQL,方法名就是查询。
基础规则
java
public interface UserRepository extends JpaRepository<User, Long> {
// 等值查询:findBy + 字段名
List<User> findByUserName(String userName);
// → SELECT * FROM sys_user WHERE user_name = ?
// 模糊查询:findBy + 字段名 + Like
List<User> findByUserNameLike(String userName);
// → SELECT * FROM sys_user WHERE user_name LIKE ?
// 多条件:findBy + 字段1 + And + 字段2
List<User> findByUserNameAndAge(String userName, Integer age);
// → SELECT * FROM sys_user WHERE user_name = ? AND age = ?
// Or 条件
List<User> findByUserNameOrAge(String userName, Integer age);
// → SELECT * FROM sys_user WHERE user_name = ? OR age = ?
}
常用关键词
java
// 大于/小于/等于
List<User> findByAgeGreaterThan(Integer age); // age > ?
List<User> findByAgeLessThan(Integer age); // age < ?
List<User> findByAgeGreaterThanEqual(Integer age); // age >= ?
List<User> findByStatusEquals(String status); // status = ?
// BETWEEN 范围
List<User> findByAgeBetween(Integer min, Integer max);
// → WHERE age BETWEEN ? AND ?
// IN 查询
List<User> findByUserNameIn(List<String> names);
// → WHERE user_name IN (?, ?, ?)
// NULL 判断
List<User> findByEmailIsNull();
List<User> findByEmailIsNotNull();
// True/False
List<User> findByActiveTrue(); // WHERE active = true
List<User> findByActiveFalse(); // WHERE active = false
// 排序
List<User> findByStatusOrderByAgeDesc(String status);
// → WHERE status = ? ORDER BY age DESC
分页和排序
java
// 分页:参数加 Pageable
Page<User> findByStatus(String status, Pageable pageable);
// 排序:Sort
List<User> findByStatus(String status, Sort sort);
// 使用
@Service
public class UserService {
public Page<User> getUserPage(int page, int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("age").descending());
return userRepository.findByStatus("ACTIVE", pageable);
}
}
@Query 自定义查询
方法名解决不了的,用 @Query。
JPQL 查询
java
@Query("SELECT u FROM User u WHERE u.userName = ?1")
User findByName(String userName);
// 占位符 ?1、?2 按参数顺序
@Query("SELECT u FROM User u WHERE u.userName = ?1 AND u.age > ?2")
List<User> findByNameAndAge(String name, Integer age);
// 命名参数(更清晰)
@Query("SELECT u FROM User u WHERE u.userName = :name AND u.age > :age")
List<User> findByNameAndAgeV2(@Param("name") String name, @Param("age") Integer age);
多表联查
假设 User 关联 Department:
java
@Entity
public class User {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_id")
private Department department;
}
// 查询用户并带出部门名称
@Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")
User findByIdWithDepartment(@Param("id") Long id);
原生 SQL
java
@Query(value = "SELECT * FROM sys_user WHERE user_name = :name", nativeQuery = true)
User findByNameNative(@Param("name") String name);
// 原生 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") String status, Pageable pageable);
@Modifying 修改操作
java
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") String status);
@Modifying
@Query("DELETE FROM User u WHERE u.status = 'INACTIVE'")
void deleteInactiveUsers();
记得在 Service 层加事务:
java
@Transactional
public void updateStatus(Long id, String status) {
userRepository.updateStatus(id, status);
}
一对多 / 多对一
实体定义
java
@Entity
@Table(name = "department")
@Data
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<User> users;
}
@Entity
@Table(name = "sys_user")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String userName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_id")
private Department department;
}
查询
java
// 根据部门查用户
List<User> findByDepartmentId(Long deptId);
// 根据用户查部门(直接访问属性就行)
User user = userRepository.findById(id).orElseThrow(...);
String deptName = user.getDepartment().getName(); // 懒加载,会再查一次
// 一次性查出来(解决 N+1)
@Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")
User findByIdWithDept(@Param("id") Long id);
常见问题
1. 懒加载异常
java
// 报错:LazyInitializationException
User user = userRepository.findById(1L).orElseThrow(...);
String deptName = user.getDepartment().getName(); // session 已关闭
解决 :在 Service 层加 @Transactional,或者用 JOIN FETCH 一次性加载。
2. N+1 问题
java
// 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:
java
@EntityGraph(attributePaths = {"department"})
List<User> findAllWithDept();
// 或
@Query("SELECT u FROM User u JOIN FETCH u.department")
List<User> findAllWithDept();
3. save 和 update 的区别
java
// save() - 如果 id 已存在就是 update,不存在就是 insert
User user = new User();
user.setId(1L); // id 存在,变成 update
user.setUserName("newName");
userRepository.save(user); // UPDATE
总结
| 方式 | 适用场景 |
|---|---|
继承 JpaRepository |
基本 CRUD |
| 方法名查询 | 单表简单查询 |
@Query + JPQL |
多表关联、复杂查询 |
@Query + 原生 SQL |
特定数据库语法 |
JOIN FETCH |
解决懒加载和 N+1 |
JPA 最大的好处是不用写 SQL,单表操作特别爽。但多表关联和复杂查询,还是得用 @Query。用久了会发现,JPA + @Query 配合起来,效率比 MyBatis 高多了。