4-1 JPA 实战
概念解析
JPA 注解分类
| 分类 | 注解 | 说明 |
|---|
| 实体 | @Entity | 标记为 JPA 实体 |
| 实体 | @Table | 指定表名 |
| 主键 | @Id | 主键字段 |
| 主键 | @GeneratedValue | 主键生成策略 |
| 列映射 | @Column | 列配置 |
| 关系 | @OneToOne | 一对一 |
| 关系 | @OneToMany | 一对多 |
| 关系 | @ManyToOne | 多对一 |
| 关系 | @ManyToMany | 多对多 |
主键生成策略
| 策略 | 说明 |
|---|
| AUTO | 数据库自增(默认) |
| IDENTITY | MySQL 自增 |
| SEQUENCE | Oracle 序列 |
| TABLE | 序列表 |
代码示例
1. 基础实体
@Entity
@Table(name = "t_user")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String username;
@Column(length = 100)
private String password;
@Column(unique = true, length = 100)
private String email;
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@Version
private Integer version;
@PrePersist
public void prePersist() {
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
@PreUpdate
public void preUpdate() {
this.updateTime = LocalDateTime.now();
}
}
2. Repository 接口
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
Optional<User> findByEmail(String email);
List<User> findByAgeGreaterThan(Integer age);
List<User> findByUsernameContaining(String keyword);
Page<User> findByStatus(Integer status, Pageable pageable);
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsernameQuery(@Param("username") String username);
@Query(value = "SELECT * FROM t_user WHERE age > :age", nativeQuery = true)
List<User> findByAgeNative(@Param("age") Integer age);
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") Integer status);
@Modifying
@Query("DELETE FROM User u WHERE u.id = :id")
void deleteByIdCustom(@Param("id") Long id);
}
3. 一对一关系
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserDetail detail;
}
@Entity
@Table(name = "t_user_detail")
public class UserDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String address;
private String phone;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
4. 一对多关系
@Entity
@Table(name = "t_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@OneToMany(mappedBy = "user",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
@Builder.Default
private List<Order> orders = new ArrayList<>();
}
@Entity
@Table(name = "t_order")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderNo;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
5. 多对多关系
@Entity
@Table(name = "t_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
@JoinTable(
name = "t_student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
@Builder.Default
private Set<Course> courses = new HashSet<>();
}
@Entity
@Table(name = "t_course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}
6. 动态查询(Specification)
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public Page<User> search(UserQuery query) {
Specification<User> spec = (root, cq, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (query.getUsername() != null) {
predicates.add(cb.like(root.get("username"),
"%" + query.getUsername() + "%"));
}
if (query.getStatus() != null) {
predicates.add(cb.equal(root.get("status"), query.getStatus()));
}
if (query.getStartTime() != null) {
predicates.add(cb.greaterThanOrEqualTo(
root.get("createTime"), query.getStartTime()));
}
if (query.getEndTime() != null) {
predicates.add(cb.lessThanOrEqualTo(
root.get("createTime"), query.getEndTime()));
}
cq.where(predicates.toArray(new Predicate[0]));
cq.orderBy(cb.desc(root.get("createTime")));
return cq.getRestriction();
};
return userRepository.findAll(spec,
PageRequest.of(query.getPage() - 1, query.getSize()));
}
}
7. EntityManager 原始操作
@Service
public class AdvancedUserService {
@PersistenceContext
private EntityManager entityManager;
public User findById(Long id) {
return entityManager.find(User.class, id);
}
public User getReference(Long id) {
return entityManager.getReference(User.class, id);
}
public User save(User user) {
if (user.getId() == null) {
entityManager.persist(user);
} else {
entityManager.merge(user);
}
return user;
}
public void delete(Long id) {
User user = entityManager.find(User.class, id);
entityManager.remove(user);
}
public List<User> findByJpql(String jpql, Map<String, Object> params) {
Query query = entityManager.createQuery(jpql);
params.forEach(query::setParameter);
return query.getResultList();
}
}
常见坑点
⚠️ 坑 1:懒加载异常
@Transactional
public UserDTO getUser(Long id) {
User user = userRepository.findById(id).orElseThrow();
return toDTO(user);
}
@Transactional
public UserDTO getUser(Long id) {
User user = userRepository.findById(id).orElseThrow();
user.getOrders().size();
return toDTO(user);
}
@EntityGraph(attributePaths = {"orders", "detail"})
Optional<User> findEntityGraphById(Long id);
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id")
Optional<User> findWithOrders(@Param("id") Long id);
⚠️ 坑 2:N+1 查询问题
List<User> users = userRepository.findAll();
users.forEach(u -> System.out.println(u.getOrders().size()));
@EntityGraph(attributePaths = {"orders"})
List<User> findAllWithOrders();
@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();
@Entity
public class User {
@OneToMany(fetch = FetchType.LAZY)
@BatchSize(size = 20)
private List<Order> orders;
}
⚠️ 坑 3:双向关系死循环
@Entity
public class User {
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
@Data
public class User {
@ToString.Exclude
@OneToMany(mappedBy = "user")
private List<Order> orders;
}
面试题
Q1:JPA 和 MyBatis 的区别?
参考答案:
| 维度 | JPA | MyBatis |
|---|
| 定位 | ORM 框架 | SQL 映射框架 |
| SQL | 自动生成 | 手写 |
| 灵活性 | 低 | 高 |
| 性能调优 | 困难 | 容易 |
| 学习成本 | 高 | 低 |
| 适用场景 | 简单 CRUD | 复杂查询 |
Q2:persist 和 merge 的区别?
参考答案:
| 方法 | 说明 | 对象状态 |
|---|
| persist | 新增,entity 进入持久化状态 | entity 变为持久态 |
| merge | 合并,同步到数据库 | 返回持久态对象,原对象不变 |
User user = new User();
user.setId(1L);
entityManager.persist(user);
User merged = entityManager.merge(user);
Q3:如何优化 JPA 查询性能?
参考答案:
- 使用懒加载 + @EntityGraph 减少不必要的查询
- 批量获取:@BatchSize(size = 20)
- 分页查询:避免一次加载全部数据
- 索引优化:@Column(indexed = true)
- 只查询需要的字段:@Query 投影
- 使用原生查询:复杂场景下使用 nativeQuery
- 二级缓存:@Cacheable