在某一次断点中发现SqlSession
中有区分sql是属于写操作还是读操作,因此我打算通过自定义SqlSession来实现读写分离。
简单介绍
- 需要实现
SqlSessionFactoryBuilder
,可以通过这个工厂构建器写入对应的数据源.。 - 实现
SqlSessionFactory
,用于创建自定义SqlSession - 实现
Executor
,用于按需打开对应的数据库连接 - 实现
SqlSession
,用于切换读写数据源
写数据源对应方法:
insert、delete、update
读数据源对应方法:
selectOne、selectList、selectMap、selectCursor、select
使用SqlSession读写分离配置
@Bean(name = "writeDruidDataSource", destroyMethod = "close")
public DruidDataSource writeDruidDataSource() throws SQLException {
// 写数据源配置
return dataSource;
}
@Bean(name = "readDruidDataSource", destroyMethod = "close")
public DruidDataSource readDruidDataSource() throws SQLException {
// 读数据源配置
return dataSource;
}
@Bean("sqlSessionFactoryBuilder")
public VirSqlSessionFactoryBuilder sqlSessionFactoryBuilder() throws SQLException {
VirSqlSessionFactoryBuilder sessionFactoryBuilder = new VirSqlSessionFactoryBuilder();
sessionFactoryBuilder.setWriteDataSource(writeDruidDataSource());
sessionFactoryBuilder.setReadDataSource(readDruidDataSource());
return sessionFactoryBuilder;
}
/**
* MyBatis配置 :配置sqlSessionFactory
*
* @param sqlSessionFactoryBuilder
* @return
* @throws Exception
*/
@Bean("sqlSessionFactoryBean")
public SqlSessionFactoryBean sqlSessionFactoryBean(@Qualifier("writeDruidDataSource") DruidDataSource druidDataSource) throws Exception {
// 分页拦截器
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
properties.put("helperDialect", "mysql");
interceptor.setProperties(properties);
// 创建SqlSession工厂
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setMapperLocations(ResourceUtil.getResources("classpath:mysql/**/*.xml"));
sqlSessionFactoryBean.setSqlSessionFactoryBuilder(sqlSessionFactoryBuilder());
sqlSessionFactoryBean.setPlugins(new Interceptor[] { interceptor });
sqlSessionFactoryBean.setDataSource(druidDataSource);
return sqlSessionFactoryBean;
}
Executor自定义的实现
package cn.virens.web.components.mybatis;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
/**
* Executor 的实现类
*
* @author virens
*/
public class VirExecutor implements Executor {
private final TransactionFactory transactionFactory;
private final TransactionIsolationLevel level;
private final Configuration configuration;
private final ExecutorType executorType;
private final DataSource dataSource;
private final boolean autoCommit;
private Transaction transaction;
private Executor executor;
public VirExecutor(TransactionFactory transactionFactory, Configuration configuration, DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit, ExecutorType executorType) {
this.transactionFactory = transactionFactory;
this.configuration = configuration;
this.executorType = executorType;
this.autoCommit = autoCommit;
this.dataSource = dataSource;
this.level = level;
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
return executor().update(ms, parameter);
}
@Override
@SuppressWarnings("rawtypes")
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
return executor().query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
@Override
@SuppressWarnings("rawtypes")
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
return executor().query(ms, parameter, rowBounds, resultHandler);
}
@Override
public Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
return executor().queryCursor(ms, parameter, rowBounds);
}
@Override
public List flushStatements() throws SQLException {
return executor().flushStatements();
}
@Override
public void commit(boolean required) throws SQLException {
executor().commit(required);
}
@Override
public void rollback(boolean required) throws SQLException {
executor().rollback(required);
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return executor().createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return executor().isCached(ms, key);
}
@Override
public void clearLocalCache() {
executor().clearLocalCache();
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class> targetType) {
executor().deferLoad(ms, resultObject, property, key, targetType);
}
@Override
public Transaction getTransaction() {
return executor().getTransaction();
}
@Override
public void close(boolean forceRollback) {
executor().close(forceRollback);
}
@Override
public boolean isClosed() {
return executor().isClosed();
}
@Override
public void setExecutorWrapper(Executor executor) {
executor().setExecutorWrapper(executor);
}
public void closeTransaction() {
if (transaction == null) return;
try {
transaction.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
private Executor executor() {
if (transaction == null || executor == null) {
this.transaction = transactionFactory.newTransaction(dataSource, level, autoCommit);
this.executor = configuration.newExecutor(transaction, executorType);
}
return executor;
}
}
SqlSession自定义的实现
package cn.virens.web.components.mybatis;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import cn.virens.exception.APIException;
/**
* SqlSession 的实现类
*
* @author virens
*/
public class VirSqlSession implements SqlSession {
private final DefaultSqlSession writeSqlSession;
private final DefaultSqlSession readSqlSession;
public VirSqlSession(Configuration configuration, Executor readExecutor, Executor writeExecutor, boolean autoCommit) {
this.writeSqlSession = new DefaultSqlSession(configuration, writeExecutor, autoCommit);
this.readSqlSession = new DefaultSqlSession(configuration, readExecutor, autoCommit);
}
@Override
public int insert(String statement) {
return writeSqlSession.insert(statement);
}
@Override
public int insert(String statement, Object parameter) {
return writeSqlSession.insert(statement, parameter);
}
@Override
public int delete(String statement) {
return writeSqlSession.delete(statement);
}
@Override
public int delete(String statement, Object parameter) {
return writeSqlSession.delete(statement, parameter);
}
@Override
public int update(String statement) {
return writeSqlSession.update(statement);
}
@Override
public int update(String statement, Object parameter) {
return writeSqlSession.update(statement, parameter);
}
@Override
public T selectOne(String statement) {
return readSqlSession.selectOne(statement);
}
@Override
public T selectOne(String statement, Object parameter) {
return readSqlSession.selectOne(statement, parameter);
}
@Override
public List selectList(String statement) {
return readSqlSession.selectList(statement);
}
@Override
public List selectList(String statement, Object parameter) {
return readSqlSession.selectList(statement, parameter);
}
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
return readSqlSession.selectList(statement, parameter, rowBounds);
}
@Override
public Map selectMap(String statement, String mapKey) {
return readSqlSession.selectMap(statement, mapKey);
}
@Override
public Map selectMap(String statement, Object parameter, String mapKey) {
return readSqlSession.selectMap(statement, parameter, mapKey);
}
@Override
public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return readSqlSession.selectMap(statement, parameter, mapKey, rowBounds);
}
@Override
public Cursor selectCursor(String statement) {
return readSqlSession.selectCursor(statement);
}
@Override
public Cursor selectCursor(String statement, Object parameter) {
return readSqlSession.selectCursor(statement, parameter);
}
@Override
public Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds) {
return readSqlSession.selectCursor(statement, parameter, rowBounds);
}
@Override
@SuppressWarnings("rawtypes")
public void select(String statement, Object parameter, ResultHandler handler) {
readSqlSession.select(statement, parameter, handler);
}
@Override
@SuppressWarnings("rawtypes")
public void select(String statement, ResultHandler handler) {
readSqlSession.select(statement, handler);
}
@Override
@SuppressWarnings("rawtypes")
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
readSqlSession.select(statement, parameter, rowBounds, handler);
}
@Override
public void close() {
writeSqlSession.close();
readSqlSession.close();
}
@Override
public void commit() {
this.commit(false);
}
@Override
public void commit(boolean force) {
writeSqlSession.commit(force);
readSqlSession.commit(force);
}
@Override
public void rollback() {
this.rollback(false);
}
@Override
public void rollback(boolean force) {
writeSqlSession.rollback(force);
readSqlSession.rollback(force);
}
@Override
public void clearCache() {
writeSqlSession.clearCache();
readSqlSession.clearCache();
}
@Override
public List flushStatements() {
List answer = new ArrayList<>();
answer.addAll(writeSqlSession.flushStatements());
answer.addAll(readSqlSession.flushStatements());
return answer;
}
@Override
public Configuration getConfiguration() {
return readSqlSession.getConfiguration();
}
@Override
public T getMapper(Class type) {
return readSqlSession.getMapper(type);
}
@Override
public Connection getConnection() {
throw new APIException("ERROR", "多数据源,禁止获取连接");
}
}
SqlSessionFactory自定义的实现
package cn.virens.web.components.mybatis;
import java.sql.Connection;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
import cn.virens.exception.APIException;
/**
* SqlSessionFactory 的实现类
*
* @author virens
*/
public class VirSqlSessionFactory implements SqlSessionFactory {
private final VirSqlSessionFactoryBuilder sessionFactory;
private final Configuration configuration;
public VirSqlSessionFactory(Configuration configuration, VirSqlSessionFactoryBuilder sessionFactory) {
this.sessionFactory = sessionFactory;
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
VirExecutor readExecutor = null;
VirExecutor writeExecutor = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
readExecutor = new VirExecutor(transactionFactory, configuration, sessionFactory.getReadDataSource(), level, autoCommit, execType);
writeExecutor = new VirExecutor(transactionFactory, configuration, sessionFactory.getWriteDataSource(), level, autoCommit, execType);
return new VirSqlSession(configuration, readExecutor, writeExecutor, autoCommit);
} catch (Exception e) {
writeExecutor.closeTransaction();
readExecutor.closeTransaction();
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
throw new APIException("ERROR", "读写分离,无法判断类型,不支持根据连接获取");
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment != null && environment.getTransactionFactory() != null) {
return environment.getTransactionFactory();
} else {
return new ManagedTransactionFactory();
}
}
}
SqlSessionFactoryBuilder自定义的实现
package cn.virens.web.components.mybatis;
import javax.sql.DataSource;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
/**
* SqlSessionFactoryBuilder 的实现类
*
* @author virens
*/
public class VirSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder {
private DataSource writeDataSource;
private DataSource readDataSource;
public DataSource getWriteDataSource() {
return writeDataSource;
}
public void setWriteDataSource(DataSource writeDataSource) {
this.writeDataSource = writeDataSource;
}
public DataSource getReadDataSource() {
return readDataSource;
}
public void setReadDataSource(DataSource readdataSource) {
this.readDataSource = readdataSource;
}
@Override
public SqlSessionFactory build(Configuration config) {
return new VirSqlSessionFactory(config, this);
}
}