网址:https://www.cnblogs.com/zeng1994/p/7575606.html
Spring家族越来越强大,作为一名javaWeb开发人员,学习Spring家族的东西是必须的。在此记录学习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)开发一般分三个步骤:
(1)创建项目并添加Maven依赖
首先我们在eclipse中创建一个Maven的java项目,然后添加依赖。
主要依赖有:
pom.xml文件的代码如下
1
2
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">3
4.0.0 4
com.zxy 5
springdata-demo 6
0.0.1-SNAPSHOT 7
8
9
10
utf-8 11
UTF-8 12
13
-Dfile.encoding=UTF-8 14
15
16
17
18
19
junit 20
junit 21
4.11 22
23
test 24
25
27
28
org.springframework.data 29
spring-data-jpa 30
1.11.7.RELEASE 31
32
33
34
org.hibernate 35
hibernate-core 36
5.0.11.Final 37
38
39
org.hibernate 40
hibernate-entitymanager 41
5.0.11.Final 42
43
44
45
com.mchange 46
c3p0 47
0.9.5.2 48
49
50
51
mysql 52
mysql-connector-java 53
5.1.29 54
55
56
57
58
59
60
61
org.apache.maven.plugins 62
maven-compiler-plugin 63
2.5.1 64
65
66
67
68
1.8 69
utf-8 70
71
72
73
74
这里我解释下为何不添加Spring的其他的依赖,主要是spring-data-jpa这个依赖了一堆spring相关的依赖。见下图就明白了
(2)整合SpringData,配置applicationContext.xml
这个整合很重要,我在网上找了好久,没找到特别好的demo;因此特意把这一步记录下来。
<1> 首先我们添加一个和数据库相关的properties文件;新建db.properties文件,内容如下
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文件。里面大致配置的东西有:
文件里面的具体内容如下:
1
2
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.xsd8
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd9
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd10
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">28
29
30
31
32
33
34
35
36
37
org.hibernate.cfg.ImprovedNamingStrategy 38
39
org.hibernate.dialect.MySQL5InnoDBDialect 40
true 41
true 42
update 43
44
45
46
47
48
49
class="org.springframework.orm.jpa.JpaTransactionManager">50
51
52
53
54
55
56
57
58
59
60
61
(3)测试整合
<1> 先测试下Spring容器是否整合成功
我们在com.zxy.test包中新建一个TestConfig的类,在类里面我们写单元测试的代码。主要内容有:
代码如下:
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
@Test
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下面的实体类。测试方法如下:
实体类的代码如下:
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
@Entity
16
@Table(name="jpa_persons")
17
public class Person {
18
@Id
19
@GeneratedValue(strategy=GenerationType.IDENTITY)
20
private Integer id;
21
@Column
22
private String name;
23
@Column
24
private String email;
25
@Column
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的代码如下:
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{
21
// 通过id查找实体
22
Person getById(Integer id);
23
}
其实也可以用注解@RepositoryDefination来代替继承接口Repository接口,这里不做过多介绍这个注解,更多和该注解的相关知识请查阅相关资料。
(5)测试dao层接口
由于我们数据库中jpa_persons这个表还没数据,先在这表中手动插入几条测试数据。
有了数据后,我们在com.zxy.test层新建一个名为TestQucikStart的测试类。还是采用静态代码快的方式来加载Spring配置文件的方式来使用Spring容器,在后续贴的代码中,这部分代码可能会不贴出来。这里先声明一下,后续在代码中看到的ctx是其实就是Spring容器的意思,它都是这样获取的。
测试类代码如下:
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
@Test
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的强大之处。
通过上面的QucikStart的案例,我们了解到在使用SpringData时只需要定义Dao层接口及定义方法就可以操作数据库。但是,这个Dao层接口中的方法也是有定义规范的,只有按这个规范来,SpringData才能识别并实现该方法。下面来说说方法定义的规范。
(1)简单的条件查询的方法定义规范
方法定义规范如下:
下面来看个案例吧,操作的实体依旧上面的Person,下面写个通过id和name查询出Person对象的案例。
在PersonDao这个接口中,定义一个通过id和name查询的方法
1
// 通过id和name查询实体,sql: select * from jpa_persons where id = ? and name = ?
2
Person findByIdAndName(Integer id, String name);
在TestQucikStart这个测试类中,写个单元测试方法testFindByIdAndName来测试这个dao层的方法是否可用
1
/** 测试getByIdAndName方法 */
2
@Test
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接口新增代码如下:
1
// where id < ? or birth < ?
2
ListfindByIdIsLessThanOrBirthLessThan(Integer id, Date birth);
3
4
// where email like ?
5
ListfindByEmailLike(String email);
6
7
// 也支持count查询
8
long countByEmailLike(String email);
在TestQucikStart中添加以下2个单元测试方法,测试dao层接口中的方法是否可用
1
/** 测试findByEmailLike方法 */
2
@Test
3
public void testFindByEmailLike() {
4
PersonDao personDao = ctx.getBean(PersonDao.class);
5
Listlist = personDao.findByEmailLike("test%");
6
for (Person person : list) {
7
System.out.println(person.getEmail());
8
}
9
}
10
11
/** 测试findByIdIsLessThanOrBirthLessThan方法 */
12
@Test
13
public void testFindByIdIsLessThanOrBirthLessThan() {
14
PersonDao personDao = ctx.getBean(PersonDao.class);
15
Listlist = 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层接口中定义的方法支持级联查询,下面通过一个案例来介绍这个级联查询:
1
// 级联查询,查询address的id等于条件值
2
ListfindByAddressId(Integer addressId);
1
/** 测试findByAddressId方法 */
2
@Test
3
public void testFindByAddressId() {
4
PersonDao personDao = ctx.getBean(PersonDao.class);
5
// 查出地址id为1的person集合
6
Listlist = personDao.findByAddressId(1);
7
for (Person person : list) {
8
System.out.println(person.getName()
9
+ "---addressId="
10
+ person.getAddress().getId());
11
}
12
}
这里我解释下这个生成的sql吧,首先是一个左外连接查询出结果,由于Person中有个Address的实体,所以就又发送了一次查询address的sql。产生这个的原因是@ManyToOne这个注解默认是禁用延迟加载的,所以会把关联属性的值也会查询出来。
(4)查询方法解析流程
通过以上的学习,掌握了在接口中定义方法的规则,我们就可以定义出很多不用写实现的方法了。这里再介绍下查询方法的解析的流程吧,掌握了这个流程,对于定义方法有更深的理解。
<1> 方法参数不带特殊参数的查询
假如创建如下的查询:findByUserDepUuid(),框架在解析该方法时,流程如下:
可能会存在一种特殊情况,比如 Doc包含一个 user 的属性,也有一个 userDep 属性,此时会存在混淆。可以明确在级联的属性之间加上 "_" 以显式表达意图,比如 "findByUser_DepUuid()" 或者 "findByUserDep_uuid()"。
<2> 方法参数带特殊参数的查询
特殊的参数: 还可以直接在方法的参数上加入分页或排序的参数,比如:
Page
List
通过上面的学习,我们在dao层接口按照规则来定义方法就可以不用写方法的实现也能操作数据库。但是如果一个条件查询有多个条件时,写出来的方法名字就太长了,所以我们就想着不按规则来定义方法名。我们可以使用@Query这个注解来实现这个功能,在定义的方法上加上@Query这个注解,将查询语句声明在注解中,也可以查询到数据库的数据。
(1)使用Query结合jpql语句实现自定义查询
1
// 自定义的查询,直接写jpql语句; 查询id 或者 名字 like?的person集合
2
@Query("from Person where id < ?1 or name like ?2")
3
ListtestPerson(Integer id, String name);
4
5
// 自定义查询之子查询,直接写jpql语句; 查询出id最大的person
6
@Query("from Person where id = (select max(p.id) from Person as p)")
7
Person testSubquery();
1
/** 测试用Query注解自定义的方法 */
2
@Test
3
public void testCustomMethod() {
4
PersonDao personDao = ctx.getBean(PersonDao.class);
5
Listlist = 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
}
(2)索引参数和命名参数
在写jpql语句时,查询条件的参数的表示有以下2种方式:
说一个特殊情况,那就是自定义的Query查询中jpql语句有like查询时,可以直接把%号写在参数的前后,这样传参数就不用把%号拼接进去了。使用案例如下,调用该方法时传递的参数直接传就ok。
(3)使用@Query来指定使用本地SQL查询
如果你不熟悉jpql语句,你也可以写sql语句查询,只需要在@Query注解中设置nativeQuery=true。直接来看案例吧
(1)@Modifying注解的使用
@Query与@Modifying这两个注解一起使用时,可实现个性化更新操作及删除操作;例如只涉及某些字段更新时最为常见。
下面演示一个案例,把id小于3的person的name都改为'admin'
1
//可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
2
//在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作
3
//UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作.
4
//默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作!
5
@Modifying
6
@Query("UPDATE Person p SET p.name = :name WHERE p.id < :id")
7
int updatePersonById(@Param("id")Integer id, @Param("name")String 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
@Service("personService")
13
public class PersonService {
14
@Autowired
15
private PersonDao personDao;
16
17
@Transactional(readOnly=false)
18
public int updatePersonById(Integer id, String updateName) {
19
return personDao.updatePersonById(id, updateName);
20
}
21
}
使用@Modifying+@Query时的注意事项:
(2)事务
(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的事务。