Spring boot+mybatis読み書き分離
spring boot + mybatis read/write splitting
参照先:https://blog.csdn.net/wuyongde_0922/article/details/70655185シナリオ4
全体的な考え方:AbstractRoutingDataSource+mybatis@Intercepts+DataSourceTransactionManager
主体の判断論理はいずれも上記の博文から来ており,ここではspringboot環境における構成方式のみを説明する.
バージョンの使用: spring boot:2.0.2.RELEASE mybatis.starter:1.3.1 druid:1.1.9 mysql:5.7
1.druidを使用した2つのデータ・ソースの構成
# application.yml(mybatis構成)
#アプリケーション-dev.yml(データソース構成)
コンピュータにmysqlが1つしかないため、テストを容易にするために、ここでは2つのデータベースシミュレーションを使用します.
# DynamicDataSourceConfig
2.直接データソースとしてAbstractRoutingDataSourceを構成する
# DynamicDataSourceConfig
#DynamicDataSource(AbstractRoutingDataSourceを継承し、割当てロジックを実現)
# DynamicDataSourceGlobal
# DynamicDataSourceHolder
3.mybatisのinterceptブロッキング読み書き操作を構成し、SqlCommandTypeに従って読み書きを区別し、データソースを割り当てる
# DynamicPlugin (intercept)
ここで注意しなければならないのはqueryメソッドのブロックです.Executorではqueryメソッドは2つあります.
クエリー文として注釈とxmlを同時に使用しているため、ここでは両方を構成します.
#DynamicDataSourceConfig(InterceptをSqlSessionFactoryに転送)
4.トランザクション状態でのデータ・ソースの割当ての構成
#D y n a m i c DataSourceTransactionManager(カスタムDataSourceTransactionManager実装割当ロジック)
#D y n amicDataSourceConfig(transactionManagerの構成)
ここから読み書き分離の構成は終了しても、基本的にビジネスロジックに侵入することはなく、簡潔明瞭で、wuyongde 0922の共有に再び感謝します.
5.demoを書いてテストする
# TestMapper
#TestService(オープントランザクション@EnableTransactionManagement)
# TestMapperTest
読み取りライブラリにレコードを手動で挿入します(1,「123」)
# run test
読み書き分離構成完了
現在の構成に基づいて、複数のライブラリ、ライブラリなど、より多くの拡張を行うことができます.
参照先:https://blog.csdn.net/wuyongde_0922/article/details/70655185シナリオ4
全体的な考え方:AbstractRoutingDataSource+mybatis@Intercepts+DataSourceTransactionManager
主体の判断論理はいずれも上記の博文から来ており,ここではspringboot環境における構成方式のみを説明する.
バージョンの使用:
1.druidを使用した2つのデータ・ソースの構成
# application.yml(mybatis構成)
spring:
profiles:
#
active: dev
mybatis:
configuration:
#
map-underscore-to-camel-case: true
#
default-fetch-size: 100
# SQL
default-statement-timeout: 3000
#
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#アプリケーション-dev.yml(データソース構成)
write:
datasource:
username: root
password: 123123
url: jdbc:mysql://localhost:3306/db1?useSSL=false&useUnicode=true&characterEncoding=utf8
#
maxActive: 1000
#
initialSize: 100
# ,
maxWait: 60000
#
minIdle: 500
# ,
timeBetweenEvictionRunsMillis: 60000
# ,,
minEvictableIdleTimeMillis: 300000
validationQuery: select 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# PSCache, PSCache
poolPreparedStatements: true
maxOpenPreparedStatements: 20
# filters, sql ,'wall'
filters: stat, wall, slf4j
# DruidDataSource
useGlobalDataSourceStat: true
read:
datasource:
username: root
password: 123123
url: jdbc:mysql://localhost:3306/db2?useSSL=false&useUnicode=true&characterEncoding=utf8
#
maxActive: 1000
#
initialSize: 100
# ,
maxWait: 60000
#
minIdle: 500
# ,
timeBetweenEvictionRunsMillis: 60000
# ,,
minEvictableIdleTimeMillis: 300000
validationQuery: select 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# PSCache, PSCache
poolPreparedStatements: true
maxOpenPreparedStatements: 20
# filters, sql ,'wall'
filters: stat, wall, slf4j
# DruidDataSource
useGlobalDataSourceStat: true
コンピュータにmysqlが1つしかないため、テストを容易にするために、ここでは2つのデータベースシミュレーションを使用します.
# DynamicDataSourceConfig
@Bean(name = "writeDataSource")
@Primary//
@ConfigurationProperties(prefix = "write.datasource")
public DataSource writeDataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean(name = "readDataSource")
@ConfigurationProperties(prefix = "read.datasource")
public DataSource readDataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
2.直接データソースとしてAbstractRoutingDataSourceを構成する
# DynamicDataSourceConfig
@Bean(name = "dataSource")
public DynamicDataSource getDynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map
#DynamicDataSource(AbstractRoutingDataSourceを継承し、割当てロジックを実現)
public class DynamicDataSource extends AbstractRoutingDataSource {
// key
@Override
protected Object determineCurrentLookupKey() {
DynamicDataSourceGlobal dynamicDataSourceGlobal = DynamicDataSourceHolder.getDataSource();
if(dynamicDataSourceGlobal == null
|| dynamicDataSourceGlobal == DynamicDataSourceGlobal.WRITE) {
return DynamicDataSourceGlobal.WRITE.name();
}
return DynamicDataSourceGlobal.READ.name();
}
}
# DynamicDataSourceGlobal
public enum DynamicDataSourceGlobal {
READ, WRITE;
}
# DynamicDataSourceHolder
public final class DynamicDataSourceHolder {
private static final ThreadLocal holder = new ThreadLocal();
private DynamicDataSourceHolder() {}
public static void putDataSource(DynamicDataSourceGlobal dataSource) {
holder.set(dataSource);
}
public static DynamicDataSourceGlobal getDataSource() {
return holder.get();
}
public static void clearDataSource() {
holder.remove();
}
}
3.mybatisのinterceptブロッキング読み書き操作を構成し、SqlCommandTypeに従って読み書きを区別し、データソースを割り当てる
# DynamicPlugin (intercept)
@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, CacheKey.class, BoundSql.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class DynamicPlugin implements Interceptor {
protected static final Logger logger = LoggerFactory.getLogger(DynamicPlugin.class);
private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
private static final Map cacheMap = new ConcurrentHashMap<>();
@Override
public Object intercept(Invocation invocation) throws Throwable {
boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
// , , DynamicDataSourceTransactionManager
if (!synchronizationActive) {
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) objects[0];
DynamicDataSourceGlobal dynamicDataSourceGlobal = null;
if ((dynamicDataSourceGlobal = cacheMap.get(ms.getId())) == null) {
//
if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
//!selectKey id (SELECT LAST_INSERT_ID() ) ,
if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
} else {
BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\
\\r]", " ");
if (sql.matches(REGEX)) {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
} else {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.READ;
}
}
} else {
dynamicDataSourceGlobal = DynamicDataSourceGlobal.WRITE;
}
logger.warn(" [{}] use [{}] Strategy, SqlCommandType [{}]..", ms.getId(), dynamicDataSourceGlobal.name(), ms.getSqlCommandType().name());
cacheMap.put(ms.getId(), dynamicDataSourceGlobal);
}
DynamicDataSourceHolder.putDataSource(dynamicDataSourceGlobal);
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {}
}
ここで注意しなければならないのはqueryメソッドのブロックです.Executorではqueryメソッドは2つあります.
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
クエリー文として注釈とxmlを同時に使用しているため、ここでは両方を構成します.
#DynamicDataSourceConfig(InterceptをSqlSessionFactoryに転送)
@Bean
public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DynamicDataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setPlugins(new DynamicPlugin[]{new DynamicPlugin()});
try {
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
@Bean
public SqlSessionTemplate getSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory);
return template;
}
4.トランザクション状態でのデータ・ソースの割当ての構成
#D y n a m i c DataSourceTransactionManager(カスタムDataSourceTransactionManager実装割当ロジック)
public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
// ,
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
//
boolean readOnly = definition.isReadOnly();
if (readOnly) {
DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.READ);
} else {
DynamicDataSourceHolder.putDataSource(DynamicDataSourceGlobal.WRITE);
}
super.doBegin(transaction, definition);
}
//
@Override
protected void doCleanupAfterCompletion(Object transaction) {
super.doCleanupAfterCompletion(transaction);
DynamicDataSourceHolder.clearDataSource();
}
#D y n amicDataSourceConfig(transactionManagerの構成)
@Bean
public DynamicDataSourceTransactionManager getDynamicDataSourceTransactionManager(
@Qualifier("dataSource") DynamicDataSource dataSource) {
DynamicDataSourceTransactionManager transactionManager = new DynamicDataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
ここから読み書き分離の構成は終了しても、基本的にビジネスロジックに侵入することはなく、簡潔明瞭で、wuyongde 0922の共有に再び感謝します.
5.demoを書いてテストする
# TestMapper
@Insert("insert into test(value) values(#{value})")
@Options(useGeneratedKeys = true, keyColumn = "id")
int insert(Test test);
@Select("select * from test where id = #{id}")
Test getById(@Param("id") Long id);
#TestService(オープントランザクション@EnableTransactionManagement)
@Service
public class TestService {
@Autowired
TestMapper testMapper;
@Transactional
public void insert(Test test) {
testMapper.insert(test);
}
}
# TestMapperTest
public class TestMapperTest extends ReadWriteSplittingMybatisApplicationTests {
@Autowired
TestMapper testMapper;
@Autowired
TestService testService;
@org.junit.Test
public void insert() {
System.out.println(testMapper.getById(1l));
Test test = new Test();
test.setValue(UUID.randomUUID().toString());
testService.insert(test);
}
@org.junit.Test
public void queryById() {
Test test = testMapper.getById(1l);
System.out.println(test);
}
読み取りライブラリにレコードを手動で挿入します(1,「123」)
# run test
#
Test{id=1, value='123'}
#
Test{id=1, value='e37ef3b8-7d23-42a8-b445-b99e88409a7b'}
読み書き分離構成完了
現在の構成に基づいて、複数のライブラリ、ライブラリなど、より多くの拡張を行うことができます.