zoukankan      html  css  js  c++  java
  • ssm实现mysql读写分离

    用于以后方便查看,借用:https://blog.csdn.net/weixin_43882034/article/details/84996443
    1.创建 DyncmicDataSourceHolder 类
    package cn.ljs.dyncmic;
    
    import com.alibaba.dubbo.common.utils.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /**
     * 在读写分离的实现上,
     * Spring为我们提供了路由数据源的AbstractRoutingDataSource抽象类,
     * 我们只需编写一个新的类继承它,
     * 即可在不改变原有的代码的基础上实现读写分离
     */
    public class DyncmicDataSourceHolder {
        private static Logger logger= LoggerFactory.getLogger(DyncmicDataSourceHolder.class);
    
        /**
         * 保证线程安全,使用线程threadlocal
         */
        private static ThreadLocal<String> contextHolder=new ThreadLocal<String>();
    
        public static final String DB_MASTER="master";
    
        public static final String DB_SLAVE="slave";
    
        /**
         * @return java.lang.String
         * @description 获取连接类型
         */
        public static String getDbType(){
            String db=contextHolder.get();
            if (StringUtils.isBlank(db)) {
                db=DB_MASTER;
            }
            return db;
        }
    
        /**
         * 设置数据源类型
         * @param str
         */
        public static void setDbType(String str){
            logger.debug("使用数据源类型---"+str);
            System.err.println("使用数据源类型---"+str);
            contextHolder.set(str);
        }
    
        /**
         * 清理连接类型
         */
        public static void clearDaType(){
            contextHolder.remove();
        }
    }
    2.创建 DynamicDataSource,继承AbstractRoutingDataSource
    package cn.ljs.dyncmic;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DyncmicDataSourceHolder.getDbType();
        }
    }
    3 创建DyncmicDataSourceInterceptor,mybatis拦截器
    package cn.ljs.dyncmic;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
    import org.apache.ibatis.mapping.BoundSql;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.mapping.SqlCommandType;
    import org.apache.ibatis.plugin.*;
    import org.apache.ibatis.session.ResultHandler;
    import org.apache.ibatis.session.RowBounds;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.transaction.support.TransactionSynchronizationManager;
    
    import java.util.Locale;
    import java.util.Properties;
    
    /**
     * 创建DyncmicDataSourceInterceptor,mybatis拦截器
     */
    @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})})
    public class DyncmicDataSourceInterceptor implements Interceptor {
    
        private static Logger logger = LoggerFactory.getLogger(DyncmicDataSourceInterceptor.class);
        // 写操作的正则表达式
        private static final String REGEX = ".*insert\u0020.*||.*delete\u0020|.*update\u0020.*";
    
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 判断方法是否是被事务管理的
            boolean synchronizationAction = TransactionSynchronizationManager.isActualTransactionActive();
            //获取sql 中的参数
            Object[] objects = invocation.getArgs();
            MappedStatement mappedStatement = (MappedStatement) objects[0];
            //数据源key
            String lookupKey = DyncmicDataSourceHolder.DB_MASTER;
            if (!synchronizationAction) {
                // 读方法
                if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                    //selectKey为自增id查询主键(SELECT LAST_INSERT_ID)方法
                    if (mappedStatement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                        lookupKey = DyncmicDataSourceHolder.DB_MASTER;
                    } else {
                        //获取sql 语句
                        BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(objects[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("\t\n\r", " ");
                        if (sql.matches(REGEX)) {//insert、update、delete
                            lookupKey = DyncmicDataSourceHolder.DB_MASTER;
                        } else {//select
                            lookupKey = DyncmicDataSourceHolder.DB_SLAVE;
                        }
                    }
                }
            } else {
                lookupKey = DyncmicDataSourceHolder.DB_MASTER;
            }
            logger.debug("设置方法[{}] use [{}] Strategy,SqlCommandType [{}]...", mappedStatement.getId(),
                    lookupKey, mappedStatement.getSqlCommandType().name());
            DyncmicDataSourceHolder.setDbType(lookupKey);
            return invocation.proceed();
        }
    
        @Override
        public Object plugin(Object target) {
            //Executor表示含有增删改查的操作 的对象
            if (target instanceof Executor) {
                //有增删改查的操作,就调用拦截方法
                return Plugin.wrap(target, this);
            } else {
                //无增删改查的操作。不做处理
                return target;
            }
        }
    
        @Override
        public void setProperties(Properties properties) {
    
        }
    }
    4.修改mybatis-config,引入拦截器
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <settings>
            <!-- 打印查询语句 -->
             <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
            <!--解决,查询返回结果含null没有对应字段值问题-->
            <setting name="callSettersOnNulls" value="true"/>
        </settings>
    
        <!-- 设置别名 -->
        <typeAliases>
            <package name="cn.ljs.entity" />
        </typeAliases>
        
        <plugins>
            <plugin interceptor="cn.ljs.dyncmic.DyncmicDataSourceInterceptor"></plugin>
        </plugins>
    </configuration>

    5,修改mybatis配置文件,

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
     
         
        <!-- 配置base数据源 -->
        <!-- Druid -->
        <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource"
            init-method="init"  destroy-method="close">
            <!-- 基本属性 url、user、password -->
    
            <!-- 本地 -->
            <property name="url" value="jdbc:mysql://localhost:3306/new_cgpt?useUnicode=true&amp;characterEncoding=utf8&amp;allowMultiQueries=true" />
            <property name="username" value="root" />
            <property name="password" value="root" />
            <!-- 配置初始化大小、最小、最大 -->
            <property name="initialSize" value="0" />
            <property name="minIdle" value="0" />
            <property name="maxActive" value="100" />
    
            <!-- 配置获取连接等待超时的时间 -->
            <property name="maxWait" value="60000" />
    
            <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
            <property name="timeBetweenEvictionRunsMillis" value="60000" />
    
            <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
            <property name="minEvictableIdleTimeMillis" value="300000" />
    
            <property name="validationQuery" value="select 1 from dual" />
            <property name="testWhileIdle" value="true" />
            <property name="testOnBorrow" value="false" />
            <property name="testOnReturn" value="false" />
    
            <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
            <property name="poolPreparedStatements" value="true" />
            <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
            
            <!-- 对于长时间不使用的连接强制关闭 -->
            <property name="removeAbandoned" value="true" />
        </bean>
    
        <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource"
              init-method="init"  destroy-method="close">
            <!-- 本地 -->
            <property name="url" value="jdbc:mysql://localhost:3306/cgpt?useUnicode=true&amp;characterEncoding=utf8&amp;allowMultiQueries=true" />
            <property name="username" value="root" />
            <property name="password" value="root" />
            <!-- 配置初始化大小、最小、最大 -->
            <property name="initialSize" value="0" />
            <property name="minIdle" value="0" />
            <property name="maxActive" value="100" />
    
            <!-- 配置获取连接等待超时的时间 -->
            <property name="maxWait" value="60000" />
    
            <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
            <property name="timeBetweenEvictionRunsMillis" value="60000" />
    
            <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
            <property name="minEvictableIdleTimeMillis" value="300000" />
    
            <property name="validationQuery" value="select 1 from dual" />
            <property name="testWhileIdle" value="true" />
            <property name="testOnBorrow" value="false" />
            <property name="testOnReturn" value="false" />
    
            <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
            <property name="poolPreparedStatements" value="true" />
            <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
    
            <!-- 对于长时间不使用的连接强制关闭 -->
            <property name="removeAbandoned" value="true" />
        </bean>
    
        <!--配置动态数据源-->
        <bean id="dynamicDataSource" class="cn.ljs.dyncmic.DynamicDataSource">
            <property name="targetDataSources">
                <map>
                    <entry key="master" value-ref="masterDataSource"/>
                    <entry key="slave" value-ref="slaveDataSource"/>
                </map>
            </property>
        </bean>
        <!--由于在运行时决定使用哪个数据源,所以使用懒加载-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
            <property name="targetDataSource">
                <ref bean="dynamicDataSource"/>
            </property>
        </bean>
    
        <!-- 注册SqlSessionFactoryBean -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            
            <!-- 自动扫描mappers.xml文件 -->
            <property name="mapperLocations" value="classpath:cn/ljs/mapper/*.xml" />
            <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        </bean>
        
        <!-- DAO接口所在包名,Spring会自动查找其下的类 -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="cn.ljs.dao" />
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        </bean>
    </beans>
  • 相关阅读:
    redis缓存穿透
    rocketmq配置文件两主两从
    jvm参数模板
    (转)volatile如何保证可见性
    Spring事务传播性与隔离级别
    Redis windows 远程连接配置修改
    Redis安装与配置( Windows10 或Windows server)
    C#中的虚函数及继承关系
    C#高级功能(三)Action、Func,Tuple
    WAMP配置httpd.conf允许外部访问
  • 原文地址:https://www.cnblogs.com/ljmm/p/13712785.html
Copyright © 2011-2022 走看看