读写分离是为了减少数据库的负荷,当用户高并发访问时,绝大部分都是用户查询,少部分用户是写入到数据库的。这些我们把数据库拆分成主从两个数据库,主数据库用高性能
服务器承载高并发的用户访问并加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();