springboot jpa 多数据源切换
参考文章:
AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换
pringBoot系列——Spring-Data-JPA
SpringBoot集成jpa 一篇就够了 超详细
mybatis多数据源配置
参考代码:
本示例代码:
详细步骤:
1、maven配置:
<!--监控sql日志-->
<dependency>
<groupId>org.bgee.log4jdbc-log4j2</groupId>
<artifactId>log4jdbc-log4j2-jdbc4.1</artifactId>
<version>${log4jdbc.version}</version>
</dependency>
<!-- druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2、application.yml配置文件信息(多数据源)
server:
port: 8080
spring:
#配置数据源
datasource:
druid:
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
username: root
password: *******
url: jdbc:log4jdbc:mysql://10.****.***.***:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
# 初始连接数
initial-size: 5
min-idle: 10
# 最大连接数
max-active: 20
# 获取连接超时时间
max-wait: 5000
# 连接有效性检测时间
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存的时间
max-evictable-idle-time-millis: 900000
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 检测连接是否有效
validation-query: select 1
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
username: root
password: *******
url: jdbc:log4jdbc:mysql://10.***.***.***:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
# 初始连接数
initial-size: 3
min-idle: 11
# 最大连接数
max-active: 15
# 获取连接超时时间
max-wait: 5000
# 连接有效性检测时间
time-between-eviction-runs-millis: 60000
# 连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 连接在池中最大生存的时间
max-evictable-idle-time-millis: 900000
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 检测连接是否有效
validation-query: select 1
#配置 Jpa
jpa:
show-sql: true
#禁用jpa缓存
open-in-view: false
hibernate:
naming:
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
3、注解类com.demo.annotation.DS
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
String value() default "";
}
4、多数据源切换工具类
package com.jpa.utils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 配置多数据源
* 借助ThreadLocal类,通过ThreadLocal类传递数据源的参数
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public static final String master= "master";
public static final String slave= "slave";
//本地线程,获取当前正在执行的currentThread
public static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
return contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return getCustomerType();
}
}
切面类:
package com.jpa.aspect;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import com.jpa.annotation.DS;
import com.jpa.utils.DynamicDataSource;
/**
* 动态切换数据源类
* @author
* @description
* 2020年4月7日
*/
@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.jpa.annotation.DS)")
public void DSPointCut() {
};
@Before("DSPointCut()")
public void beforeSwitchDS(JoinPoint point){
MethodSignature signature = (MethodSignature) point.getSignature();
// 得到访问的方法对象
Method method = signature.getMethod();
String dataSource = DynamicDataSource.master;
try {
DS dsAnnotation = method.getAnnotation(DS.class);
// 判断是否存在@DS注解
if (dsAnnotation != null) {
// 取出注解中的数据源名
dataSource = dsAnnotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DynamicDataSource.setCustomerType(dataSource);
}
@After("DSPointCut()")
public void afterSwitchDS(JoinPoint point){
DynamicDataSource.clearCustomerType();
}
}
5、数据源信息注入
package com.jpa.conf;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.jpa.utils.DynamicDataSource;
@Configuration
public class DataSourceConfig {
@Bean(name="master",initMethod = "init")
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource mysql(){
return DruidDataSourceBuilder.create().build();
}
@Bean(name="slave",initMethod = "init")
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource erpmaster(){
return DruidDataSourceBuilder.create().build();
}
@Primary
@Bean(name = "dynamicDataSource")
public DynamicDataSource dynamicDataSource(@Qualifier("master") DataSource master, @Qualifier("slave") DataSource slave) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 配置多数据源
Map<Object, Object> dataBaseMap = new HashMap<>();
dataBaseMap.put("master", master);
dataBaseMap.put("slave", slave);
dynamicDataSource.setTargetDataSources(dataBaseMap);
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(master);
return dynamicDataSource;
}
}
JPA配置:
package com.jpa.conf;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import com.jpa.utils.DynamicDataSource;
@Configuration
@EnableJpaRepositories(basePackages = "com.jpa.repository",entityManagerFactoryRef = "entityManagerFactory",transactionManagerRef = "platformTransactionManager")
public class DynamicDataSourceConfig {
@Autowired(required=true)
private DynamicDataSource dynamicDataSource;
@Autowired
private JpaProperties jpaProperties;
@Primary
@Bean(name="entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder){
//
Map<String, String> properties = jpaProperties.getProperties();
//要设置这个属性,实现 CamelCase -> UnderScore 的转换
properties.put("hibernate.physical_naming_strategy",
"org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
LocalContainerEntityManagerFactoryBean entityManagerFactory = builder
.dataSource(dynamicDataSource)
.properties(properties)
.packages("com.jpa.entity")
.persistenceUnit("primary")
.build();
return entityManagerFactory;
}
@Primary
@Bean(name="platformTransactionManager")
public PlatformTransactionManager platformTransactionManager(EntityManagerFactoryBuilder builder){
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = entityManagerFactory(builder);
return new JpaTransactionManager(localContainerEntityManagerFactoryBean.getObject());
}
}
6、在service实现层加上数据源注解:
package com.jpa.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.jpa.annotation.DS;
import com.jpa.entity.User;
import com.jpa.repository.UserRepository;
import com.jpa.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@DS("master")
@Transactional(rollbackFor = Exception.class)
@Override
public User queryByUsername(String userName) {
return userRepository.findByUsername(userName);
}
@DS("slave")
@Transactional(rollbackFor = Exception.class)
@Override
public User queryByUsername1(String userName) {
return userRepository.findByUsername1(userName);
}
}
7、controller
package com.jpa.controller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.jpa.entity.User;
import com.jpa.service.UserService;
@RestController
@RequestMapping("/api")
public class TestController {
@Autowired
private UserService userService;
@GetMapping("/users")
public List<User> query(){
List<User> list = new ArrayList<User>();
String userName = "admin";
User user1 = userService.queryByUsername1(userName);
System.out.println("nickeName:::::::::"+user1.getNickName());
//注意:jpa配置中一定要禁用jpa查询缓存,否则第二次查询即便切换了数据源,也不会从数据库查询,而是会用第一次查询的数据,
//因为缓存中已有数据,且查询条件相同。open-in-view: false
User user = userService.queryByUsername(userName);
System.out.println("nickeName:::::::::"+user.getNickName());
list.add(user);
list.add(user1);
return list;
}
}