跳到主要内容
版本:Next

整合 Spring Data JPA

JPA(Hibernate)走的是另一条路:面向对象操作数据库。定义好实体,CRUD 不用写一行 SQL,还能根据方法名自动生成查询。适合快速开发和模型驱动的项目。

第 1 步:依赖与配置​

<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/demo?serverTimezone=Asia/Shanghai&useSSL=false
username: root
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: update # 根据实体自动维护表结构
show-sql: true # 打印 SQL(生产关闭)
open-in-view: false # 关闭 OSIV,避免懒加载问题被掩盖

ddl-auto 取值:

值行为适用
create每次启动删表重建仅本地玩票
update只加新字段/新表,不删列开发环境
validate只校验实体与表是否一致生产环境
none什么都不做生产环境(推荐配合 Flyway)
注意

生产环境不要依赖 ddl-auto 改表,用 Flyway/Liquibase 做版本化数据库迁移。

第 2 步:定义实体​

package com.example.demo.entity;

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "user")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增主键
private Long id;

@Column(nullable = false, unique = true, length = 50)
private String username;

private String email;

private Integer age;

private Integer status;

@Column(name = "create_time", updatable = false)
private LocalDateTime createTime;

@Column(name = "update_time")
private LocalDateTime updateTime;

@PrePersist // 插入前回调
void prePersist() {
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
if (this.status == null) this.status = 1;
}

@PreUpdate // 更新前回调
void preUpdate() {
this.updateTime = LocalDateTime.now();
}
// getter / setter
}

常用实体注解:

注解作用
@Entity声明 JPA 实体
@Table(name)指定表名
@Id + @GeneratedValue主键及生成策略(IDENTITY 自增 / SEQUENCE / UUID)
@Column字段映射:列名、长度、唯一、非空
@Transient该字段不映射到数据库
@ManyToOne / @OneToMany关联关系
@PrePersist / @PreUpdate生命周期回调

第 3 步:Repository——零 SQL 的 CRUD​

package com.example.demo.repository;

import com.example.demo.entity.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
// 无需实现,继承即得:
// save / findById / findAll / deleteById / count / existsById ...
}

Service 中直接用:

@Service
public class UserService {

private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

public User create(User user) {
if (userRepository.existsByUsername(user.getUsername())) {
throw new BizException(1002, "用户名已存在");
}
return userRepository.save(user); // 无 id 走 INSERT,有 id 走 UPDATE
}

@Transactional
public User update(Long id, UserUpdateDTO dto) {
User user = userRepository.findById(id)
.orElseThrow(() -> new BizException(1001, "用户不存在"));
if (dto.getEmail() != null) user.setEmail(dto.getEmail());
if (dto.getAge() != null) user.setAge(dto.getAge());
return userRepository.save(user);
}
}

派生查询:方法名即 SQL​

按命名规则写方法,JPA 自动生成查询:

public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findByUsername(String username);

boolean existsByUsername(String username);

List<User> findByAgeGreaterThanEqual(Integer age);

List<User> findByUsernameLikeAndStatus(String name, Integer status);

List<User> findByCreateTimeBetween(LocalDateTime start, LocalDateTime end);

List<User> findTop10ByOrderByIdDesc();

// 分页 + 排序
Page<User> findByUsernameContaining(String name, Pageable pageable);
}

关键字速查:And / Or / Between / LessThan / GreaterThanEqual / Like / Containing / In / IsNull / OrderBy...Desc / Top10 / Distinct。

调用分页:

Pageable pageable = PageRequest.of(page - 1, size, Sort.by("id").descending());
Page<User> result = userRepository.findByUsernameContaining(name, pageable);
// result.getContent() / getTotalElements() / getTotalPages()

@Query:写 JPQL 或原生 SQL​

派生查询搞不定复杂场景时,显式写查询:

public interface OrderRepository extends JpaRepository<Order, Long> {

// JPQL:面向实体和字段,不是表和列
@Query("SELECT o FROM Order o WHERE o.user.id = :userId AND o.amount > :min")
List<Order> findBigOrders(@Param("userId") Long userId, @Param("min") BigDecimal min);

// 原生 SQL
@Query(value = "SELECT * FROM `order` WHERE DATE(create_time) = CURDATE()",
nativeQuery = true)
List<Order> findTodayOrders();

// 修改类操作必须加 @Modifying,且所在方法要有事务
@Modifying
@Query("UPDATE User u SET u.status = 0 WHERE u.id = :id")
int disable(@Param("id") Long id);
}

关联映射​

@Entity
@Table(name = "`order`")
public class Order {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY) // 多订单对一用户;务必 LAZY
@JoinColumn(name = "user_id")
private User user;

private BigDecimal amount;
// getter / setter
}
  • 关联一律用 LAZY 懒加载,EAGER 容易引发 N+1 和级联大查询。
  • 访问懒加载属性需在事务/会话内,这就是配置里关 open-in-view 后要注意的点。

JPA vs MyBatis 怎么选​

对比项Spring Data JPAMyBatis
开发效率极高,单表零 SQL需手写 SQL
SQL 控制力弱,生成 SQL 不可控完全掌控,可深度优化
复杂报表/多表关联写起来别扭得心应手
数据库迁移成本低(方言切换)SQL 与数据库耦合
国内生态一般主流(尤其 MyBatis-Plus)

经验法则:管理后台、快速原型用 JPA;SQL 复杂、性能敏感、国内团队用 MyBatis。