4-1 JPA 实战

3 阅读4分钟

4-1 JPA 实战

概念解析

JPA 注解分类

分类注解说明
实体@Entity标记为 JPA 实体
实体@Table指定表名
主键@Id主键字段
主键@GeneratedValue主键生成策略
列映射@Column列配置
关系@OneToOne一对一
关系@OneToMany一对多
关系@ManyToOne多对一
关系@ManyToMany多对多

主键生成策略

策略说明
AUTO数据库自增(默认)
IDENTITYMySQL 自增
SEQUENCEOracle 序列
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
    @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();
    // user.getOrders() 在事务外访问会异常
    return toDTO(user);
}

// ✅ 方案1:在事务内访问
@Transactional
public UserDTO getUser(Long id) {
    User user = userRepository.findById(id).orElseThrow();
    user.getOrders().size();  // 触发加载
    return toDTO(user);
}

// ✅ 方案2:使用 EntityGraph
@EntityGraph(attributePaths = {"orders", "detail"})
Optional<User> findEntityGraphById(Long id);

// ✅ 方案3:使用 join fetch
@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 查询问题

// ❌ N+1 问题
List<User> users = userRepository.findAll();
users.forEach(u -> System.out.println(u.getOrders().size()));  // 每次都查

// ✅ 使用 @EntityGraph
@EntityGraph(attributePaths = {"orders"})
List<User> findAllWithOrders();

// ✅ 使用 @Query + join fetch
@Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();

// ✅ 使用 Batch Fetching
@Entity
public class User {
    @OneToMany(fetch = FetchType.LAZY)
    @BatchSize(size = 20)
    private List<Order> orders;
}

⚠️ 坑 3:双向关系死循环

// ❌ toString 死循环
@Entity
public class User {
    @OneToMany(mappedBy = "user")
    private List<Order> orders;
    // User.toString() -> Order.toString() -> User.toString()
}

@Data
public class User {
    @ToString.Exclude  // Lombok 排除
    @OneToMany(mappedBy = "user")
    private List<Order> orders;
}

面试题

Q1:JPA 和 MyBatis 的区别?

参考答案

维度JPAMyBatis
定位ORM 框架SQL 映射框架
SQL自动生成手写
灵活性
性能调优困难容易
学习成本
适用场景简单 CRUD复杂查询

Q2:persist 和 merge 的区别?

参考答案

方法说明对象状态
persist新增,entity 进入持久化状态entity 变为持久态
merge合并,同步到数据库返回持久态对象,原对象不变
User user = new User();
user.setId(1L);  // 已有 ID

// persist:报错,ID 应由数据库生成
entityManager.persist(user);

// merge:先查询已有记录,再更新
User merged = entityManager.merge(user);  // merged 是持久态

Q3:如何优化 JPA 查询性能?

参考答案

  1. 使用懒加载 + @EntityGraph 减少不必要的查询
  2. 批量获取:@BatchSize(size = 20)
  3. 分页查询:避免一次加载全部数据
  4. 索引优化:@Column(indexed = true)
  5. 只查询需要的字段:@Query 投影
  6. 使用原生查询:复杂场景下使用 nativeQuery
  7. 二级缓存:@Cacheable