SpringMVC + MyBatis分库分表方案
mybatis作为流行的ORM框架,项目实际使用过程中可能会遇到分库分表的场景。mybatis在分表,甚至是同主机下的分库都可以说是完美支持的,只需要将表名或者库名作为动态参数组装sql就能够完成。但是多余分在不同主机上的库,就不太一样了,组装sql无法区分数据库主机。网上搜索了一下,对于此类情况,大都采用的动态数据源的概念,也即定义不同的数据源连接不同的主机数据库,在查询前通过动态数据源进行数据源切换,但从实现上来看,这个切换并不是单sql级别的,而可以理解为时间级别的切换,即查询前切到对应数据源,这种实现在并发场景下并不能满足分库减压需求,甚至会导致查错数据库的情况。
这里给出分库分表的实现方式,特别在分库的方案上,采用真正可并发的方案。
这里以银行卡消费记录为例子来看这个问题,银行有多个用户,通过Card( id,owner) 来标志,每个卡有消费记录,CostLog(id,time,amount) ,由于消费记录数据过多,我们对数据进行分库分表存储。
一、基本配置
首先我们来看下mybatis结合springmvc的基本配置方式(不进行分库分表)。
mybatis的配置链路可以有底层到上层解释为: DB(数据库对接信息) -》数据源(数据库连接池配置) -》session工厂(连接管理与数据访问映射关联) -》DAO(业务访问封装)
<!--定义mysql 数据源,连接数据库主机的连接信息 -->
<bean id="test1-datasource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="maxActive" value="40"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="30000"></property>
<property name="minIdle" value="2"/>
<property name="timeBetweenEvictionRunsMillis" value="3600000"></property>
<property name="minEvictableIdleTimeMillis" value="3600000"></property>
<property name="defaultAutoCommit" value="true"></property>
<property name="testOnBorrow" value="true"></property>
<property name="validationQuery" value="select 1"/>
</bean> <!--定义session工厂,指定数据访问映射文件和使用的数据源-->
<bean id="test1-sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="mapperLocations">
<list>
<value>classpath*:confMapper/*Mapper.xml</value>
</list>
</property>
<property name="dataSource" ref="test1-datasource"/>
</bean> <!--定义session工厂和DAO扫描路径,自动进行DAO与session工厂的绑定-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.ming.test.po"/>
<property name="sqlSessionFactoryBeanName" value="test1-sqlSessionFactory"/>
</bean>
上面配置中需要我们自己定义的 内容有
1.session工厂中的数据访问映射文件,这里需要符合配置中命名规范并放在对应路径下,以Mapper.xml结尾,可以叫做 CostLogMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="CostDao">
<resultMap id="BaseResultMap" type="CostLog">
<result property="id" column="id"/>
<result property="time" column="time"/>
<result property="amount" column="amount"/>
</resultMap> <select id="queryCostLog" resultMap="BaseResultMap">
SELECT `id`,`time`,`amount` FROM CostLog WHERE `id` = #{id}
</select>
</mapper>
2.扫描绑定中 basePackage指定的包名下的DAO类
public interface CostDao {
CostLog queryCostLog(@Param("id") int id);
}
3.上面两项所依赖的数据对象 CostLog
@Setter
@Getter
public class CostLog {
private Integer id;
private Date time;
private Integer amount;
}
4.对应的数据库表
这里我们和 CostLog 使用同样的命名
我们可以使用如下代码访问:
@Service
public class CostLogService { @Resource
CostDao costDao; public CostLog queryCostDao(int id) {
return costDao.queryCostLog(id);
}
}
二、不分主机的分库表实现
对于上例,我们只需要在DAO中增加库表名参数,并适当修改SQL即可
数据访问映射配置写法:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="CostDao">
<resultMap id="BaseResultMap" type="CostLog">
<result property="id" column="id"/>
<result property="time" column="time"/>
<result property="amount" column="amount"/>
</resultMap> <select id="queryCostLog" resultMap="BaseResultMap">
SELECT `id`,`time`,`amount` FROM ${dbName}.${tbName} WHERE `id` = #{id}
</select>
</mapper>
DAO类写法:
public interface CostDao {
CostLog queryCostLog(@Param("dbName") String dbName, @Param("tbName") String tbName, @Param("id") int id);
}
调用层计算库表名称,并传递参数:
@Service
public class CostLogService { @Resource
CostDao costDao; public CostLog queryCostDao(int id) {
//分两库两表db1、db2,每个库中又有两个表tb1、tb2,我们根据账户id模4的取模值来分库表,0:db1.tb1 ;1:db1.tb2;2:db2.tb1;3:db2.tb2
String dbName = id % 4 < 2 ? "db1" : "db2";
String tbName = id % 2 == 0 ? "tb1" : "tb2";
return costDao.queryCostLog(dbName, tbName, id);
}
}
三、分主机的分库实现
首先通过需求确认几点:
1.我们期望不同的查询根据id自动到不同的主机上去查询,也就是db1和db2在不同的主机上
2.我们分库目的是数据库减负并且会有并发访问,因此db1和db2要能够同时提供服务
鉴于第一点,我们需要定义两个数据源,同时分别连接不同的数据库主机。
鉴于第二点,我们需要将数据源的选择细化到单个请求。
a.一种是将逻辑封装到DAO中实现,使DAO进行访问前根据请求参数按照我们定义的逻辑选择数据源。遗憾的是,DAO的具体实现是又mybatis动态代理生成的,这个功能依赖mybatis的支持,我目前并不知道mybatis有提供这么一个功能。
b.另一种是定义两个DAO,分别连接不同的数据源,但是两个DAO的查询逻辑是完全一样的。我们采用这种方式。
一种实现是我们定义两套完全相同的数据映射配置和两个DAO接口,分别连接不同的数据源,但这种方式实际上会有较多的重复配置,如果分库不止两个,而是多个,那么后续维护修改就更加困难。有没有办法让多个DAO使用同一个数据访问映射文件呢,经过测试,是有的,甚至多个DAO接口可以继承同一个DAO接口的实现(通过DAO注解直接定义访问逻辑)。
我们可以定义一个父级DAO接口A,然后为每个分库定义一个空的DAO接口,每个接口都继承接口A。如下,我们定义 Db1CostDao 和 Db2CostDao 都继承 CostDao。

子接口只需挂一个名字,而无需有额外实现
public interface Db1CostDao extends CostDao {
}
然后我们在各个数据源的MapperScannerConfigurer配置中,将各个子接口关联到不同的分库session工厂上。而在数据访问映射文件中,我们定义的DAO类型为父级DAO接口A。这样在spring启动扫描时,由于每个子DAO都是接口A的子接口,因此每个子DAO都实例化为一个bean,我们可以在数据访问业务层通过自定义逻辑返回对应的DAO。最终查询的数据库为对应的子DAO接口所对应的数据库。
<!--定义mysql 数据源,连接数据库主机的连接信息 -->
<bean id="test1-datasource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="maxActive" value="40"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="30000"></property>
<property name="minIdle" value="2"/>
<property name="timeBetweenEvictionRunsMillis" value="3600000"></property>
<property name="minEvictableIdleTimeMillis" value="3600000"></property>
<property name="defaultAutoCommit" value="true"></property>
<property name="testOnBorrow" value="true"></property>
<property name="validationQuery" value="select 1"/>
</bean> <!--定义session工厂,指定数据访问映射文件和使用的数据源-->
<bean id="test1-sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="mapperLocations">
<list>
<value>classpath*:confMapper/*Mapper.xml</value>
</list>
</property>
<property name="dataSource" ref="test1-datasource"/>
</bean> <!--定义session工厂和DAO扫描路径,自动进行DAO与session工厂的绑定-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="test.dao.db1"/>
<property name="sqlSessionFactoryBeanName" value="test1-sqlSessionFactory"/>
</bean> <!--定义mysql 数据源,连接数据库主机的连接信息 -->
<bean id="test2-datasource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="maxActive" value="40"></property>
<property name="maxIdle" value="30"></property>
<property name="maxWait" value="30000"></property>
<property name="minIdle" value="2"/>
<property name="timeBetweenEvictionRunsMillis" value="3600000"></property>
<property name="minEvictableIdleTimeMillis" value="3600000"></property>
<property name="defaultAutoCommit" value="true"></property>
<property name="testOnBorrow" value="true"></property>
<property name="validationQuery" value="select 1"/>
</bean> <!--定义session工厂,指定数据访问映射文件和使用的数据源-->
<bean id="test2-sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="mapperLocations">
<list>
<value>classpath*:confMapper/*Mapper.xml</value>
</list>
</property>
<property name="dataSource" ref="test1-datasource"/>
</bean> <!--定义session工厂和DAO扫描路径,自动进行DAO与session工厂的绑定-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="test.dao.db2"/>
<property name="sqlSessionFactoryBeanName" value="test2-sqlSessionFactory"/>
</bean>
映射文件 CostLogMapper.xml则无需做任何修改。
在业务层我们通过自定义逻辑选择DAO
@Service
public class CostLogService { @Resource
Db1CostDao costDao1; @Resource
Db2CostDao costDao2; CostDao selectDao(int id) {
return id % 4 < 2 ? costDao1 : costDao2;
} public CostLog queryCostDao(int id) {
//分两库两表db1、db2,每个库中又有两个表tb1、tb2,我们根据账户id模4的取模值来分库表,0:db1.tb1 ;1:db1.tb2;2:db2.tb1;3:db2.tb2
String dbName = id % 4 < 2 ? "db1" : "db2";
String tbName = id % 2 == 0 ? "tb1" : "tb2"; return selectDao(id).queryCostLog(dbName, tbName, id);
}
}
至此,在尽量少冗余代码的情况下,满足并发情况下分库需求。如果有更优方案,欢迎交流。
SpringMVC + MyBatis分库分表方案的更多相关文章
- MySQL 分库分表方案,总结的非常好!
前言 公司最近在搞服务分离,数据切分方面的东西,因为单张包裹表的数据量实在是太大,并且还在以每天60W的量增长. 之前了解过数据库的分库分表,读过几篇博文,但就只知道个模糊概念, 而且现在回想起来什么 ...
- Mysql 分库分表方案
0 引言 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. mysql中有一种机制是表锁定和行锁 ...
- 重磅来袭,使用CRL实现大数据分库分表方案
关于分库分表方案详细介绍 http://blog.csdn.net/bluishglc/article/details/7696085 这里就不作详细描述了 分库分表方案基本脱离不了这个结构,受制于实 ...
- Mysql分库分表方案
Mysql分库分表方案 1.为什么要分表: 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. m ...
- 【分库、分表】MySQL分库分表方案
一.Mysql分库分表方案 1.为什么要分表: 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. ...
- Mysql 之分库分表方案
Mysql分库分表方案 为什么要分表 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. mysq ...
- 基于Mysql数据库亿级数据下的分库分表方案
移动互联网时代,海量的用户数据每天都在产生,基于用户使用数据的用户行为分析等这样的分析,都需要依靠数据都统计和分析,当数据量小时,问题没有暴露出来,数据库方面的优化显得不太重要,一旦数据量越来越大时, ...
- MySQL主从(MySQL proxy Lua读写分离设置,一主多从同步配置,分库分表方案)
Mysql Proxy Lua读写分离设置 一.读写分离说明 读写分离(Read/Write Splitting),基本的原理是让主数据库处理事务性增.改.删操作(INSERT.UPDATE.DELE ...
- mysql 数据库 分表后 怎么进行分页查询?Mysql分库分表方案?
Mysql分库分表方案 1.为什么要分表: 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. m ...
随机推荐
- Shell 文本处理命令
命令:cut –d’:’ -f1, 文件名 #切割处文件列的参数. -d切割字符. -f列的第几个参数. -c1-10指定字符串范围行的第一个到第十个. 命令:sort 文件名 #根据第一列第一个字符 ...
- nmon监控数据分析
性能测试中,各个服务器资源占用统计分析是一个很重要的组成部分,通常我们使用nmon这个工具来进行监控以及监控结果输出. 一. 在监控阶段使用类似下面的命令 ./nmon -f write_3s_20v ...
- poj1985和poj1849(树的直径)
题目传送门:poj1985 树是连通无环图,树上任意两点之间的路径是唯一的.定义树上任 意两点u, v的距离为u到v路径上边权的和.树的直径MN为树上最长路 径,即点M和N是树上距离最远的两个点. 题 ...
- 第二次作业-git的基本操作
作业的要求来自于:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/2097 一.修改用户名和邮箱地址: (1)配置用户名命令:$git ...
- 【Alpha】Scrum Meeting 5
目录 前言 任务分配 燃尽图 会议照片 签入记录 遇到的困难 前言 时间: 4.9日中午11.30 地点:F-220 本次会议旨在统计各位同学的进度以及催促任务的进展. 任务分配 姓名 当前阶段任务 ...
- CEF 设置Cookie
参考文档:http://magpcss.org/ceforum/apidocs3/projects/(default)/CefCookieManager.html 转载:https://www.cnb ...
- 原生js实现九宫格,全解析
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- Bootstrap3基础 table-bordered/hover 表格加外边框和鼠标悬停对应行的背景色加深
内容 参数 OS Windows 10 x64 browser Firefox 65.0.2 framework Bootstrap 3.3.7 editor ...
- 20165306 2017-2018-2《Java程序设计》课程总结
课程总结 每周作业链接汇总: 预备作业一:我期望的师生关系 预备作业二:学习基础和C语言基础调查 预备作业三:Linux安装与学习 第一周作业:Java入门 第二周作业:基本数据类型与数组,运算符.表 ...
- 3rd,Python登录模拟
需求: 1. 用户输入帐号密码进行登陆 2. 用户信息保存在文件内 3. 用户密码输入错误三次后锁定用户 #导入getpass库,用于密码验证 import getpass #定义用户名.密码 _us ...