Spring家族越來越強大,作為一名javaWeb開發人員,學習Spring家族的東西是必須的。在此記錄學習Spring-data-jpa的相關知識,方便后續查閱。
一、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 為聲明的接口創建代理對象。配置了 <jpa:repositories> 后,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>
74
1
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3
<modelVersion>4.0.0</modelVersion>
4
<groupId>com.zxy</groupId>
5
<artifactId>springdata-demo</artifactId>
6
<version>0.0.1-SNAPSHOT</version>
7
8
<!-- 全局屬性配置 -->
9
<properties>
10
<project.source.encoding>utf-8</project.source.encoding>
11
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12
<!-- 防止控制輸出台中文亂碼 -->
13
<argLine>-Dfile.encoding=UTF-8</argLine>
14
</properties>
15
16
<dependencies>
17
<!-- junit_jar包依賴 -->
18
<dependency>
19
<groupId>junit</groupId>
20
<artifactId>junit</artifactId>
21
<version>4.11</version>
22
<!--保留到測試 -->
23
<scope>test</scope>
24
</dependency>
25
<!-- spring-data-jpa相關依賴
26
(這個依賴自動把一堆spring的東西依賴進來了,所有可以不需要再引入spring的包)-->
27
<dependency>
28
<groupId>org.springframework.data</groupId>
29
<artifactId>spring-data-jpa</artifactId>
30
<version>1.11.7.RELEASE</version>
31
</dependency>
32
<!-- Hibernate -->
33
<dependency>
34
<groupId>org.hibernate</groupId>
35
<artifactId>hibernate-core</artifactId>
36
<version>5.0.11.Final</version>
37
</dependency>
38
<dependency>
39
<groupId>org.hibernate</groupId>
40
<artifactId>hibernate-entitymanager</artifactId>
41
<version>5.0.11.Final</version>
42
</dependency>
43
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
44
<dependency>
45
<groupId>com.mchange</groupId>
46
<artifactId>c3p0</artifactId>
47
<version>0.9.5.2</version>
48
</dependency>
49
<!-- mysql驅動 -->
50
<dependency>
51
<groupId>mysql</groupId>
52
<artifactId>mysql-connector-java</artifactId>
53
<version>5.1.29</version>
54
</dependency>
55
</dependencies>
56
57
<build>
58
<plugins>
59
<!-- 編譯插件 -->
60
<plugin>
61
<groupId>org.apache.maven.plugins</groupId>
62
<artifactId>maven-compiler-plugin</artifactId>
63
<version>2.5.1</version>
64
<configuration>
65
<!-- 源碼用1.8 -->
66
<source>1.8</source>
67
<!-- 打成jar用1.8 -->
68
<target>1.8</target>
69
<encoding>utf-8</encoding>
70
</configuration>
71
</plugin>
72
</plugins>
73
</build>
74
</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
6
1
jdbcUrl=jdbc:mysql://localhost:3306/springdata?useUnicode=true&characterEncoding=utf8
2
driverClass=com.mysql.jdbc.Driver
3
user=root
4
password=root
5
initialPoolSize=10
6
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>
61
1
2
<beans xmlns="http://www.springframework.org/schema/beans"
3
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
xmlns:context="http://www.springframework.org/schema/context"
5
xmlns:tx="http://www.springframework.org/schema/tx"
6
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
7
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
8
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
9
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
10
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
11
12
<!-- 配置自動掃描的包,掃描service層,service上面有注解就直接被spring容器實例化 -->
13
<context:component-scan base-package="com.zxy.service"/>
14
<!-- 1. 配置數據源 -->
15
<context:property-placeholder location="classpath:db.properties"/>
16
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
17
<property name="jdbcUrl" value="${jdbcUrl}"/>
18
<property name="driverClass" value="${driverClass}"/>
19
<property name="user" value="${user}"/>
20
<property name="password" value="${password}"/>
21
<property name="initialPoolSize" value="${initialPoolSize}"/>
22
<property name="maxPoolSize" value="${maxPoolSize}"/>
23
</bean>
24
25
<!-- 2. 配置 JPA 的 EntityManagerFactory -->
26
<bean id="entityManagerFactory"
27
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
28
<property name="dataSource" ref="dataSource"/>
29
<property name="jpaVendorAdapter">
30
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
31
</property>
32
<!-- 配置包掃描,掃描實體 -->
33
<property name="packagesToScan" value="com.zxy.entity"/>
34
<property name="jpaProperties">
35
<props>
36
<!-- 生成的數據表的列的映射策略 -->
37
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
38
<!-- hibernate 基本屬性 -->
39
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
40
<prop key="hibernate.show_sql">true</prop>
41
<prop key="hibernate.format_sql">true</prop>
42
<prop key="hibernate.hbm2ddl.auto">update</prop>
43
</props>
44
</property>
45
</bean>
46
47
<!-- 3. 配置事務管理器 -->
48
<bean id="transactionManager"
49
class="org.springframework.orm.jpa.JpaTransactionManager">
50
<property name="entityManagerFactory" ref="entityManagerFactory"/>
51
</bean>
52
53
<!-- 4. 配置支持注解的事務 -->
54
<tx:annotation-driven transaction-manager="transactionManager"/>
55
56
<!-- 5. 配置 SpringData,需要加入 jpa 的命名空間 -->
57
<!-- base-package: 掃描 Repository Bean 所在的 package -->
58
<jpa:repositories base-package="com.zxy.dao" entity-manager-factory-ref="entityManagerFactory">
59
</jpa:repositories>
60
61
</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());
}
}
29
1
package com.zxy.test;
2
import org.springframework.context.ApplicationContext;
3
import org.springframework.context.support.ClassPathXmlApplicationContext;
4
import java.sql.SQLException;
5
import javax.sql.DataSource;
6
import org.junit.Test;
7
8
/**
9
* 整合效果測試類
10
* @author ZENG.XIAO.YAN
11
* @date 2017年9月14日 下午11:01:20
12
* @version v1.0
13
*/
14
public class TestConfig {
15
private static ApplicationContext ctx ;
16
static {
17
// 通過靜態代碼塊的方式,讓程序加載spring的配置文件
18
ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
19
}
20
21
/** 測試spring容器是否實例化了數據源 。如果實例化了,說明Spring容器整合沒問題 */
22
23
public void testDataSouce() throws SQLException {
24
DataSource dataSouce = (DataSource) ctx.getBean("dataSource");
25
System.out.println("數據源:"+ dataSouce);
26
System.out.println("連接:"+ dataSouce.getConnection());
27
}
28
29
}
成功后控制台輸出結果如下:

<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;
}
}
53
1
package com.zxy.entity;
2
import java.util.Date;
3
import javax.persistence.Column;
4
import javax.persistence.Entity;
5
import javax.persistence.GeneratedValue;
6
import javax.persistence.GenerationType;
7
import javax.persistence.Id;
8
import javax.persistence.Table;
9
/**
10
* Person實體
11
* @author ZENG.XIAO.YAN
12
* @date 2017年9月14日 下午2:44:23
13
* @version v1.0
14
*/
15
16
name="jpa_persons") (
17
public class Person {
18
19
(strategy=GenerationType.IDENTITY)
20
private Integer id;
21
22
private String name;
23
24
private String email;
25
26
private Date birth;
27
28
/** setter and getter method */
29
public Integer getId() {
30
return id;
31
}
32
public void setId(Integer id) {
33
this.id = id;
34
}
35
public String getName() {
36
return name;
37
}
38
public void setName(String name) {
39
this.name = name;
40
}
41
public String getEmail() {
42
return email;
43
}
44
public void setEmail(String email) {
45
this.email = email;
46
}
47
public Date getBirth() {
48
return birth;
49
}
50
public void setBirth(Date birth) {
51
this.birth = birth;
52
}
53
}
添加完這個實體后,還是運行下
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);
}
23
1
package com.zxy.dao;
2
import org.springframework.data.repository.Repository;
3
import org.springframework.data.repository.RepositoryDefinition;
4
import com.zxy.entity.Person;
5
6
/**
7
* PersonDao
8
* @author ZENG.XIAO.YAN
9
* @date 2017年9月18日 下午4:25:39
10
* @version v1.0
11
*/
12
13
/*
14
* 1.Repository是一個空接口,即是一個標記接口
15
* 2.若我們定義的接口繼承了Repository,則該接口會被IOC容器識別為一個Repository Bean
16
* 注入到IOC容器中,進而可以在該接口中定義滿足一定規則的接口
17
* 3.實際上也可以通過一個注解@RepositoryDefination 注解來替代Repository接口
18
*/
19
//@RepositoryDefinition(domainClass=Person.class,idClass=Integer.class)
20
public interface PersonDao extends Repository<Person, Integer> {
21
// 通過id查找實體
22
Person getById(Integer id);
23
}
其實也可以用注解
@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());
}
}
29
1
package com.zxy.test;
2
import org.junit.Test;
3
import org.springframework.context.ApplicationContext;
4
import org.springframework.context.support.ClassPathXmlApplicationContext;
5
import com.zxy.dao.PersonDao;
6
import com.zxy.entity.Person;
7
8
/**
9
* SpringData快速入門測試類
10
* @author ZENG.XIAO.YAN
11
* @date 2017年9月18日 下午5:33:42
12
* @version v1.0
13
*/
14
public class TestQuickStart {
15
private static ApplicationContext ctx ;
16
static {
17
// 通過靜態代碼塊的方式,讓程序加載spring的配置文件
18
ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
19
}
20
21
/** 測試PersonDao中定義的getById的方法能否查詢出結果 */
22
23
public void testGetById() {
24
PersonDao personDao = ctx.getBean(PersonDao.class);
25
Person person = personDao.getById(1);
26
System.out.println("查詢結果: name=" + person.getName() + ",id=" + person.getId());
27
}
28
29
}
測試的結果如下圖所示,我們只聲明了接口和定義了方法就從數據庫查到了數據,這就是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);
2
1
// 通過id和name查詢實體,sql: select * from jpa_persons where id = ? and name = ?
2
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);
}
7
1
/** 測試getByIdAndName方法 */
2
3
public void testGetByIdAndName() {
4
PersonDao personDao = ctx.getBean(PersonDao.class);
5
Person person = personDao.findByIdAndName(1, "test0");
6
System.out.println(person);
7
}
運行的結果如下,成功的查詢到了數據

(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);
8
1
// where id < ? or birth < ?
2
List<Person> findByIdIsLessThanOrBirthLessThan(Integer id, Date birth);
3
4
// where email like ?
5
List<Person> findByEmailLike(String email);
6
7
// 也支持count查詢
8
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());
}
}
x
1
/** 測試findByEmailLike方法 */
2
3
public void testFindByEmailLike() {
4
PersonDao personDao = ctx.getBean(PersonDao.class);
5
List<Person> list = personDao.findByEmailLike("test%");
6
for (Person person : list) {
7
System.out.println(person.getEmail());
8
}
9
}
10
11
/** 測試findByIdIsLessThanOrBirthLessThan方法 */
12
13
public void testFindByIdIsLessThanOrBirthLessThan() {
14
PersonDao personDao = ctx.getBean(PersonDao.class);
15
List<Person> list = personDao.findByIdIsLessThanOrBirthLessThan(3, new Date());
16
for (Person person : list) {
17
System.out.println("查詢結果: name=" + person.getName()
18
+ ",id=" + person.getId() + ",birth=" + person.getBirth());
19
}
20
}
運行結果如下:


(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);
2
1
// 級聯查詢,查詢address的id等於條件值
2
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());
}
}
12
1
/** 測試findByAddressId方法 */
2
3
public void testFindByAddressId() {
4
PersonDao personDao = ctx.getBean(PersonDao.class);
5
// 查出地址id為1的person集合
6
List<Person> list = personDao.findByAddressId(1);
7
for (Person person : list) {
8
System.out.println(person.getName()
9
+ "---addressId="
10
+ person.getAddress().getId());
11
}
12
}
- 運行測試方法,通過控制台可觀察生成的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);
四、@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();
7
1
// 自定義的查詢,直接寫jpql語句; 查詢id<? 或者 名字 like?的person集合
2
"from Person where id < ?1 or name like ?2") (
3
List<Person> testPerson(Integer id, String name);
4
5
// 自定義查詢之子查詢,直接寫jpql語句; 查詢出id最大的person
6
"from Person where id = (select max(p.id) from Person as p)") (
7
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());
}
12
1
/** 測試用Query注解自定義的方法 */
2
3
public void testCustomMethod() {
4
PersonDao personDao = ctx.getBean(PersonDao.class);
5
List<Person> list = personDao.testPerson(2, "%admin%");
6
for (Person person : list) {
7
System.out.println("查詢結果: name=" + person.getName() + ",id=" + person.getId());
8
}
9
System.out.println("===============分割線===============");
10
Person person = personDao.testSubquery();
11
System.out.println("查詢結果: name=" + person.getName() + ",id=" + person.getId());
12
}
- 查詢結果及生成的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);
7
1
//可以通過自定義的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
2
//在 @Query 注解中編寫 JPQL 語句, 但必須使用 @Modifying 進行修飾. 以通知 SpringData, 這是一個 UPDATE 或 DELETE 操作
3
//UPDATE 或 DELETE 操作需要使用事務, 此時需要定義 Service 層. 在 Service 層的方法上添加事務操作.
4
//默認情況下, SpringData 的每個方法上有事務, 但都是一個只讀事務. 他們不能完成修改操作!
5
6
"UPDATE Person p SET p.name = :name WHERE p.id < :id") (
7
int updatePersonById( ("id")Integer id, ("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);
}
}
1
package com.zxy.service;
2
import org.springframework.beans.factory.annotation.Autowired;
3
import org.springframework.stereotype.Service;
4
import org.springframework.transaction.annotation.Transactional;
5
import com.zxy.dao.PersonDao;
6
/**
7
* PersonService
8
* @author ZENG.XIAO.YAN
9
* @date 2017年9月20日 下午2:57:16
10
* @version v1.0
11
*/
12
"personService") (
13
public class PersonService {
14
15
private PersonDao personDao;
16
17
(readOnly=false)
18
public int updatePersonById(Integer id, String updateName) {
19
return personDao.updatePersonById(id, updateName);
20
}
21
}
- 測試類中直接調用service的方法就ok了,測試代碼如下圖

使用@Modifying+@Query時的
注意事項:
- 方法返回值是int,表示影響的行數
- 在調用的地方必須加事務,沒事務不執行
(2)事務
- Spring Data 提供了默認的事務處理方式,即所有的查詢均聲明為只讀事務。
- 對於自定義的方法,如需改變 Spring Data 提供的事務默認方式,可以在方法上注解 @Transactional 聲明
- 進行多個 Repository 操作時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,因此,需要在 Service 層實現對多個 Repository 的調用,並在相應的方法上聲明事務。
六、本文小結
(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的事務。