前言:
  关于spring+mybatis的多源数据库配置, 其实是个老生常谈的事情. 网上的方案出奇的一致, 都是借助AbstractRoutingDataSource进行动态数据源的切换.
  这边再无耻地做一回大自然的搬运工, 除了做下笔记, 更多的希望是作为一个切入点, 能探寻下mybatis实现分库分表的解决方案.

基本原理:
  关于mybatis的配置, 基本遵循如下的概念流:

DB(数据库对接信息)->数据源(数据库连接池配置)->session工厂(连接管理与数据访问映射关联)->DAO(业务访问封装).

  对于定义的sqlmapper接口类, mybatis会为这些类动态生成一个代理类, 隐藏了连接管理(获取/释放), 参数设置/SQL执行/结果集映射等细节, 大大简化了开发工作.
  而连接管理涉及到具体的DataSource类实现机制, 在具体执行sql前, 其DB源的选定还有操作空间. 这也为DB路由(切换)提供了口子, 而AbstractRoutingDataSource的引入, 一定程度上为DB自由切换提供了便利.

配置工作:
  先编写jdbc.properties的内容:

# db1的配置
db1.jdbc.url=jdbc:mysql://127.0.0.1:3306/db_account_1?useUnicode=true&characterEncoding=utf-8
db1.jdbc.username=rd
db1.jdbc.password=rd
db1.jdbc.driver=com.mysql.jdbc.Driver # db2的配置
db2.jdbc.url=jdbc:mysql://127.0.0.1:3306/db_account_2?useUnicode=true&characterEncoding=utf-8
db2.jdbc.username=rd
db2.jdbc.password=rd
db2.jdbc.driver=com.mysql.jdbc.Driver

  编辑mybatis-config.xml(对mybatis做一些基础通用配置)的内容:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置mybatis的缓存,延迟加载等等一系列属性 -->
<settings>
<!-- 全局映射器启用缓存 -->
<setting name="cacheEnabled" value="true" />
<!-- 查询时,关闭关联对象即时加载以提高性能 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指定),不会加载关联表的所有字段,以提高性能 -->
<setting name="aggressiveLazyLoading" value="false" />
<!-- 允许插入 NULL -->
<setting name="jdbcTypeForNull" value="NULL" />
</settings>
</configuration>

  编辑application-context.xml的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:aspectj-autoproxy proxy-target-class="true"/> <context:component-scan base-package="com.springapp.mvc"/>
<context:annotation-config /> <!-- 加载jdbc.properties配置文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:conf/jdbc.properties</value>
</list>
</property>
<property name="fileEncoding" value="UTF-8"/>
<property name="ignoreUnresolvablePlaceholders" value="true"/>
</bean> <!-- 配置数据源1 -->
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${db1.jdbc.url}"/>
<property name="username" value="${db1.jdbc.username}"/>
<property name="password" value="${db1.jdbc.password}"/>
<property name="driverClassName" value="${db1.jdbc.driver}" />
</bean> <!-- 配置数据源2 -->
<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${db2.jdbc.url}"/>
<property name="username" value="${db2.jdbc.username}"/>
<property name="password" value="${db2.jdbc.password}"/>
<property name="driverClassName" value="${db2.jdbc.driver}" />
</bean> <!-- 配置动态数据源 -->
<bean id="dynamicDatasource" class="com.springapp.mvc.datasource.DynamicDataSource">
<property name="targetDataSources">
<map>
<entry key="db1" value-ref="dataSource1"/>
<entry key="db2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDatasource"/>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<property name="mapperLocations">
<list></list>
</property>
</bean> <!--mybatis的配置-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.springapp.mvc.dal"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean> </beans>

  注: 这里面涉及一些类, 会在下文中定义.

依赖引入:
  这边使用了alibaba开源的druid作为数据库连接池.

        <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency> <dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency> <dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>

  

基础代码编写:
  主要是db路由的datasource实现类, 以及辅助的注解工具类.
  定义db来源的枚举类:

@Getter
@AllArgsConstructor
public enum DataSourceKey { DB1("db1"),
DB2("db2"); private String dbKey; }

  定义标示当前激活db的工具类:

public class DatasourceContextHolder {

    private static final ThreadLocal<String> contextHolder
= new ThreadLocal<String>(); // 设置数据源
public static void setDataSourceType(DataSourceKey dbKey) {
contextHolder.set(dbKey.getDbKey());
} // 获取当前的数据源
public static String getDataSourceType() {
return contextHolder.get();
} // 清空数据源
public static void clearDataSourceType() {
contextHolder.remove();
} }

  注: 利用了ThreadLocal来保存当前选择的db源
  定义AbstractRoutingDataSource的实现类:

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
protected Object determineCurrentLookupKey() {
return DatasourceContextHolder.getDataSourceType();
} }

  注: 只要重载determineCurrentLookupKey()函数即可.
  定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceSelector {
DataSourceKey dataSource() default DataSourceKey.DB1;
}

  定义切面类:

@Aspect
@Component
@Order(1)
public class DataSourceSelectorAdvice { // 定义切点, 用于db源切换
@Pointcut("@annotation(com.springapp.mvc.datasource.DataSourceSelector)")
public void selectorDB() {
} @Around("selectorDB() && @annotation(dataSourceSelector)")
public Object aroundSelectDB(ProceedingJoinPoint pjp, DataSourceSelector dataSourceSelector) throws Throwable {
// 设置具体的数据源
DatasourceContextHolder.setDataSourceType(dataSourceSelector.dataSource());
try {
// 执行拦截的方法本体
return pjp.proceed();
} finally {
// 清空设置的数据源
DatasourceContextHolder.clearDataSourceType();
}
} }

  这些代码构成了动态切换db源的主干框架.

业务代码编写:
  编写DO类:

@Getter
@Setter
@ToString
public class AccountDO { private String username; private String password; }

  编写sqlmapper接口类:

@Repository
public interface AccountMapper { @Select("SELECT username, password FROM tb_account WHERE user_id = #{user_id}")
@Results({
@Result(property = "userId", column = "user_id", jdbcType = JdbcType.VARCHAR),
@Result(property = "username", column = "username", jdbcType = JdbcType.VARCHAR),
@Result(property = "password", column = "password", jdbcType = JdbcType.VARCHAR),
})
AccountDO queryByUserId(@Param("user_id") String userId); }

  编写service类:

@Service
public class AccountService { @Resource
private AccountMapper accountMapper; // *) 从db1获取数据
@DataSourceSelector(dataSource = DataSourceKey.DB1)
public AccountDO queryByUserId1(String userId) {
return accountMapper.queryByUserId(userId);
} // *) 从db2获取数据
@DataSourceSelector(dataSource = DataSourceKey.DB2)
public AccountDO queryByUserId2(String userId) {
return accountMapper.queryByUserId(userId);
} }

  Aspectj对接口(interface)无效, 对具体的实体类才其作用, 因为sqlmapper接口类会被mybatis生成一个动态类, 因此需要加切面(db切换), 需要在service层去实现.

验证数据准备:
  本地创建了两个db, 都创建相同的表tb_account.

CREATE TABLE `tb_account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(32) NOT NULL,
`username` varchar(32) DEFAULT NULL,
`password` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`), UNIQUE KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  db1的tb_account有账号数据(1001, lilei).
  db2的tb_account有账号数据(2001, hanmeimei).
  

测试:
  编写单测:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:application-context.xml"})
public class AccountServiceTest { @Resource
private AccountService accountService; @Test
public void queryByUserId1() {
// 用户id:1001, 落在db1中, 不在db2
String userId = "1001"; AccountDO accountDO1 = accountService.queryByUserId1(userId);
Assert.assertNotNull(accountDO1); // 存在断言 AccountDO accountDO2 = accountService.queryByUserId2(userId);
Assert.assertNull(accountDO2); // 不存在断言
} @Test
public void queryByUserId2() {
// 用户id:2001, 不在db1中, 在db2中
String userId = "2001"; AccountDO accountDO1 = accountService.queryByUserId1(userId);
Assert.assertNull(accountDO1); // 不存在断言 AccountDO accountDO2 = accountService.queryByUserId2(userId);
Assert.assertNotNull(accountDO2); // 存在断言
} }

  运行的结果符合预期.

后记:
  对于微服务的盛行, 其实多源的数据源(基于业务划分)基本就不存在, 如果存在, 要么业务刚发展起来, 要么就是公司的基础设施太薄弱了^_^. 网上也看到有人用来主从(master/slave)的配置, 其实对于有一定规模的公司而言, mysql的主从分离都由类db proxy的中间件服务承包了.
  那他的意义究竟在哪呢? 其实我感觉还是给mysql的分库分表, 提供了一种可行的思路.

spring+mybatis的多源数据库配置实战的更多相关文章

  1. Spring+mybatis+struts框架整合的配置具体解释

    学了非常久的spring+mybatis+struts.一直都是单个的用他们,或者是两两组合用过,今天总算整合到一起了,配置起来有点麻烦.可是配置完一次之后.就轻松多了,那么框架整合配置具体解释例如以 ...

  2. Spring+Mybatis+Mysql搭建分布式数据库访问框架

    一.前言 用Java开发企业应用软件, 经常会采用Spring+MyBatis+Mysql搭建数据库框架.如果数据量很大,一个MYSQL库存储数据访问效率很低,往往会采用分库存储管理的方式.本文讲述如 ...

  3. spring+mybatis最简多数据源配置

    作者:纯洁的微笑出处:http://www.ityouknow.com/ 版权所有,欢迎保留原文链接进行转载:) 说起多数据源,一般都来解决那些问题呢,主从模式或者业务比较复杂需要连接不同的分库来支持 ...

  4. spring+mybatis+druid+mysql+maven事务配置

    1.首先pom.xml文件里面需要用到的jar配置: <!-- spring事务,包含了@Transactional标注 --> <dependency> <groupI ...

  5. Spring + MyBatis 框架下处理数据库异常

    一.概述 使用JDBC API时,很多操作都要声明抛出java.sql.SQLException异常,通常情况下是要制定异常处理策略.而Spring的JDBC模块为我们提供了一套异常处理机制,这套异常 ...

  6. PropertyPlaceholderConfigurer的用法(使用spring提供的类读取数据库配置信息.properties)

    http://www.cnblogs.com/wanggd/archive/2013/07/04/3172042.html(写的很好)

  7. spring+mybatis+c3p0数据库连接池或druid连接池使用配置整理

    在系统性能优化的时候,或者说在进行代码开发的时候,多数人应该都知道一个很基本的原则,那就是保证功能正常良好的情况下,要尽量减少对数据库的操作. 据我所知,原因大概有这样两个: 一个是,一般情况下系统服 ...

  8. Spring+MyBatis双数据库配置

    Spring+MyBatis双数据库配置 近期项目中遇到要调用其它数据库的情况.本来仅仅使用一个MySQL数据库.但随着项目内容越来越多,逻辑越来越复杂. 原来一个数据库已经不够用了,须要分库分表.所 ...

  9. 微服务配置中心实战:Spring + MyBatis + Druid + Nacos

    在结合场景谈服务发现和配置中我们讲述了 Nacos 配置中心的三个典型的应用场景,包括如何在 Spring Boot 中使用 Nacos 配置中心将数据库连接信息管控起来,而在“原生”的 Spring ...

随机推荐

  1. Redhat 6.3上安装libssh

    遇到了很多坑,决定记录下来,有些经验还是很有帮助的. 最重要的一条就是:安装rpm包,总是比编译源码安装(make&make install)更快.记住两个rpm网站:https://pkgs ...

  2. 【nowcoder】 4th T2 区间

    题目链接:https://www.nowcoder.com/acm/contest/175/B 当你为时间复杂度挠头的时候 别人已经33行拿满分了 #include<cstdio> #in ...

  3. 有效的括号序列——算法面试刷题4(for google),考察stack

    给定一个字符串所表示的括号序列,包含以下字符: '(', ')', '{', '}', '[' and ']', 判定是否是有效的括号序列. 括号必须依照 "()" 顺序表示, & ...

  4. css修改原生radio样式

    日常工作中经常会用到单选框radio,而原生样式不好看无法满足项目要求,模拟写一个又比较麻烦,所以写了一个改变原生样式的demo. 原生样式: 改变后的样式: 以下为demo代码: <!DOCT ...

  5. 【基础】使用cookies,实现免登陆(七)

    实现过程: 1.测试网站:www.dx.com 2.登陆后的cookies:"DXSSO","Token=20A0FA7D-XXXX-XXXX-XXXX".至于 ...

  6. oracle 字符串 正则表达式 拆分,排序,合并

    需求,表数据如:要求圈中的数据,必须根据线芯有序排列. 思路: 1.首先根据分号分隔元素.oracle 很蛋疼,没有提供字符串分隔函数,网上倒是多觉得有点麻烦,耐着性子继续网上找了下,还真让我找到一篇 ...

  7. Java实现post和get请求

    GET请求:GET请求会向服务器发索取数据的请求,从而来获取信息,该请求就像数据库的select操作一样,只是用来查询一下数据,不会修改.增加数据,不会影响资源的内容,即该请求不会产生副作用.无论进行 ...

  8. Android使用Jenkins自动化构建测试打包apk

    Jenkins这东西搭建起来真是一点也不省心啊,看着别人的教程摸着石头过河,配置的东西有点多啊,稍有不慎,就构建不成功啦!即使步骤跟别人一样也会报各种乱七八糟的错误啊哈哈~~这东西只能佛系搭建~~在经 ...

  9. MFC的PNG贴图按钮类(详细注释)

    MFC的PNG贴图按钮类(详细注释) (转载请注明出处) 作者:梦镜谷雨 萌新第二次写帖子,请多多包涵.末尾附上相应代码(PS公司繁体系统所以部分注释繁体请别介意). 因自带控件不美观,于是网上参考学 ...

  10. Spring _day01_下载、概述、监听器

    Spring:SE/EE开发的一站式框架. ​ .一站式框架:有EE开发的每一层解决方案. ​ . WEB层 :SpringMVC ​ . Service层 :Spring的Bean管理,Spring ...