SpringBoot 整合 JPA 輕松搞定數據表增刪改查!
01、背景介紹
在之前的文章中,我們介紹了通過JdbcTemplate來實現數據庫的訪問和讀寫操作。當有一定的開發經驗之后,你會發現所有涉及到數據庫操作的代碼,除了表名和字段不同外,操作的語句基本都類似,功能上可以統一歸納為“增、刪、改、查”,編寫大量這種類似的代碼,對于開發者來說,其實非常枯燥。
為了解決重復的編寫數據操作語句,開發社區誕生了許多優秀的 ORM 框架,比如 Hibernate、OpenJPA、TopLink 等,其中 Hibernate 相對較為知名,在 Hibernate 框架的幫助下,開發者可以輕松的以操作 Java 實體的方式來完成對數據表的“增刪改查”操作,能極大的簡化代碼編寫的工作量。
國內外有不少的項目基于 Spring Boot JPA 來完成對數據庫的操作訪問,那么 Spring Boot JPA 和 ORM 框架之間有著怎樣的關系呢?
簡單的說,Spring Boot JPA 是 Spring 在 ORM 框架的基礎上封裝的一套 JPA 應用框架,具體的數據訪問和操作實現還是依賴于 ORM 框架來完成,Spring Boot JPA 只是完成了接口操作的標準封裝,包括增刪改查等在內的常用功能,可以幫助開發者降低學習成本,同時極大的提升開發效率。
值得一提的是:JPA (Java Persistence API) 是 Sun 官方提出的一套 Java 數據持久化操作的規范,不是一套產品,像上文說的 Hibernate,OpenJPA,TopLink 等,可以理解成 JPA 的具體產品實現。
以 Spring Boot 的2.0版本為例,Spring Boot JPA 的底層依賴于 Hibernate 框架來完成數據庫的訪問和操作,如果你熟悉 Hibernate 的框架使用,那么可以輕松的上手并使用它。
下面我們一起來看看 Spring Boot JPA 的具體使用姿勢。
02、應用實踐
2.1、工程配置
首先在pom.xml文件中添加相關的依賴包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
然后在application.properties文件中配置相關的數據源訪問地址,以及相關hibernate屬性配置。
# 數據源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# hibernate信息配置
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
其中spring.jpa.properties.hibernate.hbm2ddl.auto參數是hibernate的一個配置屬性,主要作用有:自動創建、更新、驗證數據庫表結構。
相關的可選參數如下:
- create:每次加載 hibernate 相關實體表時會刪除上一次生成的表,然后按照最新的 model 類生成新表,會造成數據庫表數據丟失;
- create-drop:每次加載 hibernate 時會根據 model 類生成新表,當服務關閉時,表自動刪除,通常用于測試;
- update:常用屬性,第一次加載 hibernate 時會根據 model 類自動建表,以后加載 hibernate 時根據 model 類自動更新表結構,但是不會刪除表中的數據;
- validate:每次加載 hibernate 時會驗證數據庫表的結構,只會和數據庫中的表進行比較,不會創建新表,但是會插入新值;
其次,spring.jpa.properties.hibernate.dialect參數主要用于指定生成表名時的存儲引擎為 InnoDBD,如果不配置,默認是MylSAM;spring.jpa.show-sql參數用于打印出自動生成的 SQL,方便調試。
2.2、基本增刪改查操作
下面我們一起來體驗一下 JPA 中常用的增刪改查操作。
2.2.1、創建實體
創建一個Student實體,包含三個屬性,表名為tb_student,當加載 hibernate 的時候會自動創建到數據庫中,代碼如下:
@Entity
@Table(name = "tb_student")
public class Student {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, unique = true)
private String name;
@Column(nullable = false)
private Integer age;
// set、get方法等...
}
- @Entity注解用于標識Student類是一個持久化的實體類;
- @Table注解用于標識Student類映射到數據庫中的表名稱;
- @Id注解用于標識Student映射到數據庫的主鍵字段
- @GeneratedValue注解用于標識Student映射到數據庫的主鍵為自增類型
- @Column注解用于標識Student映射到數據庫的字段相關信息
2.2.2、創建數據訪問接口
針對Student實體類,創建一個對應的JpaRepository接口,用于實現對實體的數據訪問和操作,代碼如下:
public interface StudentRepository extends JpaRepository<Student,Long> {
}
其中JpaRepository接口已經封裝好了常用的增刪改查方法邏輯,使用者只需要調用相關的方法接口實現對數據庫表的操作。
JpaRepository接口封裝的部分方法,源碼如下圖!
圖片
2.2.3、單元測試
完成以上的實體創建和數據訪問接口的編寫之后,下面我們編寫對應的單元測試類來驗證一下編寫的內容是否正確,代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentJPATest {
@Autowired
private StudentRepository studentRepository;
@Test
public void test(){
// 插入3條數據
studentRepository.save(new Student("張三", 20));
studentRepository.save(new Student("李四", 21));
studentRepository.save(new Student("王五", 22));
// 查詢全部數據
List<Student> dbList = studentRepository.findAll();
System.out.println("第一次全量查詢結果:" + dbList.toString());
System.out.println("------------------------");
// 修改數據
studentRepository.save(new Student(dbList.get(0).getId(),"趙六", 20));
// 查詢指定數據
Optional<Student> findResult = studentRepository.findById(dbList.get(0).getId());
System.out.println("查詢第一條數據結果:" + findResult.toString());
System.out.println("-----------------");
// 刪除數據
studentRepository.deleteById(dbList.get(0).getId());
// 查詢全部數據
List<Student> result = studentRepository.findAll();
System.out.println("第二次全量查詢結果:" + result.toString());
}
}
運行單元測試,輸出結果如下!
第一次全量查詢結果:[Student{id=1, name='張三', age=20}, Student{id=2, name='李四', age=21}, Student{id=3, name='王五', age=22}]
------------------------
查詢第一條數據結果:Optional[Student{id=1, name='趙六', age=20}]
------------------------
第二次全量查詢結果:[Student{id=2, name='李四', age=21}, Student{id=3, name='王五', age=22}]
2.3、自定義簡單查詢操作
Spring Boot JPA 不僅為開發者封裝了常用的模板方法,還支持根據方法名來動態生成 SQL 語句,比如findByXX,countByXX,getByXX后面跟屬性名稱,當調用方法的時候會自動生成響應的 SQL 語句,具體示例如下:
public interface StudentRepository extends JpaRepository<Student,Long> {
/**
* 自定義簡單查詢,通過姓名進行搜索
* @param name
* @return
*/
Student findByName(String name);
/**
* 自定義簡單查詢,通過姓名和年齡進行統計
* @param name
* @return
*/
Integer countByNameAndAge(String name, Integer age);
}
編寫單元測試驗證內容的正確性。
@RunWith(SpringRunner.class)
@SpringBootTest
public class StudentJPATest {
@Autowired
private StudentRepository studentRepository;
@Test
public void simpleTest(){
Student result1 = studentRepository.findByName("李四");
System.out.println("第一次查詢結果:" + result1.toString());
System.out.println("-----------------");
Integer result2 = studentRepository.countByNameAndAge("王五", 22);
System.out.println("第二次查詢結果:" + result2);
}
}
輸出結果如下!
第一次查詢結果:Student{id=2, name='李四', age=21}
-----------------
第二次查詢結果:1
方法上支持 SQL 語句中的關鍵字,比如And、Or、Like、OrderBy等。
具體關鍵字上的使用和生成的 SQL 對應的關系如下:
圖片
更多的關鍵字使用可以參閱官方文檔。
2.4、復雜查詢操作
在實際的開發過程中,由于業務的需要,我們經常需要編寫復雜的 SQL 語句,比如鏈表查詢,分頁查詢等,這個時候就需要用到自定義 SQL 語句的操作了。
2.4.1、自定義 SQL 查詢
其實大部分的 SQL 語句都可以通過方法來動態生成,如果想自定義 SQL 查詢,Spring Boot JPA 也是支持的,操作上很簡單。
在接口方法上,添加@Query注解,即可實現自定義 SQL 語句;如果涉及到新增、修改和刪除操作,需要再加上@Modifying注解,同時也需要添加@Transactional注解事務支持。
具體示例如下:
public interface StudentRepository extends JpaRepository<Student,Long> {
/**
* 自定義SQL語句,單條查詢
* @param studentName
* @return
*/
@Query(value = "select s from Student s where s.name = ?1")
Student findByStudentName(String studentName);
/**
* 自定義SQL語句,修改數據
* @param name
* @param age
* @return
*/
@Transactional
@Modifying
@Query(value = "update Student s set s.age = ?2 where s.name = ?1")
int updateAgeByName(String name, Integer age);
/**
* 自定義SQL語句,刪除數據
* @param name
*/
@Transactional
@Modifying
@Query(value = "delete from Student s where s.name = ?1")
int deleteByName(String name);
}
編寫單元測試驗證內容的正確性。
@Test
public void sqlTest(){
// 新增
studentRepository.save(new Student("王五", 22));
// 查詢
Student result1 = studentRepository.findByStudentName("王五");
System.out.println("第一次查詢結果:" + result1.toString());
System.out.println("-----------------");
// 修改
studentRepository.updateAgeByName("王五", 30);
Student result2 = studentRepository.findByStudentName("王五");
System.out.println("第二次查詢結果:" + result2.toString());
System.out.println("-----------------");
// 刪除
studentRepository.deleteByName("王五");
Student result3 = studentRepository.findByStudentName("王五");
System.out.println("第三次查詢結果:" + result3);
}
輸出結果如下!
第一次查詢結果:Student{id=4, name='王五', age=22}
-----------------
第二次查詢結果:Student{id=4, name='王五', age=30}
-----------------
第三次查詢結果:null
值得注意的是:這里自定義的 SQL 語句并非數據庫中的 SQL 語句,而是 hibernate 所支持 SQL 語句,簡稱 hsql,例如表名要采用 Java 實體而非數據庫中真實的表名,否則可能會報錯。
2.4.2、分頁查詢
分頁查詢,在實際的業務開發中非常常見,其實 Spring Boot JPA 已經幫助開發者封裝了分頁查詢的方法邏輯,在查詢的時候傳入Pageable參數即可。
示例如下:
@Test
public void pageTest(){
// 構建分頁參數
int page=0,size=10;
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = PageRequest.of(page, size, sort);
// 構建單條件查詢參數
Student param = new Student();
param.setAge(21);
Example<Student> example = Example.of(param);
// 發起分頁查詢
Page<Student> result = studentRepository.findAll(example, pageable);
System.out.println("查詢結果,總行數:" + result.getTotalElements());
System.out.println("查詢結果,明細:" + result.getContent());
}
輸出結果如下:
查詢結果,總行數:1
查詢結果,明細:[Student{id=2, name='李四', age=21}]
當然我們也可以根據自定義簡單查詢來實現分頁查詢,在 JPA 的幫助下動態生成 SQL 語句,示例如下:
public interface StudentRepository extends JpaRepository<Student,Long> {
/**
* 自定義簡單查詢,通過年齡進行分頁搜索
* @param age
* @param pageable
* @return
*/
Page<Student> findByAge(Integer age, Pageable pageable);
}
值得注意的是:自定義的方法不會自動進行count語句匯總查詢,推薦采用模板方法來進行分頁查詢。
2.4.3、多表查詢
多表查詢,也是實際開發中經常會碰到的場景,Spring Boot JPA 提供了兩種實現方式,第一種是利用 Hibernate 的級聯查詢來實現,第二種是自定義 SQL 語句來實現。
第一種就不多說了,主要通過@OneToOne、@OneToMany、@ManyToOne、@ManyToMany和@JoinTable注解來完成多表的級聯查詢,不過這種方式需要在數據庫層面建立外鍵關聯,通過外鍵來完成級聯查詢,不推薦采用。
下面我們來介紹一下自定義 SQL 語句來實現,實現起來也很簡單,示例如下:
public interface TeacherRepository extends JpaRepository<Teacher,Long> {
/**
* 自定義鏈表查詢
* @return
*/
@Query(value = "select s.id, s.name, t.teacher_id as teacherId, t.teacher_name as teacherName from tb_student s left join tb_teacher t on s.teacher_id = t.teacher_id ", nativeQuery = true)
List<Map<String,Object>> findCustomer();
}
編寫單元測試,驗證代碼的正確性。
@RunWith(SpringRunner.class)
@SpringBootTest
public class TeacherJPATest {
@Autowired
private TeacherRepository teacherRepository;
@Test
public void test2(){
// 查詢全部數據
List<Map<String,Object>> dbList = teacherRepository.findCustomer();
System.out.println("查詢結果:" + JSONObject.toJSONString(dbList));
}
}
輸出結果如下:
查詢結果:[{"id":9,"teacherId":1,"name":"李1","teacherName":"張老師"},{"name":"李2","id":10,"teacherId":1,"teacherName":"張老師"},{"id":11,"name":"李3","teacherId":1,"teacherName":"張老師"}]
直接編寫 sql 語句,非常簡單靈活。
2.5、屬性映射屏蔽操作
如果某個實體類中的屬性,不想被映射到數據庫,可以添加@Transient注解來實現,示例如下。
@Transient
private String name;
03、參考
1、http://www.ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html