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


    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.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 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

你可能感兴趣的:(Spring Boot学习笔记06--JPA)