大发888客户端:基于MySql主从星散的代码层实现

admin 2个月前 (07-28) 科技 25 0

前言

  该文是基于上篇《MySQL主从星散的实现》的代码层实现,以是本文设置的主数据库和从数据库的数据源都是在上篇博文中已经先容了的。

动态选择数据源的设置

  由于我们在写数据的时刻需要使用主库的数据源,读的时刻需要从库的数据源,我们可以在Spring源码中,通过DataSource可以找到AbstractDataSource抽象类,由于我们需要动态的选择数据源,我们可以通过AbstractDataSource发现他的一个子类是AbstractRoutingDataSource的抽象类,通过类名我们可以知道该类是具有路由功效的,可以路由到差别的数据源,这个类中有一个方式determineTargetDateSource(),该方式就是决议目的数据源的,该方式会挪用determineCurrentLookupKey(),就是决议数据源的名字了,该方式是一个抽象的,以是我们需要去继续AbstractRoutingDataSource这个类,并实现determineCurrentLookupKey()这个方式,来动态选择数据源,读数据的时刻选择从库的数据源,写操作的时刻选择主库的数据源

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDBType();
    }
}

编写DynamicDataSourceHolder

public class DynamicDataSourceHolder {
    private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceHolder.class);

    //ThreadLocal是线程平安的
    private static ThreadLocal<String> contextHolder = new ThreadLocal<>();
    public static final String DB_MASTER = "master";
    public static final String DB_SLAVE = "slave";

    public static String getDBType() {
        String db = contextHolder.get();
        if (db == null){
            db = DB_MASTER; // 默以为master,由于master即支持读也支持写
        }
        return db;
    }

    /**
     * 设置线程的dbType
     * @param str
     */
    public static void setDBType(String str) {
        logger.debug("所使用的数据源:"+ str);
        contextHolder.set(str);
    }

    /**
     * 清算毗邻类型
     */
    public static void clearDBType(){
        contextHolder.remove();
    }
}

设置mybatis的阻挡器

   完成路由后,我们需要依赖阻挡器对传递进来的SQL信息来选择数据源,例如传进来的是insert,update,delete语句,就使用主库的数据源,如果是select就选择从库的数据源。

@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 DynamicDataSourceInterceptor implements Interceptor {
    private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceInterceptor.class);
    //使用正则表达式匹配增删改
    private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

    /**
     * 阻挡方式
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 判断当前操作是否是事务的
        // 使用@Transactional来处置,则会返回true
        boolean transactionActive = TransactionSynchronizationManager.isActualTransactionActive();
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;
        if ( !transactionActive ) {
            // 如果是查询操作
            if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                //selectKey 为自增id查询主键SELECT_KEY_SUFFIX()方式,使用主库
                if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
                    lookupKey = DynamicDataSourceHolder.DB_MASTER;
                } else {
                    BoundSql boundSql = ms.getSqlSource().getBoundSql(args[1]);
                    // 对制表符,换行符,空格符就行替换
                    String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                    //增删改使用主库,查使用从库
                    if (sql.matches(REGEX)) {
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    } else {
                        lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                    }
                }
            }

        } else {
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }
        logger.debug("设置方式[{}] use [{}] Strategy,SqlCommandType [{}] ...",
                ms.getId(), lookupKey, ms.getSqlCommandType().name());
        DynamicDataSourceHolder.setDBType(lookupKey);
        return invocation.proceed();
    }

    /**
     * 决议返回封装好的工具照样署理工具
     * 增删改查得操作
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //当我们阻挡的工具是Executor时,就阻挡,通过intercept()方式 决议所使用的数据源
        //为什么要阻挡Executor类型呢?由于在我们的mybatis中,Executor是用来支持一系列增删改查操作的
        //只要我们检测到阻挡的工具包罗增删改查操作,就阻挡下来,使用intercept()方式,决议所使用的数据源
        if (target instanceof Executor) {
            return Plugin.wrap(target,this);
        } else {
            return target;
        }
    }

    /**
     * 在类初始化的时刻,去做一些相关的设置
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {

    }
}

  只是编写完这个方式是没用的,我们还需要在mybaties-config.xml设置文件中,设置上我们实现的阻挡器,如下:

<?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>
		<!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
		<setting name="useGeneratedKeys" value="true" />

		<!-- 使用列别名替换列名 默认:true -->
		<setting name="useColumnLabel" value="true" />

		<!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} -->
		<setting name="mapUnderscoreToCamelCase" value="true" />
		<!-- 打印查询语句 -->
		<setting name="logImpl" value="STDOUT_LOGGING" />
	</settings>
	<plugins>
		<plugin interceptor="cn.reminis.o2o.dao.split.DynamicDataSourceInterceptor" />
	</plugins>
</configuration>

设置多数据源

  将原来设置dataSource的bean,改为abstractDatasource,并增添主库数据源和从库数据源的设置,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 设置整合mybatis历程 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!-- 2.数据库毗邻池 -->
    <bean id="abstractDataSource" abstract="true" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <!-- 设置毗邻池属性 -->
        <!--
        <property name="driverClass" value="${jdbc.driver}" />
        <property name="jdbcUrl" value="${jdbc.url}" />
        <property name="user" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        -->

        <!-- c3p0毗邻池的私有属性 -->
        <property name="maxPoolSize" value="30" />
        <property name="minPoolSize" value="10" />
        <!-- 关闭毗邻后不自动commit -->
        <property name="autoCommitOnClose" value="false" />
        <!-- 获取毗邻超时时间 -->
        <property name="checkoutTimeout" value="10000" />
        <!-- 当获取毗邻失败重试次数 -->
        <property name="acquireRetryAttempts" value="2" />
    </bean>

    <!--主库的数据源设置-->
    <bean id="master" parent="abstractDataSource">
        <property name="driverClass" value="${jdbc.master.driver}" />
        <property name="jdbcUrl" value="${jdbc.master.url}" />
        <property name="user" value="${jdbc.master.username}" />
        <property name="password" value="${jdbc.master.password}" />
    </bean>

    <!--从库的数据源设置-->
    <bean id="slave" parent="abstractDataSource">
        <property name="driverClass" value="${jdbc.slave.driver}" />
        <property name="jdbcUrl" value="${jdbc.slave.url}" />
        <property name="user" value="${jdbc.slave.username}" />
        <property name="password" value="${jdbc.slave.password}" />
    </bean>

    <!--设置动态数据源。这里targetDataSource就是路由数据源的名称-->
    <bean id="dynamicDataSource" class="cn.reminis.o2o.dao.split.DynamicDataSource">
        <property name="targetDataSources">
            <map>
                <entry value-ref="master" key="master"></entry>
                <entry value-ref="slave" key="slave"></entry>
            </map>
        </property>
    </bean>

    <!--懒加载,由于数据源是程序运行时决议的-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
        <property name="targetDataSource">
            <ref bean="dynamicDataSource" />
        </property>
    </bean>

    <!-- 3.设置SqlSessionFactory工具 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入数据库毗邻池 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 设置MyBaties全局设置文件:mybatis-config.xml -->
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <!-- 扫描entity包 使用别名 -->
        <property name="typeAliasesPackage" value="cn.reminis.o2o.entity" />
        <!-- 扫描sql设置文件:mapper需要的xml文件 -->
        <property name="mapperLocations" value="classpath:mapper/*.xml" />
    </bean>

    <!-- 4.设置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!-- 给出需要扫描Dao接口包 -->
        <property name="basePackage" value="cn.reminis.o2o.dao" />
    </bean>
</beans>

我们在jdbc.properties设置文件中,设置主从库数据源的地址:

## 主库数据源设置
jdbc.master.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://192.168.0.188:3306/o2o?useUnicode=true&characterEncoding=utf8
jdbc.master.username=root
jdbc.master.password=123456

## 从库数据源设置
jdbc.slave.driver=com.mysql.jdbc.Driver
jdbc.slave.url=jdbc:mysql://192.168.0.152:3306/o2o?useUnicode=true&characterEncoding=utf8
jdbc.slave.username=root
jdbc.slave.password=root

测试

  我们在执行查询操作时,就会从从库中去查询,我们可以通过查看日志的知,如下:

  当我们执行增删改操作时,就会使用从库的数据源,如下:

  通过测试可知,我们设置主从星散,代码层实现已经乐成了,我们从日志也可以看到,我们的系统用户执行更多的操作都是在执行查询操作,我们也可以设置一主多从来减轻服务器的压力。

,

欧博客户端下载

欢迎进入欧博客户端下载(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

申博声明:该文看法仅代表作者自己,与本平台无关。转载请注明:大发888客户端:基于MySql主从星散的代码层实现

网友评论

  • (*)

最新评论