摘要
看完本文你将掌握如下知识点:
- Spring Boot项目中JPA的配置及使用方法
- Spring Boot项目配置Spring Data JPA的方法
- Spring Data JPA与Atomikos整合实现多数据源事务管理
- 扩展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
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中。
org.springframework.boot
spring-boot-starter-data-jpa
此时项目会自动开启如下两个自动配置类:
JpaRepositoriesAutoConfiguration
HibernateJpaAutoConfiguration
application.properties中增加jpa相关配置
#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
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 extends PagingAndSortingRepository, QueryByExampleExecutor {
List findAll();
List findAll(Sort var1);
List findAll(Iterable var1);
List save(Iterable var1);
void flush();
S saveAndFlush(S var1);
void deleteInBatch(Iterable var1);
void deleteAllInBatch();
T getOne(ID var1);
List findAll(Example var1);
List findAll(Example var1, Sort var2);
}
自定义Repository:PersonRepository,并扩展数据访问方法,具体扩展方法参看示例代码
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 {
//1.以下方法基于属性名称和查询关键字,所以方法名称必须遵循命名规则,并且参数类型要与实体的参数类型一致。
// 只用于查询方法,以下给出常用的示例
//等于
List findByPName(String PName);
//And --- 等价于 SQL 中的 and 关键字;
List findByPNameAndPAge(String PName, Integer PAge);
// Or --- 等价于 SQL 中的 or 关键字;
List findByPNameOrPAge(String PName, Integer PAge);
//Between --- 等价于 SQL 中的 between 关键字;
List findByPAgeBetween(Integer min, Integer max);
//LessThan --- 等价于 SQL 中的 "<"; 日期类型也可以使用Before关键字
List findByPAgeLessThan(Integer max);
//LessThanEqual --- 等价于 SQL 中的 "<=";
List findByPAgeLessThanEqual(Integer max);
//GreaterThan --- 等价于 SQL 中的">";日期类型也可以使用After关键字
List findByPAgeGreaterThan(Integer min);
//GreaterThanEqual --- 等价于 SQL 中的">=";
List findByPAgeGreaterThanEqual(Integer min);
//IsNull --- 等价于 SQL 中的 "is null";
List findByPNameIsNull();
//IsNotNull --- 等价于 SQL 中的 "is not null";
List findByPNameIsNotNull();
//NotNull --- 与 IsNotNull 等价;
List findByPNameNotNull();
//Like --- 等价于 SQL 中的 "like";
List findByPNameLike(String PName);
//NotLike --- 等价于 SQL 中的 "not like";
List findByPNameNotLike(String PName);
//OrderBy --- 等价于 SQL 中的 "order by";
List findByPNameNotNullOrderByPAgeAsc();
//Not --- 等价于 SQL 中的 "! =";
List findByPNameNot(String PName);
//In --- 等价于 SQL 中的 "in";
List findByPNameIn(String PName);
//NotIn --- 等价于 SQL 中的 "not in";
List findByPNameNotIn(String PName);
//Top --- 查询符合条件的前两条记录,等价与First关键字
List findTop2ByPName(String PName);
//2.以下方法基于@Query注解,方法名称可以随意,可用于查询和更新方法,更新方法要设置@Modifying注解
//使用命名参数
@Query("select p from Person p where p.pName = :name and p.pAge = :age")
List withNameAndAgeQuery(@Param("name") String name, @Param("age") Integer age);
//使用参数索引
@Query("select p from Person p where p.pName = ?1 and p.pAge = ?2")
List 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 findByPNameNot(String name, Pageable pageable);
//使用命名参数
@Query("select p from Person p where p.pName = :name ")
Page withNameQueryPage(@Param("name") String name, Pageable pageable);
}
POJO实体对象:Person
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 +
'}';
}
}
测试演示
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 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 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 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 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 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 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 pages = personRepository.findByPNameNot(name,pageable);
System.out.println("pages.getTotalElements()" + pages.getTotalElements());
System.out.println("pages.getTotalPages()" + pages.getTotalPages());
Iterator 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 pages = personRepository.withNameQueryPage(name,pageable);
System.out.println("pages.getTotalElements()" + pages.getTotalElements());
System.out.println("pages.getTotalPages()" + pages.getTotalPages());
Iterator 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的依赖。
mysql
mysql-connector-java
5.1.37
javax.transaction
jta
1.1
org.aspectj
aspectjweaver
1.8.9
org.hibernate
hibernate-core
4.3.5.Final
org.hibernate
hibernate-entitymanager
4.3.5.Final
org.springframework
spring-orm
4.3.3.RELEASE
org.springframework.data
spring-data-jpa
1.10.5.RELEASE
自定义配置类:DataSourceConfig
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的自动配置:
@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
com.atomikos
transactions-jdbc
4.0.4
com.atomikos
transactions-jta
4.0.4
com.atomikos
transactions
4.0.4
com.atomikos
atomikos-util
4.0.4
javax.transaction
jta
1.1
org.aspectj
aspectjweaver
1.8.9
org.hibernate
hibernate-core
4.3.5.Final
org.springframework
spring-orm
4.3.3.RELEASE
org.springframework.data
spring-data-jpa
1.10.5.RELEASE
org.hibernate
hibernate-entitymanager
4.3.5.Final
mysql
mysql-connector-java
5.1.37
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
application.properties
#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事务管理器
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.TransactionManager和javax.transaction.UserTransaction。
spring-data-jpa没有提供该实现类,但是hibernate提供了许多实现类,spring boot也提供了一个实现类--SpringJtaPlatform,
但是这些实现类都是通过构造函数绑定javax.transaction.TransactionManager和javax.transaction.UserTransaction,而没有提供缺省的构造方法,这就导致通过属性指定hibernate.transaction.jta.platform
时,spring不能初始化该实现类(可能是我还没有搞明白吧)。
所以,可以自己创建一个实现类,并通过set方法来绑定javax.transaction.TransactionManager和javax.transaction.UserTransaction。
这就是AtomikosJtaPlatfom
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
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
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
@NoRepositoryBean //说明这不是一个需要被扫描到的Repository
public interface BaseJpaRepository extends JpaRepository,JpaSpecificationExecutor {
Page findByAuto(T example, Pageable pageable);
}
创建实现类--BaseJpaRepositoryImpl
public class BaseJpaRepositoryImpl extends SimpleJpaRepository implements BaseJpaRepository {
//通过构造方法初始化EntityManager
private final EntityManager entityManager;
public BaseJpaRepositoryImpl(Class domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
//具体方法实现,这里使用了一个自定义工具类BaseSpecs
@Override
public Page findByAuto(T example, Pageable pageable) {
return findAll(BaseSpecs.byAuto(entityManager,example),pageable);
}
}
BaseSpecs的byAuto方法负责封装查询对象Specification,按传递实体对象的属性进行处理,如果是字符串就按模糊匹配,否则就按精确匹配。
public class BaseSpecs {
public static Specification byAuto(final EntityManager entityManager, final T example){
final Class type = (Class) example.getClass();
return new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List predicateList = new ArrayList<>();
EntityType entityType = entityManager.getMetamodel().entity(type);
for(Attribute 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 Object getValue(T example,Attribute attr){
return ReflectionUtils.getField((Field)attr.getJavaMember(),example);
}
private SingularAttribute attribute(EntityType entityType,String fieldName,Class fieldClass){
return entityType.getDeclaredSingularAttribute(fieldName,fieldClass);
}
private Predicate[] toArray(List 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:
public class BaseRepositoryFactoryBean, S, ID extends Serializable> extends JpaRepositoryFactoryBean {
@Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new BaseRepositoryFactory(entityManager);
}
}
class BaseRepositoryFactory extends JpaRepositoryFactory {
public BaseRepositoryFactory(EntityManager entityManager){
super(entityManager);
}
//指定实现类
@Override
protected SimpleJpaRepository, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
BaseJpaRepositoryImpl customRepository = new BaseJpaRepositoryImpl((Class)information.getDomainType(),entityManager);
return customRepository;
}
//指定实现类类型
@Override
protected Class> getRepositoryBaseClass(RepositoryMetadata metadata)
return BaseJpaRepositoryImpl.class;
}
}
并且在@EnableJpaRepositories注解中进行指定:
@EnableJpaRepositories(basePackages = "com.example.dao", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager",repositoryFactoryBeanClass=BaseRepositoryFactoryBean.class)
public class JpaConfig {
//………………
}
自定义Repository继承BaseJpaRepository
public interface PersonRepository extends BaseJpaRepository {
//………依然可以在该接口中对功能进行扩展………
}
测试
@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 list = personRepository.findByAuto(person,pageable);
for(Person p:list){
System.out.println(p);
}
}
}
本文示例代码下载地址:https://github.com/hanqunfeng/SpringBootStudy