**随着业务量的增大,频繁的读写操作对数据库造成很大压力。一种方式是在应用层和数据库层增加缓存来缓解对数据库的压力;另可使用读写分离的方式使应用对数据库的压力降低。
有两种方式可以实现读写分离:1.应用层实现。2.借助数据库中间件实现。**
使用Spring实现数据库读写分离:
原理,所有的读操作从库;非读操作主库。
具体实现(一主一从,基于Spring Aop):
1.自定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 使用指定的数据源,请标记到controller的public方法上
*
* @project risk-control-dynamic-datasource
* @author
* @version 1.0
* @date 2017年11月24日 上午10:54:32
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
2.切面
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import com.xxx.riskcontrol.datasource.DynamicDataSourceHolder;
import com.xxx.riskcontrol.datasource.annotation.DataSource;
/**
* 动态数据源切面
*
* @project risk-control-dynamic-datasource
* @author
* @version 1.0
* @date 2017年11月24日 上午10:57:18
*/
public class DataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
@Autowired
private RedisTemplate redisTemplate;
/**
* 在controller公共方法前执行,设置使用哪个数据源
*
* @author
* @version 1.0
* @date 2017年11月29日 下午6:12:47
* @param point void
*/
public void before(JoinPoint point) {
try {
Method m = getMethod(point);
String methodName = getMethodFlag(m);
log(methodName, "before前");
if(DynamicDataSourceHolder.getMethodFlag() == null){
DynamicDataSourceHolder.putMethodFlag(methodName);
String flag = null;
try {
flag = (String) redisTemplate.opsForValue().get("rwSwitch");
} catch(Throwable e) {
logger.error("redis异常", e);
}
if(flag != null){
if (m != null && m.isAnnotationPresent(DataSource.class) && Modifier.isPublic(m.getModifiers())) {
DataSource data = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.putDataSource(data.value());
log(methodName, "before后");
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 在controller公共方法后执行,清理掉数据源标识(tomcat容器创建的线程会重复使用)
*
* @author
* @version 1.0
* @date 2017年11月29日 下午6:13:17
* @param point void
*/
public void after(JoinPoint point) {
try{
Method m = getMethod(point);
String methodName = getMethodFlag(m);
log(methodName, "after前");
//由于线程会重复使用,需清理上次操作遗留的值
if(methodName.equals(DynamicDataSourceHolder.getMethodFlag())){
DynamicDataSourceHolder.putDataSource(null);
DynamicDataSourceHolder.putMethodFlag(null);
}
log(methodName, "after后");
}catch (Exception e){
throw new RuntimeException(e);
}
}
private void log(String methodName, String prefix) {
if(logger.isDebugEnabled()) {
logger.debug(prefix + "=====================getDataSourceKey===================================>>>>>>{},{},{}", methodName, DynamicDataSourceHolder.getDataSouce(), DynamicDataSourceHolder.getMethodFlag());
}
}
/**
* 获取方法
*
* @author
* @version 1.0
* @date 2017年12月13日 上午10:34:56
* @param point
* @return
* @throws NoSuchMethodException
* @throws SecurityException Method
*/
private Method getMethod(JoinPoint point) throws NoSuchMethodException, SecurityException {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();
return target.getClass().getMethod(method, parameterTypes);
}
/**
* 获取类名方法名字符串
*
* @author
* @version 1.0
* @date 2017年12月13日 上午10:21:48
* @param m
* @return String
*/
private String getMethodFlag(Method m) {
return m.getDeclaringClass().getName() + "." + m.getName();
}
}
3.数据源标识
/**
* 数据源标识操作类
*
* @project risk-control-dynamic-datasource
* @author
* @version 1.0
* @date 2017年11月24日 上午10:55:44
*/
public class DynamicDataSourceHolder {
/**
* 数据源标识
*/
public static final ThreadLocal<String> threadLocal = new ThreadLocal<String>();
/**
* 访问controller的第一个方法(使用第一个方法上的数据源标识)
*/
public static final ThreadLocal<String> methodFlagThreadLocal = new ThreadLocal<String>();
public static void putDataSource(String name) {
threadLocal.set(name);
}
public static String getDataSouce() {
return threadLocal.get();
}
public static void putMethodFlag(String name){
methodFlagThreadLocal.set(name);
}
public static String getMethodFlag(){
return methodFlagThreadLocal.get();
}
}
4.动态数据源
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源
*
* @project risk-control-dynamic-datasource
* @author
* @version 1.0
* @date 2017年11月24日 上午10:53:50
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
String value = DynamicDataSourceHolder.getDataSouce();
if(logger.isDebugEnabled()) {
logger.debug("===========================getDataSourceKey===================================>>>>>>\tdetermineCurrentLookupKey\t" + value);
}
return value;
}
}
5.aop配置
<bean id="manyDataSourceAspect" class="com.xxx.riskcontrol.datasource.aspect.DataSourceAspect"/>
<aop:config>
<aop:aspect id="c" ref="manyDataSourceAspect">
<aop:pointcut id="tx" expression="execution(* com.XXX..*.controller.*.*(..))"/>
<aop:before pointcut-ref="tx" method="before"/>
<aop:after pointcut-ref="tx" method="after"/>
</aop:aspect>
</aop:config>
6.配置动态数据源
<bean id="dataSource" class="com.xxx.riskcontrol.datasource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- write database -->
<entry key="master" value-ref="masterDataSource"/>
<!-- read database -->
<entry key="slave" value-ref="slaveDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>
7.controller示例
//使用从库读数据
@DataSource(DynamicDatasourceConstant.SLAVE_DATABASE_FLAG)
@RequestMapping(value ="/toApplyInfoList")
public ModelAndView toapplyInfoList(){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("applyInfo/applyInfoList");
return modelAndView;
}