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
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中。

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

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

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<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,并扩展数据访问方法,具体扩展方法参看示例代码

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

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<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的依赖。

    <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事务管理器
    @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

<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:用于注册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.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

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<T, ID extends Serializable> extends JpaRepository<T, ID>,JpaSpecificationExecutor<T> {

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

创建实现类--BaseJpaRepositoryImpl

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,按传递实体对象的属性进行处理,如果是字符串就按模糊匹配,否则就按精确匹配。

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

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注解中进行指定:

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

自定义Repository继承BaseJpaRepository

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

}

测试

@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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容