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分库分表方案的更多相关文章

  1. MySQL 分库分表方案,总结的非常好!

    前言 公司最近在搞服务分离,数据切分方面的东西,因为单张包裹表的数据量实在是太大,并且还在以每天60W的量增长. 之前了解过数据库的分库分表,读过几篇博文,但就只知道个模糊概念, 而且现在回想起来什么 ...

  2. Mysql 分库分表方案

    0 引言 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. mysql中有一种机制是表锁定和行锁 ...

  3. 重磅来袭,使用CRL实现大数据分库分表方案

    关于分库分表方案详细介绍 http://blog.csdn.net/bluishglc/article/details/7696085 这里就不作详细描述了 分库分表方案基本脱离不了这个结构,受制于实 ...

  4. Mysql分库分表方案

    Mysql分库分表方案 1.为什么要分表: 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. m ...

  5. 【分库、分表】MySQL分库分表方案

    一.Mysql分库分表方案 1.为什么要分表: 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. ...

  6. Mysql 之分库分表方案

    Mysql分库分表方案 为什么要分表 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. mysq ...

  7. 基于Mysql数据库亿级数据下的分库分表方案

    移动互联网时代,海量的用户数据每天都在产生,基于用户使用数据的用户行为分析等这样的分析,都需要依靠数据都统计和分析,当数据量小时,问题没有暴露出来,数据库方面的优化显得不太重要,一旦数据量越来越大时, ...

  8. MySQL主从(MySQL proxy Lua读写分离设置,一主多从同步配置,分库分表方案)

    Mysql Proxy Lua读写分离设置 一.读写分离说明 读写分离(Read/Write Splitting),基本的原理是让主数据库处理事务性增.改.删操作(INSERT.UPDATE.DELE ...

  9. mysql 数据库 分表后 怎么进行分页查询?Mysql分库分表方案?

    Mysql分库分表方案 1.为什么要分表: 当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间. m ...

随机推荐

  1. Python Gevent协程自动切换IO

    Gevent Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程. Gr ...

  2. P3829 [SHOI2012]信用卡凸包

    思路 注意到结果就是每个信用卡边上的四个圆心的凸包周长+一个圆的周长 然后就好做了 注意平行时把距离小的排在前面,栈中至少要有1个元素(top>1),凸包中如果存在叉积为0的点也要pop,否则可 ...

  3. 表达式引擎aviator

    Aviator是一个轻量级.高性能的Java表达式执行引擎, 本文内容主要来自于官方文档 简介 包依赖 使用手册 执行表达式 使用变量 exec 方法 调用函数 自定义函数 编译表达式 访问数组和集合 ...

  4. variable 'o' used without having been completely initialized Compiling Vertex program

    variable 'o' used without having been completely initialized Compiling Vertex program   v2f vert (ap ...

  5. Java版 人脸识别SDK dem

    虹软人脸识别SDK之Java版,支持SDK 1.1+,以及2.0版本,滴滴,抓紧上车! 前言由于业务需求,最近跟人脸识别杠上了,本以为虹软提供的SDK是那种面向开发语言的,结果是一堆dll······ ...

  6. erlang调试方法

    第一种方式,加打印记录 %%-define(debug, ok). -ifdef(debug). -define(LOG(X), io:format("pid:~p , {~p,~p}: ~ ...

  7. CentOS6上ftp服务器搭建实战

    1.安装程序包 [root@node1 ~]$ yum install -y vsftpd[root@node1 ~]$ yum install -y lftp # 安装测试软件 2.启动vsftpd ...

  8. 『Python CoolBook:Collections』数据结构和算法_容器型value字典&顺序字典

    一.collections.defaultdict:多值映射字典 defaultdict省去了初始化容器的过程,会默认value对象为指定类型的容器 指定list时可以使用.append, from ...

  9. Petrozavodsk Winter Camp, Day 8, 2014, Mosaic

    给你三个数字a,b,c,让你用1-m的数字凑出来 结论:有2个1和2个2肯定凑不出来,然后就搜索 #include <bits/stdc++.h> using namespace std; ...

  10. 关于vs code 快速生成vue 模板

    在 文件>首选项>用户代码片断里面,打开vue.json 添加以下代码: "Print to console": { "prefix": " ...