mysql 主从和读写分离(Mybatis)

1- 修改my.cnf
server-id=1 #服务器id (主从必须不一样)
log-bin=master-bin #打开日志(主机需要打开)
log-bin-index=master-bin.index

重启mysql
mysql -uroot -p
- show master status;
登陆Master主机,创建mysql 的slave用户  IDENTIFIED 
create user repl;
GRANT  REPLICATION SLAVE ON *.* TO 'repl'@'192.168.88.129' IDENTIFIED BY 'mysql';

docker 环境应该改为否则从库连接不上主库
GRANT  REPLICATION SLAVE ON *.* TO 'repl'@'%' IDENTIFIED BY 'mysql';

flush privileges;

1- 修改my.cnf
server_id=2
relay-log-index=slave-relay-bin.index
relay-log=slave-relay-bin

重启mysql
mysql -uroot -p
- change master to master_host='192.168.88.129',master_port=3306,master_user='repl',master_password='mysql',master_log_file='master-bin.000001',master_log_pos=0;

start slave;
show slave status \G;
stop slave;

主从库:
GRANT select,insert,update,delete ON *.* TO 'work'@'%' IDENTIFIED BY '230230' WITH GRANT OPTION;
flush privileges;

GRANT select,insert,update,delete ON *.* TO 'work'@'%' IDENTIFIED BY '230230' WITH GRANT OPTION;
flush privileges;

  基于xml构建Spring项目

- jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://192.168.88.129:3309/o2o?useUnicode=true&characterEncoding=utf8
jdbc.username=work
jdbc.password=230230
jdbc.slave.url=jdbc:mysql://192.168.88.129:3306/o2o?useUnicode=true&characterEncoding=utf8
- spring-dao.xml<context:property-placeholder location="classpath:jdbc.properties"/>


	<!-- 2.数据库连接池 -->
	<bean id="abstractDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true" destroy-method="close">

		<!-- 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>


	<!-- 配置主从 start -->
	<bean id="master" parent="abstractDataSource">
		<!-- 配置连接池属性 -->
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.master.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<bean id="slave" parent="abstractDataSource">
		<!-- 配置连接池属性 -->
		<property name="driverClass" value="${jdbc.driver}" />
		<property name="jdbcUrl" value="${jdbc.slave.url}" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
	</bean>

	<!-- 配置动态数据源-->
	<bean id="dynamicDataSource" class="com.fayayo.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>


	<!-- 配置主从 end -->

	<!-- 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="com.fayayo.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="com.fayayo.o2o.dao" />
	</bean>

- spring-service.xml<!-- 扫描service包下所有使用注解的类型 -->
    <context:component-scan base-package="com.fayayo.o2o.service" />

    <!-- 配置事务管理器 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据库连接池 -->
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 配置基于注解的声明式事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" />- spring-web.xml<mvc:annotation-driven />

	<!-- 2.静态资源默认servlet配置 (1)加入对静态资源的处理:js,gif,png (2)允许使用"/"做整体映射 -->
	<mvc:resources mapping="/resources/**" location="/resources/" />
	<mvc:default-servlet-handler />

	<!-- 3.定义视图解析器 -->
	<bean id="viewResolver"
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/html/"></property>
		<property name="suffix" value=".html"></property>
	</bean>

	<!-- 在spring-mvc.xml文件中加入这段配置后,spring返回给页面的都是utf-8编码了 -->
	<bean
		class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
		<property name="messageConverters">
			<list>
				<bean
					class="org.springframework.http.converter.StringHttpMessageConverter">
					<property name="supportedMediaTypes">
						<list>
							<value>text/html;charset=UTF-8</value>
						</list>
					</property>
				</bean>
			</list>
		</property>
	</bean>
	<!-- 4.扫描web相关的bean -->
	<context:component-scan base-package="com.fayayo.o2o.web" />
	<!-- 5.权限拦截器 -->
	<mvc:interceptors>
		<mvc:interceptor>
			<!--<mvc:mapping path="/**" />-->
			<mvc:mapping path="/area/**" />
			<bean id="httpInterceptor" class="com.fayayo.o2o.interceptor.HttpInterceptor"/>
		</mvc:interceptor>
	</mvc:interceptors>
	
	- mybatis-config.xml	
<!-- 配置全局属性 -->
	<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="com.fayayo.o2o.dao.split.DynamicDataSourceInterceptor"></plugin>
	</plugins>	

实现动态数据源

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

}
public class DynamicDataSourceHolder {
    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 dbType=contextHolder.get();
        if(dbType==null){
            dbType=DB_MASTER;
        }
        return dbType;
    }
    public static void setDbType(String dbType){
        System.out.println("================>所使用的数据源是:"+dbType);
        contextHolder.set(dbType);
    }
    public static void clearDbType(){
        System.out.println("================>回收Holder");
        contextHolder.remove();
    }
}
/**
 * @author xxx on 2018/10/20.
 * @version v1.0
 * @desc 基于Mybatis的拦截器  拦截sql信息
 */
@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 final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("判断主从数据库的拦截器......");
        //判断当前是否是事务
        boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
        Object[] objects = invocation.getArgs();//获取sql的变量参数
        MappedStatement ms = (MappedStatement) objects[0];
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;//决定dataSource
        if (synchronizationActive != true) {
            //读方法
            if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                //selectKey为自增id查询主键(SELECT LAST_INSERT_ID())方法,使用主库
                if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                    lookupKey = DynamicDataSourceHolder.DB_MASTER;
                } else {
                    BoundSql boundSql = ms.getSqlSource().getBoundSql(objects[1]);
                    String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replace("[\\t\\n\\r]", " ");
                    if (sql.matches(REGEX)) {
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;//增删改使用主库
                    } else {
                        lookupKey = DynamicDataSourceHolder.DB_SLAVE;//读使用从库
                    }
                }
            }
        } else {//事务管理使用主库
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }
        System.out.println("设置方法详细信息:" + ms.getId() + "," + lookupKey + "," + ms.getSqlCommandType().name());
        DynamicDataSourceHolder.setDbType(lookupKey);//设置最终的数据源
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object o) {
        if (o instanceof Executor) {//增删改查的操作  拦截
            return Plugin.wrap(o, this);
        } else {
            return o;
        }
    }
    @Override
    public void setProperties(Properties properties) {
    }
}
- 项目拦截器
@Slf4j
public class HttpInterceptor extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 正常返回时, 显式回收threadLocal里的信息
        removeThreadLocalInfo();
    }
    @Override
    public void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // 出异常时, 显式回收threadLocal里的信息
        removeThreadLocalInfo();
    }
    private void removeThreadLocalInfo() {
        DynamicDataSourceHolder.clearDbType();
    }
}

实现上面的类之后,搭配spring-*.xml中的数据源配置

就可以实现主从分离了。要先创建好自己的mapper,进行数据库的增删改查。

需要源码可以留言。

点赞