美文网首页SpringJAVA我爱编程
Spring Boot学习笔记06--JPA

Spring Boot学习笔记06--JPA

作者: 飘逸峰 | 来源:发表于2016-12-22 19:13 被阅读3562次

    摘要

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

    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

    相关文章

      网友评论

      本文标题:Spring Boot学习笔记06--JPA

      本文链接:https://www.haomeiwen.com/subject/fvshvttx.html