最近项目要支持读写分离, 网上找了很多,但都是不太完整,我自己整理了下供大家参考。
我的项目使用的框架: springMvc+spring+hibernate+springJPA+maven, 数据库连接池用阿里的druid。
1. 新建一个DynamicDataSource类, 继承spring的AbstractRoutingDataSource 类,
并重写determineCurrentLookupKey()方法,如下:
package com.dataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 数据源动态切换类
*
* Copyright: Copyright (c) 2015-3-9 下午3:15:19
*
* Company:
*
*
* @author [email protected]
* @version 1.0.0
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceSwitcher.getDataSource();
}
}
2. 新建DataSourceSwitcher类, 实现数据源选择:
package com.dataSource;
import org.springframework.util.Assert;
/**
* 数据源选择类
*
* Copyright: Copyright (c) 2015-3-9 下午3:14:55
*
* Company:
*
*
* @author [email protected]
* @version 1.0.0
*/
public class DataSourceSwitcher {
private static final ThreadLocal contextHolder = new ThreadLocal();
/** 主库(写库) **/
public static final String MASTER_DATA_SOURCE = "master";
/** 从库(读库) **/
public static final String SLAVE_DATA_SOURCE = "slave";
public static void setDataSource(String dataSource) {
Assert.notNull(dataSource, "dataSource cannot be null");
contextHolder.set(dataSource);
}
public static void setMaster() {
clearDataSource();
}
public static void setSlave() {
setDataSource(SLAVE_DATA_SOURCE);
}
public static String getDataSource() {
return (String) contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
3. 新建切面类, 判断如果调用方法以query,list,get方法开头的, 切换为查询库:
package com.dataSource;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
/**
* 配置AOP切面类,动态切换读/写数据库。
*
* Copyright: Copyright (c) 2015-3-9 下午3:16:51
*
* Company:
*
*
* @author [email protected]
* @version 1.0.0
*/
public class DataSourceAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
private static final Logger log = Logger.getLogger(DataSourceAdvice.class);
private String logInfo;
// 需要切换到从库(读库)的方法名前缀
private List slaveMethods;
/**
* service方法执行之前被调用.
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
logInfo = String.format("before切入点:%s-->%s(),切换到:", target.getClass().getName(), method.getName());
String methodName = method.getName();
boolean hasSwitchedSlave = false;
for (String slaveMethod : slaveMethods) {
if (methodName.startsWith(slaveMethod)) {
if (log.isDebugEnabled()) {
log.debug(logInfo + DataSourceSwitcher.SLAVE_DATA_SOURCE);
}
hasSwitchedSlave = true;
DataSourceSwitcher.setSlave();
break;
}
}
if (!hasSwitchedSlave) {
if (log.isDebugEnabled()) {
log.debug(logInfo + "切换到:" + DataSourceSwitcher.MASTER_DATA_SOURCE);
}
DataSourceSwitcher.setMaster();
}
}
/**
* service方法执行完之后被调用.
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
}
/**
* 抛出Exception之后被调用。
*
* @param method
* @param args
* @param target
* @param ex
* @throws Throwable
*/
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable {
logInfo = String.format("after throwing:%s类中%s方法,", target.getClass().getName(), method.getName());
log.error(logInfo + "发生异常:" + ex.getMessage() + ",切换到:" + DataSourceSwitcher.SLAVE_DATA_SOURCE);
DataSourceSwitcher.setSlave();
}
public List getSlaveMethods() {
return slaveMethods;
}
public void setSlaveMethods(List slaveMethods) {
this.slaveMethods = slaveMethods;
}
}
4. spring配置文件中, 配置数据源:
5. AOP配置(注意, 可以在service层切换(推荐), 也可以在控制器层切换):
截图为在控制层切换配置, 附件中的context.xml中给的是在service/dao层切换的配置方式。
6. mvc层支持aop(如果aop切换配置在控制层, 则需要配置这步):
7. 特殊类可以硬编码:
切换到主库 : DataSourceSwitcher.setMaster();
比如 , 登录拦截器RoleGenInterceptor.java --> genLocalSession()
附件下载地址