欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java基于SpringBoot和tk.mybatis實(shí)現(xiàn)事務(wù)讀寫分離代碼實(shí)例

 更新時(shí)間:2023年10月07日 10:00:31   作者:sunct  
這篇文章主要介紹了Java基于SpringBoot和tk.mybatis實(shí)現(xiàn)事務(wù)讀寫分離代碼實(shí)例,讀寫分離,基本的原理是讓主數(shù)據(jù)庫(kù)處理事務(wù)性增、改、刪操作,而從數(shù)據(jù)庫(kù)處理SELECT查詢操作,數(shù)據(jù)庫(kù)復(fù)制被用來把事務(wù)性操作導(dǎo)致的變更同步到集群中的從數(shù)據(jù)庫(kù),需要的朋友可以參考下

什么是讀寫分離?

讀寫分離,基本的原理是讓主數(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)文章

最新評(píng)論