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