SpringBoot integration Spring Data JPA and read write separation

  • 2020-07-21 07:51:40
  • OfStack

Related code: github OSCchina

What is JPA

JPA(Java Persistence API) is an Java persistence specification officially proposed by Sun. It provides Java developers with an object/correlation mapping tool to manage relational data in Java applications. It includes the following aspects:

1.ORM mapping supports xml and annotations to establish mappings between entities and tables.

Java persistence API defines 1 common CRUD interfaces that we can call directly without worrying about the details of the underlying JDBC and SQL.

3.JPQL Query language (JPQL) Is a very important aspect of persistence operation. By querying data through object-oriented rather than database-oriented query language, the tight coupling of SQL statements of the program is avoided.

ORM technology at work, we will use, such as Hibernate JOOQ etc., according to different requirements, we will adopt different ORM framework, when we need to replace ORM framework to meet our requirements, because different ORM framework implementation, use, and the difference between current, we often need to refactor the code. The emergence of JPA is to solve the problem, JPA fully absorb the advantage of the existing 1 some ORM framework, and is easy to use, It provides a set of standard interfaces for ORM technology to integrate different ORM frameworks.

Hibernate implementation of JPA

JPA does not do the implementation itself, but simply defines a set of interface specifications and lets other ORM implement those interfaces. For now, the best implementation of the JPA specification is Hibernate.

What is Spring Data JPA

Spring Data JPA just Spring Data framework of a module, can greatly simplify JPA use, Spring Data JPA powerful place is for persistence layer to be able to simplify our business logic development, through the specification of persistence layer method name, by name to determine what needs to implement the business logic, we can not write 1 sentence sql opportunity, don't do any dao layer Logical case to complete most of our development, of course, for 1 some complex, performance demanding queries,Spring Data JPA1 sample support us to use the native sql.

We will not cover JPA and Spring Data JPA here, but mainly the details and examples of integration with SpringBoot.

Introduction of depend on


<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

After we introduced this dependency, we found that Hibernate package was also introduced, which is now the default practice. Hibernate has been adopted as the best implementation of the JPA specification. We will not introduce the configuration of THE Druid data source here, you can see another article XXXX.

Configure our data source and JPA(Hibernate)


# The configuration template 
#https://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/html/common-application-properties.html
# The data source 
spring.datasource.druid.write.url=jdbc:mysql://localhost:3306/jpa
spring.datasource.druid.write.username=root
spring.datasource.druid.write.password=1
spring.datasource.druid.write.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.read.url=jdbc:mysql://localhost:3306/jpa
spring.datasource.druid.read.username=root
spring.datasource.druid.read.password=1
spring.datasource.druid.read.driver-class-name=com.mysql.jdbc.Driver
#JPA (JpaBaseConfiguration, HibernateJpaAutoConfiguration)
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.database=mysql
spring.jpa.generate-ddl=true
# is hibernate.hbm2ddl.auto, You can look at the details README
spring.jpa.hibernate.ddl-auto=update
# By method name resolution sql The strategy of , You can look at the details README, I'm not going to configure it here 
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy
spring.jpa.show-sql=true
#spring.jpa.properties.*
#spring.jpa.properties.hibernate.hbm2ddl.auto=update
#spring.jpa.properties.hibernate.show_sql=true
#spring.jpa.properties.hibernate.use-new-id-generator-mappings=true

druid data source injection


@Configuration
public class DruidDataSourceConfig {
  /**
   * DataSource  configuration 
   * @return
   */
  @ConfigurationProperties(prefix = "spring.datasource.druid.read")
  @Bean(name = "readDruidDataSource")
  public DataSource readDruidDataSource() {
    return new DruidDataSource();
  }
  /**
   * DataSource  configuration 
   * @return
   */
  @ConfigurationProperties(prefix = "spring.datasource.druid.write")
  @Bean(name = "writeDruidDataSource")
  @Primary
  public DataSource writeDruidDataSource() {
    return new DruidDataSource();
  }
}

EntityManagerFactory instance injection

EntityManagerFactory is similar to ES1124en,mybatis is similar to ES1118en, mybatis is similar to sqlSession. There are two ways to inject EntityManagerFactory, one is to inject directly and the other is to inject indirectly through LocalContainerEntityManagerFactoryBean. Although both methods are based on LocalContainerEntityManagerFactoryBean, there are some differences in configuration.

1. Inject EntityManagerFactory directly

Configuration: Configure Hibernate properties via ES130en.jpa.properties. *


spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.use-new-id-generator-mappings=true
@Configuration
@EnableJpaRepositories(value = "com.lc.springBoot.jpa.repository",
            entityManagerFactoryRef = "writeEntityManagerFactory",
            transactionManagerRef="writeTransactionManager")
public class WriteDataSourceConfig {
  @Autowired
  JpaProperties jpaProperties;
  @Autowired
  @Qualifier("writeDruidDataSource")
  private DataSource writeDruidDataSource;
  /**
   * EntityManagerFactory Similar to the Hibernate the SessionFactory,mybatis the SqlSessionFactory
   *  In a word , Before the operation is executed , We always have to get 1 a EntityManager, So this is something like Hibernate the Session,
   * mybatis the sqlSession.
   * @return
   */
  @Bean(name = "writeEntityManagerFactory")
  @Primary
  public EntityManagerFactory writeEntityManagerFactory() {
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
    factory.setJpaVendorAdapter(vendorAdapter);
    factory.setPackagesToScan("com.lc.springBoot.jpa.entity");
    factory.setDataSource(writeDruidDataSource);// The data source 
    factory.setJpaPropertyMap(jpaProperties.getProperties());
    factory.afterPropertiesSet();// After all other relevant configuration loads and property Settings have been completed , To initialize the 
    return factory.getObject();
  }
  /**
   *  Configure the thing manager 
   * @return
   */
  @Bean(name = "writeTransactionManager")
  @Primary
  public PlatformTransactionManager writeTransactionManager() {
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
    jpaTransactionManager.setEntityManagerFactory(this.writeEntityManagerFactory());
    return jpaTransactionManager;
  }
}

2. Inject LocalContainerEntityManagerFactoryBean and obtain EntityManagerFactory

Configuration:


spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.database=mysql
spring.jpa.generate-ddl=true
# is hibernate.hbm2ddl.auto, You can look at the details README
spring.jpa.hibernate.ddl-auto=update
# By method name resolution sql The strategy of , You can look at the details README, I'm not going to configure it here 
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy
spring.jpa.show-sql=true
@Configuration
@EnableJpaRepositories(value = "com.lc.springBoot.jpa.repository",
    entityManagerFactoryRef = "writeEntityManagerFactory",
    transactionManagerRef = "writeTransactionManager")
public class WriteDataSourceConfig1 {
  @Autowired
  JpaProperties jpaProperties;
  @Autowired
  @Qualifier("writeDruidDataSource")
  private DataSource writeDruidDataSource;
  /**
   *  We're through LocalContainerEntityManagerFactoryBean In order to get EntityManagerFactory The instance 
   * @return
   */
  @Bean(name = "writeEntityManagerFactoryBean")
  @Primary
  public LocalContainerEntityManagerFactoryBean writeEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
    return builder
        .dataSource(writeDruidDataSource)
        .properties(jpaProperties.getProperties())
        .packages("com.lc.springBoot.jpa.entity") // Sets the location of the entity class 
        .persistenceUnit("writePersistenceUnit")
        .build();
    //.getObject();// Don't get it directly here EntityManagerFactory
  }
  /**
   * EntityManagerFactory Similar to the Hibernate the SessionFactory,mybatis the SqlSessionFactory
   *  In a word , Before the operation is executed , We always have to get 1 a EntityManager, So this is something like Hibernate the Session,
   * mybatis the sqlSession.
   * @param builder
   * @return
   */
  @Bean(name = "writeEntityManagerFactory")
  @Primary
  public EntityManagerFactory writeEntityManagerFactory(EntityManagerFactoryBuilder builder) {
    return this.writeEntityManagerFactoryBean(builder).getObject();
  }
  /**
   *  Configure the thing manager 
   * @return
   */
  @Bean(name = "writeTransactionManager")
  @Primary
  public PlatformTransactionManager writeTransactionManager(EntityManagerFactoryBuilder builder) {
    return new JpaTransactionManager(writeEntityManagerFactory(builder));
  }
}

For this configuration


@Bean(name = "writeEntityManagerFactoryBean")
  @Primary
  public LocalContainerEntityManagerFactoryBean writeEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
    return builder
        .dataSource(writeDruidDataSource)
        .properties(jpaProperties.getProperties())
        .packages("com.lc.springBoot.jpa.entity") // Sets the location of the entity class 
        .persistenceUnit("writePersistenceUnit")
        .build();
    //.getObject();// Don't get it directly here EntityManagerFactory
  }

The getObject() method can get an instance of EntityManagerFactory, which seems to be no different from the first method, but we cannot use getObject() directly, otherwise we will fail to get it and report null pointer exception.

Read-write separation configuration

Custom injection of AbstractRoutingDataSource


@Configuration
public class DataSourceConfig {
  private final static String WRITE_DATASOURCE_KEY = "writeDruidDataSource";
  private final static String READ_DATASOURCE_KEY = "readDruidDataSource";
  /**
   *  injection AbstractRoutingDataSource
   * @param readDruidDataSource
   * @param writeDruidDataSource
   * @return
   * @throws Exception
   */
  @Bean
  public AbstractRoutingDataSource routingDataSource(
      @Qualifier(READ_DATASOURCE_KEY) DataSource readDruidDataSource,
      @Qualifier(WRITE_DATASOURCE_KEY) DataSource writeDruidDataSource
  ) throws Exception {
    DynamicDataSource dataSource = new DynamicDataSource();
    Map<Object, Object> targetDataSources = new HashMap();
    targetDataSources.put(WRITE_DATASOURCE_KEY, writeDruidDataSource);
    targetDataSources.put(READ_DATASOURCE_KEY, readDruidDataSource);
    dataSource.setTargetDataSources(targetDataSources);
    dataSource.setDefaultTargetDataSource(writeDruidDataSource);
    return dataSource;
  }
}

Custom annotations


  @Target({ElementType.METHOD, ElementType.TYPE})
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  public @interface TargetDataSource {
    String dataSource() default "";// The data source 
  }

Use ThreadLocal to bind data sources to threads


 public class DynamicDataSourceHolder {
    // use ThreadLocal Binds the data source to the current thread 
    private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
    public static void setDataSource(String dataSourceName) {
      dataSources.set(dataSourceName);
    }
    public static String getDataSource() {
      return (String) dataSources.get();
    }
    public static void clearDataSource() {
      dataSources.remove();
    }
  }
  public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
      // You can do 1 A simple load balancing strategy 
      String lookupKey = DynamicDataSourceHolder.getDataSource();
      System.out.println("------------lookupKey---------"+lookupKey);
      return lookupKey;
    }
  }

Define the plane


@Aspect
  @Component
  public class DynamicDataSourceAspect {
    @Around("execution(public * com.lc.springBoot.jpa.service..*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
      MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
      Method targetMethod = methodSignature.getMethod();
      if (targetMethod.isAnnotationPresent(TargetDataSource.class)) {
        String targetDataSource = targetMethod.getAnnotation(TargetDataSource.class).dataSource();
        System.out.println("---------- The data source is :" + targetDataSource + "------");
        DynamicDataSourceHolder.setDataSource(targetDataSource);
      }
      Object result = pjp.proceed();// Execution method 
      DynamicDataSourceHolder.clearDataSource();
      return result;
    }
  }

Related articles: