博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring+mybatis的多源数据库配置实战
阅读量:5241 次
发布时间:2019-06-14

本文共 7272 字,大约阅读时间需要 24 分钟。

 

前言:

  关于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-8db1.jdbc.username=rddb1.jdbc.password=rddb1.jdbc.driver=com.mysql.jdbc.Driver# db2的配置db2.jdbc.url=jdbc:mysql://127.0.0.1:3306/db_account_2?useUnicode=true&characterEncoding=utf-8db2.jdbc.username=rddb2.jdbc.password=rddb2.jdbc.driver=com.mysql.jdbc.Driver

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

  编辑application-context.xml的配置:

classpath:conf/jdbc.properties

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

 

依赖引入:

  这边使用了alibaba开源的druid作为数据库连接池.

mysql
mysql-connector-java
8.0.11
com.alibaba
druid
1.1.10
org.mybatis
mybatis
3.4.6
org.mybatis
mybatis-spring
1.3.2
org.springframework
spring-jdbc
4.1.1.RELEASE

  

基础代码编写:

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

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

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

public class DatasourceContextHolder {    private static final ThreadLocal
contextHolder = new ThreadLocal
(); // 设置数据源 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@ToStringpublic class AccountDO {    private String username;    private String password;}

  编写sqlmapper接口类:

@Repositorypublic 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类:

@Servicepublic 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的分库分表, 提供了一种可行的思路.

 

转载于:https://www.cnblogs.com/mumuxinfei/p/9366222.html

你可能感兴趣的文章
MEF: MSDN 杂志上的文章(14) 稳定的组合
查看>>
python常用模块之sys, os, random
查看>>
HDU 2548 A strange lift
查看>>
Linux服务器在外地,如何用eclipse连接hdfs
查看>>
react双组件传值和传参
查看>>
BNU29140——Taiko taiko——————【概率题、规律题】
查看>>
POJ 2289——Jamie's Contact Groups——————【多重匹配、二分枚举匹配次数】
查看>>
java 得到以后的日期
查看>>
[Kaggle] Sentiment Analysis on Movie Reviews
查看>>
python安装easy_intall和pip
查看>>
AC日记——舒适的路线 codevs 1001 (并查集+乱搞)
查看>>
宏脚本链接数据库
查看>>
HDU1004
查看>>
MySQL高速缓存
查看>>
DropdownList绑定的两种方法
查看>>
价值观
查看>>
数值计算中,浮点类型给我们挖的坑
查看>>
(String)、toString、String.valueOf
查看>>
mongodb命令----批量更改文档字段名
查看>>
python多线程下载网页图片并保存至特定目录
查看>>