Spring Boot学习笔记06--JPA

摘要

看完本文你将掌握如下知识点:

  1. Spring Boot项目中JPA的配置及使用方法
  2. Spring Boot项目配置Spring Data JPA的方法
  3. Spring Data JPA与Atomikos整合实现多数据源事务管理
  4. 扩展JPA的方法

SpringBoot系列Spring Boot学习笔记


前言

JPA即Java Persistence API,是一个基于O/R映射的标准规范,该规范只负责定义规则的标准(注解或接口),而不需要提供具体实现,具体的实现交由软件提供商来实现,目前主要的JPA提供商为Hibernate,EclipseLink和OperJPA。

Spring Data JPA是Spring Data的一个子项目,通过提供基于JPA的Repository来简化代码量。
其提供了一个org.springframework.data.jpa.repository.JpaRepository,我们的Repository只要继承该JpaRepository,即可享受到JPA带来的好处。

Spring Boot通过spring-boot-starter-data-jpa来提供对JPA的支持,Spring Boot默认的JPA实现者是Hibernate。


说明
在讲解下面的内容前,我们先在数据库中创建一张表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建库1
CREATE SCHEMA `springboot1` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot1`.`person` (
`p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`p_name` VARCHAR(45) NULL COMMENT '姓名',
`p_age` INT NULL COMMENT '年龄',
PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人员信息表';

INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '张三', '20');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('3', '王五', '18');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('4', '王五', '18');

Spring Boot项目中使用JPA

创建项目时选择JPA依赖,或者手工将spring-boot-starter-data-jpa添加到pom中。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

此时项目会自动开启如下两个自动配置类:

JpaRepositoriesAutoConfiguration
HibernateJpaAutoConfiguration

application.properties中增加jpa相关配置

1
2
3
4
5
6
7
8
9
10
11
12
13
#datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot1?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=newpwd

#spring_jpa
#启动时会根据实体类生成数据表,或者更新表结构,不清空数据,开发阶段使用;validate:表结构稳定后使用,可用于正式环境;
spring.jpa.hibernate.ddl-auto=update
#控制台打印sql
spring.jpa.show-sql=true
#让控制器输出的json格式更美观
spring.jackson.serialization.indent-output=true

在项目中使用JPA时,只需要创建一个继承于JpaRepository的Repository接口,即可拥有JpaRepository及其父类中提供的全部数据访问方法。如果提供的方法不满足业务需要,可以按如下规则扩展数据方法。

JpaRepository

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package org.springframework.data.jpa.repository;

import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();

List<T> findAll(Sort var1);

List<T> findAll(Iterable<ID> var1);

<S extends T> List<S> save(Iterable<S> var1);

void flush();

<S extends T> S saveAndFlush(S var1);

void deleteInBatch(Iterable<T> var1);

void deleteAllInBatch();

T getOne(ID var1);

<S extends T> List<S> findAll(Example<S> var1);

<S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

自定义Repository:PersonRepository,并扩展数据访问方法,具体扩展方法参看示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package com.example.dao;

import com.example.model.Person;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;

public interface PersonRepository extends JpaRepository<Person, Integer> {

//1.以下方法基于属性名称和查询关键字,所以方法名称必须遵循命名规则,并且参数类型要与实体的参数类型一致。
// 只用于查询方法,以下给出常用的示例

//等于
List<Person> findByPName(String PName);

//And --- 等价于 SQL 中的 and 关键字;
List<Person> findByPNameAndPAge(String PName, Integer PAge);

// Or --- 等价于 SQL 中的 or 关键字;
List<Person> findByPNameOrPAge(String PName, Integer PAge);

//Between --- 等价于 SQL 中的 between 关键字;
List<Person> findByPAgeBetween(Integer min, Integer max);

//LessThan --- 等价于 SQL 中的 "<"; 日期类型也可以使用Before关键字
List<Person> findByPAgeLessThan(Integer max);

//LessThanEqual --- 等价于 SQL 中的 "<=";
List<Person> findByPAgeLessThanEqual(Integer max);

//GreaterThan --- 等价于 SQL 中的">";日期类型也可以使用After关键字
List<Person> findByPAgeGreaterThan(Integer min);

//GreaterThanEqual --- 等价于 SQL 中的">=";
List<Person> findByPAgeGreaterThanEqual(Integer min);

//IsNull --- 等价于 SQL 中的 "is null";
List<Person> findByPNameIsNull();

//IsNotNull --- 等价于 SQL 中的 "is not null";
List<Person> findByPNameIsNotNull();

//NotNull --- 与 IsNotNull 等价;
List<Person> findByPNameNotNull();

//Like --- 等价于 SQL 中的 "like";
List<Person> findByPNameLike(String PName);

//NotLike --- 等价于 SQL 中的 "not like";
List<Person> findByPNameNotLike(String PName);

//OrderBy --- 等价于 SQL 中的 "order by";
List<Person> findByPNameNotNullOrderByPAgeAsc();

//Not --- 等价于 SQL 中的 "! =";
List<Person> findByPNameNot(String PName);

//In --- 等价于 SQL 中的 "in";
List<Person> findByPNameIn(String PName);

//NotIn --- 等价于 SQL 中的 "not in";
List<Person> findByPNameNotIn(String PName);


//Top --- 查询符合条件的前两条记录,等价与First关键字
List<Person> findTop2ByPName(String PName);

//2.以下方法基于@Query注解,方法名称可以随意,可用于查询和更新方法,更新方法要设置@Modifying注解
//使用命名参数
@Query("select p from Person p where p.pName = :name and p.pAge = :age")
List<Person> withNameAndAgeQuery(@Param("name") String name, @Param("age") Integer age);

//使用参数索引
@Query("select p from Person p where p.pName = ?1 and p.pAge = ?2")
List<Person> withNameAndAgeQuery2(String name, Integer age);


//删除操作,使用hql,如果要使用sql,需要增加nativeQuery = true
@Query(value = "delete from Person where pId=?1")
@Modifying
int deletePersonById(Integer id);

//修改操作
@Query(value = "update Person set pName=?1 where pId=?2 ")
@Modifying
int updatePersonName(String name, Integer id);

//插入操作,使用sql操作
@Query(value = "insert into person(p_name,p_age) value(?1,?2)",nativeQuery = true)
@Modifying
int insertPersonByParam(String name, Integer age);


//3.以下方法实现分页查询功能,只需要在方法中增加Pageable pageable参数即可,返回结果为Page集合
Page<Person> findByPNameNot(String name, Pageable pageable);

//使用命名参数
@Query("select p from Person p where p.pName = :name ")
Page<Person> withNameQueryPage(@Param("name") String name, Pageable pageable);
}

POJO实体对象:Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.model;
import javax.persistence.*;

import static javax.persistence.GenerationType.IDENTITY;

@Entity
@Table(name = "person"
, catalog = "springboot1"
)
public class Person implements java.io.Serializable {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "p_id", unique = true, nullable = false)
private Integer pId;

@Column(name = "p_name", length = 45)
private String pName;

@Column(name = "p_age")
private Integer pAge;

//setter and getter

@Override
public String toString() {
return "Person{" +
"pId=" + pId +
", pName='" + pName + '\'' +
", pAge=" + pAge +
'}';
}
}

测试演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package com.example;

import com.example.dao.PersonRepository;
import com.example.model.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.Iterator;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class JpaSingleDatasourceApplicationTests {

@Autowired
private PersonRepository personRepository;

@Test
public void findByPName() {
String name = "王五";
List<Person> list = personRepository.findByPName(name);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}

@Test
public void findByPNameAndPAge() {
String name = "王五";
int age = 18;
List<Person> list = personRepository.findByPNameAndPAge(name,age);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}

@Test
public void findByPNameOrPAge() {
String name = "王五";
int age = 25;
List<Person> list = personRepository.findByPNameOrPAge(name,age);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}

@Test
public void findTop2ByPName() {
String name = "王五";
List<Person> list = personRepository.findTop2ByPName(name);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}

@Test
public void withNameAndAgeQuery() {
String name = "王五";
int age = 18;
List<Person> list = personRepository.withNameAndAgeQuery(name,age);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}

@Test
public void withNameAndAgeQuery2() {
String name = "王五";
int age = 18;
List<Person> list = personRepository.withNameAndAgeQuery2(name,age);
System.out.println(list.size());
for(Person person : list){
System.out.println(person);
}
}


@Test
public void deletePersonById(){
int id = 1;
int result = personRepository.deletePersonById(id);
System.out.println("result = " + result);
}

@Test
public void updatePersonName(){
int id = 1;
String name = "哈哈";
int result = personRepository.updatePersonName(name,id);
System.out.println("result = " + result);
}

@Test
public void insertPersonByParam(){
int age = 10;
String name = "哈哈";
int result = personRepository.insertPersonByParam(name,age);
System.out.println("result = " + result);
}

@Test
public void findByPNameNot(){
String name = "哈哈";
//排序
Sort sort = new Sort(Sort.Direction.DESC, "pId");
//查询第一页,按一页三行分页
Pageable pageable = new PageRequest(0, 3, sort);

Page<Person> pages = personRepository.findByPNameNot(name,pageable);
System.out.println("pages.getTotalElements()" + pages.getTotalElements());
System.out.println("pages.getTotalPages()" + pages.getTotalPages());
Iterator<Person> it=pages.iterator();
while(it.hasNext()){
System.out.println("value:"+((Person)it.next()));
}
}

@Test
public void withNameQueryPage(){
String name = "王五";
//排序
Sort sort = new Sort(Sort.Direction.DESC, "pId");
//查询第二页,按一页三行分页
Pageable pageable = new PageRequest(1, 3, sort);

Page<Person> pages = personRepository.withNameQueryPage(name,pageable);
System.out.println("pages.getTotalElements()" + pages.getTotalElements());
System.out.println("pages.getTotalPages()" + pages.getTotalPages());
Iterator<Person> it=pages.iterator();
while(it.hasNext()){
System.out.println("value:"+((Person)it.next()));
}
}
}

Spring Boot项目配置Spring Data JPA的方法

如果不想依赖于spring-boot-starter-data-jpa,我们依然可以通过配置类来实现Spring Boot对Spring Data JPA的支持。

pom替换依赖
这里说明一下,实际上我们可以不用替换掉spring-boot-starter-data-jpa的依赖,替换掉的好处仅仅是减少对不需要的jar的依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>

<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>

<!-- hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.5.Final</version>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.5.Final</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.10.5.RELEASE</version>
</dependency>

<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>-->

自定义配置类:DataSourceConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.example;

import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
//开启Spring Data JPA的支持
@EnableJpaRepositories(basePackages = "com.example.dao", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
public class DataSourceConfig {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;

@Bean(name = "dataSource")
public DataSource dataSource() {

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(passWord);
return dataSource;
}

// jpa事务管理器
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setDataSource(dataSource());
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return jpaTransactionManager;
}

@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(true);
adapter.setDatabase(Database.MYSQL);
adapter.setGenerateDdl(true);
return adapter;
}


@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(dataSource());
entityManager.setJpaVendorAdapter(jpaVendorAdapter());
entityManager.setPackagesToScan("com.example.model");// entity package
entityManager.setPersistenceProviderClass(HibernatePersistenceProvider.class);
return entityManager;
}
}

项目启动类中要关闭jpa的自动配置:

1
2
3
4
5
6
7
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,JpaRepositoriesAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class JpaSingleDatasourceApplication {

public static void main(String[] args) {
SpringApplication.run(JpaSingleDatasourceApplication.class, args);
}
}

Spring Data JPA与Atomikos整合实现多数据源事务管理

spring-data-jpa虽说默认使用的是Hibernate,但是其与Atomikos整合方式与Hibernate略有不同。

pom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions</artifactId>
<version>4.0.4</version>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>atomikos-util</artifactId>
<version>4.0.4</version>
</dependency>

<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>

<!-- hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.5.Final</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.10.5.RELEASE</version>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.5.Final</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.37</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

application.properties

1
2
3
4
5
6
7
8
9
10
11
#datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot1?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=newpwd

#datasource2
spring.datasource.driver-class-name2=com.mysql.jdbc.Driver
spring.datasource.url2=jdbc:mysql://localhost:3306/springboot2?useUnicode=true&characterEncoding=utf-8
spring.datasource.username2=root
spring.datasource.password2=newpwd

MainConfig:用于注册Atomikos事务管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.example;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

@Configuration
public class MainConfig {

@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}

@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}

@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}

//上面三个都认识,下面说一下这个bean
@Bean(name = "atomikosJtaPlatfom")
public AtomikosJtaPlatfom atomikosJtaPlatfom(){
AtomikosJtaPlatfom atomikosJtaPlatfom = new AtomikosJtaPlatfom();
try {
atomikosJtaPlatfom.setTm(atomikosTransactionManager());
atomikosJtaPlatfom.setUt(userTransaction());
} catch (Throwable throwable) {
throwable.printStackTrace();
}

return atomikosJtaPlatfom;

}
}

配置JPA的LocalContainerEntityManagerFactoryBean时候,如果要使其能够支持JTA事务,则在配置其JpaProperties时需要为其指定如下参数:

hibernate.transaction.jta.platform
hibernate.current_session_context_class
hibernate.transaction.factory_class

后面我们配置LocalContainerEntityManagerFactoryBean的时候会看到相应的配置,
这里要说的是,hibernate.transaction.jta.platform需要指定org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform的实现类,其主要功能就是要绑定javax.transaction.TransactionManagerjavax.transaction.UserTransaction

spring-data-jpa没有提供该实现类,但是hibernate提供了许多实现类,spring boot也提供了一个实现类–SpringJtaPlatform
但是这些实现类都是通过构造函数绑定javax.transaction.TransactionManagerjavax.transaction.UserTransaction,而没有提供缺省的构造方法,这就导致通过属性指定hibernate.transaction.jta.platform时,spring不能初始化该实现类(可能是我还没有搞明白吧)。

所以,可以自己创建一个实现类,并通过set方法来绑定javax.transaction.TransactionManagerjavax.transaction.UserTransaction
这就是AtomikosJtaPlatfom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.example;

import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

public class AtomikosJtaPlatfom extends AbstractJtaPlatform {

private static UserTransaction ut;
private static TransactionManager tm;
@Override
protected TransactionManager locateTransactionManager() {
return tm;
}

@Override
protected UserTransaction locateUserTransaction() {
return ut;
}

public UserTransaction getUt() {
return ut;
}

public void setUt(UserTransaction ut) {
AtomikosJtaPlatfom.ut = ut;
}

public TransactionManager getTm() {
return tm;
}

public void setTm(TransactionManager tm) {
AtomikosJtaPlatfom.tm = tm;
}
}

接下来需要在配置类中注册LocalContainerEntityManagerFactoryBean
由于*@EnableJpaRepositories*注解不能在同一个配置类上声明两次,所以就按数据源进行分别设置:

JpaConfigDs1:数据源1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.example;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
//指定数据源1的Repository路径,数据源1的entityManagerFactory,事务是公共事务
@EnableJpaRepositoryies(basePackages = "com.example.dao.ds1", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
public class JpaConfigDs1 {

@Value("${spring.datasource.driver-class-name}")
String driverClass;
@Value("${spring.datasource.url}")
String url;
@Value("${spring.datasource.username}")
String userName;
@Value("${spring.datasource.password}")
String passWord;


@Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
System.out.println("dataSource init");
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPassword(passWord);
mysqlXaDataSource.setUser(userName);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);

return xaDataSource;
}

@Bean(name = "jpaVendorAdapter")
public JpaVendorAdapter jpaVendorAdapter() {
System.out.println("jpaVendorAdapter init");
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(true);
adapter.setDatabase(Database.MYSQL);
adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
adapter.setGenerateDdl(true);
return adapter;
}

@Bean(name = "entityManagerFactory")
@DependsOn({"atomikosJtaPlatfom"}) //需要先注册atomikosJtaPlatfom
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
System.out.println("entityManagerFactory init");
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();

entityManager.setJpaVendorAdapter(jpaVendorAdapter());
// entity package
entityManager.setPackagesToScan("com.example.model.ds1");
entityManager.setJtaDataSource(dataSource());

Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");

//jta设置
properties.put("hibernate.current_session_context_class", "jta");
properties.put("hibernate.transaction.factory_class", "org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory");
//这里指定我们自己创建的AtomikosJtaPlatfom
properties.put("hibernate.transaction.jta.platform","com.example.AtomikosJtaPlatfom");
entityManager.setJpaProperties(properties);
return entityManager;

}
}

JpaConfigDs2:数据源2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.example;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@EnableJpaRepositories(basePackages = "com.example.dao.ds2", entityManagerFactoryRef = "entityManagerFactory2", transactionManagerRef = "transactionManager")
public class JpaConfigDs2 {

@Value("${spring.datasource.driver-class-name2}")
String driverClass;
@Value("${spring.datasource.url2}")
String url;
@Value("${spring.datasource.username2}")
String userName;
@Value("${spring.datasource.password2}")
String passWord;


@Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
public DataSource dataSource() {
System.out.println("dataSource2 init");
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(url);
mysqlXaDataSource.setPassword(passWord);
mysqlXaDataSource.setUser(userName);
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource2");
xaDataSource.setMinPoolSize(10);
xaDataSource.setPoolSize(10);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setBorrowConnectionTimeout(60);
xaDataSource.setReapTimeout(20);
xaDataSource.setMaxIdleTime(60);
xaDataSource.setMaintenanceInterval(60);

return xaDataSource;
}

@Bean(name = "jpaVendorAdapter2")
public JpaVendorAdapter jpaVendorAdapter() {
System.out.println("jpaVendorAdapter2 init");
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(true);
adapter.setDatabase(Database.MYSQL);
adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
adapter.setGenerateDdl(true);
return adapter;
}

@Bean(name = "entityManagerFactory2")
@DependsOn({"atomikosJtaPlatfom"})
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
System.out.println("entityManagerFactory2 init");
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();


entityManager.setJpaVendorAdapter(jpaVendorAdapter());
entityManager.setPackagesToScan("com.example.model.ds2");// entity package
entityManager.setJtaDataSource(dataSource());

Properties properties = new Properties();
properties.put("hibernate.transaction.jta.platform","com.example.AtomikosJtaPlatfom");

properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");
properties.put("hibernate.current_session_context_class", "jta");
properties.put("hibernate.transaction.factory_class", "org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory");

entityManager.setJpaProperties(properties);
return entityManager;

}
}

其它方面与单数据源使用JPA没有区别,这里就不罗列代码了。


扩展JPA的方法

上面我们介绍过,一般情况下我们的Repository接口继承JpaRepository,所以可以默认使用JpaRepository提供的所有方法,如果提供的方法不满足需求时,可以在自己的Repository中通过命名规则或者@Query注解等实现方法的扩展。那么,我们如果希望将一些自己扩展公共的方法放在父类中,以便我们所有的Repository都能拥有该扩展功能,该如何实现呢?

本例只举例说明,实现的功能为接收查询条件的分页查询,查询时按传递实体对象的属性进行处理,如果是字符串就按模糊匹配,否则就按精确匹配。

定义父类接口–BaseJpaRepository

1
2
3
4
5
@NoRepositoryBean //说明这不是一个需要被扫描到的Repository
public interface BaseJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID>,JpaSpecificationExecutor<T> {

Page<T> findByAuto(T example, Pageable pageable);
}

创建实现类–BaseJpaRepositoryImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BaseJpaRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseJpaRepository<T, ID> {

//通过构造方法初始化EntityManager
private final EntityManager entityManager;
public BaseJpaRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}

//具体方法实现,这里使用了一个自定义工具类BaseSpecs
@Override
public Page<T> findByAuto(T example, Pageable pageable) {
return findAll(BaseSpecs.byAuto(entityManager,example),pageable);
}
}

BaseSpecs的byAuto方法负责封装查询对象Specification,按传递实体对象的属性进行处理,如果是字符串就按模糊匹配,否则就按精确匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class BaseSpecs {

public static <T> Specification<T> byAuto(final EntityManager entityManager, final T example){
final Class<T> type = (Class<T>) example.getClass();
return new Specification<T>() {
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

List<Predicate> predicateList = new ArrayList<>();
EntityType<T> entityType = entityManager.getMetamodel().entity(type);

for(Attribute<T,?> attribute : entityType.getDeclaredAttributes()){
Object attrValue = getValue(example,attribute);
if(attrValue != null){
if(attribute.getJavaType() == String.class){
if(!StringUtils.isEmpty(attrValue)){
predicateList.add(criteriaBuilder.like(root.get(attribute(entityType,attribute.getName(),String.class)),pattern((String)attrValue)));
}
}else{
predicateList.add(criteriaBuilder.equal(root.get(attribute(entityType,attribute.getName(),attrValue.getClass())),attrValue));
}
}
}
return predicateList.isEmpty()?criteriaBuilder.conjunction():criteriaBuilder.and(toArray(predicateList));
}

private <T> Object getValue(T example,Attribute<T,?> attr){
return ReflectionUtils.getField((Field)attr.getJavaMember(),example);
}

private <E,T> SingularAttribute<T,E> attribute(EntityType<T> entityType,String fieldName,Class<E> fieldClass){
return entityType.getDeclaredSingularAttribute(fieldName,fieldClass);
}

private Predicate[] toArray(List<Predicate> predicateList){
Predicate[] array = predicateList.toArray(new Predicate[predicateList.size()]);
return array;
}
};
}

static private String pattern(String str){
return "%" + str + "%";
}
}

说明
当我们的Repository实现的是JpaRepository的时候,Spring-data-jpa会为我们动态使用JpaRepository的实现类SimpleJpaRepository,这也是为什么我们只需要创建接口而不需要提供实现类。

这里,我们创建了新的父类接口BaseJpaRepository,并为其提供了实现类BaseJpaRepositoryImpl,所以我们要告诉Spring-data-jpa要使用我们自己的实现类,而不能去使用SimpleJpaRepository,所以我们要改写JpaRepositoryFactoryBean

创建一个BaseRepositoryFactoryBean继承于JpaRepositoryFactoryBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class BaseRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable>   extends JpaRepositoryFactoryBean<T,S,ID> {

@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new BaseRepositoryFactory(entityManager);
}
}

class BaseRepositoryFactory extends JpaRepositoryFactory {
public BaseRepositoryFactory(EntityManager entityManager){
super(entityManager);
}


//指定实现类
@Override
protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
BaseJpaRepositoryImpl customRepository = new BaseJpaRepositoryImpl<T,ID>((Class<T>)information.getDomainType(),entityManager);
return customRepository;

}

//指定实现类类型
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata)
return BaseJpaRepositoryImpl.class;
}
}

并且在*@EnableJpaRepositories*注解中进行指定:

1
2
3
4
@EnableJpaRepositories(basePackages = "com.example.dao", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager",repositoryFactoryBeanClass=BaseRepositoryFactoryBean.class)
public class JpaConfig {
//………………
}

自定义Repository继承BaseJpaRepository

1
2
3
4
public interface PersonRepository extends BaseJpaRepository<Person, Integer> {
//………依然可以在该接口中对功能进行扩展………

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class JpaExtendApplicationTests {

@Autowired
private PersonRepository personRepository;

@Test
public void findByAuto() {
Person person = new Person();
person.setpName("王五");
person.setpAge(18);
Sort sort = new Sort(Sort.Direction.DESC, "pId");
//查询第一页,按一页三行分页
Pageable pageable = new PageRequest(0, 3, sort);
Page<Person> list = personRepository.findByAuto(person,pageable);
for(Person p:list){
System.out.println(p);
}
}
}

本文示例代码下载地址:https://github.com/hanqunfeng/SpringBootStudy