ssm搭建 略
需要三个java文件
1.DynamicDataSource.java
package com.sykj.plugin;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.ReflectionUtils;
/**
* 动态数据源设置
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private Integer slaveCount;
// 轮询计数,初始为-1,AtomicInteger是线程安全的
private AtomicInteger counter = new AtomicInteger(-1);
// 记录读库的key
private List<Object> slaveDataSources = new ArrayList<Object>(0);
@Override
protected Object determineCurrentLookupKey() {
//如果使用主库,则直接返回
if (DynamicDataSourceHolder.isMaster()) {
return DynamicDataSourceHolder.getDbType();
}
//如果不是主库进行轮询选择从库
return getSlaveKey();
}
/**
* 该方法会在Spring Bean 加载初始化的时候执行,功能和 bean 标签的属性 init-method 一样
* 把所有的slave库放到slaveDataSources里
*/
@SuppressWarnings("unchecked")
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
// 由于父类的resolvedDataSources属性是私有的子类获取不到,需要使用反射获取
Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources");
field.setAccessible(true); // 设置可访问
try {
Map<Object, DataSource> resolvedDataSources = (Map<Object, DataSource>) field.get(this);
// 读库的数据量等于数据源总数减去写库的数量
this.slaveCount = resolvedDataSources.size() - 1;
for (Map.Entry<Object, DataSource> entry : resolvedDataSources.entrySet()) {
if (DynamicDataSourceHolder.DB_MASTER.equals(entry.getKey())) {
continue;
}
slaveDataSources.add(entry.getKey());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 轮询算法实现
*
* @return
*/
public Object getSlaveKey() {
// 得到的下标为:0、1、2、3……
Integer index = counter.incrementAndGet() % slaveCount;
if (counter.get() > 9999) { // 以免超出Integer范围
counter.set(-1); // 还原
}
return slaveDataSources.get(index);
}
}
2.DynamicDataSourceHolder.java
package com.sykj.plugin;
/**
* 动态数据源类持有者,线程安全
* 持有动态数据源的信息,以及修改清理数据源
*/
public class DynamicDataSourceHolder {
//线程安全
private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static final String DB_MASTER = "master";
public static final String DB_SLAVE = "slave";
/**
* 0
* 获取线程的dbType
*
* @return
*/
public static String getDbType() {
String db = contextHolder.get();
if (db == null)
db = DB_MASTER;
System.out.println("所使用的数据源为:" + db);
return db;
}
/**
* 设置线程的dbType
*
* @param datasource 数据源类型
*/
public static void setDbType(String datasource) {
contextHolder.set(datasource);
}
/**
* 清理连接类型
*/
public static void clearDbType() {
contextHolder.remove();
}
//判断是否是使用主库,提高部分使用
public static boolean isMaster() {
return DB_MASTER.equals(getDbType());
}
}
3.DynamicDataSourceInterceptor.java
package com.sykj.plugin;
import java.util.Locale;
import java.util.Properties;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* 对mybatis中操作进行拦截
* 增删改使用master,查询使用slave
*/
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class DynamicDataSourceInterceptor implements Interceptor {
//sql匹配规则
private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
/**
* 进行拦截操作,增删改和事务操作使用master,查询使用slave,里面具体的一下实现代码,感兴趣可以学习mybatis源码去理解
*你也可以根据自己的实际业务逻辑去控制
* @param invocation
* @return
* @throws Throwable
*/
public Object intercept(Invocation invocation) throws Throwable {
//是否是用事务管理
boolean syschronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
Object[] objects = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) objects[0];
String lookupKey = DynamicDataSourceHolder.DB_MASTER;
if (syschronizationActive != true) {
//读方法
if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
//如果selectKey为自增id查询主键(SELECT LAST INSERT_ID)方法,使用主库
if (mappedStatement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
lookupKey = DynamicDataSourceHolder.DB_MASTER;
} else {
BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(objects[1]);
String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
if (sql.matches(REGEX)) {
lookupKey = DynamicDataSourceHolder.DB_MASTER;
} else {
lookupKey = DynamicDataSourceHolder.DB_SLAVE;
}
}
}
} else {
lookupKey = DynamicDataSourceHolder.DB_MASTER;
}
System.out.println("设置方法:"+mappedStatement.getId()+"; use:"+lookupKey+"; Strategy,SqlCommanType:"+mappedStatement.getSqlCommandType().name());
DynamicDataSourceHolder.setDbType(lookupKey);
return invocation.proceed();
}
/**
* 设置拦截对象
* Executor在mybatis中是用来增删改查的,进行拦截
*
* @param target 拦截的对象
* @return
*/
public Object plugin(Object target) {
if (target instanceof Executor)
return Plugin.wrap(target, this);
else
return target;
}
public void setProperties(Properties properties) {}
}
最后修改spring配置文件,添加多个数据源
<!-- 抽象数据源,提供继承使用 -->
<bean id="abstractDataSource" abstract="true" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="10" />
<property name="minIdle" value="10" />
<property name="maxActive" value="50" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="60000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 如果用Oracle,则把poolPreparedStatements配置为true,mysql可以配置为false。 -->
<property name="poolPreparedStatements" value="false" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="20" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="wall,stat" />
</bean>
<bean id="master" parent="abstractDataSource">
<!-- 基本属性 url、user、password -->
<property name="driverClassName" value="${jdbc_driverClassName}" />
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
</bean>
<bean id="slave" parent="abstractDataSource">
<!-- 基本属性 url、user、password -->
<property name="driverClassName" value="${jdbc_driverClassName}" />
<property name="url" value="${jdbc_slave_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" />
</bean>
<!-- 设置我们编写的动态数据源 -->
<bean id="dynamicDataSource" class="com.sykj.plugin.DynamicDataSource">
<property name="targetDataSources">
<map>
<entry value-ref="master" key="master"></entry>
<entry value-ref="slave" key="slave"></entry>
</map>
</property>
</bean>
<!-- 如果我们的数据源是使用了 LazyConnectionDataSourceProxy 则在执行 Connection#prepareStatement
之前,spring 是不会向数据库连接池获取数据库链接的,以防在我们拦截sql判断数据源的同时,他拿到数据源,拿到了就不能更改了 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<property name="targetDataSource">
<ref bean="dynamicDataSource"></ref>
</property>
</bean>
添加mybatis插件
<!-- spring和MyBatisPlus完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.sykj.bean" />
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/sykj/mapper/*.xml"></property>
<!-- <property name="configLocation" value="classpath:mybatis-config.xml"></property> -->
<property name="plugins">
<array>
<!-- 注册分页插件 -->
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
helperDialect=mysql
</value>
</property>
</bean>
<bean class="com.sykj.plugin.DynamicDataSourceInterceptor"></bean>
</array>
</property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 ,自动扫描了所有的XxxxMapper.xml对应的mapper接口文件,只要Mapper接口类和Mapper映射文件对应起来就可以了 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.sykj.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
大功告成