MyBatis分库分表方案

article/2025/4/24 9:51:44

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 {@ResourceCostDao 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 {@ResourceCostDao costDao;public CostLog queryCostDao(int id) {//分两库两表db1、db2,每个库中又有两个表tb1、tb2,我们根据账户id模4的取模值来分库表,0:db1.tb1 ;1:db1.tb2;2:db2.tb1;3:db2.tb2String 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 {@ResourceDb1CostDao costDao1;@ResourceDb2CostDao 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.tb2String dbName = id % 4 < 2 ? "db1" : "db2";String tbName = id % 2 == 0 ? "tb1" : "tb2";return selectDao(id).queryCostLog(dbName, tbName, id);}
}

 至此,在尽量少冗余代码的情况下,满足并发情况下分库需求。

spring+mybatis 实现多数据源切换

jdbc.properties

#============================================================================
# MySQL
#============================================================================
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
jdbc.mysql.username=root
jdbc.mysql.password=root#============================================================================
# MS SQL Server
#============================================================================
#jdbc.sqlserver.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
#jdbc.sqlserver.url=jdbc:sqlserver://127.0.0.1:1433;database=test;
#jdbc.sqlserver.username=sa
#jdbc.sqlserver.password=sa#============================================================================
# MS SQL Server (JTDS)
#============================================================================
jdbc.sqlserver.driver=net.sourceforge.jtds.jdbc.Driver
jdbc.sqlserver.url=jdbc:jtds:sqlserver://127.0.0.1:1433/test
jdbc.sqlserver.username=sa
jdbc.sqlserver.password=sa#============================================================================
# 通用配置
#============================================================================
jdbc.initialSize=5
jdbc.minIdle=5
jdbc.maxIdle=20
jdbc.maxActive=100
jdbc.maxWait=100000
jdbc.defaultAutoCommit=false
jdbc.removeAbandoned=true
jdbc.removeAbandonedTimeout=600
jdbc.testWhileIdle=true
jdbc.timeBetweenEvictionRunsMillis=60000
jdbc.numTestsPerEvictionRun=20
jdbc.minEvictableIdleTimeMillis=300000

多数据源时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"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.0.xsd"><bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="location" value="classpath:jdbc.properties"/></bean><bean id="sqlServerDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><property name="driverClassName" value="${jdbc.sqlserver.driver}"/><property name="url" value="${jdbc.sqlserver.url}"/><property name="username" value="${jdbc.sqlserver.username}"/><property name="password" value="${jdbc.sqlserver.password}"/><property name="initialSize" value="${jdbc.initialSize}"/><property name="minIdle" value="${jdbc.minIdle}"/><property name="maxIdle" value="${jdbc.maxIdle}"/><property name="maxActive" value="${jdbc.maxActive}"/><property name="maxWait" value="${jdbc.maxWait}"/><property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/><property name="removeAbandoned" value="${jdbc.removeAbandoned}"/><property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/><property name="testWhileIdle" value="${jdbc.testWhileIdle}"/><property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/><property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/><property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/></bean><bean id="mySqlDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"><property name="driverClassName" value="${jdbc.mysql.driver}"/><property name="url" value="${jdbc.mysql.url}"/><property name="username" value="${jdbc.mysql.username}"/><property name="password" value="${jdbc.mysql.password}"/><property name="initialSize" value="${jdbc.initialSize}"/><property name="minIdle" value="${jdbc.minIdle}"/><property name="maxIdle" value="${jdbc.maxIdle}"/><property name="maxActive" value="${jdbc.maxActive}"/><property name="maxWait" value="${jdbc.maxWait}"/><property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/><property name="removeAbandoned" value="${jdbc.removeAbandoned}"/><property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/><property name="testWhileIdle" value="${jdbc.testWhileIdle}"/><property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/><property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/><property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/></bean><bean id="multipleDataSource" class="com.cnblogs.lzrabbit.MultipleDataSource"><property name="defaultTargetDataSource" ref="mySqlDataSource"/><property name="targetDataSources"><map><entry key="mySqlDataSource" value-ref="mySqlDataSource"/><entry key="sqlServerDataSource" value-ref="sqlServerDataSource"/></map></property></bean><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="multipleDataSource"/></bean><!-- mybatis.spring自动映射 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.cnblogs.lzrabbit"/></bean><!-- 自动扫描,多个包以 逗号分隔 --><context:component-scan base-package="com.cnblogs.lzrabbit"/><aop:aspectj-autoproxy/>
</beans>

Java代码编写

MultipleDataSource 多数据源配置类

package com.xxx.gfw.pubfound;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/**** 
* Project Name:gfw-public-foundation-impl
* <p>自定义多数据源配置类  
*
* @ClassName: MultipleDataSource
* @date 2018年5月18日  下午4:47:20
*
* @author youqiang.xiong
* @version 1.0
* @since*/
public class MultipleDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();public static void setDataSourceKey(String dataSource) {dataSourceKey.set(dataSource);}@Overrideprotected Object determineCurrentLookupKey() {return dataSourceKey.get();}
}

自定义注解DataSourceType

package com.xxx.pubfound.aop.anntion;import java.lang.annotation.*;/**** 
* Project Name:gfw-base-common-service
* <p>自定义数据源类型注解,标志当前的dao接口使用的数据源类型
*
* @ClassName: DataSourceType
* @date 2018年5月18日  下午5:09:49
*
* @author youqiang.xiong
* @version 1.0
* @since*/
@Target({ ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented 
public @interface DataSourceType {String value() default "dataSource";
}

两个service层分别加上DataSourceType 注解

ProvinceServiceImpl.java

package com.xxx.pubfound.service;import java.util.ArrayList;
import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import com.xxx.pubfound.aop.anntion.DataSourceType;
import com.xxx.pubfound.dao.SysProvinceDao;
import com.xxx.pubfound.dto.SysProvinceDTO;
import com.xxx.pubfound.struct.SysProvinceListStruct;
import com.xxx.pubfound.struct.SysProvinceStruct;
import com.xxx.rpc.api.AbstractRpcService;/**** 
* Project Name:gfw-public-foundation-impl
* <p> 省份服务层实现类 
*
* @ClassName: ProvinceServiceImpl
* @date 2018年5月18日  下午6:29:35
*
* @author youqiang.xiong
* @version 1.0
* @since*/
@DataSourceType(value="gfwDataSource")
@Service
public class ProvinceServiceImpl extends AbstractRpcService implements ProvinceService {@Autowiredprivate SysProvinceDao sysProvinceDao;@Overridepublic SysProvinceListStruct getProvinceList() {List<SysProvinceDTO> list = sysProvinceDao.getProvinceList();return beanToStruct(list);}/**** * Project Name: gfw-public-foundation-impl* <p>将dto对象封装struct对象 ** @author youqiang.xiong* @date 2018年5月28日  下午3:31:42* @version v1.0* @since * @param provinceList*           省份列表dto* @return  省份列表struct*/private SysProvinceListStruct beanToStruct(List<SysProvinceDTO> provinceList){if(provinceList == null || provinceList.size() == 0){return null;}List<SysProvinceStruct> resultList = new ArrayList<SysProvinceStruct>();for(SysProvinceDTO dto:provinceList){SysProvinceStruct struct = new SysProvinceStruct();struct.provinceId = dto.getProvinceId();struct.provinceName = dto.getProvinceName();resultList.add(struct);}SysProvinceListStruct rsStruct = new SysProvinceListStruct(resultList);return rsStruct;}}

DefaultExceptionCollector.java

package com.xxx.pubfound.service;import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageInfo;
import com.xxx.pubfound.aop.anntion.DataSourceType;
import com.xxx.pubfound.dao.PfExceptionLogDao;
import com.xxx.pubfound.dto.NotifyLevelEnum;
import com.xxx.pubfound.entity.PfExceptionLog;
import com.xxx.pubfound.struct.PagedPfExceptionLogStruct;
import com.xxx.pubfound.struct.PfExceptionLogStruct;
import com.xxx.rpc.api.AbstractRpcService;/**** 
* Project Name:gfw-public-foundation-impl
* <p>异常采集
*
* @ClassName: DefaultExceptionCollector
* @date 2018年5月28日  下午8:01:43
*
* @author youqiang.xiong
* @version 1.0
* @since*/
@DataSourceType(value="dataSource")
@Service
public class DefaultExceptionCollector extends AbstractRpcService implements ExceptionCollector {@AutowiredPfExceptionLogDao pfExceptionLogDao;@AutowiredDruidDataSource dataSource;@Overridepublic void collect(long reqTime, String exceptionClass, String stackTrace, int resultCode, String environment,String nodeNameEn) {//FIXME 采集异常!!//TODO luoshanSystem.out.println(dataSource.getConnectProperties());PfExceptionLog exceptionLog = new PfExceptionLog();exceptionLog.setEnvironment(environment);exceptionLog.setExceptionClass(exceptionClass);exceptionLog.setExceptionTime(new Date(reqTime));exceptionLog.setResultCode(resultCode);exceptionLog.setServiceName(nodeNameEn);exceptionLog.setStackTrace(stackTrace);pfExceptionLogDao.insert(exceptionLog);System.out.println("Exception ex:" + exceptionClass);System.out.println("Exception ex:" + resultCode);}@Overridepublic void collectNotify(long reqTime, String exceptionClass, String stackTrace, int resultCode,String environment, String nodeNameEn, NotifyLevelEnum level) {try{this.collect(reqTime, exceptionClass, stackTrace, resultCode, environment, nodeNameEn);}catch(Exception ex){ex.printStackTrace();}//FIXME  根据不停的level 分别执行策略 startif(level.compareTo(NotifyLevelEnum.WARN) == 0){//发邮件!}else if(level.compareTo(NotifyLevelEnum.ERROR) == 0){//发邮件,1/3概率 发短信!}else if(level.compareTo(NotifyLevelEnum.FATAL) == 0){//发邮件,并且要发短信!}//FIXME  根据不停的level 分别执行策略 end}/*** 分页获取异常日志列表** @param pageNo      页码* @param size        每页数据量* @param serviceName* @param beginTime* @param endTime** @return*/
//  @Override
//  public PagedPfExceptionLogStruct queryExceptionLogList(int pageNo, int size, String serviceName, Long beginTime, Long endTime) {
//      PageHelper.startPage(pageNo , size);
//      Date beginTimeDate = beginTime == null || beginTime <= 0 ? null : new Date(beginTime);
//      Date endTimeDate = endTime == null || beginTime <= 0 ? null : new Date(endTime);
//      List<PfExceptionLog> list = pfExceptionLogDao.selectPfExceptionLogList(serviceName , beginTimeDate , endTimeDate);
//
//      List<PfExceptionLogStruct> structList = new ArrayList<>();
//      if (list != null || !list.isEmpty()) {
//          for (PfExceptionLog pfExceptionLog: list) {
//              structList.add(entityToStruct(pfExceptionLog));
//          }
//      }
//      PageInfo<PfExceptionLogStruct> page = new PageInfo<>(structList);
//      PagedPfExceptionLogStruct result = new PagedPfExceptionLogStruct(page.getPageNum(), page.getTotal(), page.getPages(), page.getList());
//      return result;
//  }@Overridepublic PagedPfExceptionLogStruct queryExceptionLogList(int pageNo, int size, String serviceName, Long beginTime, Long endTime) {Date beginTimeDate = beginTime == null || beginTime <= 0 ? null : new Date(beginTime);Date endTimeDate = endTime == null || beginTime <= 0 ? null : new Date(endTime);int offset = pageNo < 1 ? 0 : (pageNo - 1) * size;List<PfExceptionLog> list = pfExceptionLogDao.selectPfExceptionLogList(offset , size , serviceName , beginTimeDate , endTimeDate);List<PfExceptionLogStruct> structList = new ArrayList<>();if (list != null || !list.isEmpty()) {for (PfExceptionLog pfExceptionLog: list) {structList.add(entityToStruct(pfExceptionLog));}}int total = pfExceptionLogDao.selectPfExceptionLogListCount(serviceName , beginTimeDate , endTimeDate);int pages = total % size == 0 ? total/size : total/size + 1;PageInfo<PfExceptionLogStruct> page = new PageInfo<>(structList);PagedPfExceptionLogStruct result = new PagedPfExceptionLogStruct(pageNo, total, pages, page.getList());return result;}private PfExceptionLogStruct entityToStruct(PfExceptionLog pfExceptionLog) {if (pfExceptionLog == null) {return null;}PfExceptionLogStruct pfExceptionLogStruct = new PfExceptionLogStruct();pfExceptionLogStruct.id = pfExceptionLog.getId();pfExceptionLogStruct.environment = pfExceptionLog.getEnvironment();pfExceptionLogStruct.exceptionClass = pfExceptionLog.getExceptionClass();pfExceptionLogStruct.exceptionTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(pfExceptionLog.getExceptionTime());pfExceptionLogStruct.resultCode = pfExceptionLog.getResultCode();pfExceptionLogStruct.serviceName = pfExceptionLog.getServiceName();pfExceptionLogStruct.stackTrace = pfExceptionLog.getStackTrace();return pfExceptionLogStruct;}/*** 根据异常日志id 获取异常日志详情** @param id 异常日志id** @return*/@Overridepublic PfExceptionLogStruct queryExceptionLogById(int id) {PfExceptionLog pfExceptionLog = pfExceptionLogDao.selectByPrimaryKey(id);return entityToStruct(pfExceptionLog);}
}

MultipleDataSourceAop.java多数据源自动切换切面类

package com.xxx.pubfound.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import com.shangde.gfw.util.proxy.ProxyUtil;
import com.xxx.pubfound.MultipleDataSource;
import com.xxx.pubfound.aop.anntion.DataSourceType;/**** 
* Project Name:gfw-public-foundation-impl
* <p>多数据源自动切换通知类(拦截com.xxx.pubfound.dao中所有的类中的方法)<br>
*  首先判断当前类是否被该DataSourceType注解进行注释,如果没有采用默认的uam数据源配置;<br>
*  如果有,则读取注解中的value值,将数据源切到value指定的数据源
* @ClassName: MultipleDataSourceAspectAdvice
* @date 2018年5月18日  下午5:13:51
*
* @author youqiang.xiong
* @version 1.0
* @since*/
@Component
@Aspect
public class MultipleDataSourceAop {private final Logger logger = LoggerFactory.getLogger(getClass());/**** * Project Name gfw-public-foundation-impl* <p>* 拦截 pubfound.service中所有的方法,根据情况进行数据源切换** @author youqiang.xiong* @date 2018年5月18日 下午5:49:48* @version v1.0* @since* @param pjp*            连接点* @throws Throwable*             抛出异常*/@Before("execution(* com.xxx.pubfound.service.*.*(..))")public void changeDataSource(JoinPoint joinPoint) throws Throwable {// 拦截的实体类,就是当前正在执行的serviceObject target = ProxyUtil.getTarget(joinPoint.getTarget());if(target.getClass().isAnnotationPresent(DataSourceType.class)){DataSourceType dataSourceType = target.getClass().getAnnotation(DataSourceType.class);String type = dataSourceType.value();logger.info("数据源切换至--->{}",type);MultipleDataSource.setDataSourceKey(type);}else{logger.info("此{}不涉及数据源操作.",target.getClass());}}
}
  • 以上就是通过spring aop 切面实现多数据源自动切换的代码和配置,请根据各各自实际情况稍作修改.


http://chatgpt.dhexx.cn/article/apA154SK.shtml

相关文章

数据库分库分表解决方案

数据库分库分表解决方案 前言MySQL表大小限制分表方案垂直分表水平分表按月分表MySQL分区表 分库方案按业务分库按表分库 拆分后的问题及常见的解决方案垂直拆分跨库Join问题全局表数据同步 分布式事务问题MySQL XA事务本地消息表 水平拆分分布式全局唯一ID分片键选择数据扩容跨…

MySQL-分库分表方案

一、业务背景 随着业务量的增长&#xff0c;数据量会随之增长&#xff0c;单机情况下DB服务器会面临存储容量、连接数和处理能力的瓶颈&#xff0c;当数据量达到一定量级时&#xff0c;DDL变更时间变长&#xff0c;影响业务可用性&#xff0c;此时需要考虑分库分表&#xff0c…

最全的MySQL分库分表方案总结

“ 面试中我们经常会碰到的关于分库分表的问题&#xff01;今天就给大家介绍互联网公司常用 MySQL 分库分表方案&#xff01;希望对大家的面试有所帮助&#xff01; 数据库瓶颈 不管是 IO 瓶颈&#xff0c;还是 CPU 瓶颈&#xff0c;最终都会导致数据库的活跃连接数增加&#x…

python mysql分库分表_干货 : 常用MySQL分库分表方案

Python乱炖推荐搜索后浪 动森玩家 送书 数据分析 一、数据库瓶颈 不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞…

php分库分表技术,谈谈关于分库分表的方案

1. 数据库瓶颈 不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞吐量、崩溃)。 1.1. IO瓶颈 第一种:磁盘读IO瓶颈,…

这应该是全网最全的分库分表方案了

一、数据库瓶颈 不管是IO瓶颈&#xff0c;还是CPU瓶颈&#xff0c;最终都会导致数据库的活跃连接数增加&#xff0c;进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是&#xff0c;可用数据库连接少甚至无连接可用。接下来就可以想象了吧&#xff08;并发…

MySQL第六讲 MySQL分库分表方案

分库分表概念 分库分表就是业务系统将数据写请求分发到master节点&#xff0c;而读请求分发到slave 节点的一种方案&#xff0c;可以大大提高整个数据库集群的性能。但是要注意&#xff0c;分库分表的 一整套逻辑全部是由客户端自行实现的。而对于MySQL集群&#xff0c;数据主从…

分库分表方案对比

房晓乐&#xff08;葱头巴巴&#xff09;&#xff0c;PingCAP 资深解决方案架构师&#xff0c;前美团数据库专家、美团云 CDS 架构师、前搜狗、百度资深 DBA&#xff0c;擅长研究各种数据库架构&#xff0c;NewSQL 布道者。 原文链接&#xff1a;https://dbaplus.cn/news-11-1…

分库分表方案

一、为什么要进行分库分表 当MySQL单表数据量过大&#xff0c;比如超过5千万条的时候&#xff0c;读写性能变得很差。而且常规的优化手段已经不起作用了&#xff0c;比如&#xff1a;SQL调优、添加索引、主从复制、读写分离。这时候就需要用到MySQL终极优化方案 — 分库分表。 …

分库分表设计方案

一、为什么要分库分表? 随着业务的不断发展&#xff0c;数据量不断增加&#xff0c;因此数据操作&#xff0c;如增删改查的开销也会越来越大&#xff0c;原来基于单库单表的设计已经不能满足存储需求&#xff0c;数据库随时面临爆库风险&#xff1b; 再加上物理服务器的资源有…

python:numpy的corrcoef计算相关系数

corrcoef(x, yNone, rowvarTrue, biasnp._NoValue, ddofnp._NoValue)函数常用的是前三个参数&#xff0c;x和y分别是需要计算相关系数的两个随机变量&#xff0c;当rowvar为True(默认情况)时&#xff0c;每一行代表一个随机变量&#xff0c;否则每一列代表一个随机变量。 该函数…

python 计算相关性系数np.corrcoef()

计算相关性是分析连续型与连续型双变量的常用方法&#xff0c;散点图只能直观的显示双变量&#xff08;特征&#xff09;之间的关系&#xff0c;但并不能说明关系的强弱&#xff0c;而相关性可以对变量之间的关系进行量化分析。 相关性系数的公式如下&#xff1a; 相关性系数…

Numpy库 numpy.corrcoef()函数

相关系数公式&#xff1a; 其他详见&#xff1a; 1. Python Numpy库 numpy.corrcoef()函数讲解 2. 协方差、方差、标准差、协方差系数 3. 标准差、方差、协方差三者的表示意义

MATLAB中的corrcoef函数求两个向量的相关系数。

&#xfeff;&#xfeff; 想用MATLAB中的corrcoef函数求两个向量的相关系数。 比如A[1 2 3];B[5 3 7]; r corrcoef(A,B)可以求出相关系数是0.5.为什么两个向量的元素都要是3个以上才行&#xff1f;而只有两个元素的向量如A[1 2];B[5 3];不管怎么随机的取&#xff0c;相关系数都…

使用numpy计算相关系数矩阵:np.corrcoef()

【小白从小学Python、C、Java】 【Python-计算机等级考试二级】 【Python-数据分析】 使用numpy计算相关系数矩阵 np.corrcoef() 选择题 关于以下代码说法错误的是? import numpy as np array1np.array([[1,2,3], [2,3,4], [2,3,3], [4,3,2], [4,3,3]]) print(array1) print(&…

matlab 计算相关系数,MATLAB如何使用corrcoef函数计算样本数据的相关系数

MATLAB如何使用corrcoef函数计算样本数据的相关系数 【语法说明】 Rcorrcoef(X)&#xff1a;如果X为向量&#xff0c;函数返回1&#xff1b;如果X为mn 矩阵&#xff0c;则以每行为观测值&#xff0c;每列为一个随机变量计算相关系数&#xff0c;返回一个nn对称矩阵。假设协方差…

numpy.corrcoef()函数讲解

numpy.corrcoef(x, y无, rowvar True, 偏差<无值>, ddof<无值>) 【学习参考】&#xff1a;Python Numpy库 numpy.corrcoef()函数讲解_Hello_xzy_Word的博客-CSDN博客_numpy.corrcoef x&#xff1a; array_like&#xff0c;包含多个变量和观测值的1-D或2-D数组&a…

Python Numpy库 numpy.corrcoef()函数讲解

例子&#xff1a; 代码&#xff1a; import numpy as npArray1 [[1, 2, 3], [4, 5, 6]] Array2 [[11, 25, 346], [734, 48, 49]] Mat1 np.array(Array1) Mat2 np.array(Array2) correlation np.corrcoef(Mat1, Mat2) print("矩阵1\n", Mat1) print("矩阵…

一、求相关函数 corrcoef+协方差矩阵cov

1. 求相关函数corrcoef 一般作用&#xff1a;1&#xff09;结合图表评估数据拟合效果&#xff1b;2&#xff09;评估两组数据之间的互相关联程度&#xff0c;大于0正相关&#xff0c;小于0负相关&#xff0c;等于0不相关。 但此种计算方法反映的是“线性相关”程度&#xff0…

MATLAB 协方差 [cov] 和相关系数 [corrcoef] 说明

A,B为两个长度相同的向量 求协方差 Scov(A,B); b和c数值上是相等的。 求相关系数 Rcorrcoef(A,B) 数值上,f和g是相同到。 相关系数存在许多种类&#xff0c;上述corrcoef 指 pearson correlation coefficient。 性质&#xff1a;向量乘常数&#xff0c;不改变Pearson 相关…