mybatis 使用 继承spring的SqlSessionDaoSupport,注入 SqlSessionTemplate, 在xml中定义bean 1 2 3 4 5 6 7 8 9 10 public class BaseDao extends SqlSessionDaoSupport { @Resource(name = "sqlSession-touch") public SqlSessionTemplate sqlSessionTouch; @PostConstruct public void initSqlSessionTemplate() { super.setSqlSessionTemplate(sqlSessionTouch); } }
然后继承定义的baseDao,直接使用就可以 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Repository public class SubscribeDao extends BaseDao{ public void insertSubscribeRecord(Map args)throws DuplicateKeyException { sqlSessionTouch.insert("subscribe",args); } public boolean hasSubscribed(Map args){ return sqlSessionTouch.selectList("record",args).size() > 0 ? true : false; } public List selectListByVid(Map paraMap){ return (List)sqlSessionTouch.selectList("selectListByVid",paraMap); } }
我们看一下 定义的bean
注入 sqlSessionFactory,在sqlSessionFactory 中 加入dataSource 和 mybatis的配置文件
这块可以优化下,重写一个快速失败的SqlSessionFactory工厂,只需要重写方法 用System.exit 退出就好
1 2 3 4 5 6 7 8 9 @Override protected SqlSessionFactory buildSqlSessionFactory() throws IOException { try { return super.buildSqlSessionFactory(); } catch (Exception ex) { System.exit(-1); // 强退吧! } return null; }
我们来看一下 dataSource的 配置
我们再看一下mybatis的配置文件
这就是一个基本的spring 集成 mybatis的流程
多数据源 的动态切换 原理: AbstractRoutingDataSource类的determineTargetDataSource方法,重新获取数据源
AbstractRoutingDataSource类 说明 变量: targetDataSources: 备选的数据源了,用一个map存储
defaultTargetDataSource 就是我们在配置的时候一般都会制定一个默认的数据源就是它了,
执行顺序 (1)程序运行的时候在加载配置文件的时候,首先会执行setTargetDataSources方法,这个方法会加载配置文件中配置的数据源,存储在上面说的targetDataSource中,
(2)设置setDefaultTergetDataSource,这个就是上面说的默认的数据源,
(3)执行这个方法,在其中会将所有的数据源用来初始化resolvedDataSources,而当实际上和数据库产生交互的时候那么会调用到下面的方法:
思路 (1)mybatis 每次获取数据库连接 , 都会走 determineCurrentLookupKey方法,重写这个方法,让程序在每次和数据库 做交互的时候 使用自己指定的source, 使用spring aop 通过注解 进行动态切换数据源 (2)使用一个类存放 lookupKey 值,使用threadLocal 避免线程不安全,每个线程一个key值,即每个线程可以使用一个数据源 1.定义注解
1 2 3 4 5 6 @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Datasource { DappDbType value() default DbType.TTS_MASTER;//默认主库 }
2.DbType 是枚举类,
1 2 TTS_MASTER("touch_tts_master"), TTS_SLAVE("touch_tts_salve");
3.重写 AbstractRoutingDataSource的determineCurrentLookupKey方法 返回的 数据 我新建一个 Holder 类进行处理
说明: 1.通过Datasource注解 + RoutingDataSourceAdvice切点 来实现切换数据源
2.通过硬编码DataSourceContextHolder.setDataSourceType()来切换数据源也是可以的,这种方式用于应对需要对一个Service方法需要调用两种以上的数据源 *
3.如果一个Service方法里面只操作一次数据库,则Datasource注解可以加在Service上,也可以加在Dao上; * 如果操作不同的数据源,则加在Service上或者使用硬编码DataSourceContextHolder.setDataSourceType()
4.如果考虑事务,则不能加在Dao上;如果只是主从数据源都要操作,则建议注解为主库,然后添加事务注解;如果不是主从数据源的关系,分为不同的数据库,则需要采用分布式事务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class TouchTTSDynamicDataSource extends AbstractRoutingDataSource { private static final Logger logger = LoggerFactory.getLogger(TouchTTSDynamicDataSource.class); @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } @Override public Connection getConnection() throws SQLException { Connection connection = super.getConnection(); if (DataSourceContextHolder.getDataSourceType() != null) { logger.debug(xxxxxxxxxxxxxxxxx"); } else{ logger.debug("xxxxxxxxxxxxxxx"); } return connection; } }
4.定义 存放dataSource Type的 holder 类 (1)使用ThreadLocal 用来确保线程安全,每个线程 有一个私有变量,就不会出现线程不安全的问题, 每一个线程操作数据库的时候 都明确了 我操作的是哪个库 (2)使用aop 让每次请求来的时候自动设置 contenHolder属性值,这样在获取datasource连接的时候就会从这个类中取 设置后的值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class DataSourceContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal(); // 设置数据源类型 public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } // 获取数据源类型 public static String getDataSourceType() { } // 清除数据源类型 public static void clearDataSourceType() { contextHolder.remove(); } }
(2) 此处可以加开关 用来进行切换 的开关控制
1 2 3 4 5 6 7 8 9 10 public static String getDataSourceType() { //加入主从切换开关,如果线上出现问题,切换为主库 if(分布式config.getBoolean("touch.ttsdb.switch", false)){ return contextHolder.get(); //开关打开,则根据TouchTTSDatasource注解来选择数据源,未配置注解,则选择从库(默认) } else{ return DappDbType.TTS_MASTER.getName();//开关关闭,写死为主库 } }
5.定义注解处理器 和@DataSource 注解配合使用
(1)如果一个Service上的方法,需要考虑事务,则需要考虑上述配置是否会影响事务的正确性,需要考虑添加AOP 切点的顺序问题 (2)从注解中拿到 数据库的type 然后设置给holder类的属性中,这样来连接数据库的时候 就会自动取 holder类中的数据库type 从而进行操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class RoutingDataSourceAdvice { //对*Service类或者*Dao里标注了@RoutingDataSource注解的方法 进行AOP @Pointcut( "execution(@xxxxxxxxxe * com.xxxx.xxxx..*.*(..)) ") private void routingDappDataSource(){} @Around("routingDappDataSource()") public Object routing(ProceedingJoinPoint joinPoint) throws Exception { Class clazz = joinPoint.getTarget().getClass(); String className = clazz.getName(); Method method = ((MethodSignature)joinPoint.getSignature()).getMethod(); String methodName = method.getName(); Object[] arguments = joinPoint.getArgs(); String key = null; DappDatasource routingDataSource = method.getAnnotation(DappDatasource.class); key = routingDataSource.value().getName(); Object result = null; DataSourceContextHolder.setDataSourceType(key); try { result = joinPoint.proceed(arguments); } catch (Throwable e) { xxxxxxxx } finally { DataSourceContextHolder.clearDataSourceType(); } return result; } }
6.使用 可以在方法上加
1 2 @Datasource(DbType.TTS_MASTER) public int insertFavorite(CookieUser user,String supplierId) {
Mybatis 使用接口注解方式改造: