一、为什么需要读写分离
随着网站的业务不断扩展,数据不断增加,用户越来越多,数据库的压力也就越来越大,采用传统的方式,比如:数据库或者SQL的优化基本已达不到要求,这个时候可以采用读写分离的策略来改变现状。采用读写分离技术能够有效减轻Master库的压力,又可以把用户查询数据的请求分发到不同的Slave库,从而保证系统的健壮性。
二、常用的两种方式
1、定义两个数据库链接。
一个是masterDataSource,另个是slaveDataSource,更新数据时读取masterDataSource,查询是读取slaveDataSource。
2、动态数据源切换。
在程序运行时,把数据源动态织如入程序中,从而选择主库还是从库。主要技术采用annotation,Spring AOP,反射,接下来详细介绍该种方法。
三、动态数据源切换实现读写分离
1、mybatis查询和更新的注解区分读写
package org.apache.ibatis.annotations;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ java.lang.annotation.ElementType.METHOD })
public @interface Select {
public abstract String[] value();
}
package org.apache.ibatis.annotations;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ java.lang.annotation.ElementType.METHOD })
public @interface Update {
public abstract String[] value();
}
也可自定义注解识别所需读写数据源,如下:
package com.javagaozhi.db;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义识别读写数据源注解
*
* @author Administrator
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
2、继承抽象类AbstractRoutingDataSource来实现DynamicDataSource方法
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
/**
* 多数据源管理
*
* @author Administrator
*
*/
@Component
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
/**
* 设置数据源
*
* @param dataSource
*/
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
/**
* 返回当前数据源
*/
@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
}
3、定义切面DataSourceAspect,切换读写数据源
import java.lang.reflect.Method;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
/**
* 定义数据源切面,程序运行时动态切换数据源
*
* @author Administrator
*
*/
@Aspect
@Component
public class DataSourceAspect {
@Around("execution(* com.javagaozhi.dao.*.*(..))")
public Object before(ProceedingJoinPoint point) throws Throwable {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
Method m = classz[0].getMethod(method, parameterTypes);
// 根据注解类型选择对应数据源,Select选择读数据源,Update选择写数据源
if (null != m && m.isAnnotationPresent(Select.class)) {
MultipleDataSource.setDataSourceKey("dataSource-slave");
} else if (null != m && m.isAnnotationPresent(Update.class)) {
MultipleDataSource.setDataSourceKey("dataSource-master");
} else {
return null;
}
return point.proceed();
}
}
4、applicationContext-dataSource.xml配置
<!--数据源1-->
<bean id="dataSource-master" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
<property name="poolProperties">
<bean class="org.apache.tomcat.jdbc.pool.PoolProperties">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="jmxEnabled" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="false"/>
<property name="validationInterval" value="30000"/>
<property name="validationQuery" value="SELECT 1"/>
<property name="timeBetweenEvictionRunsMillis" value="30000"/>
<property name="maxActive" value="100"/>
<property name="initialSize" value="1"/>
<property name="maxWait" value="10000"/>
<property name="minEvictableIdleTimeMillis" value="30000"/>
<property name="minIdle" value="10"/>
<property name="logAbandoned" value="false"/>
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="60"/>
<property name="jdbcInterceptors" value="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"/>
</bean>
</property>
</bean>
<!--数据源2-->
<bean id="dataSource-slave" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
<property name="poolProperties">
<bean class="org.apache.tomcat.jdbc.pool.PoolProperties">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test_read?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="jmxEnabled" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="false"/>
<property name="validationInterval" value="30000"/>
<property name="validationQuery" value="SELECT 1"/>
<property name="timeBetweenEvictionRunsMillis" value="30000"/>
<property name="maxActive" value="100"/>
<property name="initialSize" value="1"/>
<property name="maxWait" value="10000"/>
<property name="minEvictableIdleTimeMillis" value="30000"/>
<property name="minIdle" value="10"/>
<property name="logAbandoned" value="false"/>
<property name="removeAbandoned" value="true"/>
<property name="removeAbandonedTimeout" value="60"/>
<property name="jdbcInterceptors" value="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"/>
</bean>
</property>
</bean>
<bean id="multipleDataSource" class="com.alifi.uums.db.MultipleDataSource">
<property name="defaultTargetDataSource" ref="dataSource-master"/>
<property name="targetDataSources">
<map>
<entry key="dataSource-master" value-ref="dataSource-master"/>
<entry key="dataSource-slave" value-ref="dataSource-slave"/>
</map>
</property>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<property name="dataSource" ref="multipleDataSource"/>
<property value="classpath:system-mybatis-conf.xml" name="configLocation" />
</bean>
5、DAO层直接使用注解Select、Update操作数据库
/**
* 根据id查找用户
*
* @param user
* @return user
*/
@Select("select * from piao_user where id= #{id}")
User getUser(String id);
/**
* 更新用户信息
*
* @param user
* @return user
*/
@Update("update from piao_user set nickname=’张三’ where id= #{id}")
User getUser(String id);
