spring+mybatis实现数据库读写分离

本文介绍了如何使用Spring和Mybatis实现数据库的读写分离,以减轻高并发场景下数据库的压力。通过配置AbstractRoutingDataSource,动态切换数据源,实现读操作和写操作分别使用不同的数据库。详细配置包括Spring的bean定义,数据源的设置,以及核心的DynamicDataSource类的实现,确保在适当的时候选择正确的数据源进行操作。
摘要由CSDN通过智能技术生成

读写分离是为了减少数据库的负荷,当用户高并发访问时,绝大部分都是用户查询,少部分用户是写入到数据库的。这些我们把数据库拆分成主从两个数据库,主数据库用高性能


服务器承载高并发的用户访问并加redis缓存。在这里我不讲mysql的主从同步配置,大家可以去查下资料,我接下来重点讲怎么动态的给每个sql注入数据源。


首先大家需要先了解AbstractRoutingDataSource这个抽象类,这是spring-jdbc下的一个底层类,当sqlsession去获得一个数据库连接时会调用这个类的determineTargetDataSource()方法获得数据源。

我们先从配置文件开始说起,这是楼主实验时自己写的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:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc" 
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
   
   <mvc:annotation-driven/>  //注册handdlemapping
   <context:annotation-config /> //引入注解
   <context:component-scan base-package="com.hbut.inspiration"/>// 扫描该包下所有bean
   <aop:aspectj-autoproxy />  //引入aspectj切面编程


   
   <bean id="jdbcConfig" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> //定义spring上下文的全局变量即数据库的配置
    <property name="locations" value="classpath:jdbc.properties"/>
   </bean>
   
   <bean id="abstractDatasource" class="org.apache.commons.dbcp.BasicDataSource">    //配置一个抽象数据源
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="initialSize" value="${jdbc.initialSize}"/>
    <property name="maxActive" value="${jdbc.maxActive}"/>
    <property name="maxIdle" value="${jdbc.maxIdle}"/>
    <property name="minIdle" value="${jdbc.minIdle}"/>
    <property name="maxWait" value="${jdbc.maxWait}"/>
   </bean>
   
   <bean id="writeDataSource" parent="abstractDatasource">   //配置一个从数据源即写数据源
     <property name="url" value="${jdbc.writeUrl}"></property>
   </bean>
   
   <bean id="readDataSource" parent="abstractDatasource">    //配置一个主数据源即读数据源
     <property name="url" value="${jdbc.readUrl}"></property>
   </bean>
   
   <bean id="dataSource" class="com.hbut.inspiration.system.DynamicDataSource">  //核心类,自己写的,需继承AbstractRoutingDataSource,每当获得数据源的时候就                                                                                                                                                                                      会执行DynamicDataSource.determineTargetDataSource()
        <property name="writeDataSource" ref="writeDataSource"/>
        <property name="readDataSource" ref="readDataSource">
        </property>
        <property name="defaultTargetDataSource" ref="writeDataSource"/>
    </bean>
   
   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">   // 装配所有的mapping映射文件
        <property name="dataSource" ref="dataSource" />  
        <property name="mapperLocations" value="classpath:sqlMap/*.xml"></property>    
   </bean>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">      //装配所有的map文件
        <property name="basePackage" value="com.hbut.inspiration.map" />  
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>    
    </bean> 
    
    <bean id="transactionManager"  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">    //事务管理配置,这是楼主为了测事务配置,大家可以                                                                                                                                                                                                                      不用写
        <property name="dataSource" ref="dataSource" />  
    </bean> 
    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">    //定义事务管理的属性
        <tx:attributes> 
            <tx:method name="get*" read-only="true" />
            <tx:method name="find*" read-only="true" />
            <tx:method name="query*" read-only="true" />
            <tx:method name="is*" read-only="true" />
            <tx:method name="do*" propagation="REQUIRES_NEW" rollback-for="java.lang.Exception"/>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
            <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception" />
        </tx:attributes> 
    </tx:advice> 
    
    <aop:config proxy-target-class="true">      //定义切面
        <aop:advisor pointcut="execution(* com.hbut.inspiration.service..*.*(..))" advice-ref="txAdvice" />
    </aop:config>
    
    <tx:annotation-driven transaction-manager="txManager" order="0"/>     //楼主自己测事务的时候想用注解事务,大家可以不用写
</beans>


jdbc.properties文件

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.writeUrl=jdbc:mysql://***********:3306/数据库名
jdbc.readUrl=jdbc:mysql://**********:3306/数据库名l
jdbc.username=root
jdbc.password=123456
jdbc.initialSize=30
jdbc.maxActive=100
jdbc.maxIdle=100
jdbc.minIdle=5
jdbc.maxWait=1000



首先定义一个枚举类

package com.hbut.inspiration.system;

//用户定义读写两个数据源的key值
public enum DynamicDataSourceGlobal {
     READ,WRITE;
}


定义一个设置数据源的key的类,因为servlet是单例多线程的,所有定义变量的时候需考率多线程安全问题,这里楼主用到了threadlocal,用于把当前线程的用到的数据源的key存储在threadlocal里,大家想要了解threadlocal可以看楼主的 另一条博客http://blog.csdn.net/csdnzhangtao5/article/details/52932275

package com.hbut.inspiration.system;


public class DynamicDataSourceHolder {     //dynamicDataSourceGlobal必须是静态的,禁止实例化多个,只能单例或者直接使用静态方法。大家可以加个恶汉单例模式
    
static ThreadLocal<DynamicDataSourceGlobal> dynamicDataSourceGlobal=new ThreadLocal<DynamicDataSourceGlobal>();

static public  DynamicDataSourceGlobal getDataSource(){
return dynamicDataSourceGlobal.get();
}
    static public void putDataSource(DynamicDataSourceGlobal t){
    dynamicDataSourceGlobal.set(t);
    }
    
    static public void clearDataSource(){
    dynamicDataSourceGlobal.remove();
    }
}

核心类,上面在spring的配置文件里已经配过

public class DynamicDataSource extends AbstractRoutingDataSource{

public Object writeDataSource;
public Object readDataSource;
public Object defaultTargetDataSource;

@Override
protected Object determineCurrentLookupKey() {         //必须实现的类,用于给determineTargetDataSource()提供key值

DynamicDataSourceGlobal targetDataSource=DynamicDataSourceHolder.getDataSource();   //从DynamicDataSourceGlobal得到数据源的key值
if(targetDataSource==null || targetDataSource == DynamicDataSourceGlobal.WRITE){       //默认key值为write
return DynamicDataSourceGlobal.WRITE.name(); 
}else{
return DynamicDataSourceGlobal.READ.name();
}
}

public void afterPropertiesSet(){      //用于在该类所有属性都初始化完成后执行,把我们在spring定义的readdatasource,writedatasource装配到targetDataSources中
                                                         因为determineTargetDataSource()是根据determineCurrentLookupKey()它返回的key值在targetDataSources这个map中去取

                                                                          对应的数据源
setDefaultTargetDataSource(writeDataSource);
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put(DynamicDataSourceGlobal.READ.name(), readDataSource);
targetDataSources.put(DynamicDataSourceGlobal.WRITE.name(), writeDataSource);
setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}


public Object getWriteDataSource() {
return writeDataSource;
}


public void setWriteDataSource(Object writeDataSource) {
this.writeDataSource = writeDataSource;
}


public Object getReadDataSource() {
return readDataSource;
}


public void setReadDataSource(Object readDataSource) {
this.readDataSource = readDataSource;
}


public Object getDefaultTargetDataSource() {
return defaultTargetDataSource;
}


public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}

}


这样一个基本的动态获取数据源的功能就好了


要用的时候只需在每个controller调service的方法前设置就好了,例如


DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.WRITE); //设置数据源为写数据源
mainService.getUserInfo();

或者

DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.READ); //设置数据源为读数据源
mainService.getUserInfo();


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值