摘要
看完本文你將掌握如下知識(shí)點(diǎn):
- Spring Boot項(xiàng)目中JPA的配置及使用方法
- Spring Boot項(xiàng)目配置Spring Data JPA的方法
- Spring Data JPA與Atomikos整合實(shí)現(xiàn)多數(shù)據(jù)源事務(wù)管理
- 擴(kuò)展JPA的方法
SpringBoot系列:Spring Boot學(xué)習(xí)筆記
前言
JPA即Java Persistence API,是一個(gè)基于O/R映射的標(biāo)準(zhǔn)規(guī)范撤防,該規(guī)范只負(fù)責(zé)定義規(guī)則的標(biāo)準(zhǔn)(注解或接口)虽风,而不需要提供具體實(shí)現(xiàn),具體的實(shí)現(xiàn)交由軟件提供商來實(shí)現(xiàn),目前主要的JPA提供商為Hibernate辜膝,EclipseLink和OperJPA无牵。
Spring Data JPA是Spring Data的一個(gè)子項(xiàng)目,通過提供基于JPA的Repository來簡(jiǎn)化代碼量厂抖。
其提供了一個(gè)org.springframework.data.jpa.repository.JpaRepository茎毁,我們的Repository只要繼承該JpaRepository,即可享受到JPA帶來的好處忱辅。
Spring Boot通過spring-boot-starter-data-jpa來提供對(duì)JPA的支持七蜘,Spring Boot默認(rèn)的JPA實(shí)現(xiàn)者是Hibernate。
說明
在講解下面的內(nèi)容前墙懂,我們先在數(shù)據(jù)庫中創(chuàng)建一張表
# 創(chuàng)建庫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項(xiàng)目中使用JPA
創(chuàng)建項(xiàng)目時(shí)選擇JPA依賴橡卤,或者手工將spring-boot-starter-data-jpa添加到pom中。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
此時(shí)項(xiàng)目會(huì)自動(dòng)開啟如下兩個(gè)自動(dòng)配置類:
JpaRepositoriesAutoConfiguration
HibernateJpaAutoConfiguration
application.properties中增加jpa相關(guān)配置
#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
#啟動(dòng)時(shí)會(huì)根據(jù)實(shí)體類生成數(shù)據(jù)表垒在,或者更新表結(jié)構(gòu)蒜魄,不清空數(shù)據(jù),開發(fā)階段使用场躯;validate:表結(jié)構(gòu)穩(wěn)定后使用谈为,可用于正式環(huán)境;
spring.jpa.hibernate.ddl-auto=update
#控制臺(tái)打印sql
spring.jpa.show-sql=true
#讓控制器輸出的json格式更美觀
spring.jackson.serialization.indent-output=true
在項(xiàng)目中使用JPA時(shí)踢关,只需要?jiǎng)?chuàng)建一個(gè)繼承于JpaRepository的Repository接口伞鲫,即可擁有JpaRepository及其父類中提供的全部數(shù)據(jù)訪問方法。如果提供的方法不滿足業(yè)務(wù)需要签舞,可以按如下規(guī)則擴(kuò)展數(shù)據(jù)方法秕脓。
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<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,并擴(kuò)展數(shù)據(jù)訪問方法儒搭,具體擴(kuò)展方法參看示例代碼
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.以下方法基于屬性名稱和查詢關(guān)鍵字吠架,所以方法名稱必須遵循命名規(guī)則,并且參數(shù)類型要與實(shí)體的參數(shù)類型一致搂鲫。
// 只用于查詢方法傍药,以下給出常用的示例
//等于
List<Person> findByPName(String PName);
//And --- 等價(jià)于 SQL 中的 and 關(guān)鍵字;
List<Person> findByPNameAndPAge(String PName, Integer PAge);
// Or --- 等價(jià)于 SQL 中的 or 關(guān)鍵字魂仍;
List<Person> findByPNameOrPAge(String PName, Integer PAge);
//Between --- 等價(jià)于 SQL 中的 between 關(guān)鍵字拐辽;
List<Person> findByPAgeBetween(Integer min, Integer max);
//LessThan --- 等價(jià)于 SQL 中的 "<"; 日期類型也可以使用Before關(guān)鍵字
List<Person> findByPAgeLessThan(Integer max);
//LessThanEqual --- 等價(jià)于 SQL 中的 "<="擦酌;
List<Person> findByPAgeLessThanEqual(Integer max);
//GreaterThan --- 等價(jià)于 SQL 中的">"俱诸;日期類型也可以使用After關(guān)鍵字
List<Person> findByPAgeGreaterThan(Integer min);
//GreaterThanEqual --- 等價(jià)于 SQL 中的">=";
List<Person> findByPAgeGreaterThanEqual(Integer min);
//IsNull --- 等價(jià)于 SQL 中的 "is null"赊舶;
List<Person> findByPNameIsNull();
//IsNotNull --- 等價(jià)于 SQL 中的 "is not null"睁搭;
List<Person> findByPNameIsNotNull();
//NotNull --- 與 IsNotNull 等價(jià)赶诊;
List<Person> findByPNameNotNull();
//Like --- 等價(jià)于 SQL 中的 "like";
List<Person> findByPNameLike(String PName);
//NotLike --- 等價(jià)于 SQL 中的 "not like";
List<Person> findByPNameNotLike(String PName);
//OrderBy --- 等價(jià)于 SQL 中的 "order by"介袜;
List<Person> findByPNameNotNullOrderByPAgeAsc();
//Not --- 等價(jià)于 SQL 中的 "甫何! =";
List<Person> findByPNameNot(String PName);
//In --- 等價(jià)于 SQL 中的 "in";
List<Person> findByPNameIn(String PName);
//NotIn --- 等價(jià)于 SQL 中的 "not in";
List<Person> findByPNameNotIn(String PName);
//Top --- 查詢符合條件的前兩條記錄遇伞,等價(jià)與First關(guān)鍵字
List<Person> findTop2ByPName(String PName);
//2.以下方法基于@Query注解辙喂,方法名稱可以隨意,可用于查詢和更新方法鸠珠,更新方法要設(shè)置@Modifying注解
//使用命名參數(shù)
@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);
//使用參數(shù)索引
@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.以下方法實(shí)現(xiàn)分頁查詢功能,只需要在方法中增加Pageable pageable參數(shù)即可驯耻,返回結(jié)果為Page集合
Page<Person> findByPNameNot(String name, Pageable pageable);
//使用命名參數(shù)
@Query("select p from Person p where p.pName = :name ")
Page<Person> withNameQueryPage(@Param("name") String name, Pageable pageable);
}
POJO實(shí)體對(duì)象: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 +
'}';
}
}
測(cè)試演示
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項(xiàng)目配置Spring Data JPA的方法
如果不想依賴于spring-boot-starter-data-jpa可缚,我們依然可以通過配置類來實(shí)現(xiàn)Spring Boot對(duì)Spring Data JPA的支持霎迫。
pom替換依賴
這里說明一下,實(shí)際上我們可以不用替換掉spring-boot-starter-data-jpa的依賴帘靡,替換掉的好處僅僅是減少對(duì)不需要的jar的依賴知给。
<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
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事務(wù)管理器
@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;
}
}
項(xiàng)目啟動(dòng)類中要關(guān)閉jpa的自動(dòng)配置:
@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整合實(shí)現(xiàn)多數(shù)據(jù)源事務(wù)管理
spring-data-jpa雖說默認(rèn)使用的是Hibernate,但是其與Atomikos整合方式與Hibernate略有不同描姚。
pom
<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
#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:用于注冊(cè)Atomikos事務(wù)管理器
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;
}
//上面三個(gè)都認(rèn)識(shí)涩赢,下面說一下這個(gè)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時(shí)候,如果要使其能夠支持JTA事務(wù)轩勘,則在配置其JpaProperties時(shí)需要為其指定如下參數(shù):
hibernate.transaction.jta.platform
hibernate.current_session_context_class
hibernate.transaction.factory_class
后面我們配置LocalContainerEntityManagerFactoryBean的時(shí)候會(huì)看到相應(yīng)的配置筒扒,
這里要說的是,hibernate.transaction.jta.platform
需要指定org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform
的實(shí)現(xiàn)類绊寻,其主要功能就是要綁定javax.transaction.TransactionManager和javax.transaction.UserTransaction霎肯。
spring-data-jpa沒有提供該實(shí)現(xiàn)類,但是hibernate提供了許多實(shí)現(xiàn)類榛斯,spring boot也提供了一個(gè)實(shí)現(xiàn)類--SpringJtaPlatform,
但是這些實(shí)現(xiàn)類都是通過構(gòu)造函數(shù)綁定javax.transaction.TransactionManager和javax.transaction.UserTransaction搂捧,而沒有提供缺省的構(gòu)造方法驮俗,這就導(dǎo)致通過屬性指定hibernate.transaction.jta.platform
時(shí),spring不能初始化該實(shí)現(xiàn)類(可能是我還沒有搞明白吧)允跑。
所以王凑,可以自己創(chuàng)建一個(gè)實(shí)現(xiàn)類搪柑,并通過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;
}
}
接下來需要在配置類中注冊(cè)LocalContainerEntityManagerFactoryBean索烹,
由于@EnableJpaRepositories注解不能在同一個(gè)配置類上聲明兩次工碾,所以就按數(shù)據(jù)源進(jìn)行分別設(shè)置:
JpaConfigDs1:數(shù)據(jù)源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)
//指定數(shù)據(jù)源1的Repository路徑,數(shù)據(jù)源1的entityManagerFactory百姓,事務(wù)是公共事務(wù)
@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"}) //需要先注冊(cè)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設(shè)置
properties.put("hibernate.current_session_context_class", "jta");
properties.put("hibernate.transaction.factory_class", "org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory");
//這里指定我們自己創(chuàng)建的AtomikosJtaPlatfom
properties.put("hibernate.transaction.jta.platform","com.example.AtomikosJtaPlatfom");
entityManager.setJpaProperties(properties);
return entityManager;
}
}
JpaConfigDs2:數(shù)據(jù)源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;
}
}
其它方面與單數(shù)據(jù)源使用JPA沒有區(qū)別渊额,這里就不羅列代碼了。
擴(kuò)展JPA的方法
上面我們介紹過垒拢,一般情況下我們的Repository接口繼承JpaRepository旬迹,所以可以默認(rèn)使用JpaRepository提供的所有方法,如果提供的方法不滿足需求時(shí)求类,可以在自己的Repository中通過命名規(guī)則或者@Query注解等實(shí)現(xiàn)方法的擴(kuò)展奔垦。那么,我們?nèi)绻M麑⒁恍┳约簲U(kuò)展公共的方法放在父類中尸疆,以便我們所有的Repository都能擁有該擴(kuò)展功能椿猎,該如何實(shí)現(xiàn)呢?
本例只舉例說明寿弱,實(shí)現(xiàn)的功能為接收查詢條件的分頁查詢犯眠,查詢時(shí)按傳遞實(shí)體對(duì)象的屬性進(jìn)行處理,如果是字符串就按模糊匹配脖捻,否則就按精確匹配阔逼。
定義父類接口--BaseJpaRepository
@NoRepositoryBean //說明這不是一個(gè)需要被掃描到的Repository
public interface BaseJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID>,JpaSpecificationExecutor<T> {
Page<T> findByAuto(T example, Pageable pageable);
}
創(chuàng)建實(shí)現(xiàn)類--BaseJpaRepositoryImpl
public class BaseJpaRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseJpaRepository<T, ID> {
//通過構(gòu)造方法初始化EntityManager
private final EntityManager entityManager;
public BaseJpaRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
//具體方法實(shí)現(xiàn),這里使用了一個(gè)自定義工具類BaseSpecs
@Override
public Page<T> findByAuto(T example, Pageable pageable) {
return findAll(BaseSpecs.byAuto(entityManager,example),pageable);
}
}
BaseSpecs的byAuto方法負(fù)責(zé)封裝查詢對(duì)象Specification地沮,按傳遞實(shí)體對(duì)象的屬性進(jìn)行處理嗜浮,如果是字符串就按模糊匹配,否則就按精確匹配摩疑。
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 + "%";
}
}
說明
當(dāng)我們的Repository實(shí)現(xiàn)的是JpaRepository的時(shí)候危融,Spring-data-jpa會(huì)為我們動(dòng)態(tài)使用JpaRepository的實(shí)現(xiàn)類SimpleJpaRepository,這也是為什么我們只需要?jiǎng)?chuàng)建接口而不需要提供實(shí)現(xiàn)類雷袋。
這里吉殃,我們創(chuàng)建了新的父類接口BaseJpaRepository,并為其提供了實(shí)現(xiàn)類BaseJpaRepositoryImpl楷怒,所以我們要告訴Spring-data-jpa要使用我們自己的實(shí)現(xiàn)類蛋勺,而不能去使用SimpleJpaRepository,所以我們要改寫JpaRepositoryFactoryBean鸠删;
創(chuàng)建一個(gè)BaseRepositoryFactoryBean繼承于JpaRepositoryFactoryBean:
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);
}
//指定實(shí)現(xiàn)類
@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;
}
//指定實(shí)現(xiàn)類類型
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata)
return BaseJpaRepositoryImpl.class;
}
}
并且在@EnableJpaRepositories注解中進(jìn)行指定:
@EnableJpaRepositories(basePackages = "com.example.dao", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager",repositoryFactoryBeanClass=BaseRepositoryFactoryBean.class)
public class JpaConfig {
//………………
}
自定義Repository繼承BaseJpaRepository
public interface PersonRepository extends BaseJpaRepository<Person, Integer> {
//………依然可以在該接口中對(duì)功能進(jìn)行擴(kuò)展………
}
測(cè)試
@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);
}
}
}