MyBatis 基于源码的同等多数据源

目录

 

一、背景

二、方案描述

3.1、思考细节点

3.2、代码细节

三、总结

五、代码物料


一、背景

当前流行方案:

(1)当前mybatis不支持多数据源

(2)目前较通用方案是,写一个工厂类,进行数据源切换,并且数据源有主从之分master和slave

(3)不能直接将dao层绑定对应数据源上,需要做逻辑判断

读前需要了解知识:

(1)mybatis源码解析:https://blog.csdn.net/CCCdingding/article/details/87805368

(2)BeanFactoryPostProcessor spring处理流程: https://www.jianshu.com/p/a90a3e617ba6

(3)spring启动流程

二、方案描述

3.1、思考细节点

(1)对于MyBatis orm的dao,其最终是交给MapperFactoryBean来实例化dao层的bean,在源码中

由于MapperFactoryBean获取sqlSession只获取默认的sqlSession,而无法指定某一个bean,因此

基于此弊端,重写MapperFactoryBean,每个数据源对应一个MapperFactoryBean,其中sqlSession

作为入参指定数据源。

(2)由于Spring在启动的时候获取beanFactory之后,会将当前的spring的xml和注解的bean全部添

加spring容器中,作为一个初始的BeanDefinition。此时如果修改BeanDefinition对应的class的话,有

两种方案:第一,在BeanDefinition还未修改之前就修改dao的bean对应class;第二,加载好之后实

例化之前修改BeanDefinition。

对于第一种方案,需要重新MapperScannerRegistrar扫描解析类,里面逻辑较复杂,对于第二种方

案,只需要重新MapperFactoryBean,然后修改BeanDefinition的class就可以了。

综合上述两种方案,采用第二种方案。

3.2、代码细节

(1)重写MapperFactoryBean,里面内容基本上不变,只是需要设置自己对应的sqLSession模板

package bootdemo.db;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import static org.springframework.util.Assert.notNull;
/**
 * Created by dingjie on 19/2/28.
 */
public class BabyMapperFactoryBeanOne<T> extends BabySqlSessionDaoSupport implements FactoryBean<T> {
    private Class<T> mapperInterface;//需要创建的bean
    private boolean addToConfig = true;//是否需要添加到容器中
    @Autowired
    @Qualifier("sqlSessionTemplate1")//指定sql数据源的SQLSession
    private SqlSessionTemplate sqlSessionTemplate1;//对应自己的sqlSession
    //初始化SqlSession,设置成自己对应的数据源即可
    public BabyMapperFactoryBeanOne() {
        super.setSqlSessionFactory(sqlSessionTemplate1.getSqlSessionFactory());
    }
    public BabyMapperFactoryBeanOne(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    //属性检查
    @Override
    protected void checkDaoConfig() {
        super.checkDaoConfig();
        notNull(this.mapperInterface, "Property 'mapperInterface' is required");
        //设置自己的configuration,注意次数的sqlSessionTemplate1
        Configuration configuration = sqlSessionTemplate1.getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
                configuration.addMapper(this.mapperInterface);
            } catch (Exception e) {
                logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
                throw new IllegalArgumentException(e);
            } finally {
                ErrorContext.instance().reset();
            }
        }
    }

  //获取由BabyMapperFactoryBeanOne创建的bean对象
    @Override
    public T getObject() throws Exception {
        //设置自己的configuration,注意此处的sqlSessionTemplate2
        return sqlSessionTemplate1.getMapper(this.mapperInterface);
    }

    @Override
    public Class<T> getObjectType() {
        return this.mapperInterface;
    }
    public boolean isSingleton() {
        return true;
    }
    public void setMapperInterface(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    public Class<T> getMapperInterface() {
        return mapperInterface;
    }
    public void setAddToConfig(boolean addToConfig) {
        this.addToConfig = addToConfig;
    }
    public boolean isAddToConfig() {
        return addToConfig;
    }
}

(2)更新dao对应的class的beanClass

由于spring提供了bean的后置工厂处理(BeanFactoryPostProcessor),因此只需要在bean加载好之后,找到对应的daobean,绑定指定的MapperFactoryBean。

package bootdemo.db;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.type.AnnotationMetadata;

/**
 * Created by dingjie on 19/2/28.
 */
public class BabyBeanPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("******调用BeanFactoryPostProcessor开始");
        String[] beanStr = beanFactory.getBeanDefinitionNames();
        for (String beanName : beanStr) {
            System.out.println("bean name:"+beanName);
            ScannedGenericBeanDefinition beanDefinition = null;
            if(beanFactory.getBeanDefinition(beanName) instanceof ScannedGenericBeanDefinition){
                beanDefinition=(ScannedGenericBeanDefinition)beanFactory.getBeanDefinition(beanName);
                AnnotationMetadata meteData = beanDefinition.getMetadata();
                //数据源绑定:替换掉className,更换成当前dao对应的BabyMapperFactoryBeanOne(此处利用了注解特性,稍后会介绍。。。。。。)
                if(meteData.getAnnotationAttributes("bootdemo.annotation.SqlSessionTemplateClass")!=null){
                    Class sqlSessionClass= (Class) meteData.getAnnotationAttributes("bootdemo.annotation.SqlSessionTemplateClass").get("value");
                    beanDefinition.setBeanClass(sqlSessionClass);
                }
            }
        }
        System.out.println("******调用BeanFactoryPostProcessor结束");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <bean class="bootdemo.db.BabyBeanPostProcessor"></bean>
</beans>

(3)创建DataSource,此处采用的是阿里的Druid数据库连接池工具

 @Bean(name="testOne")
    public DataSource dataSource1(){
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl("jdbc:mysql://127.0.0.1:3306/test1?characterEncoding=utf8&useSSL=true");
        datasource.setUsername("root");
        datasource.setPassword("123456");
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        //configuration
        datasource.setInitialSize(5);//初始化大小
        datasource.setMinIdle(3);//最小
        datasource.setMaxActive(10);//最大值
        datasource.setMaxWait(60000);//获取连接等待超时时间
        datasource.setTimeBetweenEvictionRunsMillis(6000);//配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        datasource.setMinEvictableIdleTimeMillis(300000);//配置一个连接在池中最小生存的时间,单位是毫秒
        datasource.setValidationQuery("SELECT 1 FROM DUAL");
        datasource.setTestWhileIdle(true);
        datasource.setTestOnBorrow(false);
        datasource.setTestOnReturn(false);
        datasource.setPoolPreparedStatements(true);//# 打开PSCache,并且指定每个连接上PSCache的大小
        datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
       /* try {
            datasource.setFilters(filters);//# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        } catch (SQLException e) {
        }*/
        //datasource.setConnectionProperties(connectionProperties);//# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        return datasource;
    }



    @Bean(name="testTwo")
    public DataSource dataSource2(){
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl("jdbc:mysql://127.0.0.1:3306/activity?characterEncoding=utf8&useSSL=true");
        datasource.setUsername("root");
        datasource.setPassword("123456");
        datasource.setDriverClassName("com.mysql.jdbc.Driver");
        //configuration
        datasource.setInitialSize(5);//初始化大小
        datasource.setMinIdle(3);//最小
        datasource.setMaxActive(10);//最大值
        datasource.setMaxWait(60000);//获取连接等待超时时间
        datasource.setTimeBetweenEvictionRunsMillis(6000);//配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        datasource.setMinEvictableIdleTimeMillis(300000);//配置一个连接在池中最小生存的时间,单位是毫秒
        datasource.setValidationQuery("SELECT 1 FROM DUAL");
        datasource.setTestWhileIdle(true);
        datasource.setTestOnBorrow(false);
        datasource.setTestOnReturn(false);
        datasource.setPoolPreparedStatements(true);//# 打开PSCache,并且指定每个连接上PSCache的大小
        datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
       /* try {
            datasource.setFilters(filters);//# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        } catch (SQLException e) {
        }*/
        //datasource.setConnectionProperties(connectionProperties);//# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        return datasource;
    }

(4)重写SqlSessionFactory

由于SqlSessionFactory在加载实例化的时候会根据type类型来加载,无法加载多个SqlSessionFactory,因此

为了解决此问题,可以用一个新的类来包装一下SqlSessionFactory。

package bootdemo.db;

import org.apache.ibatis.session.SqlSessionFactory;

/**
 * Created by dingjie on 19/2/28.
 */
public class BabySqlSessionFactory {
    private SqlSessionFactory sqlSessionFactory;
    private String name;
    public SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

(5)设置SqlSessionFactory

@Bean(name="sqlSessionFactory1")
    public BabySqlSessionFactory sqlSessionFactory1() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(testOne);
        List<Resource> resources=new ArrayList<>();
        resources.addAll(Arrays.asList(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/*/person.xml")));
        factoryBean.setMapperLocations(resources.toArray(new Resource[resources.size()]));
        BabySqlSessionFactory babySqlSessionFactory=new BabySqlSessionFactory();
        babySqlSessionFactory.setSqlSessionFactory(factoryBean.getObject());
        babySqlSessionFactory.setName("sqlSessionFactory1");
        return babySqlSessionFactory;
    }
    @Bean(name="babySqlSessionFactory")
    public BabySqlSessionFactory babySqlSessionFactory() throws Exception {
        BabySqlSessionFactory babySqlSessionFactory=new BabySqlSessionFactory();
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(testTwo);
        List<Resource> resources=new ArrayList<>();
        resources.addAll(Arrays.asList(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/*/person.xml")));
        factoryBean.setMapperLocations(resources.toArray(new Resource[resources.size()]));
        babySqlSessionFactory.setName("babySqlSessionFactory");
        babySqlSessionFactory.setSqlSessionFactory(factoryBean.getObject());
        return babySqlSessionFactory;
    }

(6)为每个SqlSessionFactory创建SqlSessionTemplate

@Bean("sqlSessionTemplate1")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1().getSqlSessionFactory()); // 对应的SqlSessionFactory
        return template;
    }
    @Bean("sqlSessionTemplate2")
    public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(babySqlSessionFactory().getSqlSessionFactory()); // 对应的SqlSessionFactory
        return template;
    }

(7)为dao指定数据源类注解

我们知道MapperFactoryBean就知道数据源了,所以此处采用类注解的方式,为dao绑定数据源,即创建一个类注解,参

数没class,这样我们在第(2)步只需要获取注解类的信息就可以获得当前到对应的MapperFactoryBean,降低开发的复杂性。

package bootdemo.annotation;

import java.lang.annotation.*;
/**
 * Created by dingjie on 19/2/28.
 */
@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SqlSessionTemplateClass {
    public Class value();//对应的MapperFactoryBean
}
package bootdemo.dao.db1;

import bootdemo.annotation.SqlSessionTemplateClass;
import bootdemo.db.BabyMapperFactoryBeanOne;
import bootdemo.entity.Person;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * Created by dingjie on 19/2/18.
 */
@Mapper
@SqlSessionTemplateClass(BabyMapperFactoryBeanOne.class)//对应的MapperFactoryBean,只需要添加一行注解就可以了,很简单
public interface PersonDaoOne{
    List<Person> findById(@Param("id") int id, @Param("name") String name);
    List<Person> chooseDemo(@Param("id") int id, @Param("name") String name);
    List<Person> trimDemo(@Param("id") int id, @Param("name") String name);
    List<Person> whereDemo(@Param("id") int id, @Param("name") String name);
    List<Person> foreachDemo(@Param("idList") List<Integer> idList);
}

(8)到此处已经全部将dao的bean全部注入到spring的容器中了,最后这一步只需要调用bean就可以了

package bootdemo.controller;

import bootdemo.dao.db1.PersonDaoOne;
import bootdemo.dao.db2.PersonDaoTwo;
import bootdemo.entity.Person;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by dingjie on 19/1/28.
 */
@RestController
public class MybatisControllerDemo {
    @Autowired(required=true)
    private PersonDaoOne personDaoOne;
    @Autowired(required=true)
    private PersonDaoTwo personDaoTwo;
    @RequestMapping("getDataTest")
    public String getFlowsHistory(@RequestParam("assignee") String assignee) throws Exception {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        List<Person> foreachDemo1 = personDaoOne.foreachDemo(list);
        List<Person> foreachDemo2 = personDaoTwo.foreachDemo(list);
        return JSON.toJSONString(foreachDemo1);
    }
}

三、总结

(1)数据没有主从之分,真正意义上的多数据源,不需要给默认数据源之类的。

(2)只需要@SqlSessionTemplateClass(BabyMapperFactoryBeanOne.class)一个注解就可以将dao绑定到对应的数据源

(3)多个SqlSessionFactory相互隔离,不受影响,同时SqlSessionFactory拥有自己的sqlSession,全部隔离开

五、代码物料

(1)附件为demo代码,框架:spring boot+mybatis+mysql

(2)sql脚本

CREATE TABLE `Person` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;

代码下载地址:MyBatis多数据源java代码

有啥疑问请指出,联系方式qq:158479841

拒绝转载!拒绝转载!拒绝转载!拒绝转载!

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值