Springboot 實現mybatis+jpa+動態數據源

        本文介紹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<String> CONTEXT_HOLDER = new TransmittableThreadLocal<>();

    /**
     * All DataSource List
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();

    /**
     * To switch DataSource
     *
     * @param key the key
     */
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    /**
     * Get current DataSource
     *
     * @return data source key
     */
    public static String getDataSourceKey() {
        return CONTEXT_HOLDER.get();
    }

    /**
     * To set DataSource as default
     */
    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }

    /**
     * Check if give DataSource is in current DataSource list
     *
     * @param key the key
     * @return boolean boolean
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }
}

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<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("gf", db1());
        dataSourceMap.put("bx", db2());
        dataSourceMap.put("kt", db3());


        // Set master datasource as default
        dynamicRoutingDataSource.setDefaultTargetDataSource(db1());
        // Set master and slave datasource as target datasource
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);

        // To put datasource keys into DataSourceContextHolder to judge if the datasource is exist
        DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
        return dynamicRoutingDataSource;
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean() throws IOException {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dynamicDataSource());
        bean.setConfigLocation(new DefaultResourceLoader().getResource("classpath:config/mybatis-config.xml"));

        try {
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

/*  如下針對只有jpa+springboot2.0的 ,在springboot1.4+mybatis不適用
    @Bean(name = "entityManagerFactoryBean")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) {

        // 獲取application.yml中spring.jpa.properties的配置
        Map<String, String> properties = jpaProperties.getProperties();

        return builder
            // 設置動態數據源
            .dataSource(dynamicDataSource())
            .properties(properties)
            .packages("com.hi.base.domain")
            .persistenceUnit("persistenceUnit")
            .build();
    }

    @Primary
    @Bean(name = "entityManagerFactory")
    public EntityManagerFactory entityManagerFactory(EntityManagerFactoryBuilder builder) {
        return this.entityManagerFactoryBean(builder).getObject();
    }

    @Primary
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(entityManagerFactory(builder));
    }*/

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

}

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

 

發佈了14 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章