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;
}
}