Spring-Data-JPA 學習筆記(一)
作者:zeng1994
一、spring-data-jpa的簡單介紹
SpringData : Spring 的一個子專案。用於簡化資料庫訪問,支援NoSQL 和 關係資料儲存。其主要目標是使資料庫的訪問變得方便快捷。
SpringData 專案所支援 NoSQL 儲存:
- MongoDB (文件資料庫)
- Neo4j(圖形資料庫)
- Redis(鍵/值儲存)
- Hbase(列族資料庫)
SpringData 專案所支援的關係資料儲存技術:
- JDBC
- JPA
JPA Spring Data : 致力於減少資料訪問層 (DAO) 的開發量, 開發者唯一要做的就只是宣告持久層的介面,其他都交給 Spring Data JPA 來幫你完成!
框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個 UserDao.findUserById() 這樣一個方法宣告,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 物件。Spring Data JPA 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。
Spring Data JPA 進行持久層(即Dao)開發一般分三個步驟:
- 宣告持久層的介面,該介面繼承 Repository(或Repository的子介面,其中定義了一些常用的增刪改查,以及分頁相關的方法)。
- 在介面中宣告需要的業務方法。Spring Data 將根據給定的策略生成實現程式碼。
- 在 Spring 配置檔案中增加一行宣告,讓 Spring 為宣告的介面建立代理物件。配置了 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,為繼承 Repository 或其子介面的介面建立代理物件,並將代理物件註冊為 Spring Bean,業務層便可以通過 Spring 自動封裝的特性來直接使用該物件。
二、QuickStart
(1)建立專案並新增Maven依賴
首先我們在eclipse中建立一個Maven的java專案,然後新增依賴。
專案結構:
主要依賴:
- spring-data-jpa
- Hibernate相關依賴
- c3p0依賴
- mysql驅動
pom.xml檔案的程式碼如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zxy</groupId>
<artifactId>springdata-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 全域性屬性配置 -->
<properties>
<project.source.encoding>utf-8</project.source.encoding>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 防止控制輸出臺中文亂碼 -->
<argLine>-Dfile.encoding=UTF-8</argLine>
</properties>
<dependencies>
<!-- junit_jar包依賴 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<!--保留到測試 -->
<scope>test</scope>
</dependency>
<!-- spring-data-jpa相關依賴
(這個依賴自動把一堆spring的東西依賴進來了,所有可以不需要再引入spring的包)-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.11.7.RELEASE</version>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.0.11.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.0.11.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- mysql驅動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 編譯外掛 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<!-- 原始碼用1.8 -->
<source>1.8</source>
<!-- 打成jar用1.8 -->
<target>1.8</target>
<encoding>utf-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
這裡我解釋下為何不新增Spring的其他的依賴,主要是spring-data-jpa這個依賴了一堆spring相關的依賴。見下圖就明白了:
(2)整合SpringData,配置applicationContext.xml
這個整合很重要,我在網上找了好久,沒找到特別好的demo;因此特意把這一步記錄下來。
<1> 首先我們新增一個和資料庫相關的properties檔案;新建db.properties檔案,內容如下
jdbcUrl=jdbc:mysql://localhost:3306/springdata?useUnicode=true&characterEncoding=utf8
driverClass=com.mysql.jdbc.Driver
user=root
password=root
initialPoolSize=10
maxPoolSize=30
<2> 然後我們需要新建一個Spring的配置檔案,因此新建一個applicationContext.xml檔案。裡面大致配置的東西有:
- 開啟包掃描,掃描service層,讓service層的物件交給Spring容器管理
- 讀取properties檔案
- 配置資料來源dataSource
- 配置JPA的EntityManagerFactory, 這裡面有個包掃描,是掃描實體類那一層的
- 配置事務管理器transactionManager
- 配置支援註解的事務
- 配置SpringData這裡包掃描是掃描dao層,掃描那些定義的介面
檔案裡面的具體內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<!-- 配置自動掃描的包,掃描service層,service上面有註解就直接被spring容器例項化 -->
<context:component-scan base-package="com.zxy.service"/>
<!-- 1. 配置資料來源 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbcUrl}"/>
<property name="driverClass" value="${driverClass}"/>
<property name="user" value="${user}"/>
<property name="password" value="${password}"/>
<property name="initialPoolSize" value="${initialPoolSize}"/>
<property name="maxPoolSize" value="${maxPoolSize}"/>
</bean>
<!-- 2. 配置 JPA 的 EntityManagerFactory -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置包掃描,掃描實體 -->
<property name="packagesToScan" value="com.zxy.entity"/>
<property name="jpaProperties">
<props>
<!-- 生成的資料表的列的對映策略 -->
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<!-- hibernate 基本屬性 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 3. 配置事務管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- 4. 配置支援註解的事務 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 5. 配置 SpringData,需要加入 jpa 的名稱空間 -->
<!-- base-package: 掃描 Repository Bean 所在的 package -->
<jpa:repositories base-package="com.zxy.dao" entity-manager-factory-ref="entityManagerFactory">
</jpa:repositories>
</beans>
(3)測試整合
<1> 先測試下Spring容器是否整合成功
我們在com.zxy.test包中新建一個TestConfig的類,在類裡面我們寫單元測試的程式碼。主要內容有:
通過靜態程式碼塊建立 ClassPathXmlApplicationContext物件,讓它讀取applicationContext.xml,這樣就啟動了Spring容器
通過Spring容器獲取dataSource物件,如果成功獲取,說明整合成功了。
程式碼如下:
package com.zxy.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.Test;
/**
* 整合效果測試類
* @author ZENG.XIAO.YAN
* @date 2017年9月14日 下午11:01:20
* @version v1.0
*/
public class TestConfig {
private static ApplicationContext ctx ;
static {
// 通過靜態程式碼塊的方式,讓程式載入spring的配置檔案
ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
/** 測試spring容器是否例項化了資料來源 。如果例項化了,說明Spring容器整合沒問題 */
@Test
public void testDataSouce() throws SQLException {
DataSource dataSouce = (DataSource) ctx.getBean("dataSource");
System.out.println("資料來源:"+ dataSouce);
System.out.println("連線:"+ dataSouce.getConnection());
}
}
成功後控制檯輸出結果如下:
<2> 測試JPA是否整合成功
JPA是否整合成功主要是看entityManagerFactory這個物件是否起作用,這個物件起作用了就會去掃描com.zxy.eneity下面的實體類。測試方法如下:
* 有一個前提,資料庫必須先建立。這裡springdata這個資料庫我先建立了
* 給實體類加上註解,然後開啟Hibernate自動建表功能,啟動Spring容器;如果資料庫自動建表成功,那就整合成功
實體類的程式碼如下:
package com.zxy.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Person實體
* @author ZENG.XIAO.YAN
* @date 2017年9月14日 下午2:44:23
* @version v1.0
*/
@Entity
@Table(name="jpa_persons")
public class Person {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column
private String name;
@Column
private String email;
@Column
private Date birth;
/** setter and getter method */
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
}
新增完這個實體後,還是執行下TestConfig下的testDataSource方法,執行完後,資料庫應該已經建立了一張表了。
如果表建立成功,那就代表JPA整合成功。
(4)在dao層宣告介面
在框架整合完成後,我們就可以開始使用SpringData了,在(3)中我們新建了一個Person實體類,我們就利用這個Person類來展開講解。
使用SpringData後,我們只需要在com.zxy.dao層宣告介面,介面中定義我們要的方法,且介面繼承Repository介面或者是Repository的子介面,這樣就可以操作資料庫了。但是在介面中定義的方法是要符合一定的規則的,這個規則在後面會講到。其實我們也可以寫介面的實現類,這個在後續也會講解。
先新建一個名為PersonDao的介面,它繼承Repository介面;繼承Repository介面的時候那兩個泛型需要指定具體的java型別。第一個泛型是寫實體類的型別,這裡是Person;第二個泛型是主鍵的型別,這裡是Integer。 在這個介面中定義一個叫做getById(Integer id)的方法,然後我們後面在呼叫這個方法測試下。
PersonDao的程式碼如下:
package com.zxy.dao;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
import com.zxy.entity.Person;
/**
* PersonDao
* @author ZENG.XIAO.YAN
* @date 2017年9月18日 下午4:25:39
* @version v1.0
*/
/*
* 1.Repository是一個空介面,即是一個標記介面
* 2.若我們定義的介面繼承了Repository,則該介面會被IOC容器識別為一個Repository Bean
* 注入到IOC容器中,進而可以在該介面中定義滿足一定規則的介面
* 3.實際上也可以通過一個註解@RepositoryDefination 註解來替代Repository介面
*/
//@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
public interface PersonDao extends Repository<Person, Integer> {
// 通過id查詢實體
Person getById(Integer id);
}
其實也可以用註解@RepositoryDefination來代替繼承介面Repository介面,這裡不做過多介紹這個註解,更多和該註解的相關知識請查閱相關資料。
(5)測試dao層介面
由於我們資料庫中jpa_persons這個表還沒資料,先在這表中手動插入幾條測試資料。
有了資料後,我們在com.zxy.test層新建一個名為TestQucikStart的測試類。還是採用靜態程式碼快的方式來載入Spring配置檔案的方式來使用Spring容器,在後續貼的程式碼中,這部分程式碼可能會不貼出來。這裡先宣告一下,後續在程式碼中看到的ctx是其實就是Spring容器的意思,它都是這樣獲取的。
測試類程式碼如下:
package com.zxy.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.zxy.dao.PersonDao;
import com.zxy.entity.Person;
/**
* SpringData快速入門測試類
* @author ZENG.XIAO.YAN
* @date 2017年9月18日 下午5:33:42
* @version v1.0
*/
public class TestQuickStart {
private static ApplicationContext ctx ;
static {
// 通過靜態程式碼塊的方式,讓程式載入spring的配置檔案
ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
}
/** 測試PersonDao中定義的getById的方法能否查詢出結果 */
@Test
public void testGetById() {
PersonDao personDao = ctx.getBean(PersonDao.class);
Person person = personDao.getById(1);
System.out.println("查詢結果: name=" + person.getName() + ",id=" + person.getId());
}
}
測試的結果如下圖所示,我們只聲明瞭介面和定義了方法就從資料庫查到了資料,這就是SpringData的強大之處。
三、SpringData方法定義規範
通過上面的QucikStart的案例,我們瞭解到在使用SpringData時只需要定義Dao層介面及定義方法就可以操作資料庫。但是,這個Dao層介面中的方法也是有定義規範的,只有按這個規範來,SpringData才能識別並實現該方法。下面來說說方法定義的規範。
(1)簡單的條件查詢的方法定義規範
方法定義規範如下:
* 簡單條件查詢:查詢某一個實體或者集合
* 按照SpringData規範,查詢方法於find|read|get開頭,涉及條件查詢時,條件的屬性用條件關鍵字連線,要注意的是:屬性首字母需要大寫
* 支援屬性的級聯查詢;若當前類有符合條件的屬性, 則優先使用, 而不使用級聯屬性。 若需要使用級聯屬性, 則屬性之間使用 _ 進行連線
下面來看個案例吧,操作的實體依舊上面的Person,下面寫個通過id和name查詢出Person物件的案例。
在PersonDao這個介面中,定義一個通過id和name查詢的方法
// 通過id和name查詢實體,sql: select * from jpa_persons where id = ? and name = ?
Person findByIdAndName(Integer id, String name);
在TestQucikStart這個測試類中,寫個單元測試方法testFindByIdAndName來測試這個dao層的方法是否可用
/** 測試getByIdAndName方法 */
@Test
public void testGetByIdAndName() {
PersonDao personDao = ctx.getBean(PersonDao.class);
Person person = personDao.findByIdAndName(1, "test0");
System.out.println(person);
}
執行的結果如下,成功的查詢到了資料
(2)支援的關鍵字
直接在介面中定義方法,如果符合規範,則不用寫實現。目前支援的關鍵字寫法如下:
下面直接展示個案例來介紹下這些方法吧,
PersonDao介面新增程式碼如下:
// where id < ? or birth < ?
List<Person> findByIdIsLessThanOrBirthLessThan(Integer id, Date birth);
// where email like ?
List<Person> findByEmailLike(String email);
// 也支援count查詢
long countByEmailLike(String email);
在TestQucikStart中新增以下2個單元測試方法,測試dao層介面中的方法是否可用
/** 測試findByEmailLike方法 */
@Test
public void testFindByEmailLike() {
PersonDao personDao = ctx.getBean(PersonDao.class);
List<Person> list = personDao.findByEmailLike("test%");
for (Person person : list) {
System.out.println(person.getEmail());
}
}
/** 測試findByIdIsLessThanOrBirthLessThan方法 */
@Test
public void testFindByIdIsLessThanOrBirthLessThan() {
PersonDao personDao = ctx.getBean(PersonDao.class);
List<Person> list = personDao.findByIdIsLessThanOrBirthLessThan(3, new Date());
for (Person person : list) {
System.out.println("查詢結果: name=" + person.getName()
+ ",id=" + person.getId() + ",birth=" + person.getBirth());
}
}
執行結果如下:
(3)一個屬性級聯查詢的案例
Dao層介面中定義的方法支援級聯查詢,下面通過一個案例來介紹這個級聯查詢:
在com.zxy.entity包下新建一個Address的實體,程式碼如下圖,setter和getter方法我省略了
修改Person類,新增address屬性,使Person和Address成多對一的關係,設定外來鍵列名為address_id ,新增的程式碼如下圖:
執行我們上面的任意一個測試方法,只要啟動了專案,資料庫的表都會更新。在表更新後我們需要手動插入一些資料,我插入的資料如下:
修改jpa_persons表,使address_id這個外來鍵列有值,修改後的效果如下圖:
在PersonDao介面中定義一個方法,程式碼如下:
// 級聯查詢,查詢address的id等於條件值
List<Person> findByAddressId(Integer addressId);
在TestQucik這個測試類中定義一個單元測試方法,測試這個dao的方法是否可用。程式碼如下:
/** 測試findByAddressId方法 */
@Test
public void testFindByAddressId() {
PersonDao personDao = ctx.getBean(PersonDao.class);
// 查出地址id為1的person集合
List<Person> list = personDao.findByAddressId(1);
for (Person person : list) {
System.out.println(person.getName()
+ "---addressId="
+ person.getAddress().getId());
}
}
執行測試方法,通過控制檯可觀察生成的sql語句和查詢的結果。結果如下圖所示:
這裡我解釋下這個生成的sql吧,首先是一個左外連線查詢出結果,由於Person中有個Address的實體,所以就又傳送了一次查詢address的sql。產生這個的原因是@ManyToOne這個註解預設是禁用延遲載入的,所以會把關聯屬性的值也會查詢出來。
(4)查詢方法解析流程
通過以上的學習,掌握了在介面中定義方法的規則,我們就可以定義出很多不用寫實現的方法了。這裡再介紹下查詢方法的解析的流程吧,掌握了這個流程,對於定義方法有更深的理解。
<1> 方法引數不帶特殊引數的查詢
假如建立如下的查詢:findByUserDepUuid(),框架在解析該方法時,流程如下:
- 首先剔除 findBy,然後對剩下的屬性進行解析,假設查詢實體為Doc
- 先判斷 userDepUuid(根據 POJO 規範,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續往下走
- 從右往左擷取第一個大寫字母開頭的字串(此處為Uuid),然後檢查剩下的字串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複這一步,繼續從右往左擷取;最後假設 user 為查詢實體的一個屬性
- 接著處理剩下部分(DepUuid),先判斷 user 所對應的型別是否有depUuid屬性,如果有,則表示該方法最終是根據 “Doc.user.depUuid” 的取值進行查詢;否則繼續按照步驟3的規則從右往左擷取,最終表示根據 “Doc.user.dep.uuid” 的值進行查詢。
可能會存在一種特殊情況,比如 Doc包含一個 user 的屬性,也有一個 userDep 屬性,此時會存在混淆。可以明確在級聯的屬性之間加上 “_” 以顯式表達意圖,比如 “findByUser_DepUuid()” 或者 “findByUserDep_uuid()”。
<2> 方法引數帶特殊引數的查詢
特殊的引數: 還可以直接在方法的引數上加入分頁或排序的引數,比如:
Page findByName(String name, Pageable pageable)
List findByName(String name, Sort sort);
四、@Query註解
通過上面的學習,我們在dao層介面按照規則來定義方法就可以不用寫方法的實現也能操作資料庫。但是如果一個條件查詢有多個條件時,寫出來的方法名字就太長了,所以我們就想著不按規則來定義方法名。我們可以使用@Query這個註解來實現這個功能,在定義的方法上加上@Query這個註解,將查詢語句宣告在註解中,也可以查詢到資料庫的資料。
(1)使用Query結合jpql語句實現自定義查詢
在PersonDao介面中宣告方法,放上面加上Query註解,註解裡面寫jpql語句,程式碼如下:
// 自定義的查詢,直接寫jpql語句; 查詢id<? 或者 名字 like?的person集合
@Query("from Person where id < ?1 or name like ?2")
List<Person> testPerson(Integer id, String name);
// 自定義查詢之子查詢,直接寫jpql語句; 查詢出id最大的person
@Query("from Person where id = (select max(p.id) from Person as p)")
Person testSubquery();
在TestQuickStart中新增以下程式碼,測試dao層中使用Query註解的方法是否可用
/** 測試用Query註解自定義的方法 */
@Test
public void testCustomMethod() {
PersonDao personDao = ctx.getBean(PersonDao.class);
List<Person> list = personDao.testPerson(2, "%admin%");
for (Person person : list) {
System.out.println("查詢結果: name=" + person.getName() + ",id=" + person.getId());
}
System.out.println("===============分割線===============");
Person person = personDao.testSubquery();
System.out.println("查詢結果: name=" + person.getName() + ",id=" + person.getId());
}
查詢結果及生成的sql語句如下所示
(2)索引引數和命名引數
在寫jpql語句時,查詢條件的引數的表示有以下2種方式:
索引引數方式如下圖所示,索引值從1開始,查詢中’?x’的個數要和方法的引數個數一致,且順序也要一致
命名引數方式(推薦使用這種方式)如下圖所示,可以用’:引數名’的形式,在方法引數中使用@Param(”引數名”)註解,這樣就可以不用按順序來定義形參
說一個特殊情況,那就是自定義的Query查詢中jpql語句有like查詢時,可以直接把%號寫在引數的前後,這樣傳引數就不用把%號拼接進去了。使用案例如下,呼叫該方法時傳遞的引數直接傳就ok。
(3)使用@Query來指定使用本地SQL查詢
如果你不熟悉jpql語句,你也可以寫sql語句查詢,只需要在@Query註解中設定nativeQuery=true。直接來看案例吧
dao層介面寫法如下圖所示
測試程式碼這裡直接不貼了,下面是控制檯中列印的語句和結果
五、@Modifying註解和事務
(1)@Modifying註解的使用
@Query與@Modifying這兩個註解一起使用時,可實現個性化更新操作及刪除操作;例如只涉及某些欄位更新時最為常見。
下面演示一個案例,把id小於3的person的name都改為’admin’
dao層程式碼如下所示
//可以通過自定義的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支援使用 INSERT
//在 @Query 註解中編寫 JPQL 語句, 但必須使用 @Modifying 進行修飾. 以通知 SpringData, 這是一個 UPDATE 或 DELETE 操作
//UPDATE 或 DELETE 操作需要使用事務, 此時需要定義 Service 層. 在 Service 層的方法上新增事務操作.
//預設情況下, SpringData 的每個方法上有事務, 但都是一個只讀事務. 他們不能完成修改操作!
@Modifying
@Query("UPDATE Person p SET p.name = :name WHERE p.id < :id")
int updatePersonById(@Param("id")Integer id, @Param("name")String updateName);
由於這個更新操作,只讀事務是不能實現的,因此新建PersonService類,在Service方法中新增事務註解。PersonService的程式碼如下圖所示
package com.zxy.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zxy.dao.PersonDao;
/**
* PersonService
* @author ZENG.XIAO.YAN
* @date 2017年9月20日 下午2:57:16
* @version v1.0
*/
@Service("personService")
public class PersonService {
@Autowired
private PersonDao personDao;
@Transactional(readOnly=false)
public int updatePersonById(Integer id, String updateName) {
return personDao.updatePersonById(id, updateName);
}
}
測試類中直接呼叫service的方法就ok了,測試程式碼如下圖
使用@[email protected]時的注意事項:
方法返回值是int,表示影響的行數
在呼叫的地方必須加事務,沒事務不執行
(2)事務
- Spring Data 提供了預設的事務處理方式,即所有的查詢均宣告為只讀事務
- 對於自定義的方法,如需改變 Spring Data 提供的事務預設方式,可以在方法上註解 @Transactional 宣告
- 進行多個 Repository 操作時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,因此,需要在 Service 層實現對多個 Repository 的呼叫,並在相應的方法上宣告事務。
六、本文小結
- 本文只簡單介紹了下SpringData,知道了SpringData簡化了dao層的程式碼,我們可以只宣告介面就可以完成對資料庫的操作。
- 介紹了一個SpringData的入門案例,其中包含了需要哪些依賴的jar包,如何整合Spring-data-jpa以及怎麼測試是否整合成功等。
- 介紹了Dao層介面繼承了Repository介面後,該按照什麼規則去定義方法就可以被SpringData解析;且展示了SpringData對級聯查詢的案例。同時也講解了SpringData解析方法的整個流程。
- 介紹了@Query註解的使用,有了這個註解,我們就可以隨便定義方法的名字,方法的功能由我們自己寫jqpl語句或者是sql語句來實現。在介紹這個註解的時候,也講解了jpql或者sql中引數可以用索引引數和命名引數的兩種方式來表示。
- 介紹了@Modifying註解結合@Query註解,實現更新和刪除。同時也介紹了SpringData的事務。
追加:
- JPA的save介面:有主鍵id時是做更新操作,把所有欄位更新,包括null的欄位。沒有主鍵id時是做插入操作
- @Transient()
private String typeName;//資料庫忽略對映的欄位 - BeanUtiles的copyProperties方法,複製兩個類中型別和名稱相同的屬性,包括父類繼承的屬性,null值也會複製。有個copyProperties 的過載方法中可以新增需要忽略複製的屬性名。