Spring Boot學(xué)習(xí)筆記06--JPA

摘要

看完本文你將掌握如下知識(shí)點(diǎn):

  1. Spring Boot項(xiàng)目中JPA的配置及使用方法
  2. Spring Boot項(xiàng)目配置Spring Data JPA的方法
  3. Spring Data JPA與Atomikos整合實(shí)現(xiàn)多數(shù)據(jù)源事務(wù)管理
  4. 擴(kuò)展JPA的方法

SpringBoot系列Spring Boot學(xué)習(xí)筆記


前言

JPA即Java Persistence API,是一個(gè)基于O/R映射的標(biāo)準(zhǔn)規(guī)范撤防,該規(guī)范只負(fù)責(zé)定義規(guī)則的標(biāo)準(zhǔn)(注解或接口)虽风,而不需要提供具體實(shí)現(xiàn),具體的實(shí)現(xiàn)交由軟件提供商來實(shí)現(xiàn),目前主要的JPA提供商為Hibernate辜膝,EclipseLink和OperJPA无牵。

Spring Data JPA是Spring Data的一個(gè)子項(xiàng)目,通過提供基于JPA的Repository來簡(jiǎn)化代碼量厂抖。
其提供了一個(gè)org.springframework.data.jpa.repository.JpaRepository茎毁,我們的Repository只要繼承該JpaRepository,即可享受到JPA帶來的好處忱辅。

Spring Boot通過spring-boot-starter-data-jpa來提供對(duì)JPA的支持七蜘,Spring Boot默認(rèn)的JPA實(shí)現(xiàn)者是Hibernate。


說明
在講解下面的內(nèi)容前墙懂,我們先在數(shù)據(jù)庫中創(chuàng)建一張表

# 創(chuàng)建庫1
CREATE SCHEMA `springboot1` DEFAULT CHARACTER SET utf8 ;
CREATE TABLE `springboot1`.`person` (
  `p_id` INT NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `p_name` VARCHAR(45) NULL COMMENT '姓名',
  `p_age` INT NULL COMMENT '年齡',
  PRIMARY KEY (`p_id`))
ENGINE = InnoDB
COMMENT = '人員信息表';

INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('1', '張三', '20');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('2', '李四', '25');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('3', '王五', '18');
INSERT INTO `springboot1`.`person` (`p_id`, `p_name`, `p_age`) VALUES ('4', '王五', '18');

Spring Boot項(xiàng)目中使用JPA

創(chuàng)建項(xiàng)目時(shí)選擇JPA依賴橡卤,或者手工將spring-boot-starter-data-jpa添加到pom中。

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

此時(shí)項(xiàng)目會(huì)自動(dòng)開啟如下兩個(gè)自動(dòng)配置類:

JpaRepositoriesAutoConfiguration
HibernateJpaAutoConfiguration

application.properties中增加jpa相關(guān)配置

#datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot1?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=newpwd

#spring_jpa
#啟動(dòng)時(shí)會(huì)根據(jù)實(shí)體類生成數(shù)據(jù)表垒在,或者更新表結(jié)構(gòu)蒜魄,不清空數(shù)據(jù),開發(fā)階段使用场躯;validate:表結(jié)構(gòu)穩(wěn)定后使用谈为,可用于正式環(huán)境;
spring.jpa.hibernate.ddl-auto=update
#控制臺(tái)打印sql
spring.jpa.show-sql=true
#讓控制器輸出的json格式更美觀
spring.jackson.serialization.indent-output=true

在項(xiàng)目中使用JPA時(shí)踢关,只需要?jiǎng)?chuàng)建一個(gè)繼承于JpaRepository的Repository接口伞鲫,即可擁有JpaRepository及其父類中提供的全部數(shù)據(jù)訪問方法。如果提供的方法不滿足業(yè)務(wù)需要签舞,可以按如下規(guī)則擴(kuò)展數(shù)據(jù)方法秕脓。

JpaRepository

package org.springframework.data.jpa.repository;

import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAll(Iterable<ID> var1);

    <S extends T> List<S> save(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
}

自定義Repository:PersonRepository,并擴(kuò)展數(shù)據(jù)訪問方法儒搭,具體擴(kuò)展方法參看示例代碼

package com.example.dao;

import com.example.model.Person;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;

public interface PersonRepository extends JpaRepository<Person, Integer> {

    //1.以下方法基于屬性名稱和查詢關(guān)鍵字吠架,所以方法名稱必須遵循命名規(guī)則,并且參數(shù)類型要與實(shí)體的參數(shù)類型一致搂鲫。
    // 只用于查詢方法傍药,以下給出常用的示例

    //等于
    List<Person> findByPName(String PName);

    //And --- 等價(jià)于 SQL 中的 and 關(guān)鍵字;  
    List<Person> findByPNameAndPAge(String PName, Integer PAge);

    // Or --- 等價(jià)于 SQL 中的 or 關(guān)鍵字魂仍;  
    List<Person> findByPNameOrPAge(String PName, Integer PAge);

    //Between --- 等價(jià)于 SQL 中的 between 關(guān)鍵字拐辽;  
    List<Person> findByPAgeBetween(Integer min, Integer max);

    //LessThan --- 等價(jià)于 SQL 中的 "<";  日期類型也可以使用Before關(guān)鍵字
    List<Person> findByPAgeLessThan(Integer max);

    //LessThanEqual --- 等價(jià)于 SQL 中的 "<="擦酌;
    List<Person> findByPAgeLessThanEqual(Integer max);

    //GreaterThan --- 等價(jià)于 SQL 中的">"俱诸;日期類型也可以使用After關(guān)鍵字
    List<Person> findByPAgeGreaterThan(Integer min);

    //GreaterThanEqual --- 等價(jià)于 SQL 中的">=";
    List<Person> findByPAgeGreaterThanEqual(Integer min);

    //IsNull --- 等價(jià)于 SQL 中的 "is null"赊舶;
    List<Person> findByPNameIsNull();

    //IsNotNull --- 等價(jià)于 SQL 中的 "is not null"睁搭;
    List<Person> findByPNameIsNotNull();

    //NotNull --- 與 IsNotNull 等價(jià)赶诊;  
    List<Person> findByPNameNotNull();

    //Like --- 等價(jià)于 SQL 中的 "like";
    List<Person> findByPNameLike(String PName);

    //NotLike --- 等價(jià)于 SQL 中的 "not like";
    List<Person> findByPNameNotLike(String PName);

    //OrderBy --- 等價(jià)于 SQL 中的 "order by"介袜;
    List<Person> findByPNameNotNullOrderByPAgeAsc();

    //Not --- 等價(jià)于 SQL 中的 "甫何! =";
    List<Person> findByPNameNot(String PName);

    //In --- 等價(jià)于 SQL 中的 "in";
    List<Person> findByPNameIn(String PName);

    //NotIn --- 等價(jià)于 SQL 中的 "not in";
    List<Person> findByPNameNotIn(String PName);


    //Top --- 查詢符合條件的前兩條記錄遇伞,等價(jià)與First關(guān)鍵字
    List<Person> findTop2ByPName(String PName);

    //2.以下方法基于@Query注解辙喂,方法名稱可以隨意,可用于查詢和更新方法鸠珠,更新方法要設(shè)置@Modifying注解
    //使用命名參數(shù)
    @Query("select p from Person p where p.pName = :name and p.pAge = :age")
    List<Person> withNameAndAgeQuery(@Param("name") String name, @Param("age") Integer age);

    //使用參數(shù)索引
    @Query("select p from Person p where p.pName = ?1 and p.pAge = ?2")
    List<Person> withNameAndAgeQuery2(String name, Integer age);


    //刪除操作巍耗,使用hql,如果要使用sql渐排,需要增加nativeQuery = true
    @Query(value = "delete from Person where pId=?1")
    @Modifying
    int deletePersonById(Integer id);

    //修改操作
    @Query(value = "update Person set pName=?1 where pId=?2 ")
    @Modifying
    int updatePersonName(String name, Integer id);

    //插入操作炬太,使用sql操作
    @Query(value = "insert into person(p_name,p_age) value(?1,?2)",nativeQuery = true)
    @Modifying
    int insertPersonByParam(String name, Integer age);


    //3.以下方法實(shí)現(xiàn)分頁查詢功能,只需要在方法中增加Pageable pageable參數(shù)即可驯耻,返回結(jié)果為Page集合
    Page<Person> findByPNameNot(String name, Pageable pageable);

    //使用命名參數(shù)
    @Query("select p from Person p where p.pName = :name ")
    Page<Person> withNameQueryPage(@Param("name") String name, Pageable pageable);
}

POJO實(shí)體對(duì)象:Person

package com.example.model;
import javax.persistence.*;

import static javax.persistence.GenerationType.IDENTITY;

@Entity
@Table(name = "person"
        , catalog = "springboot1"
)
public class Person implements java.io.Serializable {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "p_id", unique = true, nullable = false)
    private Integer pId;

    @Column(name = "p_name", length = 45)
    private String pName;

    @Column(name = "p_age")
    private Integer pAge;

    //setter and getter

    @Override
    public String toString() {
        return "Person{" +
                "pId=" + pId +
                ", pName='" + pName + '\'' +
                ", pAge=" + pAge +
                '}';
    }
}

測(cè)試演示

package com.example;

import com.example.dao.PersonRepository;
import com.example.model.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.Iterator;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class JpaSingleDatasourceApplicationTests {

    @Autowired
    private PersonRepository personRepository;

    @Test
    public void findByPName() {
        String name = "王五";
        List<Person> list = personRepository.findByPName(name);
        System.out.println(list.size());
        for(Person person : list){
            System.out.println(person);
        }
    }

    @Test
    public void findByPNameAndPAge() {
        String name = "王五";
        int age = 18;
        List<Person> list = personRepository.findByPNameAndPAge(name,age);
        System.out.println(list.size());
        for(Person person : list){
            System.out.println(person);
        }
    }

    @Test
    public void findByPNameOrPAge() {
        String name = "王五";
        int age = 25;
        List<Person> list = personRepository.findByPNameOrPAge(name,age);
        System.out.println(list.size());
        for(Person person : list){
            System.out.println(person);
        }
    }

    @Test
    public void findTop2ByPName() {
        String name = "王五";
        List<Person> list = personRepository.findTop2ByPName(name);
        System.out.println(list.size());
        for(Person person : list){
            System.out.println(person);
        }
    }

    @Test
    public void withNameAndAgeQuery() {
        String name = "王五";
        int age = 18;
        List<Person> list = personRepository.withNameAndAgeQuery(name,age);
        System.out.println(list.size());
        for(Person person : list){
            System.out.println(person);
        }
    }

    @Test
    public void withNameAndAgeQuery2() {
        String name = "王五";
        int age = 18;
        List<Person> list = personRepository.withNameAndAgeQuery2(name,age);
        System.out.println(list.size());
        for(Person person : list){
            System.out.println(person);
        }
    }


    @Test
    public void deletePersonById(){
        int id = 1;
        int result = personRepository.deletePersonById(id);
        System.out.println("result = " + result);
    }

    @Test
    public void updatePersonName(){
        int id = 1;
        String name = "哈哈";
        int result = personRepository.updatePersonName(name,id);
        System.out.println("result = " + result);
    }

    @Test
    public void insertPersonByParam(){
        int age = 10;
        String name = "哈哈";
        int result = personRepository.insertPersonByParam(name,age);
        System.out.println("result = " + result);
    }

    @Test
    public void findByPNameNot(){
        String name = "哈哈";
        //排序
        Sort sort = new Sort(Sort.Direction.DESC, "pId");
        //查詢第一頁亲族,按一頁三行分頁
        Pageable pageable = new PageRequest(0, 3, sort);

        Page<Person> pages = personRepository.findByPNameNot(name,pageable);
        System.out.println("pages.getTotalElements()" + pages.getTotalElements());
        System.out.println("pages.getTotalPages()" + pages.getTotalPages());
        Iterator<Person> it=pages.iterator();
        while(it.hasNext()){
            System.out.println("value:"+((Person)it.next()));
        }
    }

    @Test
    public void withNameQueryPage(){
        String name = "王五";
        //排序
        Sort sort = new Sort(Sort.Direction.DESC, "pId");
        //查詢第二頁,按一頁三行分頁
        Pageable pageable = new PageRequest(1, 3, sort);

        Page<Person> pages = personRepository.withNameQueryPage(name,pageable);
        System.out.println("pages.getTotalElements()" + pages.getTotalElements());
        System.out.println("pages.getTotalPages()" + pages.getTotalPages());
        Iterator<Person> it=pages.iterator();
        while(it.hasNext()){
            System.out.println("value:"+((Person)it.next()));
        }
    }
}

Spring Boot項(xiàng)目配置Spring Data JPA的方法

如果不想依賴于spring-boot-starter-data-jpa可缚,我們依然可以通過配置類來實(shí)現(xiàn)Spring Boot對(duì)Spring Data JPA的支持霎迫。

pom替換依賴
這里說明一下,實(shí)際上我們可以不用替換掉spring-boot-starter-data-jpa的依賴帘靡,替換掉的好處僅僅是減少對(duì)不需要的jar的依賴知给。

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.37</version>
    </dependency>

    <dependency>
        <groupId>javax.transaction</groupId>
        <artifactId>jta</artifactId>
        <version>1.1</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
    </dependency>

    <!-- hibernate -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.5.Final</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.5.Final</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>4.3.3.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.10.5.RELEASE</version>
    </dependency>

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

自定義配置類:DataSourceConfig

package com.example;

import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
//開啟Spring Data JPA的支持
@EnableJpaRepositories(basePackages = "com.example.dao", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
public class DataSourceConfig {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;

    @Bean(name = "dataSource")
    public DataSource dataSource() {

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driverClass);
        dataSource.setUrl(url);
        dataSource.setUsername(userName);
        dataSource.setPassword(passWord);
        return dataSource;
    }

    //  jpa事務(wù)管理器
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setDataSource(dataSource());
        jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return jpaTransactionManager;
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(true);
        adapter.setDatabase(Database.MYSQL);
        adapter.setGenerateDdl(true);
        return adapter;
    }


    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
        entityManager.setDataSource(dataSource());
        entityManager.setJpaVendorAdapter(jpaVendorAdapter());
        entityManager.setPackagesToScan("com.example.model");// entity package
        entityManager.setPersistenceProviderClass(HibernatePersistenceProvider.class);
        return entityManager;
    }
}

項(xiàng)目啟動(dòng)類中要關(guān)閉jpa的自動(dòng)配置:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,JpaRepositoriesAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class JpaSingleDatasourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(JpaSingleDatasourceApplication.class, args);
    }
}

Spring Data JPA與Atomikos整合實(shí)現(xiàn)多數(shù)據(jù)源事務(wù)管理

spring-data-jpa雖說默認(rèn)使用的是Hibernate,但是其與Atomikos整合方式與Hibernate略有不同描姚。

pom

<dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jdbc</artifactId>
            <version>4.0.4</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions-jta</artifactId>
            <version>4.0.4</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>transactions</artifactId>
            <version>4.0.4</version>
        </dependency>
        <dependency>
            <groupId>com.atomikos</groupId>
            <artifactId>atomikos-util</artifactId>
            <version>4.0.4</version>
        </dependency>

        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.1</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>

        <!-- hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.5.Final</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.3.3.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.10.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.3.5.Final</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

application.properties

#datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springboot1?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=newpwd

#datasource2
spring.datasource.driver-class-name2=com.mysql.jdbc.Driver
spring.datasource.url2=jdbc:mysql://localhost:3306/springboot2?useUnicode=true&characterEncoding=utf-8
spring.datasource.username2=root
spring.datasource.password2=newpwd

MainConfig:用于注冊(cè)Atomikos事務(wù)管理器

package com.example;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

@Configuration
public class MainConfig {

    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, atomikosTransactionManager);
        jtaTransactionManager.setAllowCustomIsolationLevels(true);
        return jtaTransactionManager;
    }

    //上面三個(gè)都認(rèn)識(shí)涩赢,下面說一下這個(gè)bean
    @Bean(name = "atomikosJtaPlatfom")
    public AtomikosJtaPlatfom atomikosJtaPlatfom(){
        AtomikosJtaPlatfom atomikosJtaPlatfom = new AtomikosJtaPlatfom();
        try {
            atomikosJtaPlatfom.setTm(atomikosTransactionManager());
            atomikosJtaPlatfom.setUt(userTransaction());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return atomikosJtaPlatfom;

    }
}

配置JPA的LocalContainerEntityManagerFactoryBean時(shí)候,如果要使其能夠支持JTA事務(wù)轩勘,則在配置其JpaProperties時(shí)需要為其指定如下參數(shù):

hibernate.transaction.jta.platform
hibernate.current_session_context_class
hibernate.transaction.factory_class

后面我們配置LocalContainerEntityManagerFactoryBean的時(shí)候會(huì)看到相應(yīng)的配置筒扒,
這里要說的是,hibernate.transaction.jta.platform需要指定org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform的實(shí)現(xiàn)類绊寻,其主要功能就是要綁定javax.transaction.TransactionManagerjavax.transaction.UserTransaction霎肯。

spring-data-jpa沒有提供該實(shí)現(xiàn)類,但是hibernate提供了許多實(shí)現(xiàn)類榛斯,spring boot也提供了一個(gè)實(shí)現(xiàn)類--SpringJtaPlatform
但是這些實(shí)現(xiàn)類都是通過構(gòu)造函數(shù)綁定javax.transaction.TransactionManagerjavax.transaction.UserTransaction搂捧,而沒有提供缺省的構(gòu)造方法驮俗,這就導(dǎo)致通過屬性指定hibernate.transaction.jta.platform時(shí),spring不能初始化該實(shí)現(xiàn)類(可能是我還沒有搞明白吧)允跑。

所以王凑,可以自己創(chuàng)建一個(gè)實(shí)現(xiàn)類搪柑,并通過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;
    }
}

接下來需要在配置類中注冊(cè)LocalContainerEntityManagerFactoryBean索烹,
由于@EnableJpaRepositories注解不能在同一個(gè)配置類上聲明兩次工碾,所以就按數(shù)據(jù)源進(jìn)行分別設(shè)置:

JpaConfigDs1:數(shù)據(jù)源1

package com.example;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
//指定數(shù)據(jù)源1的Repository路徑,數(shù)據(jù)源1的entityManagerFactory百姓,事務(wù)是公共事務(wù)
@EnableJpaRepositoryies(basePackages = "com.example.dao.ds1", entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "transactionManager")
public class JpaConfigDs1 {

    @Value("${spring.datasource.driver-class-name}")
    String driverClass;
    @Value("${spring.datasource.url}")
    String url;
    @Value("${spring.datasource.username}")
    String userName;
    @Value("${spring.datasource.password}")
    String passWord;


    @Bean(name = "dataSource", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource() {
        System.out.println("dataSource init");
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url);
        mysqlXaDataSource.setPassword(passWord);
        mysqlXaDataSource.setUser(userName);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("dataSource");
        xaDataSource.setMinPoolSize(10);
        xaDataSource.setPoolSize(10);
        xaDataSource.setMaxPoolSize(30);
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setReapTimeout(20);
        xaDataSource.setMaxIdleTime(60);
        xaDataSource.setMaintenanceInterval(60);

        return xaDataSource;
    }

    @Bean(name = "jpaVendorAdapter")
    public JpaVendorAdapter jpaVendorAdapter() {
        System.out.println("jpaVendorAdapter init");
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(true);
        adapter.setDatabase(Database.MYSQL);
        adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
        adapter.setGenerateDdl(true);
        return adapter;
    }

    @Bean(name = "entityManagerFactory")
    @DependsOn({"atomikosJtaPlatfom"}) //需要先注冊(cè)atomikosJtaPlatfom
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        System.out.println("entityManagerFactory init");
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();

        entityManager.setJpaVendorAdapter(jpaVendorAdapter());
        // entity package
        entityManager.setPackagesToScan("com.example.model.ds1");
        entityManager.setJtaDataSource(dataSource());

        Properties properties = new Properties();       
        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        properties.put("hibernate.show_sql", "true");
        properties.put("hibernate.format_sql", "true");

        //jta設(shè)置
        properties.put("hibernate.current_session_context_class", "jta");
        properties.put("hibernate.transaction.factory_class", "org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory");
        //這里指定我們自己創(chuàng)建的AtomikosJtaPlatfom
        properties.put("hibernate.transaction.jta.platform","com.example.AtomikosJtaPlatfom");
        entityManager.setJpaProperties(properties);
        return entityManager;

    }
}

JpaConfigDs2:數(shù)據(jù)源2

package com.example;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Properties;

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@EnableJpaRepositories(basePackages = "com.example.dao.ds2", entityManagerFactoryRef = "entityManagerFactory2", transactionManagerRef = "transactionManager")
public class JpaConfigDs2 {

    @Value("${spring.datasource.driver-class-name2}")
    String driverClass;
    @Value("${spring.datasource.url2}")
    String url;
    @Value("${spring.datasource.username2}")
    String userName;
    @Value("${spring.datasource.password2}")
    String passWord;


    @Bean(name = "dataSource2", initMethod = "init", destroyMethod = "close")
    public DataSource dataSource() {
        System.out.println("dataSource2 init");
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(url);
        mysqlXaDataSource.setPassword(passWord);
        mysqlXaDataSource.setUser(userName);
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("dataSource2");
        xaDataSource.setMinPoolSize(10);
        xaDataSource.setPoolSize(10);
        xaDataSource.setMaxPoolSize(30);
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setReapTimeout(20);
        xaDataSource.setMaxIdleTime(60);
        xaDataSource.setMaintenanceInterval(60);

        return xaDataSource;
    }

    @Bean(name = "jpaVendorAdapter2")
    public JpaVendorAdapter jpaVendorAdapter() {
        System.out.println("jpaVendorAdapter2 init");
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(true);
        adapter.setDatabase(Database.MYSQL);
        adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
        adapter.setGenerateDdl(true);
        return adapter;
    }

    @Bean(name = "entityManagerFactory2")
    @DependsOn({"atomikosJtaPlatfom"})
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        System.out.println("entityManagerFactory2 init");
        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();


        entityManager.setJpaVendorAdapter(jpaVendorAdapter());
        entityManager.setPackagesToScan("com.example.model.ds2");// entity package
        entityManager.setJtaDataSource(dataSource());

        Properties properties = new Properties();
        properties.put("hibernate.transaction.jta.platform","com.example.AtomikosJtaPlatfom");

        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        properties.put("hibernate.show_sql", "true");
        properties.put("hibernate.format_sql", "true");
        properties.put("hibernate.current_session_context_class", "jta");
        properties.put("hibernate.transaction.factory_class", "org.hibernate.engine.transaction.internal.jta.CMTTransactionFactory");

        entityManager.setJpaProperties(properties);
        return entityManager;

    }
}

其它方面與單數(shù)據(jù)源使用JPA沒有區(qū)別渊额,這里就不羅列代碼了。


擴(kuò)展JPA的方法

上面我們介紹過垒拢,一般情況下我們的Repository接口繼承JpaRepository旬迹,所以可以默認(rèn)使用JpaRepository提供的所有方法,如果提供的方法不滿足需求時(shí)求类,可以在自己的Repository中通過命名規(guī)則或者@Query注解等實(shí)現(xiàn)方法的擴(kuò)展奔垦。那么,我們?nèi)绻M麑⒁恍┳约簲U(kuò)展公共的方法放在父類中尸疆,以便我們所有的Repository都能擁有該擴(kuò)展功能椿猎,該如何實(shí)現(xiàn)呢?

本例只舉例說明寿弱,實(shí)現(xiàn)的功能為接收查詢條件的分頁查詢犯眠,查詢時(shí)按傳遞實(shí)體對(duì)象的屬性進(jìn)行處理,如果是字符串就按模糊匹配脖捻,否則就按精確匹配阔逼。

定義父類接口--BaseJpaRepository

@NoRepositoryBean //說明這不是一個(gè)需要被掃描到的Repository
public interface BaseJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID>,JpaSpecificationExecutor<T> {

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

創(chuàng)建實(shí)現(xiàn)類--BaseJpaRepositoryImpl

public class BaseJpaRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseJpaRepository<T, ID> {

    //通過構(gòu)造方法初始化EntityManager
    private final EntityManager entityManager;
    public BaseJpaRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
        super(domainClass, entityManager);
        this.entityManager = entityManager;
    }

    //具體方法實(shí)現(xiàn),這里使用了一個(gè)自定義工具類BaseSpecs
    @Override
    public Page<T> findByAuto(T example, Pageable pageable) {
        return findAll(BaseSpecs.byAuto(entityManager,example),pageable);
    }
}

BaseSpecs的byAuto方法負(fù)責(zé)封裝查詢對(duì)象Specification地沮,按傳遞實(shí)體對(duì)象的屬性進(jìn)行處理嗜浮,如果是字符串就按模糊匹配,否則就按精確匹配摩疑。

public class BaseSpecs {

    public static <T> Specification<T> byAuto(final EntityManager entityManager, final T example){
        final Class<T> type = (Class<T>) example.getClass();
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

                List<Predicate> predicateList = new ArrayList<>();
                EntityType<T> entityType = entityManager.getMetamodel().entity(type);

                for(Attribute<T,?> attribute : entityType.getDeclaredAttributes()){
                    Object attrValue = getValue(example,attribute);
                    if(attrValue != null){
                        if(attribute.getJavaType() == String.class){
                            if(!StringUtils.isEmpty(attrValue)){
                                predicateList.add(criteriaBuilder.like(root.get(attribute(entityType,attribute.getName(),String.class)),pattern((String)attrValue)));
                            }
                        }else{
                            predicateList.add(criteriaBuilder.equal(root.get(attribute(entityType,attribute.getName(),attrValue.getClass())),attrValue));
                        }
                    }
                }
                return predicateList.isEmpty()?criteriaBuilder.conjunction():criteriaBuilder.and(toArray(predicateList));
            }

            private <T> Object getValue(T example,Attribute<T,?> attr){
                return ReflectionUtils.getField((Field)attr.getJavaMember(),example);
            }

            private <E,T> SingularAttribute<T,E> attribute(EntityType<T> entityType,String fieldName,Class<E> fieldClass){
                return entityType.getDeclaredSingularAttribute(fieldName,fieldClass);
            }

            private Predicate[] toArray(List<Predicate> predicateList){
                Predicate[] array = predicateList.toArray(new Predicate[predicateList.size()]);
                return array;
            }
        };
    }

    static private String pattern(String str){
        return "%" + str + "%";
    }
}

說明
當(dāng)我們的Repository實(shí)現(xiàn)的是JpaRepository的時(shí)候危融,Spring-data-jpa會(huì)為我們動(dòng)態(tài)使用JpaRepository的實(shí)現(xiàn)類SimpleJpaRepository,這也是為什么我們只需要?jiǎng)?chuàng)建接口而不需要提供實(shí)現(xiàn)類雷袋。

這里吉殃,我們創(chuàng)建了新的父類接口BaseJpaRepository,并為其提供了實(shí)現(xiàn)類BaseJpaRepositoryImpl楷怒,所以我們要告訴Spring-data-jpa要使用我們自己的實(shí)現(xiàn)類蛋勺,而不能去使用SimpleJpaRepository,所以我們要改寫JpaRepositoryFactoryBean鸠删;

創(chuàng)建一個(gè)BaseRepositoryFactoryBean繼承于JpaRepositoryFactoryBean

public class BaseRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable>   extends JpaRepositoryFactoryBean<T,S,ID> {

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager)    {
        return new BaseRepositoryFactory(entityManager);
    }
}

class BaseRepositoryFactory extends JpaRepositoryFactory {
    public BaseRepositoryFactory(EntityManager entityManager){
        super(entityManager);
    }


    //指定實(shí)現(xiàn)類
    @Override
    protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) {
        BaseJpaRepositoryImpl customRepository = new BaseJpaRepositoryImpl<T,ID>((Class<T>)information.getDomainType(),entityManager);
        return customRepository;

    }

    //指定實(shí)現(xiàn)類類型
    @Override
    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata)     
        return BaseJpaRepositoryImpl.class;
    }
}

并且在@EnableJpaRepositories注解中進(jìn)行指定:

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

自定義Repository繼承BaseJpaRepository

public interface PersonRepository extends BaseJpaRepository<Person, Integer> {
    //………依然可以在該接口中對(duì)功能進(jìn)行擴(kuò)展………

}

測(cè)試

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class JpaExtendApplicationTests {

    @Autowired
    private PersonRepository personRepository;

    @Test
    public void findByAuto() {
        Person person = new Person();
        person.setpName("王五");
        person.setpAge(18);
        Sort sort = new Sort(Sort.Direction.DESC, "pId");
        //查詢第一頁抱完,按一頁三行分頁
        Pageable pageable = new PageRequest(0, 3, sort);
        Page<Person> list = personRepository.findByAuto(person,pageable);
        for(Person p:list){
            System.out.println(p);
        }
    }
}

本文示例代碼下載地址:https://github.com/hanqunfeng/SpringBootStudy

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市刃泡,隨后出現(xiàn)的幾起案子巧娱,更是在濱河造成了極大的恐慌碉怔,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,185評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禁添,死亡現(xiàn)場(chǎng)離奇詭異撮胧,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)老翘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門芹啥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酪捡,你說我怎么就攤上這事叁征。” “怎么了逛薇?”我有些...
    開封第一講書人閱讀 157,684評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵捺疼,是天一觀的道長。 經(jīng)常有香客問我永罚,道長啤呼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,564評(píng)論 1 284
  • 正文 為了忘掉前任呢袱,我火速辦了婚禮官扣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羞福。我一直安慰自己惕蹄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,681評(píng)論 6 386
  • 文/花漫 我一把揭開白布治专。 她就那樣靜靜地躺著卖陵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪张峰。 梳的紋絲不亂的頭發(fā)上泪蔫,一...
    開封第一講書人閱讀 49,874評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音喘批,去河邊找鬼撩荣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛饶深,可吹牛的內(nèi)容都是我干的餐曹。 我是一名探鬼主播,決...
    沈念sama閱讀 39,025評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼敌厘,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼台猴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,761評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤卿吐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后锋华,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嗡官,經(jīng)...
    沈念sama閱讀 44,217評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,545評(píng)論 2 327
  • 正文 我和宋清朗相戀三年毯焕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了衍腥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,694評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纳猫,死狀恐怖婆咸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芜辕,我是刑警寧澤尚骄,帶...
    沈念sama閱讀 34,351評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站侵续,受9級(jí)特大地震影響倔丈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜状蜗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,988評(píng)論 3 315
  • 文/蒙蒙 一需五、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轧坎,春花似錦宏邮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至属百,卻和暖如春记劝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背族扰。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評(píng)論 1 266
  • 我被黑心中介騙來泰國打工厌丑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渔呵。 一個(gè)月前我還...
    沈念sama閱讀 46,427評(píng)論 2 360
  • 正文 我出身青樓怒竿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親扩氢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耕驰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,580評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容