目录
一、背景
当前流行方案:
(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
拒绝转载!拒绝转载!拒绝转载!拒绝转载!