mybatis: 利用多数据源实现分库存储
之前写过一篇mybatis 使用经验小结 提到过多数据源的处理方式,虽然简单但是姿势不太优雅,今天介绍一些更美观的办法:
spring中有一个AbstractRoutingDataSource的抽象类可以很好的支持多数据源,我们只需要继续它即可。
package com.cnblogs.yjmyzz.utils;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DBContext.getDBKey();
}
}
很简单,就一个方法。其中DBContext的代码如下:
package com.cnblogs.yjmyzz.utils;
public class DBContext {
//define count of database and it must match with resources/properties/jdbc.properties
private static final int DB_COUNT = 2;
private static final ThreadLocal<String> tlDbKey = new ThreadLocal<String>();
public static String getDBKey() {
return tlDbKey.get();
}
public static void setDBKey(String dbKey) {
tlDbKey.set(dbKey);
}
public static String getDBKeyByUserId(int userId) {
int dbIndex = userId % DB_COUNT;
return "db_" + (++dbIndex);
}
}
主要利用了ThreadLocal这个类在每个线程中保持自己私有的变量。
这里我模拟了一个分库的场景:假设一个应用允许用户注册,但是用户数量太多,全都放在一个数据库里,记录过多,会导致数据库性能瓶颈,比较容易想到的办法,把用户的数据分散到多个数据库中保存(注:可能马上有同学会说了,分开存了,要查询所有用户怎么办?这确实是分库带来的一个弊端,但也有相应的解决方案,本文先不讨论这个,以免跑题)。
假设我们有二个数据库,里面的表结构完全相同,有一张表T_USER用于保存用户数据,问题来了,如果有N个用户要注册,id分别是1、2、3...,服务端接到参数后,怎么知道把这些数据分别插入到这二个库中,必然要有一个规则 ,比较简单的办法就是取模,所以上面的getDBKeyByUserId就是干这个的。
然后是jdbc的属性配置文件:
jdbc-driver=com.mysql.jdbc.Driver jdbc-key-1=db_1
jdbc-url-1=jdbc:mysql://default:3306/db_1?useUnicode=true&characterEncoding=utf8
jdbc-user-1=test
jdbc-password-1=123456 jdbc-key-2=db_2
jdbc-url-2=jdbc:mysql://default:3306/db_2?useUnicode=true&characterEncoding=utf8
jdbc-user-2=test
jdbc-password-2=123456
接下来是spring的配置文件:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <context:component-scan base-package="com.cnblogs.yjmyzz"/> <bean id="propertiesFactoryBean"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath:properties/jdbc.properties</value>
</list>
</property>
</bean> <context:property-placeholder properties-ref="propertiesFactoryBean" ignore-unresolvable="true"/> <bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<property name="driverClassName" value="${jdbc-driver}"/>
<property name="url" value="${jdbc-url-1}"/>
<property name="username" value="${jdbc-user-1}"/>
<property name="password" value="${jdbc-password-1}"/>
<property name="filters" value="stat"/>
<property name="maxActive" value="20"/>
<property name="initialSize" value="1"/>
<property name="maxWait" value="60000"/>
<property name="minIdle" value="1"/>
<property name="timeBetweenEvictionRunsMillis" value="3000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
<property name="connectionInitSqls" value="set names utf8mb4;"/>
</bean> <bean id="dataSource1" parent="parentDataSource">
<property name="url" value="${jdbc-url-1}"/>
<property name="username" value="${jdbc-user-1}"/>
<property name="password" value="${jdbc-password-1}"/>
</bean> <bean id="dataSource2" parent="parentDataSource">
<property name="url" value="${jdbc-url-2}"/>
<property name="username" value="${jdbc-user-2}"/>
<property name="password" value="${jdbc-password-2}"/>
</bean> <!-- config switch routing db -->
<bean id="dataSource" class="com.cnblogs.yjmyzz.utils.RoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="${jdbc-key-1}" value-ref="dataSource1"/>
<entry key="${jdbc-key-2}" value-ref="dataSource2"/>
</map>
</property>
</bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations">
<array>
<value>classpath:mybatis/*.xml</value>
</array>
</property>
</bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cnblogs.yjmyzz.mapper"/>
</bean> </beans>
关键的是parentDataSource,dataSource1,dataSource2,dataSource这几个bean的配置,一看就懂。
服务端的核心代码:
package com.cnblogs.yjmyzz.service.impl; import com.cnblogs.yjmyzz.entity.UserEntity;
import com.cnblogs.yjmyzz.mapper.UserEntityMapper;
import com.cnblogs.yjmyzz.service.UserService;
import com.cnblogs.yjmyzz.utils.DBContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; /**
* Created by yangjunming on 2/15/16.
* author: yangjunming@huijiame.com
*/ @Service("userService")
public class UserServiceImpl implements UserService { @Autowired
UserEntityMapper userEntityMapper; @Override
public void addUser(UserEntity userEntity) {
//switch db
DBContext.setDBKey(DBContext.getDBKeyByUserId(userEntity.getUserId()));
userEntityMapper.insertSelective(userEntity);
} @Override
public UserEntity getUser(int userId) {
//switch db
DBContext.setDBKey(DBContext.getDBKeyByUserId(userId));
return userEntityMapper.selectByPrimaryKey(userId);
}
}
注意:25,32行在调用mybatis操作数据库前,先根据需要切换到不同的数据库,然后再操作。
运行完成后,可以看下db_1,db_2这二个数据库,确认数据是否已经分散存储到每个库中:
如果不喜欢在代码里手动切换db,也可以用注解的方式自动切换,比如:我们又增加了一个db_main
jdbc-driver=com.mysql.jdbc.Driver jdbc-key-1=db_1
jdbc-url-1=jdbc:mysql://default:3306/db_1?useUnicode=true&characterEncoding=utf8
jdbc-user-1=test
jdbc-password-1=123456 jdbc-key-2=db_2
jdbc-url-2=jdbc:mysql://default:3306/db_2?useUnicode=true&characterEncoding=utf8
jdbc-user-2=test
jdbc-password-2=123456 jdbc-key-main=db_main
jdbc-url-main=jdbc:mysql://default:3306/db_main?useUnicode=true&characterEncoding=utf8
jdbc-user-main=test
jdbc-password-main=123456
然后在spring配置文件里,要做些调整:
<bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<property name="driverClassName" value="${jdbc-driver}"/>
<property name="url" value="${jdbc-url-1}"/>
<property name="username" value="${jdbc-user-1}"/>
<property name="password" value="${jdbc-password-1}"/>
<property name="filters" value="stat"/>
<property name="maxActive" value="20"/>
<property name="initialSize" value="1"/>
<property name="maxWait" value="60000"/>
<property name="minIdle" value="1"/>
<property name="timeBetweenEvictionRunsMillis" value="3000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
<property name="connectionInitSqls" value="set names utf8mb4;"/>
</bean> <bean id="dataSource1" parent="parentDataSource">
<property name="url" value="${jdbc-url-1}"/>
<property name="username" value="${jdbc-user-1}"/>
<property name="password" value="${jdbc-password-1}"/>
</bean> <bean id="dataSource2" parent="parentDataSource">
<property name="url" value="${jdbc-url-2}"/>
<property name="username" value="${jdbc-user-2}"/>
<property name="password" value="${jdbc-password-2}"/>
</bean> <bean id="dataSourceMain" parent="parentDataSource">
<property name="url" value="${jdbc-url-main}"/>
<property name="username" value="${jdbc-user-main}"/>
<property name="password" value="${jdbc-password-main}"/>
</bean> <!-- method 1: config switch routing db -->
<bean id="dataSource" class="com.cnblogs.yjmyzz.utils.RoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="${jdbc-key-1}" value-ref="dataSource1"/>
<entry key="${jdbc-key-2}" value-ref="dataSource2"/>
<entry key="${jdbc-key-main}" value-ref="dataSourceMain"/>
</map>
</property>
</bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations">
<array>
<value>classpath:mybatis/*.xml</value>
</array>
</property>
</bean> <bean id="userScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cnblogs.yjmyzz.mapper.user"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean> <!-- method 2: config annotation auto switch-->
<bean id="sqlSessionFactoryMain" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="dataSource" ref="dataSourceMain"/>
<property name="mapperLocations">
<array>
<value>classpath:mybatis/*.xml</value>
</array>
</property>
</bean> <bean id="orderScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cnblogs.yjmyzz.mapper.order"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryMain"/>
</bean>
注意:67-81行,主要是增加了一个单独的sqlSessionFactoryMain,然后将一个新的MapperScannerConfigurer关联到它。
新库里对应表的Mapper类可以这么写:
@Resource(name = "orderScannerConfigurer")
public interface OrderEntityMapper extends Mapper<OrderEntity> {
}
注解里name对应的值,必须与刚才spring文件里新增的MapperScannerConfigurer对应。
这样,服务层就可以省去手动切换的代码了,即:
public class UserServiceImpl implements UserService {
@Autowired
UserEntityMapper userEntityMapper;
@Autowired
OrderEntityMapper orderEntityMapper;
@Override
public void addUser(UserEntity userEntity) {
//switch db
DBContext.setDBKey(DBContext.getDBKeyByUserId(userEntity.getUserId()));
userEntityMapper.insertSelective(userEntity);
}
@Override
public UserEntity getUser(int userId) {
//switch db
DBContext.setDBKey(DBContext.getDBKeyByUserId(userId));
return userEntityMapper.selectByPrimaryKey(userId);
}
@Override
public void addOrder(OrderEntity orderEntity) {
//since orderEntityMapper can auto switch db by annotation
//so we don't need to switch db manually
orderEntityMapper.insertSelective(orderEntity);
}
@Override
public OrderEntity getOrder(int orderId) {
//since orderEntityMapper can auto switch db by annotation
//so we don't need to switch db manually
return orderEntityMapper.selectByPrimaryKey(orderId);
}
}
上述二种方式可以共存在同一个项目中,个人建议:如果分库的表结构相同,且表数量较多,第1种手动切换的方式比较适合,这样mapper类不用重复建多个,如果分库的表结构完全不同,第2种比较合适,因为表结构不同,mapper肯定也不同,所以mapper多个是无法避免的,这时候就宁可加点配置,代码中就不用手动切换,可以省事点。
最后,在github上放了一份示例代码,供有需要的同学下载。
mybatis: 利用多数据源实现分库存储的更多相关文章
- spring+mybatis利用interceptor(plugin)兑现数据库读写分离
使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是"地位相当"的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这 ...
- SpringBoot集成Mybatis配置动态数据源
很多人在项目里边都会用到多个数据源,下面记录一次SpringBoot集成Mybatis配置多数据源的过程. pom.xml <?xml version="1.0" encod ...
- SpringBoot+MyBatis配置多数据源
SpringBoot 可以支持多数据源,这是一个非常值得学习的功能,但是从现在主流的微服务的架构模式中,每个应用都具有唯一且准确的功能,多数据源的需求很难用到,考虑到实际情况远远比理论复杂的多,这里还 ...
- spring+myBatis 配置多数据源,切换数据源
注:本文来源于 tianzhiwuqis <spring+myBatis 配置多数据源,切换数据源> 一个项目里一般情况下只会使用到一个数据库,但有的需求是要显示其他数据库的内容,像这样 ...
- Mybatis利用拦截器做统一分页
mybatis利用拦截器做统一分页 查询传递Page参数,或者传递继承Page的对象参数.拦截器查询记录之后,通过改造查询sql获取总记录数.赋值Page对象,返回. 示例项目:https://git ...
- spring boot + druid + mybatis + atomikos 多数据源配置 并支持分布式事务
文章目录 一.综述 1.1 项目说明 1.2 项目结构 二.配置多数据源并支持分布式事务 2.1 导入基本依赖 2.2 在yml中配置多数据源信息 2.3 进行多数据源的配置 三.整合结果测试 3.1 ...
- Spring Boot + Mybatis 配置多数据源
Spring Boot + Mybatis 配置多数据源 Mybatis拦截器,字段名大写转小写 package com.sgcc.tysj.s.common.mybatis; import java ...
- 【springboot spring mybatis】看我怎么将springboot与spring整合mybatis与druid数据源
目录 概述 1.mybatis 2.druid 壹:spring整合 2.jdbc.properties 3.mybatis-config.xml 二:java代码 1.mapper 2.servic ...
- springboot整合mybatis,利用mybatis-genetor自动生成文件
springboot整合mybatis,利用mybatis-genetor自动生成文件 项目结构: xx 实现思路: 1.添加依赖 <?xml version="1.0" e ...
随机推荐
- PDF编辑神器
转自网络 http://files.cnblogs.com/files/quejuwen/pdfeditportable.zip
- spring笔记6 spring IOC的中级知识
1,spring ioc的整体流程,xml配置 spring ioc初始化的流程结合上图 步骤编号 完成的工作 1 spring容器读取配置文件,解析称注册表 2 根据注册表,找到相应的bean实现类 ...
- 在一个项目各个子模块中使用Maven的一些通用的准则
1.各个子模块都应该使用相同的groupId(如:com.mvnbook.account); 2.各个子模块如果一起开发和发布,还应该使用相同的版本:version: 3.各个子模块还应该使用一致的前 ...
- 20个不可思议的 WebGL 示例和演示
WebGL 是一项在网页浏览器呈现3D画面的技术,有别于过去需要安装浏览器插件,通过 WebGL 的技术,只需要编写网页代码即可实现3D图像的展示.WebGL 可以为 Canvas 提供硬件3D加速渲 ...
- 兼容IE8 input的placeholder的文字显示
if( !('placeholder' in document.createElement('input')) ){ $('input[placeholder],textarea[placeholde ...
- 使用IdleTest进行TDD单元测试驱动开发演练(1)
[前言] 开发工具:Visual Studio 2012 测试库:Visual Studio 2012自带的MSTest DI框架:Unity 数据持久层:Entity Framework 前端UI: ...
- listview侧滑删除
自定义Listview,向左滑动,右边刚好显示删除按钮: public class SlideListView extends ListView { private int mScreenWidth; ...
- block为什么用copy以及如何解决循环引用
在完成项目期间,不可避免的会使用到block,因为block有着比delegate和notification可读性更高,而且看起来代码也会很简洁.于是在目前的项目中大量的使用block. 之前给大家介 ...
- ASP .NET MVC 之Entity Framework入门教程及源码
本文主要的目的是 1. 说明Entity Framework Power Tools如何使用. 2. Entity Framework 快速门 实验环境: OS: Windows Server 20 ...
- Hibernate 系列 01 - 框架技术 (介绍Hibernate框架的发展由来)
引导目录: Hibernate 系列教程 目录 本篇导航: 为什么学习框架技术 框架的概念 主流框架的介绍 1.为什么学习框架技术 如何制作一份看上去具有专业水准的PPT文档呢?一个简单的方法就是使用 ...