前几天完成了mysql搭建一个主从复制,现在用搭建好的环境来springk来实现读写分离
参考
- 1.首先看spring是如何得到连接的
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
//可看出,实现其determineCurrentLookupKey() 方法,该方法返回Map的key,从而来调用不同的数据源
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
- 2.自定义一个数据源类DynamicDataSource用他来继承spring中提供的
AbstractRoutingDataSource,中实现determineCurrentLookupKey
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* spring提供的AbstractRoutingDataSource类中每次与数据库连接时都会调用determineCurrentLookupKey()
* 来得到对应数据源的key值
*由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成
*/
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
// 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
return DynamicDataSourceHolder.getDataSourceKey();
}
}
/**
* 使用ThreadLocal技术来记录当前线程中的数据源的key
*/
public class DynamicDataSourceHolder {
//使用ThreadLocal保证线程安全
private static final ThreadLocal<String> holder=new ThreadLocal<String>();
public static void setDataSourceKkey(String key){
holder.set(key);
}
public static String getDataSourceKey(){
return holder.get();
}
}
- 3.通过自定义注解来标识方法,看改调用那个数据源
/**
* 对每个mapper接口中的方法进行标记;例:@DataSource("master"或是"slave")
* 在调用该方法是通过aop切入选择数据源
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
- 4.配置一个切面来在对要切入的方法进行处理
@Aspect
@Service
public class DataSourceAspect {
/**
* 通过在调用对应的接口时,通过aop切入来选择对应的数据源
* @param point
*/
//@Before("execution(* com.itarget.dao.mapper.*.*(..)))")
public void before(JoinPoint point){
//得到代理对象
Object target=point.getTarget();
//等到切入时的方面名
String method=point.getSignature().getName();
//等到代理对象实现的接口类
Class<?>[] classes=target.getClass().getInterfaces();
Class<?>[] parameterTypes=((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
try {
//通过通过方法名,和参数类型来得到方法
Method m=classes[0].getMethod(method, parameterTypes);
if(m!=null&&m.isAnnotationPresent(DataSource.class)){
DataSource dataSource=m.getAnnotation(DataSource.class);
//通过得到注解来的等到传入的value值是(master或slave)
DynamicDataSourceHolder.setDataSourceKkey(dataSource.value());
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
- 5.在配置文件中配置
<!-- 配置master数据源 -->
<bean name="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${master.url}" />
<property name="username" value="${master.userName}" />
<property name="password" value="${master.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="2" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="500" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="2" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<!-- 缓存PreparedStatements,也就是PSCache,支持游标的数据库才有用 如oracle mysql5.5以上 -->
<property name="poolPreparedStatements" value="true"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 对长时间不使用的连接进行关闭 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<!-- <property name="filters" value="stat" /> -->
<property name="filters" value="mergeStat" />
</bean>
<!-- 配置slave数据源 -->
<bean name="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${slave.url}" />
<property name="username" value="${slave.userName}" />
<property name="password" value="${slave.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="2" />
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value="500" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="2" />
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="60000" />
<!-- 缓存PreparedStatements,也就是PSCache,支持游标的数据库才有用 如oracle mysql5.5以上 -->
<property name="poolPreparedStatements" value="true"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="25200000" />
<!-- 对长时间不使用的连接进行关闭 -->
<property name="removeAbandoned" value="true" />
<!-- 1800秒,也就是30分钟 -->
<property name="removeAbandonedTimeout" value="1800" />
<!-- 关闭abanded连接时输出错误日志 -->
<property name="logAbandoned" value="true" />
<!-- 监控数据库 -->
<!-- <property name="filters" value="stat" /> -->
<property name="filters" value="mergeStat" />
</bean>
<!--根据自己的设定的数据源类来配置数据源-->
<bean id="dataSource" class="com.system.dateSource.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!--这里设置的key应该与你用@DataSource注解标记时传入的value值保持一致-->
<entry key="master" value-ref="masterDataSource"></entry>
<entry key="slave" value-ref="slaveDataSource"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>
<!--配置切面-->
<!--自动为spring容器中那些配置@aspectJ切面的bean创建代理-- <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<bean id="manyDataSourceAspect"class="com.system.dateSource.aspect.DataSourceAspect"/>
<aop:config>
<aop:aspect id="b" ref="manyDataSourceAspect">
<!--配置切点配置exection中第一*代表返回值,第二个*代表所有子包*所有方法-->
<aop:pointcut id="pc" expression="execution(* com.itarget.dao.mapper.*.*(..))"/>
<aop:before method="before" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>