美文网首页
SpringMVC主从数据库切换

SpringMVC主从数据库切换

作者: MC_Honva | 来源:发表于2018-05-31 17:26 被阅读81次
配置文件中添加数据库配置信息
<!-- 定义数据库数据源 -->
    <bean id="masterDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        <property name="driverClass" value="${master.spay.db.driverClass}" />
        <property name="jdbcUrl" value="${master.spay.db.url}" />
        <property name="user" value="${master.spay.db.username}" />
        <property name="password" value="${master.spay.db.password}" />
        <property name="autoCommitOnClose" value="${master.spay.db.autoCommitOnClose}" />
        <property name="initialPoolSize" value="${master.spay.db.pool.initialPoolSize}" />
        <property name="minPoolSize" value="${master.spay.db.pool.minPoolSize}" />
        <property name="maxPoolSize" value="${master.spay.db.pool.maxPoolSize}" />
        <property name="maxIdleTime" value="${master.spay.db.pool.maxIdleTime}" />
        <property name="maxIdleTimeExcessConnections" value="${master.spay.db.pool.maxIdleTimeExcessConnections}" />
        <property name="idleConnectionTestPeriod" value="${master.spay.db.pool.idleConnectionTestPeriod}" />
        <property name="acquireIncrement" value="${master.spay.db.pool.acquireIncrement}" />
        <property name="testConnectionOnCheckout" value="${master.spay.db.pool.testConnectionOnCheckout}" />
        <property name="checkoutTimeout" value="${master.spay.db.pool.checkoutTimeout}" />
        <property name="acquireRetryAttempts" value="${master.spay.db.pool.acquireRetryAttempts}" />
        <property name="maxStatements" value="${master.spay.db.pool.maxStatements}" />
        <property name="maxStatementsPerConnection" value="${master.spay.db.pool.maxStatementsPerConnection}" />
        <property name="preferredTestQuery" value="${master.spay.db.pool.preferredTestQuery}" />
    </bean>
    
    <bean id="slaveDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close">
        .......
    </bean> 
    
    
    <bean id="dataSource" class="cn.manager.dbconfig.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="read" value-ref="slaveDataSource"></entry>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource"></property>
    </bean>
    
    <bean id="sqlSessionFactory" class="cn.mybatis.springext.PathSqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        .....
        <property name="plugins">
            <list>
                <bean class="cn.swiftpass.core.common.mybatis.pagehelper.PageHelper">
                    <property name="properties">
                        <value>
                            dialect=mysql
                            reasonable=true
                        </value>
                    </property>
                </bean>
            </list>
        </property> 
    </bean>
    
自定义注解

可以添加在方法上指定是走主库还是从库,不添加则根据方法名选择

/**
 * 
 * @author Honva
 * @Description 数据库切换标记注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    public String value();
}

数据库适配类
/**
 * 
 * @author Honva
 * @Description 数据源
 */
public class DbContextHolder {
    public static final String WRITE = "write";
    public static final String READ = "read";
    private  static ThreadLocal<String> dataSource = new ThreadLocal<String>();
    
    public static void set(String name){
        dataSource.set(name);
    }
    public static String get(){
        return dataSource.get();
    }
    public static void remove(){
        dataSource.remove();
    }
}
自定义切面

自定义切面入口,在执行方法前先判断方法名,再选择主从库

/**
 * @author Honva
 * @Description 数据库切换切面
 */
@Aspect
@Component
public class DbAspect {
    private String PRE_PAGING = "paging";
    private String PRE_GET = "get";
    private String PRE_FIND = "find";

    @Before("execution(* cn.manager.mapper..*.*(..))")
    public void dbAspect(JoinPoint point) throws Exception {
        MethodSignature s = (MethodSignature) point.getSignature();
        Method method = s.getMethod();
        String methodName = method.getName();
        DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource != null) {
            DbContextHolder.set(dataSource.value());
        } else if (methodName.startsWith(PRE_PAGING) || methodName.startsWith(PRE_GET)
                || methodName.startsWith(PRE_FIND)) {
            DbContextHolder.set(DbContextHolder.READ);
        } else {
            DbContextHolder.set(DbContextHolder.WRITE);
        }
    }
}

动态数据库切换类(重点类)
/**
 * 
 * @author Honva
 * @Description 动态数据源切换
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
    
    @Override
    protected Object determineCurrentLookupKey() {
        String key = DbContextHolder.get();
        try {
            if(key==null || key.equals(DbContextHolder.WRITE)){
                return null;
            }
            if(key.equals(DbContextHolder.READ)){
                try {
                    Field field = this.getClass().getSuperclass().getDeclaredField("targetDataSources");    field.setAccessible(true);
                    @SuppressWarnings("unchecked")
                    Map<Object,Object> targetDataSources = (Map<Object, Object>) field.get(this);
                    if(targetDataSources.isEmpty()){
                        return DbContextHolder.WRITE;
                    }
                    List<Object> list = new ArrayList<Object>(targetDataSources.keySet());
                    Object object = list.get((UUID.randomUUID().hashCode() & 0x7FFFFFFF) % list.size());
                    return object;
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }
            return null;
        }finally{
            DbContextHolder.remove();
        }
    }
}

this.getClass().getSuperclass().getDeclaredField("targetDataSources")是为了获取父类targetDataSources属性。

field.setAccessible(true),因为此变量是private修饰,所以需要改变其属性变成可获取。

散列算法:UUID.randomUUID().hashCode() & 0x7FFFFFFF),UUID随机生成哈希值会生成一个10位的随机数字(有正负值),&0x7FFFFFFF是为了避免出现负数。

主从数据库切换成功后,事务失效的话,可参考此篇文章
Spring主从数据库切换,事务失效

相关文章

  • SpringMVC主从数据库切换

    配置文件中添加数据库配置信息 自定义注解 可以添加在方法上指定是走主库还是从库,不添加则根据方法名选择 数据库适配...

  • Mysql高可用套件

    Mysql发生宕机的时候,主从机器不会自动切换,需要高可用框架对数据库主从进行管理。 高可用套件负责数据库的Fai...

  • MySQL Replace Into & Replication

    问题 数据库主从切换后,应用使用Replace into Statement 更新插入抛出异常 原因解释 Repl...

  • Redis高可用(哨兵机制)

    一 主从切换 master和slave的主从切换,保证了服务的高可用; 二 哨兵(sential)机制 senti...

  • mysql常用架构设计

    数据库扩展解决了哪些问题?热备份、多活、故障切换、负载均衡、读写分离 1.主从架构(master-slave) 在...

  • MySQL的主从复制

    介绍 为什么要主从复制 做数据的热备 如果主数据库宕机,可以快速将业务系统切换到从数据库上,可避免数据丢失。 业务...

  • 主从复制&读写分离

    为什么要做主从? 1、做数据的热备,作为后备数据库,当主数据库故障或宕机,可以切换到从库继续工作,防止数据丢失。2...

  • 教你如何配置mysql主从配置

    主从复制是为了加强系统数据库的可用性,当主库挂掉时,从数据库保存数据,数据不会丢失,将从库切换为主库,等主库弄好之...

  • 自我系统学习Redis小记-04

    08 | 哨兵集群:哨兵挂了,主从库还能切换吗? 1、引言 哨兵机制,它可以实现主从库的自动切换。通过部署多个实例...

  • MySQL主从复制

    为什么要主从复制 做数据的热备份;如果主数据库宕机,可以快速将业务切换到从数据库上,可避免数据丢失;业务量越来越大...

网友评论

      本文标题:SpringMVC主从数据库切换

      本文链接:https://www.haomeiwen.com/subject/ukxcsftx.html