切数据源
当数据库需要使用读写分离的时候,需要根据读或者写方法切不同的数据源进行操作。
使用的框架:Spring+mybatis
数据源管理:DruidDataSource
基础配置文件
spring配置
数据源配置
数据源管理的数据库的连接,主要作用需要获取数据库连接,配置数据库的地址,用户名,密码等
<bean id="abstractDataSource" abstract="true" class="com.alibaba.druid.pool.DruidDataSource">
<!-- <bean id="abstractDataSource" abstract="true" class="com.datasource.dbPool.MyDataSourcePool">-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--最大连接池数量-->
<property name="maxActive" value="15"/>
<!-- 最小连接池数量-->
<property name="minIdle" value="2"/>
<!--连接池初始化数量 add-->
<property name="initialSize" value="2"/>
<!--获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,需手动置为非公平锁-->
<property name="maxWait" value="60000"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="180000"/>
<property name="validationQuery" value="SELECT 'x'"/>
<!--建议配置为true,不影响性能,并且保证安全性。
申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,
执行validationQuery检测连接是否有效。-->
<property name="testWhileIdle" value="true"/>
<!--申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。-->
<property name="testOnBorrow" value="false"/>
<!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能-->
<property name="testOnReturn" value="false"/>
<!--是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。-->
<property name="poolPreparedStatements" value="false"/>
<!--要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true>-->
<property name="maxPoolPreparedStatementPerConnectionSize" value="-1"/>
<!-- 配置监控统计拦截的filters -->
<property name="proxyFilters">
<list>
<bean class="com.alibaba.druid.filter.stat.StatFilter">
<property name="slowSqlMillis" value="500"/>
<property name="logSlowSql" value="true"/>
</bean>
</list>
</property>
<!--配置了maxWait druid默认使用公平锁,手动设置使用非公平锁-->
<property name="useUnfairLock" value="true"/>
<!--可以看到未关闭连接的具体堆栈信息,方便查看链接泄露-->
<property name="removeAbandoned" value="true"/>
</bean>
<!-- 主库数据源 -->
<bean id="masterDataSource" parent="abstractDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8"/>
<property name="username" value="admin"/>
<property name="password" value="passw"/>
<!--数据源名字-->
<property name="name" value="master"/>
</bean>
<!-- 从库数据源 -->
<bean id="slaveDataSource" parent="abstractDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8"/>
<property name="username" value="admin"/>
<property name="password" value="passw"/>
<!--数据源名字-->
<property name="name" value="slave"/>
</bean>
数据源路由
数据源配置了多个,选择数据源 1 是可以使用模版写死 使用哪个数据源,还有一种就是进行路由,Spring提供了一个路由基础类:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
public class MultipleDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 这个方法里可以用注解动态解析吗 如果是null 返回默认的数据源
// return String.valueOf(DataSourceType.MASTER);
return String.valueOf(DataSourceTypeManager.getDataSourceType());
}
}
DataSourceTypeManager 是管理数据源的一个类,选择数据源的时候,会向DataSourceTypeManager 中设置数据源
mybatis配置
mybatis 需要配置扫描的包,sqlSessionFactory需要设置数据源
<!-- 该包下的类支持注解,表示会被当作{@code mybatis mapper}处理 配置了之后表示可以自动引入mapper类-->
<mybatis:scan base-package="com.jd.sep.dao" />
<!-- mybatis配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-configure.xml" />
</bean>
dao 层编写
数据源切换
选择使用方法上的注解,根据不同的注解解析选择出不同的数据源
加上数据源的注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface DB {
String name() default "master";
}
注解解析,设置数据源
这块的逻辑直接写在了拦截器里边,若优化,可以将逻辑提出
@Aspect
@Component
public class DBInterceptor {
public void setDb(JoinPoint jp){
// System.out.println("-["+jp.getTarget().getClass().getName() + "]设置数据源:" + DataSourceType.MASTER);
MethodSignature methodSignature = (MethodSignature) jp.getSignature();
Method method = methodSignature.getMethod();
//如果有使用Db的注解
if(Stream.of(method.getDeclaredAnnotations()).anyMatch(e->e.annotationType().equals(DB.class))){
//获取注解参数
DB db = method.getAnnotation(DB.class);
String dbName = db.name();
// System.out.println(method.getName()+"---->设置数据源:"+ JSON.toJSONString(DataSourceType.valueOf(dbName)));
// 设置数据源
DataSourceTypeManager.setDataSourceType(DataSourceType.valueOf(dbName));
}
}
}
DataSourceTypeManager:
public class DataSourceTypeManager {
private static final ThreadLocal<DataSourceType> dataSourceHolder = new NamedThreadLocal("dataSourceType") {
@Override
protected DataSourceType initialValue() {
return DataSourceType.MASTER;
}
};
public static DataSourceType getDataSourceType() {
return dataSourceHolder.get();
}
public static void setDataSourceType(DataSourceType dataSourceType) {
dataSourceHolder.set(dataSourceType);
}
public static void remove() {
dataSourceHolder.remove();
}
}
aop拦截器配置
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- To enable @AspectJ support with XML based configuration -->
<aop:aspectj-autoproxy />
<bean id="dataSourceInterceptor" class="com.datasource.interceptor.DBInterceptor" />
<aop:config>
<aop:pointcut id="managerLayer" expression="execution(* com.manager.impl.*.*(..))" />
<aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor">
<aop:before method="setDb" pointcut-ref="managerLayer" />
</aop:aspect>
</aop:config>
</beans>
使用
public interface TTableManager {
@DB(name = "SLAVE")
List<TTable> getTList(TTable tTable);
@DB(name = "MASTER")
int addT(TTable tTable);
}