一、环境:

  三个mysql数据库。一个master,两个slaver。master写数据,slaver读数据。

二、原理:

  借助Spring的 AbstractRoutingDataSource 这个抽象实现。我们要实现 determineCurrentLookupKey()这个方法来动态的选择使用哪个数据源操着数据库

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    protected abstract Object determineCurrentLookupKey();

}

三、实现步骤:

1、添加spring,mybatis,mysql相关的pom依赖。

2、写jdbc.properties,定义三个数据库。

jdbc.driver=com.mysql.jdbc.Driver

#master库
master.jdbc.url=jdbc:mysql://127.0.0.1:3306/master?characterEncoding=utf8
master.jdbc.user=root
master.jdbc.password=tiger
#slave 一 库
slave.one.jdbc.url=jdbc:mysql://127.0.0.1:3306/slave-one?characterEncoding=utf8
slave.one.jdbc.user=root
slave.one.jdbc.password=tiger
#slave 二 库
slave.two.jdbc.url=jdbc:mysql://127.0.0.1:3306/slave-two?characterEncoding=utf8
slave.two.jdbc.user=root
slave.two.jdbc.password=tiger

3、配置三个数据源,分别写到三个配置文件中。

datasources-master.xml、datasource-slave-one.xml和datasource-slave-two.xml三个文件都一样,这里就写一个

<!--master数据源,支持读写-->
<bean id="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${master.jdbc.url}"/>
<property name="username" value="${master.jdbc.user}"/>
<property name="password" value="${master.jdbc.password}"/> <property name="filters" value="stat"/> <property name="maxActive" value="20"/>
<property name="initialSize" value="1"/>
<property name="maxWait" value="60000"/>
<property name="minIdle" value="1"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 'x'"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
</bean>

4、写spring的配置文件applicationContext.xml。

    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:jdbc.properties</value>
</list>
</property>
<property name="fileEncoding" value="UTF-8" />
<property name= "ignoreResourceNotFound" value="false"/>
</bean> <context:component-scan base-package="org.hope.lee"/> <aop:aspectj-autoproxy/> <!--spring的路由来管理数据源-->
<bean id="dynamicDataSource" class="org.hope.lee.utils.DynamicDataSource">
<property name="targetDataSources">
<map>
<entry value-ref="dataSourceMaster" key="db_master"/>
<entry value-ref="dataSourceSlaveOne" key="db_slave_one"/>
<entry value-ref="dataSourceSlaveTwo" key="db_slave_two"/>
</map>
</property>
</bean> <!--spring-mybatis整合-->
<bean id="dynamicsqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource"/>
<property name="mapperLocations">
<array>
<value>classpath:mappers/*Mapper.xml</value>
</array>
</property>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="typeAliasesPackage" value="org.hope.lee.model"/>
</bean> <!--自动扫描所有的Mapper接口与文件-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.hope.lee.dao"></property>
<property name="sqlSessionFactoryBeanName" value="dynamicsqlSessionFactory"></property>
</bean> <!--配置事务-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource"/>
</bean>
<!--开启注解事务-->
<tx:annotation-driven transaction-manager="transactionManager"/> <import resource="classpath:datasource-master.xml"/>
<import resource="classpath:datasource-slave-one.xml"/>
<import resource="classpath:datasource-slave-two.xml"/>

5、新建DBContextHolder,DBType为动态设置数据库的util

package org.hope.lee.utils;

public class DBType {
public final static String DB_TYPE_MASTER = "db_master"; public final static String DB_TYPE_SLAVE_ONE = "db_slave_one"; public final static String DB_TYPE_SLAVE_TWO = "db_slave_two";
}
package org.hope.lee.utils;

public class DBContextHolder {

    private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    public static String getDBType() {
String db = contextHolder.get();
if(db == null) {
db = DBType.DB_TYPE_MASTER; //默认是master库
} return db;
} public static void setDBType(String dbType) {
contextHolder.set(dbType);
} public static void clearDBType() {
contextHolder.remove();
} }

6、继承Spring的 AbstractRoutingDataSource 来动态的进行数据库路由

package org.hope.lee.utils;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

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

7、创建三个数据库master、slave-one、slave-two。三个库建同一张user表进行测试。

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

8、写mybatis的dao层,service层,model层

package org.hope.lee.model;

public class User {
private int id;
private String name; setters()&getters()
}
package org.hope.lee.dao;

import org.hope.lee.model.User;
import org.springframework.stereotype.Repository; @Repository
public interface UserMapper {
void insert(User user);
User selectOne(int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.hope.lee.dao.UserMapper">
<resultMap id="usersResultMap" type="User">
<id column="id" property="id" javaType="Integer" />
<result column="name" property="name" />
</resultMap> <insert id="insert" useGeneratedKeys="true" parameterType="User">
INSERT INTO `user`(name) VALUES(#{name, jdbcType=VARCHAR});
</insert> <select id="selectOne" resultMap="usersResultMap" parameterType="int" >
SELECT id, name FROM `user` WHERE id=#{id, jdbcType=INTEGER}
</select>
</mapper>
package org.hope.lee.service;

import org.hope.lee.dao.UserMapper;
import org.hope.lee.model.User;
import org.hope.lee.utils.DBContextHolder;
import org.hope.lee.utils.DBType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; @Service("userService")
public class UserServiceImpl implements UserService { @Autowired
private UserMapper userMapper; public void addUser(User user) {
//设置数据库
DBContextHolder.setDBType(DBType.DB_TYPE_MASTER);
userMapper.insert(user);
} public User getUserById(int id) {
//设置数据库,单元测试的时候自己手动修改一下,看看效果
    DBContextHolder.setDBType(DBType.DB_TYPE_SLAVE_ONE);
return userMapper.selectOne(id);
}
}

9、单元测试。

import org.hope.lee.model.User;
import org.hope.lee.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
public class UserServiceTest { @Autowired
private UserService userService; @Test
public void addUserTest() {
User user = new User();
user.setName("马云"); userService.addUser(user);
} @Test
public void getUserOneTest() {
int id = 1;
User u = userService.getUserById(id);
System.out.println(u.getName());
}
}

10、在service层修改 DBContextHolder.setDBType()来看看效果。

四、遇到的问题:

  1、遇到Spring的PropertyPlaceholderConfigurer不起效果,在数据源配置的${jdbc.driver}中获取不到jdbc.properties中的值。

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:jdbc.properties</value>
</list>
</property>
<property name="fileEncoding" value="UTF-8" />
<property name= "ignoreResourceNotFound" value="false"/>
</bean>

  解决:

<!--
  原来id的名称是sqlSessionFactory,但是在spring里使用org.mybatis.spring.mapper.MapperScannerConfigurer
进行自动扫描的时候,设置了sqlSessionFactory 的话,他会优先于PropertyPlaceholderConfigurer执行。
从而导致PropertyPlaceholderConfigurer失效,
这时在xml中用${url}、${username}、${password}等这样之类的表达式,
将无法获取到properties文件里的内容。
-->
<bean id="dynamicsqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource"/>
<property name="mapperLocations">
<array>
<value>classpath:mappers/*Mapper.xml</value>
</array>
</property>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="typeAliasesPackage" value="org.hope.lee.model"/>
</bean>

六、还有一种方式是使用mysql自带的replicationDriver来实现读写分离。大家自己也可以试试

      http://blog.csdn.net/lixiucheng005/article/details/17391857

https://gitee.com/huayicompany/mybatis-learn/tree/master/separated-read-write

参考:

[1] 博客,http://blog.csdn.net/xtj332/article/details/43953699

[2] 博客,http://blog.csdn.net/keda8997110/article/details/16827215

mybatis用spring的动态数据源实现读写分离的更多相关文章

  1. 使用Spring配置动态数据源实现读写分离

    最近搭建的一个项目需要实现数据源的读写分离,在这里将代码进行分享,以供参考.关键词:DataSource .AbstractRoutingDataSource.AOP 首先是配置数据源 <!-- ...

  2. 阿里P7教你如何使用 Spring 配置动态数据源实现读写分离

    最近搭建的一个项目需要实现数据源的读写分离,在这里将代码进行分享,以供参考. 关键词:DataSource .AbstractRoutingDataSource.AOP 首先是配置数据源 <!- ...

  3. 使用 Spring 配置动态数据源实现读写分离

    关键词:DataSource .AbstractRoutingDataSource.AOP 首先是配置数据源 <!--读数据源配置--><bean id="readData ...

  4. Spring动态数据源实现读写分离

    一.创建基于ThreadLocal的动态数据源容器,保证数据源的线程安全性 package com.bounter.mybatis.extension; /** * 基于ThreadLocal实现的动 ...

  5. 原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么

    开心一刻 女孩睡醒玩手机,收到男孩发来一条信息:我要去跟我喜欢的人表白了! 女孩的心猛的一痛,回了条信息:去吧,祝你好运! 男孩回了句:但是我没有勇气说不来,怕被打! 女孩:没事的,我相信你!此时女孩 ...

  6. Spring Boot2(四):使用Spring Boot多数据源实现读写分离

    前言 实际业务场景中,不可能只有一个库,所以就有了分库分表,多数据源的出现.实现了读写分离,主库负责增改删,从库负责查询.这篇文章将实现Spring Boot如何实现多数据源,动态数据源切换,读写分离 ...

  7. spring 配置双数据源并读写分离

    摘自 开源项目Ibase4j    关键思想在于AbstractRoutingSource 类 还有方法名称和切入点去控制使用哪个数据源    1.首先在配置文件配置多个数据源 并且交给继承自spri ...

  8. Spring实现动态数据源,支持动态加入、删除和设置权重及读写分离

    当项目慢慢变大,訪问量也慢慢变大的时候.就难免的要使用多个数据源和设置读写分离了. 在开题之前先说明下,由于项目多是使用Spring,因此下面说到某些操作可能会依赖于Spring. 在我经历过的项目中 ...

  9. MyBatis多数据源配置(读写分离)

    原文:http://blog.csdn.net/isea533/article/details/46815385 MyBatis多数据源配置(读写分离) 首先说明,本文的配置使用的最直接的方式,实际用 ...

随机推荐

  1. python将数据写入excel代码,python与office交互

    # -*- coding: utf-8 -*- from smartframe.header import * import pymysql import json import importlib, ...

  2. 开启 TLS 1.3 加密协议,极速 HTTPS 体验

    随着互联网的发展,用户对网络速度的要求也越来越高,尤其是目前在大力发展 HTTPS 的情况下,TLS 加密协议变得至关重要.又拍云在 HTTPS 的普及和性能优化上,始终做着自己的努力和贡献.2018 ...

  3. Django--进阶--中间件的使用

    - 中间件是什么? 中间件是一个钩子框架,它们可以介入Django 的请求和响应处理过程.它是一个轻量级.底层的“插件”系统,用于在全局修改Django 的输入或输出. 每个中间件组件负责完成某个特定 ...

  4. [转]scp用法

    从本地复制到远程 复制目录命令格式: scp -r local_folder remote_username@remote_ip:remote_folder 或者 scp -r local_folde ...

  5. JAVA基础-IO流(一)

    一.IO流 IO流是Java为方便我们对文件的读写进行操作而提供的一种技术.按照读取写入文件的方式不同可以分为字符流和字节流,而每个流派按照功能又分为读和写.字符流读写操作的根类为Reader和Wri ...

  6. 12、ABPZero系列教程之拼多多卖家工具 拼团提醒功能登录拼多多实现

    上篇文章已经完成了整个拼多多拼团提醒功能,本篇继续完成拼多多帐号登录,拼多多帐号登录的目的是为了获取拼团商品的SKU和订单号,便于商家备货. 以下是拼多多官方的后台登录,要实现的功能并不是直接在这里登 ...

  7. Java DB 访问之 mybatis mapper xml 配置方式

    1 项目说明 项目采用 maven 组织 ,jdbc 唯一的依赖就是 mysql-connector-java pom 依赖如下: mysql 数据连接 : mysql-connector-java ...

  8. JavaSE(四)之接口、访问控制

    上面我们学习了几个修饰符,在开发中经常会用的到,所以必须熟练的掌握.接下来我学习一下接口和访问控制. 一.接口 一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方 ...

  9. 【批处理学习笔记】第十三课:常用dos命令(3)

    网络命令ping 进行网络连接测试.名称解析ftp 文件传输net 网络命令集及用户管理telnet 远程登陆ipconfig显示.修改TCP/IP设置msg 给用户发送消息arp 显示.修改局域网的 ...

  10. HDU 2503 a/b + c/d(最大公约数与最小公倍数,板子题)

    话不多说,日常一水题,水水更健康!┗|`O′|┛ 嗷~~ a/b + c/d Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768 ...