本文介绍SpringBoot 框架中实现mybatis和jpa 同时实现多数据源。Springboot 版本1.4.0.Release 因为Springboot版本不到2.0 ,所以还是遇到了一些小挫折,不过最终解决。
1. yml文件配置多数据源
2. 动态数据源基础类 DynamicRoutingDataSource.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源设置
*
* @date 2019-11-05
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Set dynamic DataSource to Application Context
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
logger.debug("Current DataSource is [{}]",
DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
3.动态数据源上下文 DynamicDataSourceContextHolder.java
package com.hi.base.config.datasource;
import com.alibaba.ttl.TransmittableThreadLocal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* 动态数据源上下文配置 DynamicDataSourceContextHolder.java
*
* @author lida
* @date 2019-11-05
*/
public class DynamicDataSourceContextHolder {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* Maintain variable for every thread, to avoid effect other thread
*/
private static final ThreadLocal CONTEXT_HOLDER = new TransmittableThreadLocal<>();
/**
* All DataSource List
*/
public static List
4. 多数据源配置类
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;
import com.hi.base.config.Constants;
import com.hi.base.config.liquibase.AsyncSpringLiquibase;
import com.zaxxer.hikari.HikariDataSource;
import liquibase.integration.spring.SpringLiquibase;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
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 org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.inject.Inject;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableJpaRepositories(value = {"com.hi.base.repository", "com.hi.base.export.refrigerator.business.repository", "com.hi.base.export.airconditioner.business.repository", "com.hi.base.export.common.repository"})
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement
public class DatabaseConfiguration {
private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
@Inject
private Environment env;
@Value("${spring.datasource.hikari.gf.driverClassName}")
private String gfDriverClassName;
@Value("${spring.datasource.hikari.gf.jdbcUrl}")
private String gfJdbcUrl;
@Value("${spring.datasource.hikari.gf.username}")
private String gfUsername;
@Value("${spring.datasource.hikari.gf.password}")
private String gfPassword;
@Value("${spring.datasource.hikari.bx.driverClassName}")
private String bxDriverClassName;
@Value("${spring.datasource.hikari.bx.jdbcUrl}")
private String bxJdbcUrl;
@Value("${spring.datasource.hikari.bx.username}")
private String bxUsername;
@Value("${spring.datasource.hikari.bx.password}")
private String bxPassword;
@Value("${spring.datasource.hikari.kt.driverClassName}")
private String ktDriverClassName;
@Value("${spring.datasource.hikari.kt.jdbcUrl}")
private String ktJdbcUrl;
@Value("${spring.datasource.hikari.kt.username}")
private String ktUsername;
@Value("${spring.datasource.hikari.kt.password}")
private String ktPassword;
@Value("${spring.datasource.hikari.poolName}")
private String poolName;
@Bean
public SpringLiquibase liquibase(DataSource dataSource, LiquibaseProperties liquibaseProperties) {
// Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously
SpringLiquibase liquibase = new AsyncSpringLiquibase();
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
liquibase.setContexts(liquibaseProperties.getContexts());
liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
liquibase.setDropFirst(liquibaseProperties.isDropFirst());
if (env.acceptsProfiles(Constants.SPRING_PROFILE_NO_LIQUIBASE)) {
liquibase.setShouldRun(false);
} else {
liquibase.setShouldRun(liquibaseProperties.isEnabled());
log.debug("Configuring Liquibase");
}
return liquibase;
}
@Bean
public Hibernate4Module hibernate4Module() {
return new Hibernate4Module();
}
/**
// springboot2.0之前的版本不支持这样的写法,所以下面得手动构造hikari 数据源,坑!
@Bean("db1")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.hikari.31")
public DataSource db1() {
return DataSourceBuilder.create().build();
}*/
/**
* db1 DataSource
* 数据源1
* @return data source
*/
@Bean(name = "db1")
public DataSource db1() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(gfDriverClassName);
dataSource.setJdbcUrl(gfJdbcUrl);
dataSource.setUsername(gfUsername);
dataSource.setPassword(gfPassword);
dataSource.setPoolName(poolName);
return dataSource;
}
/**
* db2 DataSource
* 数据源2
* @return data source
*/
@Bean(name = "db2")
public DataSource db2() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(bxDriverClassName);
dataSource.setJdbcUrl(bxJdbcUrl);
dataSource.setUsername(bxUsername);
dataSource.setPassword(bxPassword);
dataSource.setPoolName(poolName);
return dataSource;
}
/**
* db3 DataSource
* 数据源3
* @return data source
*/
@Bean(name = "db3")
public DataSource db3() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setDriverClassName(ktDriverClassName);
dataSource.setJdbcUrl(ktJdbcUrl);
dataSource.setUsername(ktUsername);
dataSource.setPassword(ktPassword);
dataSource.setPoolName(poolName);
return dataSource;
}
/**
* Dynamic data source.
* 配置动态数据源 一定要加@Primary注解,不然给liquibase传参会出现问题。
* @return the data source
*/
@Bean(name = "dynamicDataSource")
@Primary
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map
5. mybatis 扫描类
import com.hi.base.config.datasource.DatabaseConfiguration;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 扫描mybatis的接口
*
* @author lida
*
*/
@Configuration
// 因为这个对象的扫描,需要在DatabaseConfiguration的后面注入,所以加上下面的注解
@AutoConfigureAfter(DatabaseConfiguration.class)
public class MyBatisMapperScannerConfig {
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
//获取之前注入的beanName为sqlSessionFactory的对象
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
//指定xml配置文件的路径
mapperScannerConfigurer.setBasePackage("com.hi.base.mapper");
return mapperScannerConfigurer;
}
}
6. 为前后端接口做切面,按照登录用户所处公司来切换数据源。
package com.hi.base.config.datasource;
import com.hi.base.domain.sys.User;
import com.hi.base.repository.sys.UserRepository;
import com.hi.base.security.SecurityUtils;
import org.apache.commons.lang3.StringUtils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
/**
* 动态据源切面
*
* @author lida
* @date 2019-11-05
*/
@Aspect
@Component
public class DynamicDataSourceRestAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRestAspect.class);
@Inject
private UserRepository userRepository;
/**
* 在Rest上做切面
*/
@Pointcut("execution(public * com.hi.base.web.rest.*.*(..))")
public void restAspect() {
}
/**
* Switch DataSource
*
* @param point the point
*/
@Before("restAspect()")
public void switchDataSource(JoinPoint point) {
String oldCapany = DynamicDataSourceContextHolder.getDataSourceKey();
//获取用户表公司。设置默认公司
DynamicDataSourceContextHolder.setDataSourceKey("gf");
try {
String userName = SecurityUtils.getCurrentUserLogin();
if(!StringUtils.isBlank(userName)) {
User user = userRepository.findByUserName(userName);
if(null!=user) {
String nowCampany=user.getCompany();
if (!StringUtils.isBlank(nowCampany)) {
// 通过人员公司设置数据源
DynamicDataSourceContextHolder.setDataSourceKey(nowCampany);
}
}
}
logger.debug("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
} catch (Exception e) {
if (!StringUtils.isBlank(oldCapany)) {
DynamicDataSourceContextHolder.setDataSourceKey(oldCapany);
}
logger.error("数据源设置失败", e);
}
}
/**
* Restore DataSource
*
* @param point the point
*/
@After("restAspect()")
public void restoreDataSource(JoinPoint point) {
DynamicDataSourceContextHolder.clearDataSourceKey();
logger.debug("Restore DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
}
}