Java基于SpringBoot和tk.mybatis實現(xiàn)事務(wù)讀寫分離代碼實例
什么是讀寫分離?
讀寫分離,基本的原理是讓主數(shù)據(jù)庫處理事務(wù)性增、改、刪操作( INSERT、UPDATE、 DELETE) ,而從數(shù)據(jù)庫處理SELECT查詢操作。
數(shù)據(jù)庫復制被用來把事務(wù)性操作導致的變更同步到集群中的從數(shù)據(jù)庫。
為什么要讀寫分離呢?
- 因為數(shù)據(jù)庫的“寫”(寫10000條數(shù)據(jù)可能要3分鐘)操作是比較耗時的。
- 但是數(shù)據(jù)庫的“讀”(讀10000條數(shù)據(jù)可能只要5秒鐘)
- 所以讀寫分離,解決的是,數(shù)據(jù)庫的寫入,影響了查詢的效率。
源碼
先定義數(shù)據(jù)源讀寫類型
/**
* 數(shù)據(jù)源類型
*
* @author sunchangtan
*/
public enum DataSourceType {
WRITE, READ
}定義數(shù)據(jù)庫連接的Holder
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.NamedThreadLocal;
/**
* 數(shù)據(jù)庫連接的Holder
*
* @author sunchangtan
*/
public class ConnectionHolder {
/**
* 當前數(shù)據(jù)庫鏈接是讀還是寫
*/
public final static ThreadLocal<DataSourceType> CURRENT_CONNECTION = new NamedThreadLocal<DataSourceType>("routingdatasource's key") {
protected DataSourceType initialValue() {
return DataSourceType.WRITE;
}
};
/**
* 當前線程所有數(shù)據(jù)庫鏈接
*/
public final static ThreadLocal<Map<DataSourceType, Connection>> CONNECTION_CONTEXT = new NamedThreadLocal<Map<DataSourceType, Connection>>("connection map") {
protected Map<DataSourceType, Connection> initialValue() {
return new HashMap<>();
}
};
/**
* 強制寫數(shù)據(jù)源
*/
public final static ThreadLocal<Boolean> FORCE_WRITE = new NamedThreadLocal<Boolean>("FORCE_WRITE");
}定義數(shù)據(jù)源的Holder
import org.springframework.core.NamedThreadLocal;
/**
* 數(shù)據(jù)源的Holder
*
* @author sunchangtan
*/
public class DataSourceHolder {
/**
* 當前數(shù)據(jù)組
*/
public final static ThreadLocal<DataSourceType> CURRENT_DATASOURCE = new NamedThreadLocal<>("routingdatasource's key");
static {
setCurrentDataSource(DataSourceType.WRITE);
}
public static void setCurrentDataSource(DataSourceType dataSourceType){
CURRENT_DATASOURCE.set(dataSourceType);
}
public static DataSourceType getCurrentDataSource(){
return CURRENT_DATASOURCE.get();
}
public static void clearDataSource() {
CURRENT_DATASOURCE.remove();
}
}定義數(shù)據(jù)源代理類,處理讀寫數(shù)據(jù)庫的路由
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Logger;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Constants;
import org.springframework.jdbc.datasource.ConnectionProxy;
/**
* 數(shù)據(jù)庫代理,具體數(shù)據(jù)源由DataSourceRouter提供
*
* @author sunchangtan
*/
public class DataSourceProxy implements DataSource {
private static final Constants constants = new Constants(Connection.class);
private static final Log logger = LogFactory.getLog(DataSourceProxy.class);
private Boolean defaultAutoCommit = Boolean.TRUE;
private Integer defaultTransactionIsolation = 2;
private DataSourceRouter dataSourceRouter;
public DataSourceProxy(DataSourceRouter dataSourceRouter) {
this.dataSourceRouter = dataSourceRouter;
}
public void setDefaultAutoCommit(boolean defaultAutoCommit) {
this.defaultAutoCommit = defaultAutoCommit;
}
public void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
this.defaultTransactionIsolation = defaultTransactionIsolation;
}
public void setDefaultTransactionIsolationName(String constantName) {
setDefaultTransactionIsolation(constants.asNumber(constantName).intValue());
}
protected Boolean defaultAutoCommit() {
return this.defaultAutoCommit;
}
protected Integer defaultTransactionIsolation() {
return this.defaultTransactionIsolation;
}
@Override
public Connection getConnection() throws SQLException {
return (Connection) Proxy.newProxyInstance(ConnectionProxy.class.getClassLoader(),
new Class<?>[] { ConnectionProxy.class }, new LazyConnectionInvocationHandler());
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return (Connection) Proxy.newProxyInstance(ConnectionProxy.class.getClassLoader(),
new Class<?>[] { ConnectionProxy.class }, new LazyConnectionInvocationHandler(username, password));
}
private class LazyConnectionInvocationHandler implements InvocationHandler {
private String username;
private String password;
private Boolean readOnly = Boolean.FALSE;
private Integer transactionIsolation;
private Boolean autoCommit;
private boolean closed = false;
public LazyConnectionInvocationHandler() {
this.autoCommit = defaultAutoCommit();
this.transactionIsolation = defaultTransactionIsolation();
}
public LazyConnectionInvocationHandler(String username, String password) {
this();
this.username = username;
this.password = password;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Invocation on ConnectionProxy interface coming in...
if (method.getName().equals("setTransactionIsolation") && args != null && (Integer) args[0] == Connection.TRANSACTION_SERIALIZABLE) {
args[0] = defaultTransactionIsolation();
ConnectionHolder.FORCE_WRITE.set(Boolean.TRUE);
}
if (method.getName().equals("equals")) {
// We must avoid fetching a target Connection for "equals".
// Only consider equal when proxies are identical.
return (proxy == args[0]);
} else if (method.getName().equals("hashCode")) {
// We must avoid fetching a target Connection for "hashCode",
// and we must return the same hash code even when the target
// Connection has been fetched: use hashCode of Connection
// proxy.
return System.identityHashCode(proxy);
} else if (method.getName().equals("unwrap")) {
if (((Class<?>) args[0]).isInstance(proxy)) {
return proxy;
}
} else if (method.getName().equals("isWrapperFor")) {
if (((Class<?>) args[0]).isInstance(proxy)) {
return true;
}
} else if (method.getName().equals("getTargetConnection")) {
// Handle getTargetConnection method: return underlying
// connection.
return getTargetConnection(method);
}
if (!hasTargetConnection()) {
// No physical target Connection kept yet ->
// resolve transaction demarcation methods without fetching
// a physical JDBC Connection until absolutely necessary.
if (method.getName().equals("toString")) {
return "Lazy Connection proxy for target DataSource [" + dataSourceRouter.getTargetDataSource() + "]";
} else if (method.getName().equals("getMetaData")) {
return dataSourceRouter.getTargetDataSource().getConnection().getMetaData();
} else if (method.getName().equals("isReadOnly")) {
return this.readOnly;
} else if (method.getName().equals("setReadOnly")) {
this.readOnly = (Boolean) args[0];
return null;
} else if (method.getName().equals("getTransactionIsolation")) {
if (this.transactionIsolation != null) {
return this.transactionIsolation;
}
// Else fetch actual Connection and check there,
// because we didn't have a default specified.
} else if (method.getName().equals("setTransactionIsolation")) {
this.transactionIsolation = (Integer) args[0];
return null;
} else if (method.getName().equals("getAutoCommit")) {
if (this.autoCommit != null) {
return this.autoCommit;
}
// Else fetch actual Connection and check there,
// because we didn't have a default specified.
} else if (method.getName().equals("setAutoCommit")) {
this.autoCommit = (Boolean) args[0];
return null;
} else if (method.getName().equals("commit")) {
// Ignore: no statements created yet.
return null;
} else if (method.getName().equals("rollback")) {
// Ignore: no statements created yet.
return null;
} else if (method.getName().equals("getWarnings")) {
return null;
} else if (method.getName().equals("clearWarnings")) {
return null;
} else if (method.getName().equals("close")) {
// Ignore: no target connection yet.
this.closed = true;
return null;
} else if (method.getName().equals("isClosed")) {
return this.closed;
} else if (this.closed) {
// Connection proxy closed, without ever having fetched a
// physical JDBC Connection: throw corresponding
// SQLException.
throw new SQLException("Illegal operation: connection is closed");
}
} else {
if (method.getName().equals("commit")) {
Map<DataSourceType, Connection> connectionMap = ConnectionHolder.CONNECTION_CONTEXT.get();
Connection writeCon = connectionMap.get(DataSourceType.WRITE);
if (writeCon != null) {
writeCon.commit();
}
return null;
}
if (method.getName().equals("rollback")) {
Map<DataSourceType, Connection> connectionMap = ConnectionHolder.CONNECTION_CONTEXT.get();
Connection writeCon = connectionMap.get(DataSourceType.WRITE);
if (writeCon != null) {
writeCon.rollback();
}
return null;
}
if (method.getName().equals("close")) {
ConnectionHolder.FORCE_WRITE.set(Boolean.FALSE);
Map<DataSourceType, Connection> connectionMap = ConnectionHolder.CONNECTION_CONTEXT.get();
Connection readCon = connectionMap.remove(DataSourceType.READ);
if (readCon != null) {
readCon.close();
}
Connection writeCon = connectionMap.remove(DataSourceType.WRITE);
if (writeCon != null) {
writeCon.close();
}
this.closed = true;
return null;
}
}
// Target Connection already fetched,
// or target Connection necessary for current operation ->
// invoke method on target connection.
try {
return method.invoke(
ConnectionHolder.CONNECTION_CONTEXT.get().get(ConnectionHolder.CURRENT_CONNECTION.get()), args);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
/**
* Return whether the proxy currently holds a target Connection.
*/
private boolean hasTargetConnection() {
return (ConnectionHolder.CONNECTION_CONTEXT.get() != null
&& ConnectionHolder.CONNECTION_CONTEXT.get().get(ConnectionHolder.CURRENT_CONNECTION.get()) != null);
}
/**
* Return the target Connection, fetching it and initializing it if
* necessary.
*/
private Connection getTargetConnection(Method operation) throws SQLException {
// No target Connection held -> fetch one.
if (logger.isDebugEnabled()) {
logger.debug("Connecting to database for operation '" + operation.getName() + "'");
}
// Fetch physical Connection from DataSource.
Connection target = (this.username != null)
? dataSourceRouter.getTargetDataSource().getConnection(this.username, this.password)
: dataSourceRouter.getTargetDataSource().getConnection();
// Apply kept transaction settings, if any.
if (this.readOnly) {
try {
target.setReadOnly(this.readOnly);
} catch (Exception ex) {
// "read-only not supported" -> ignore, it's just a hint
// anyway
logger.debug("Could not set JDBC Connection read-only", ex);
}
}
if (this.transactionIsolation != null && !this.transactionIsolation.equals(defaultTransactionIsolation())) {
target.setTransactionIsolation(this.transactionIsolation);
}
if (DataSourceType.READ == ConnectionHolder.CURRENT_CONNECTION.get()) {
try {
target.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
}
if (this.autoCommit != null && this.autoCommit != target.getAutoCommit()) {
if (DataSourceType.WRITE == ConnectionHolder.CURRENT_CONNECTION.get()) {
target.setAutoCommit(this.autoCommit);
}
}
return target;
}
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return dataSourceRouter.getTargetDataSource().getLogWriter();
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
dataSourceRouter.getTargetDataSource().setLogWriter(out);
}
@Override
public int getLoginTimeout() throws SQLException {
return dataSourceRouter.getTargetDataSource().getLoginTimeout();
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
dataSourceRouter.getTargetDataSource().setLoginTimeout(seconds);
}
// ---------------------------------------------------------------------
// Implementation of JDBC 4.0's Wrapper interface
// ---------------------------------------------------------------------
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
return dataSourceRouter.getTargetDataSource().unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return (iface.isInstance(this) || dataSourceRouter.getTargetDataSource().isWrapperFor(iface));
}
// ---------------------------------------------------------------------
// Implementation of JDBC 4.1's getParentLogger method
// ---------------------------------------------------------------------
@Override
public Logger getParentLogger() {
return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
}
}定義路由接口
/**
* 數(shù)據(jù)庫路由
*
*/
public interface DataSourceRouter {
/**
* 根據(jù)自己的需要,實現(xiàn)數(shù)據(jù)庫路由,可以是讀寫分離的數(shù)據(jù)源,或者是分表后的數(shù)據(jù)源
* @return
*/
public DataSource getTargetDataSource();
}實現(xiàn)讀庫路由的基類
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
/**
* 讀寫數(shù)據(jù)源路由的基類
*
* @author sunchangtan
*/
public abstract class AbstractMasterSlaverDataSourceRouter implements DataSourceRouter, InitializingBean {
// 配置文件中配置的read-only datasoure
// 可以為真實的datasource,也可以jndi的那種
private List<Object> readDataSources;
private Object writeDataSource;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private List<DataSource> resolvedReadDataSources;
private DataSource resolvedWriteDataSource;
// read-only data source的數(shù)量,做負載均衡的時候需要
private int readDsSize;
public List<DataSource> getResolvedReadDataSources() {
return resolvedReadDataSources;
}
public int getReadDsSize() {
return readDsSize;
}
public void setReadDataSoures(List readDataSoures) {
this.readDataSources = readDataSoures;
}
public void setWriteDataSource(Object writeDataSource) {
this.writeDataSource = writeDataSource;
}
public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
}
@Override
public void afterPropertiesSet() {
if (writeDataSource == null) {
throw new IllegalArgumentException("Property 'writeDataSource' is required");
}
this.resolvedWriteDataSource = resolveSpecifiedDataSource(writeDataSource);
if (this.readDataSources == null || this.readDataSources.size() ==0) {
throw new IllegalArgumentException("Property 'resolvedReadDataSources' is required");
}
resolvedReadDataSources = new ArrayList<DataSource>(readDataSources.size());
for (Object item : readDataSources) {
resolvedReadDataSources.add(resolveSpecifiedDataSource(item));
}
readDsSize = readDataSources.size();
}
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
}
@Override
public DataSource getTargetDataSource() {
if (DataSourceType.WRITE.equals(ConnectionHolder.CURRENT_CONNECTION.get())) {
return resolvedWriteDataSource;
} else {
return loadBalance();
}
}
protected abstract DataSource loadBalance();
}實現(xiàn)簡單的輪詢路由,其他路由方式,大家可以自行實現(xiàn)
import javax.sql.DataSource;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* 簡單實現(xiàn)讀數(shù)據(jù)源負載均衡
*
*/
public class RoundRobinMasterSlaverDataSourceRouter extends AbstractMasterSlaverDataSourceRouter {
private AtomicInteger count = new AtomicInteger(0);
@Override
protected DataSource loadBalance() {
int index = Math.abs(count.incrementAndGet()) % getReadDsSize();
return getResolvedReadDataSources().get(index);
}
}處理“只讀事務(wù)到讀庫,讀寫事務(wù)到寫庫”的事務(wù)
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import javax.sql.DataSource;
/**
* 事務(wù)管理
* 處理“只讀事務(wù)到讀庫,讀寫事務(wù)到寫庫”
*
* @author sunchangtan
*/
public class MasterSlaverDataSourceTransactionManager extends DataSourceTransactionManager {
public MasterSlaverDataSourceTransactionManager(DataSource dataSource) {
super(dataSource);
}
/**
* 只讀事務(wù)到讀庫,讀寫事務(wù)到寫庫
* @param transaction
* @param definition
*/
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
//設(shè)置數(shù)據(jù)源
boolean readOnly = definition.isReadOnly();
if(readOnly) {
DataSourceHolder.setCurrentDataSource(DataSourceType.READ);
} else {
DataSourceHolder.setCurrentDataSource(DataSourceType.WRITE);
}
super.doBegin(transaction, definition);
}
/**
* 清理本地線程的數(shù)據(jù)源
* @param transaction
*/
@Override
protected void doCleanupAfterCompletion(Object transaction) {
super.doCleanupAfterCompletion(transaction);
DataSourceHolder.clearDataSource();
}
}mybatis的讀寫分離的插件,需要配置到mybatis-config.xml
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.springframework.jdbc.datasource.ConnectionProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.util.Properties;
/**
* 數(shù)據(jù)源讀寫分離路由
*/
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MasterSlaveInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Connection conn = (Connection) invocation.getArgs()[0];
conn = unwrapConnection(conn);
if (conn instanceof ConnectionProxy) {
//強制走寫庫
if (ConnectionHolder.FORCE_WRITE.get() != null && ConnectionHolder.FORCE_WRITE.get()) {
if (log.isDebugEnabled()) {
log.debug("本事務(wù)強制走寫庫");
}
routeConnection(DataSourceType.WRITE, conn);
return invocation.proceed();
}
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
MappedStatement mappedStatement;
if (statementHandler instanceof RoutingStatementHandler) {
mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
} else {
mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");
}
if(mappedStatement.getId().endsWith(".insertSelective!selectKey")) {
System.out.println("111");
}
DataSourceType key = DataSourceHolder.getCurrentDataSource();
if (key == null) {
key = DataSourceType.WRITE;
String sel = statementHandler.getBoundSql().getSql().trim().substring(0, 3);
if (sel.equalsIgnoreCase("sel")
&& !mappedStatement.getId().endsWith(".insert!selectKey")
&& !mappedStatement.getId().endsWith(".insertSelective!selectKey")) {
key = DataSourceType.READ;
}
}
if(key == DataSourceType.WRITE) {
if (log.isDebugEnabled()) {
log.debug("當前數(shù)據(jù)庫為寫庫");
}
} else if(key == DataSourceType.READ) {
if (log.isDebugEnabled()) {
log.debug("當前數(shù)據(jù)庫為讀庫");
}
}
routeConnection(key, conn);
}
return invocation.proceed();
}
private void routeConnection(DataSourceType key, Connection conn) {
ConnectionHolder.CURRENT_CONNECTION.set(key);
// 同一個線程下保證最多只有一個寫數(shù)據(jù)鏈接和讀數(shù)據(jù)鏈接
if (!ConnectionHolder.CONNECTION_CONTEXT.get().containsKey(key)) {
ConnectionProxy conToUse = (ConnectionProxy) conn;
conn = conToUse.getTargetConnection();
ConnectionHolder.CONNECTION_CONTEXT.get().put(key, conn);
}
}
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
public void setProperties(Properties properties) {
// NOOP
}
/**
* MyBatis wraps the JDBC Connection with a logging proxy but Spring registers the original connection so it should
* be unwrapped before calling {@code DataSourceUtils.isConnectionTransactional(Connection, DataSource)}
*
* @param connection May be a {@code ConnectionLogger} proxy
* @return the original JDBC {@code Connection}
*/
private Connection unwrapConnection(Connection connection) {
if (Proxy.isProxyClass(connection.getClass())) {
InvocationHandler handler = Proxy.getInvocationHandler(connection);
if (handler instanceof ConnectionLogger) {
return ((ConnectionLogger) handler).getConnection();
}
}
return connection;
}
}定義springboot的主從數(shù)據(jù)庫配置
import com.sample.dao.dynamic.DataSourceProxy;
import com.sample.dao.dynamic.DataSourceRouter;
import com.sample.dao.dynamic.RoundRobinMasterSlaverDataSourceRouter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Collections;
/**
* 主從數(shù)據(jù)源的配置
* @author sunchangtan
*/
@EnableConfigurationProperties({DataSourceMasterConfig.class, DataSourceSlaveConfig.class})
@Configuration
public class MasterSlaverDataSourceConfig {
@Resource
private DataSourceSlaveConfig dataSourceSlaveConfig;
@Resource
private DataSourceMasterConfig dataSourceMasterConfig;
@Bean
@ConditionalOnMissingBean
public DataSourceRouter readRoutingDataSource() {
RoundRobinMasterSlaverDataSourceRouter proxy = new RoundRobinMasterSlaverDataSourceRouter();
proxy.setReadDataSoures(Collections.singletonList(dataSourceSlaveConfig.createDataSource()));
proxy.setWriteDataSource(dataSourceMasterConfig.createDataSource());
return proxy;
}
@Bean
public DataSource dataSource(DataSourceRouter dataSourceRouter) {
return new DataSourceProxy(dataSourceRouter);
}
}springboot中配置數(shù)據(jù)庫事務(wù)
import com.sample.dao.dynamic.MasterSlaverDataSourceTransactionManager;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.*;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* 事務(wù)管理的配置
*
* @author sunchangtan
* @date 2018/8/30 11:22
*/
@Aspect
@Configuration
public class TransactionManagerConfigurer {
private static final int TX_METHOD_TIMEOUT = 50000;
private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.sample.***.service..*.*(..))";
@Resource
private DataSource dataSource;
@Bean
public PlatformTransactionManager transactionManager() {
return new MasterSlaverDataSourceTransactionManager(dataSource);
}
/**
* 事務(wù)的實現(xiàn)Advice
*
* @return
*/
@Bean
public TransactionInterceptor txAdvice(PlatformTransactionManager transactionManager) {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
//使用PROPAGATION_SUPPORTS:支持當前事務(wù),如果當前沒有事務(wù),就以非事務(wù)方式執(zhí)行。 如果查詢中出現(xiàn)異常,那么當前事務(wù)也可以回滾
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS);
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
//使用PROPAGATION_REQUIRED:如果當前沒有事務(wù),就新建一個事務(wù),如果已經(jīng)存在一個事務(wù)中,加入到這個事務(wù)中。 如果需要數(shù)據(jù)庫增刪改,必須要使用事務(wù)
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
requiredTx.setTimeout(TX_METHOD_TIMEOUT);
Map<String, TransactionAttribute> txMap = new HashMap<>();
txMap.put("add*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("delete*", requiredTx);
txMap.put("remove*", requiredTx);
txMap.put("upload*", requiredTx);
txMap.put("generate*", requiredTx);
txMap.put("import*", requiredTx);
txMap.put("bind*", requiredTx);
txMap.put("unbind*", requiredTx);
txMap.put("cancel*", requiredTx);
txMap.put("send*", requiredTx);
txMap.put("create*", requiredTx);
txMap.put("compute*", requiredTx);
txMap.put("recompute*", requiredTx);
txMap.put("execute*", requiredTx);
//txMap.put("submit*", requiredTx);
txMap.put("get*", readOnlyTx);
txMap.put("query*", readOnlyTx);
txMap.put("list*", readOnlyTx);
txMap.put("has*", readOnlyTx);
txMap.put("exist*", readOnlyTx);
txMap.put("download*", readOnlyTx);
txMap.put("export*", readOnlyTx);
txMap.put("search*", readOnlyTx);
txMap.put("check*", readOnlyTx);
txMap.put("load*", readOnlyTx);
txMap.put("find*", readOnlyTx);
source.setNameMap(txMap);
return new TransactionInterceptor(transactionManager, source);
}
/**
* 切面的定義,pointcut及advice
*
* @param txAdvice
* @return
*/
@Bean
public Advisor txAdviceAdvisor(@Qualifier("txAdvice") TransactionInterceptor txAdvice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice);
}
}到此這篇關(guān)于Java基于SpringBoot和tk.mybatis實現(xiàn)事務(wù)讀寫分離代碼實例的文章就介紹到這了,更多相關(guān)SpringBoot事務(wù)讀寫分離實例內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java線程之鎖對象Lock-同步問題更完美的處理方式代碼實例
這篇文章主要介紹了Java線程之鎖對象Lock-同步問題更完美的處理方式代碼實例,還是挺不錯的,這里分享給大家,需要的朋友可以參考。2017-11-11
SpringBoot Validation入?yún)⑿r瀲H化的項目實踐
在Spring Boot中,可以使用Validation和國際化來實現(xiàn)對入?yún)⒌男r?本文就來介紹一下SpringBoot Validation入?yún)⑿r瀲H化,具有一定的參考價值,感興趣的可以了解一下2023-10-10
兩個小例子輕松搞懂 java 中遞歸與尾遞歸的優(yōu)化操作
這篇文章主要介紹了兩個小例子輕松搞懂 java 中遞歸與尾遞歸的優(yōu)化操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
使用@Validated 和 BindingResult 遇到的坑及解決
這篇文章主要介紹了使用@Validated 和 BindingResult 遇到的坑及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10

