Spring-data-jpa 學習筆記(一)

        Spring家族愈來愈強大,做爲一名javaWeb開發人員,學習Spring家族的東西是必須的。在此記錄學習Spring-data-jpa的相關知識,方便後續查閱。java

1、spring-data-jpa的簡單介紹

SpringData : Spring 的一個子項目。用於簡化數據庫訪問,支持NoSQL 和 關係數據存儲。其主要目標是使數據庫的訪問變得方便快捷。mysql

SpringData 項目所支持 NoSQL 存儲:spring

  •  MongoDB (文檔數據庫)
  •  Neo4j(圖形數據庫)
  •  Redis(鍵/值存儲)
  •  Hbase(列族數據庫)

SpringData 項目所支持的關係數據存儲技術:sql

  • JDBC
  • JPA


JPA Spring Data : 致力於減小數據訪問層 (DAO) 的開發量, 開發者惟一要作的就只是聲明持久層的接口,其餘都交給 Spring Data JPA 來幫你完成!數據庫

框架怎麼可能代替開發者實現業務邏輯呢?好比:當有一個 UserDao.findUserById() 這樣一個方法聲明,大體應該能判斷出這是根據給定條件的 ID 查詢出知足條件的 User 對象。Spring Data JPA 作的即是規範方法的名字,根據符合規範的名字來肯定方法須要實現什麼樣的邏輯。apache


Spring Data JPA 進行持久層(即Dao)開發通常分三個步驟:api

    • 聲明持久層的接口,該接口繼承 Repository(或Repository的子接口,其中定義了一些經常使用的增刪改查,以及分頁相關的方法)。
    • 在接口中聲明須要的業務方法。Spring Data 將根據給定的策略生成實現代碼。
    • 在 Spring 配置文件中增長一行聲明,讓 Spring 爲聲明的接口建立代理對象。配置了 <jpa:repositories> 後,Spring 初始化容器時將會掃描 base-package 指定的包目錄及其子目錄,爲繼承 Repository 或其子接口的接口建立代理對象,並將代理對象註冊爲 Spring Bean,業務層即可以經過 Spring 自動封裝的特性來直接使用該對象。


2、QuickStart

(1)建立項目並添加Maven依賴

         首先咱們在eclipse中建立一個Maven的java項目,而後添加依賴。架構

         項目結構見右圖:        app

 

         主要依賴有:框架

      • 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的強大之處。
            
 

3、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<UserModel> findByName(String name, Pageable pageable)
         List<UserModel> findByName(String name, Sort sort);

4、@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層接口寫法以下圖所示
                        
    • 測試代碼這裏直接不貼了,下面是控制檯中打印的語句和結果
                    

5、@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了,測試代碼以下圖
                   

        使用@Modifying+@Query時的 注意事項
      • 方法返回值是int,表示影響的行數
      • 在調用的地方必須加事務,沒事務不執行
(2)事務
    • Spring Data 提供了默認的事務處理方式,即全部的查詢均聲明爲只讀事務
    • 對於自定義的方法,如需改變 Spring Data 提供的事務默認方式,能夠在方法上註解 @Transactional 聲明 
    • 進行多個 Repository 操做時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,所以,須要在 Service 層實現對多個 Repository 的調用,並在相應的方法上聲明事務

6、本文小結

        (1)本文只簡單介紹了下SpringData,知道了SpringData簡化了dao層的代碼,咱們能夠只聲明接口就能夠完成對數據庫的操做。
        (2)介紹了一個SpringData的入門案例,其中包含了 須要哪些依賴的jar包, 如何整合Spring-data-jpa以及怎麼測試是否整合成功等。
        (3)介紹了Dao層接口繼承了Repository接口後,該按照什麼規則去定義方法就能夠被SpringData解析;且展現了SpringData對級聯查詢的案例。同時也講解了SpringData解析方法的整個流程。
        (4)介紹了@Query註解的使用,有了這個註解,咱們就能夠隨便定義方法的名字,方法的功能由咱們本身寫jqpl語句或者是sql語句來實現。在介紹這個註解的時候,也講解了jpql或者sql中參數能夠用索引參數和命名參數的兩種方式來表示。
        (5)介紹了@Modifying註解結合@Query註解,實現更新和刪除。同時也介紹了SpringData的事務。
相關文章
相關標籤/搜索