spring+mybatis的多源数据库配置实战
前言:
关于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的多源数据库配置实战的更多相关文章
- Spring+mybatis+struts框架整合的配置具体解释
学了非常久的spring+mybatis+struts.一直都是单个的用他们,或者是两两组合用过,今天总算整合到一起了,配置起来有点麻烦.可是配置完一次之后.就轻松多了,那么框架整合配置具体解释例如以 ...
- Spring+Mybatis+Mysql搭建分布式数据库访问框架
一.前言 用Java开发企业应用软件, 经常会采用Spring+MyBatis+Mysql搭建数据库框架.如果数据量很大,一个MYSQL库存储数据访问效率很低,往往会采用分库存储管理的方式.本文讲述如 ...
- spring+mybatis最简多数据源配置
作者:纯洁的微笑出处:http://www.ityouknow.com/ 版权所有,欢迎保留原文链接进行转载:) 说起多数据源,一般都来解决那些问题呢,主从模式或者业务比较复杂需要连接不同的分库来支持 ...
- spring+mybatis+druid+mysql+maven事务配置
1.首先pom.xml文件里面需要用到的jar配置: <!-- spring事务,包含了@Transactional标注 --> <dependency> <groupI ...
- Spring + MyBatis 框架下处理数据库异常
一.概述 使用JDBC API时,很多操作都要声明抛出java.sql.SQLException异常,通常情况下是要制定异常处理策略.而Spring的JDBC模块为我们提供了一套异常处理机制,这套异常 ...
- PropertyPlaceholderConfigurer的用法(使用spring提供的类读取数据库配置信息.properties)
http://www.cnblogs.com/wanggd/archive/2013/07/04/3172042.html(写的很好)
- spring+mybatis+c3p0数据库连接池或druid连接池使用配置整理
在系统性能优化的时候,或者说在进行代码开发的时候,多数人应该都知道一个很基本的原则,那就是保证功能正常良好的情况下,要尽量减少对数据库的操作. 据我所知,原因大概有这样两个: 一个是,一般情况下系统服 ...
- Spring+MyBatis双数据库配置
Spring+MyBatis双数据库配置 近期项目中遇到要调用其它数据库的情况.本来仅仅使用一个MySQL数据库.但随着项目内容越来越多,逻辑越来越复杂. 原来一个数据库已经不够用了,须要分库分表.所 ...
- 微服务配置中心实战:Spring + MyBatis + Druid + Nacos
在结合场景谈服务发现和配置中我们讲述了 Nacos 配置中心的三个典型的应用场景,包括如何在 Spring Boot 中使用 Nacos 配置中心将数据库连接信息管控起来,而在“原生”的 Spring ...
随机推荐
- List Except 失效 差集失效
https://www.cnblogs.com/benhua/p/6805192.html
- python之路-----前端之html协议一
一.概述 1.1 什么是html语句? 超文本标记语言(Hypertext Markup Language,HTML)通过标签语言来标记要显示的网页中的各个部分.一套规则,浏览器认识的规则 浏览器按顺 ...
- 用with打开文件
rep_word = 'The piece is gone, left the puzzle undone' # \ 换行,跟shell一样 with open('nothing', 'r', enc ...
- 'Tensorboard.util' has no attribute 'Retrier' - 'Tensorboard.util'没有属性'Retrier'
Here is a popular issue when you want to use tensorbard with your upgraded tensorflow and tensorboar ...
- mysql5.7设置默认编码
1.通过 show variables like '%char%';查看MySQL字符集情况 mysql> show variables like '%char%';+------------- ...
- tpot ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
机器学习训练的时候报出这个问题 是因为dataframe中的数据类型有一个是‘object’,把它转成int,或float 就行,如下 df['A'] = df['A‘].astype(int) 参考 ...
- maven的依赖特性
若排版紊乱可查看我的个人博客原文地址 maven的依赖特性很多很杂,这里大概总结一下,maven的依赖特性主要是依赖范围和传递依赖,前者会影响后者,这篇文章会介绍传递依赖的传递原则,出现冲突传递依赖默 ...
- 【Appium】Appium工作原理(2)
Appium原理 面试的时候,被问到appium原理,一点不会,实在尴尬. 大家可以直接翻看原作https://blog.csdn.net/jffhy2017/article/details/6922 ...
- Spring Cloud分布式微服务云架构
分布式.微服务.云架构 JAVA语言开发.跨平台.高性能.高可用.安全.服务化.模块化.組件化.驱动式开发模式 commonservice eurekaNetflix 云端服务发现,一个基于 REST ...
- java关于redis的快速配置
1.关于Jedis安装配置很简单,我主要写一个,能够快速使用redis的工具类,首先导入依赖, 就一个 jedis 最好选用老一点版本 <!-- https://mvnrepository.co ...