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);
    }
}

 

 至此,在尽量少冗余代码的情况下,满足并发情况下分库需求。如果有更优方案,欢迎交流。

转载于:https://www.cnblogs.com/lmsthoughts/p/9160431.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ShardingSphere:SpringBoot2+MybatisPlus+读写分离+分库分表课程目标快速的掌握读写分离+分表的实战,即插即用适用人群IT从业人员,开发人员,Java从业者,互联网从业者,性能调优人群课程简介ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈。它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成,shardingSphere定位为关系型数据库中间件。 Sharding-JDBCSharding-JDBC是Sharding-Sphere的第一个产品,也是Sharding-Sphere的前身,是当当网开源的一个产品。定位为轻量级的Java框架,在Java的JDBC层提供额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。他们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。Sharding-JDBC可以通过Java,YAML,Spring命名空间和Spring Boot Starter四种方式配置,开发者可根据场景选择适合的配置方式。课程特色 本章节以尽量短的时间,为使用者提供最简单的ShardingSphere的快速入门。课程说明该课程属于系列课程,分为读写分离,分库不分表,不分库分表分库分表,读写分离+分库分表共5个回合。本课程属于其中一个回合,请各位小哥哥们注意,课程的标题哦~

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值