查看原文
其他

Spring Boot 集成 Mybatis 实现双数据源

不羁码农 Java技术栈 2018-08-06


Java技术栈

优质的 Java 技术公众号

 


这里用到了Spring Boot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离。

添加依赖

加入Mybatis启动器,这里添加了Druid连接池、Oracle数据库驱动为例。

  1. <dependency>

  2.    <groupId>org.mybatis.spring.boot</groupId>

  3.    <artifactId>mybatis-spring-boot-starter</artifactId>

  4. </dependency>

  5. <dependency>

  6.    <groupId>com.alibaba</groupId>

  7.    <artifactId>druid</artifactId>

  8. </dependency>

  9. <dependency>

  10.    <groupId>com.oracle</groupId>

  11.    <artifactId>ojdbc6</artifactId>

  12. </dependency>

添加启动类

  1. @EnableMybatis

  2. @EnableTransactionManagement

  3. @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })

  4. public class Application {

  5.    public static void main(String[] args) {

  6.        SpringApplication.run(ServiceApplication.class, args);

  7.    }

  8. }

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }): 这里用到了双数据源,需要排除数据源的自动配置,如果只有一个数据源用Spring Boot的自动配置就行。

@EnableTransactionManagement:开启事务支持。

@EnableMybatis:开启Mybatis功能

  1. @Target(ElementType.TYPE)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. @Import(MybatisConfig.class)

  5. public @interface EnableMybatis {

  6. }

Mybatis配置类

  1. @Configuration

  2. @MapperScan(basePackages = DSConfig.BASE_PACKAGES)

  3. public class MybatisConfig implements DSConfig {

  4.    @Primary

  5.    @Bean

  6.    public DynamicDataSource dynamicDataSource(@Qualifier(DB_MASTER) DataSource master,

  7.            @Qualifier(DB_SLAVE) DataSource slave) {

  8.        Map<Object, Object> dsMap = new HashMap<>();

  9.        dsMap.put(DB_MASTER, master);

  10.        dsMap.put(DB_MASTER, slave);

  11.        DynamicDataSource dynamicDataSource = new DynamicDataSource();

  12.        dynamicDataSource.setDefaultTargetDataSource(master);

  13.        dynamicDataSource.setTargetDataSources(dsMap);

  14.        return dynamicDataSource;

  15.    }

  16.    @Bean

  17.    public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {

  18.        return new DataSourceTransactionManager(dynamicDataSource);

  19.    }

  20.    @Bean

  21.    public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource)

  22.            throws Exception {

  23.        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();

  24.        sessionFactory.setDataSource(dynamicDataSource);

  25.        sessionFactory.setMapperLocations(

  26.                ((ResourcePatternResolver) new PathMatchingResourcePatternResolver())

  27.                        .getResources(DSConfig.MAPPER_LOCATIONS));

  28.        return sessionFactory.getObject();

  29.    }

  30. }

DSConfig常量类:

  1. public interface DSConfig {

  2.    String DS_PREFIX = "spring.datasource";

  3.    String DS_ACTIVE = "active";

  4.    String DB_MASTER = "db-master";

  5.    String DB_SLAVE = "db-slave";

  6.    String DRUID = "druid";

  7.    String DRUID_MONITOR_USERNAME = "spring.druid.username";

  8.    String DRUID_MONITOR_PASSWORD = "spring.druid.password";

  9.    String DRUID_MONITOR_URL = "/druid/*";

  10.    String DRUID_FILTER_EXCLUSIONS = "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*";

  11.    String DRUID_FILTER_URL = "/*";

  12.    String BASE_PACKAGES = "com.example.**.mapper";

  13.    String MAPPER_LOCATIONS = "mapper/**/*.xml";

  14. }

连接池配置类

Druid连接池的自动配置类:

  1. @Configuration

  2. @Import({ PropertiesConfig.class })

  3. @ConditionalOnClass(DruidDataSource.class)

  4. @ConditionalOnProperty(prefix = DSConfig.DS_PREFIX, value = DSConfig.DS_ACTIVE, havingValue = DSConfig.DRUID)

  5. public class DruidAutoConfig implements DSConfig {

  6.    private Logger logger = LoggerUtils.getLogger(this);

  7.    @Bean(name = DB_MASTER, initMethod = "init", destroyMethod = "close")

  8.    public DataSource dataSourceMaster(DruidMasterProperties masterProperties) throws SQLException {

  9.        logger.debug("master properties: {}", masterProperties.toString());

  10.        DruidDataSource dds = new DruidDataSource();

  11.        dds.setDriverClassName(masterProperties.getDriverClassName());

  12.        dds.setUrl(masterProperties.getUrl());

  13.        dds.setUsername(masterProperties.getUsername());

  14.        dds.setPassword(masterProperties.getPassword());

  15.        dds.setInitialSize(masterProperties.getInitialSize());

  16.        dds.setMinIdle(masterProperties.getMinIdle());

  17.        dds.setMaxActive(masterProperties.getMaxActive());

  18.        dds.setMaxWait(masterProperties.getMaxWait());

  19.        dds.setTimeBetweenEvictionRunsMillis(masterProperties.getTimeBetweenEvictionRunsMillis());

  20.        dds.setMinEvictableIdleTimeMillis(masterProperties.getMinEvictableIdleTimeMillis());

  21.        dds.setValidationQuery(masterProperties.getValidationQuery());

  22.        dds.setTestOnBorrow(masterProperties.isTestOnBorrow());

  23.        dds.setTestWhileIdle(masterProperties.isTestWhileIdle());

  24.        dds.setTestOnReturn(masterProperties.isTestOnReturn());

  25.        dds.setPoolPreparedStatements(masterProperties.isPoolPreparedStatements());

  26.        dds.setMaxPoolPreparedStatementPerConnectionSize(

  27.                masterProperties.getMaxPoolPreparedStatementPerConnectionSize());

  28.        dds.setFilters(masterProperties.getFilters());

  29.        return dds;

  30.    }

  31.    @Bean(name = DB_SLAVE, initMethod = "init", destroyMethod = "close")

  32.    public DataSource dataSourceSlave(DruidSlaveProperties slaveProperties) throws SQLException {

  33.        logger.debug("slave properties: {}", slaveProperties.toString());

  34.        DruidDataSource dds = new DruidDataSource();

  35.        dds.setDriverClassName(slaveProperties.getDriverClassName());

  36.        dds.setUrl(slaveProperties.getUrl());

  37.        dds.setUsername(slaveProperties.getUsername());

  38.        dds.setPassword(slaveProperties.getPassword());

  39.        dds.setInitialSize(slaveProperties.getInitialSize());

  40.        dds.setMinIdle(slaveProperties.getMinIdle());

  41.        dds.setMaxActive(slaveProperties.getMaxActive());

  42.        dds.setMaxWait(slaveProperties.getMaxWait());

  43.        dds.setTimeBetweenEvictionRunsMillis(slaveProperties.getTimeBetweenEvictionRunsMillis());

  44.        dds.setMinEvictableIdleTimeMillis(slaveProperties.getMinEvictableIdleTimeMillis());

  45.        dds.setValidationQuery(slaveProperties.getValidationQuery());

  46.        dds.setTestOnBorrow(slaveProperties.isTestOnBorrow());

  47.        dds.setTestWhileIdle(slaveProperties.isTestWhileIdle());

  48.        dds.setTestOnReturn(slaveProperties.isTestOnReturn());

  49.        dds.setPoolPreparedStatements(slaveProperties.isPoolPreparedStatements());

  50.        dds.setMaxPoolPreparedStatementPerConnectionSize(

  51.                slaveProperties.getMaxPoolPreparedStatementPerConnectionSize());

  52.        dds.setFilters(slaveProperties.getFilters());

  53.        return dds;

  54.    }

  55.    @Bean

  56.    public ServletRegistrationBean druidServletRegistrationBean(EnvConfig env) {

  57.        String username = env.getStringValue(DSConfig.DRUID_MONITOR_USERNAME);

  58.        String password = env.getStringValue(DSConfig.DRUID_MONITOR_PASSWORD);

  59.        return new ServletRegistrationBean(new DruidStatViewServlet(username, password),

  60.                DSConfig.DRUID_MONITOR_URL);

  61.    }

  62.    @Bean

  63.    public FilterRegistrationBean druidFilterRegistrationBean() {

  64.        WebStatFilter wsf = new WebStatFilter();

  65.        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();

  66.        filterRegistrationBean.setFilter(wsf);

  67.        filterRegistrationBean.setUrlPatterns(Arrays.asList(DSConfig.DRUID_FILTER_URL));

  68.        filterRegistrationBean.setInitParameters(

  69.                Collections.singletonMap("exclusions", DSConfig.DRUID_FILTER_EXCLUSIONS));

  70.        return filterRegistrationBean;

  71.    }

  72. }

根据类路径下有DruidDataSource这个类即有Druid这个jar包和配置文件中spring.datasource.active=druid才开启对Druid连接池的自动配置。

导入的配置文件:

  1. @Configuration

  2. @ComponentScan(basePackages = "com.example.common.config.properties")

  3. public class PropertiesConfig {

  4. }

DruidMasterProperties、DruidSlaveProperties属性文件读取的配置省略。

连接池监控配置类:

  1. public class DruidStatViewServlet extends StatViewServlet {

  2.    private static final long serialVersionUID = 1L;

  3.    private String username;

  4.    private String password;

  5.    @Override

  6.    public String getInitParameter(String name) {

  7.        if ("loginUsername".equals(name)) {

  8.            return username;

  9.        }

  10.        if ("loginPassword".equals(name)) {

  11.            return password;

  12.        }

  13.        return super.getInitParameter(name);

  14.    }

  15.    public DruidStatViewServlet(String username, String password) {

  16.        super();

  17.        this.username = username;

  18.        this.password = password;

  19.    }

  20.    public String getUsername() {

  21.        return username;

  22.    }

  23.    public String getPassword() {

  24.        return password;

  25.    }

  26. }

在META-INF/spring.factories中加入Druid自动配置映射:

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

  2. com.example.common.config.ds.DruidAutoConfig


切换数据源

切换数据源注解:

  1. @Target(ElementType.METHOD)

  2. @Retention(RetentionPolicy.RUNTIME)

  3. @Documented

  4. public @interface DS {

  5.    String value() default DSConfig.DB_MASTER;

  6. }

动态数据源类:

  1. public class DynamicDataSource extends AbstractRoutingDataSource {

  2.    private final Logger logger = LoggerUtils.getLogger(this);

  3.    @Override

  4.    protected Object determineCurrentLookupKey() {

  5.        logger.debug("当前数据源为{}", DataSourceContextHolder.getDS());

  6.        return DataSourceContextHolder.getDS();

  7.    }

  8. }

动态数据源AOP实现类:

  1. @Aspect

  2. @Component

  3. public class DynamicDataSourceAspect {

  4.    @Before("@annotation(DS)")

  5.    public void beforeSwitchDS(JoinPoint point) {

  6.        Class<?> className = point.getTarget().getClass();

  7.        String methodName = point.getSignature().getName();

  8.        Class<?>[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();

  9.        String dataSource = DataSourceContextHolder.DEFAULT_DS;

  10.        try {

  11.            Method method = className.getMethod(methodName, argClass);

  12.            if (method.isAnnotationPresent(DS.class)) {

  13.                DS annotation = method.getAnnotation(DS.class);

  14.                dataSource = annotation.value();

  15.            }

  16.        } catch (Exception e) {

  17.            e.printStackTrace();

  18.        }

  19.        DataSourceContextHolder.setDS(dataSource);

  20.    }

  21.    @After("@annotation(DS)")

  22.    public void afterSwitchDS(JoinPoint point) {

  23.        DataSourceContextHolder.clearDS();

  24.    }

  25. }

绑定当前线程数据源类:

  1. public class DataSourceContextHolder {

  2.    public static final String DEFAULT_DS = DSConfig.DB_MASTER;

  3.    private static final ThreadLocal<String> DS_HOLDER = new ThreadLocal<>();

  4.    public static void setDS(String dbType) {

  5.        DS_HOLDER.set(dbType);

  6.    }

  7.    public static String getDS() {

  8.        return (DS_HOLDER.get());

  9.    }

  10.    public static void clearDS() {

  11.        DS_HOLDER.remove();

  12.    }

  13. }


-END-

不错,转发分享一下吧


往期推荐

 

【1】Spring Boot & Cloud 技术教程汇总

【2】京东购物车的 Java 架构实现及原理

【3】最新后端架构师技术图谱!附学习资料~

【4】Java序列化技术即将被废除!!

【5】Java 中的伪共享详解及解决方案

点击左下方阅读原文,加入Java知识星球!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存