Spring Data JPA 實戰(zhàn)指南
用過 MyBatis 再用 JPA,可能會覺得 JPA 很別扭——SQL 都不用寫了,框架自動搞定。
但用久了會發(fā)現(xiàn),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=false
username: root
password: 123456
jpa:
hibernate:
ddl-auto: update # 開發(fā)用 update,生產(chǎn)用 validate
show-sql: true # 打印 SQL
properties:
hibernate:
format_sql: trueddl-auto 幾個選項:
update:自動更新表結(jié)構(gòu)(不會刪數(shù)據(jù))create:每次啟動刪表重建validate:只驗證,不改表none:什么都不做
實體映射
基本映射
@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 // 不映射到數(shù)據(jù)庫
private String tempField;
}主鍵生成策略
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) // MySQL 自增 // 或 @GeneratedValue(strategy = GenerationType.SEQUENCE) // Oracle、序列 // 或 @GeneratedValue(strategy = GenerationType.UUID) // UUID private String id;
Repository 基礎 CRUD
繼承 JpaRepository 就有基本的增刪改查:
public interface UserRepository extends JpaRepository<User, Long> {
// 繼承來的方法:
// save(entity) - 保存或更新
// findById(id) - 查詢
// deleteById(id) - 刪除
// count() - 計數(shù)
// existsById(id) - 是否存在
}@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,方法名就是查詢。
基礎規(guī)則
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 = ?
}常用關(guān)鍵詞
// 大于/小于/等于 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
分頁和排序
// 分頁:參數(shù)加 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 查詢
@Query("SELECT u FROM User u WHERE u.userName = ?1")
User findByName(String userName);
// 占位符 ?1、?2 按參數(shù)順序
@Query("SELECT u FROM User u WHERE u.userName = ?1 AND u.age > ?2")
List<User> findByNameAndAge(String name, Integer age);
// 命名參數(shù)(更清晰)
@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);多表聯(lián)查
假設 User 關(guān)聯(lián) Department:
@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
@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 修改操作
@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 層加事務:
@Transactional
public void updateStatus(Long id, String status) {
userRepository.updateStatus(id, status);
}
一對多 / 多對一
實體定義
@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;
}查詢
// 根據(jù)部門查用戶
List<User> findByDepartmentId(Long deptId);
// 根據(jù)用戶查部門(直接訪問屬性就行)
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. 懶加載異常
// 報錯:LazyInitializationException User user = userRepository.findById(1L).orElseThrow(...); String deptName = user.getDepartment().getName(); // session 已關(guān)閉
解決:在 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 的區(qū)別
// save() - 如果 id 已存在就是 update,不存在就是 insert
User user = new User();
user.setId(1L); // id 存在,變成 update
user.setUserName("newName");
userRepository.save(user); // UPDATE
總結(jié)
| 方式 | 適用場景 |
|---|---|
| 繼承 JpaRepository | 基本 CRUD |
| 方法名查詢 | 單表簡單查詢 |
| @Query + JPQL | 多表關(guān)聯(lián)、復雜查詢 |
| @Query + 原生 SQL | 特定數(shù)據(jù)庫語法 |
| JOIN FETCH | 解決懶加載和 N+1 |
JPA 最大的好處是不用寫 SQL,單表操作特別爽。但多表關(guān)聯(lián)和復雜查詢,還是得用 @Query。用久了會發(fā)現(xiàn),JPA + @Query 配合起來,效率比 MyBatis 高多了。
到此這篇關(guān)于Spring Data JPA 實戰(zhàn)指南的文章就介紹到這了,更多相關(guān)Spring Data JPA 實戰(zhàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java面向國際化項目開發(fā)需遵循的命名規(guī)范
這篇文章主要為大家介紹了在參與開發(fā)國際化項目時需遵循的java命名規(guī)范,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-03-03
SpringBoot靜態(tài)方法調(diào)用Spring容器bean的三種解決方案
在SpringBoot中靜態(tài)方法調(diào)用Spring容器bean時出現(xiàn)的null值問題,本文就來介紹一下SpringBoot靜態(tài)方法調(diào)用Spring容器bean的三種解決方案,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧2025-01-01
Java中BigDecimal與0比較的一個坑實戰(zhàn)記錄
BigDecimal屬于大數(shù)據(jù),精度極高,不屬于基本數(shù)據(jù)類型,屬于java對象,下面這篇文章主要給大家介紹了關(guān)于Java中BigDecimal與0比較的一個坑的相關(guān)資料,需要的朋友可以參考下2022-12-12
Spring Cloud Config 使用本地配置文件方式
這篇文章主要介紹了Spring Cloud Config 使用本地配置文件方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07

