Java基于SpringBoot和tk.mybatis實(shí)現(xiàn)事務(wù)讀寫分離代碼實(shí)例
什么是讀寫分離?
讀寫分離,基本的原理是讓主數(shù)據(jù)庫(kù)處理事務(wù)性增、改、刪操作( INSERT、UPDATE、 DELETE) ,而從數(shù)據(jù)庫(kù)處理SELECT查詢操作。
數(shù)據(jù)庫(kù)復(fù)制被用來把事務(wù)性操作導(dǎo)致的變更同步到集群中的從數(shù)據(jù)庫(kù)。
為什么要讀寫分離呢?
- 因?yàn)閿?shù)據(jù)庫(kù)的“寫”(寫10000條數(shù)據(jù)可能要3分鐘)操作是比較耗時(shí)的。
- 但是數(shù)據(jù)庫(kù)的“讀”(讀10000條數(shù)據(jù)可能只要5秒鐘)
- 所以讀寫分離,解決的是,數(shù)據(jù)庫(kù)的寫入,影響了查詢的效率。
源碼
先定義數(shù)據(jù)源讀寫類型
/** * 數(shù)據(jù)源類型 * * @author sunchangtan */ public enum DataSourceType { WRITE, READ }
定義數(shù)據(jù)庫(kù)連接的Holder
import java.sql.Connection; import java.util.HashMap; import java.util.Map; import org.springframework.core.NamedThreadLocal; /** * 數(shù)據(jù)庫(kù)連接的Holder * * @author sunchangtan */ public class ConnectionHolder { /** * 當(dāng)前數(shù)據(jù)庫(kù)鏈接是讀還是寫 */ public final static ThreadLocal<DataSourceType> CURRENT_CONNECTION = new NamedThreadLocal<DataSourceType>("routingdatasource's key") { protected DataSourceType initialValue() { return DataSourceType.WRITE; } }; /** * 當(dāng)前線程所有數(shù)據(jù)庫(kù)鏈接 */ public final static ThreadLocal<Map<DataSourceType, Connection>> CONNECTION_CONTEXT = new NamedThreadLocal<Map<DataSourceType, Connection>>("connection map") { protected Map<DataSourceType, Connection> initialValue() { return new HashMap<>(); } }; /** * 強(qiáng)制寫數(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 { /** * 當(dāng)前數(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ù)庫(kù)的路由
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ù)庫(kù)代理,具體數(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ù)庫(kù)路由 * */ public interface DataSourceRouter { /** * 根據(jù)自己的需要,實(shí)現(xiàn)數(shù)據(jù)庫(kù)路由,可以是讀寫分離的數(shù)據(jù)源,或者是分表后的數(shù)據(jù)源 * @return */ public DataSource getTargetDataSource(); }
實(shí)現(xiàn)讀庫(kù)路由的基類
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 // 可以為真實(shí)的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ù)量,做負(fù)載均衡的時(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(); }
實(shí)現(xiàn)簡(jiǎn)單的輪詢路由,其他路由方式,大家可以自行實(shí)現(xiàn)
import javax.sql.DataSource; import java.util.concurrent.atomic.AtomicInteger; /** * * 簡(jiǎn)單實(shí)現(xiàn)讀數(shù)據(jù)源負(fù)載均衡 * */ 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ù)到讀庫(kù),讀寫事務(wù)到寫庫(kù)”的事務(wù)
import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.TransactionDefinition; import javax.sql.DataSource; /** * 事務(wù)管理 * 處理“只讀事務(wù)到讀庫(kù),讀寫事務(wù)到寫庫(kù)” * * @author sunchangtan */ public class MasterSlaverDataSourceTransactionManager extends DataSourceTransactionManager { public MasterSlaverDataSourceTransactionManager(DataSource dataSource) { super(dataSource); } /** * 只讀事務(wù)到讀庫(kù),讀寫事務(wù)到寫庫(kù) * @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) { //強(qiáng)制走寫庫(kù) if (ConnectionHolder.FORCE_WRITE.get() != null && ConnectionHolder.FORCE_WRITE.get()) { if (log.isDebugEnabled()) { log.debug("本事務(wù)強(qiáng)制走寫庫(kù)"); } 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("當(dāng)前數(shù)據(jù)庫(kù)為寫庫(kù)"); } } else if(key == DataSourceType.READ) { if (log.isDebugEnabled()) { log.debug("當(dāng)前數(shù)據(jù)庫(kù)為讀庫(kù)"); } } routeConnection(key, conn); } return invocation.proceed(); } private void routeConnection(DataSourceType key, Connection conn) { ConnectionHolder.CURRENT_CONNECTION.set(key); // 同一個(gè)線程下保證最多只有一個(gè)寫數(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ù)庫(kù)配置
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ù)庫(kù)事務(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ù)的實(shí)現(xiàn)Advice * * @return */ @Bean public TransactionInterceptor txAdvice(PlatformTransactionManager transactionManager) { NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource(); RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute(); readOnlyTx.setReadOnly(true); //使用PROPAGATION_SUPPORTS:支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行。 如果查詢中出現(xiàn)異常,那么當(dāng)前事務(wù)也可以回滾 readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_SUPPORTS); RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute(); requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class))); //使用PROPAGATION_REQUIRED:如果當(dāng)前沒有事務(wù),就新建一個(gè)事務(wù),如果已經(jīng)存在一個(gè)事務(wù)中,加入到這個(gè)事務(wù)中。 如果需要數(shù)據(jù)庫(kù)增刪改,必須要使用事務(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實(shí)現(xiàn)事務(wù)讀寫分離代碼實(shí)例的文章就介紹到這了,更多相關(guān)SpringBoot事務(wù)讀寫分離實(shí)例內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java線程之鎖對(duì)象Lock-同步問題更完美的處理方式代碼實(shí)例
這篇文章主要介紹了Java線程之鎖對(duì)象Lock-同步問題更完美的處理方式代碼實(shí)例,還是挺不錯(cuò)的,這里分享給大家,需要的朋友可以參考。2017-11-11Java?延時(shí)隊(duì)列及簡(jiǎn)單使用方式詳解
這篇文章主要介紹了Java延時(shí)隊(duì)列簡(jiǎn)單使用方式,通過本文學(xué)習(xí)知道延時(shí)隊(duì)列是什么可以用來干什么,本文通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08SpringBoot Validation入?yún)⑿r?yàn)國(guó)際化的項(xiàng)目實(shí)踐
在Spring Boot中,可以使用Validation和國(guó)際化來實(shí)現(xiàn)對(duì)入?yún)⒌男r?yàn),本文就來介紹一下SpringBoot Validation入?yún)⑿r?yàn)國(guó)際化,具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10兩個(gè)小例子輕松搞懂 java 中遞歸與尾遞歸的優(yōu)化操作
這篇文章主要介紹了兩個(gè)小例子輕松搞懂 java 中遞歸與尾遞歸的優(yōu)化操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09使用@Validated 和 BindingResult 遇到的坑及解決
這篇文章主要介紹了使用@Validated 和 BindingResult 遇到的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Spring Data JPA中的動(dòng)態(tài)查詢實(shí)例
本篇文章主要介紹了詳解Spring Data JPA中的動(dòng)態(tài)查詢。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04