实现数据库读写分离步骤
1 mysql数据库 设置 主从 日志同步
2 spring配置文件配置 2个数据源 然后放到一个datasource, 其他的txmanager和sessionfactory直接使用datasource
3 新建一个类 继承 AbstractRoutingDataSource 重写determineCurrentLookupKey方法
4 写一个 dbhelper类, 用来 设置 和获取 数据源
5 在controller层 需要使用数据库的地方 手工设置 数据源 DBContextHolder.setDbType(DBContextHolder.DB_RW 或者 DB_R);
读写分离最好不要用注解的方案, 应该在CONTROLLER层 手动 切换
因为 比如更新信息, 更新到主库,更新完后 为了防止数据延迟,读到旧数据,所以应该接着读主库, 而不是去读从库。
1 MAVEN 配置文件
<properties>
<!-- spring版本号 -->
<spring.version>4.2.1.RELEASE</spring.version>
<!-- mybatis版本号 -->
<mybatis.version>3.3.0</mybatis.version>
<!-- spring mybatis融合包 版本号 -->
<mybatis_spring.version>1.2.3 </mybatis_spring.version>
<!-- log4j日志文件管理包版本 -->
<slf.version>1.7.10</slf.version>
</properties>
<dependencies>
<!-- mybatis逆向工程 需要使用的包 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!--test 表示开发的时候引入,发布的时候不会加载此包 -->
<scope>compile</scope>
</dependency>
<!-- spring核心包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId>
<version>${spring.version}</version> </dependency> -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- AOP需要的 切面包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.7</version>
</dependency>
<!-- spring上传组件 依赖的 上传组件包 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!-- jackson包, springmvc 用他来为@ ResponseBody 注解的方法 , 返回 json -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.6.1</version>
</dependency>
<!-- mybatis和 spring的整合包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis_spring.version}</version>
</dependency>
<!-- mybatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- 导入Mysql数据库链接jar包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<!-- dbcp2 连接池 下面的pool2也一起需要 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.3</version>
</dependency>
<!-- 日志文件管理包 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf.version}</version>
</dependency>
<!-- log end -->
<!-- web 相关包 tomcat 7 servlet 3.0 jsp 2.2 jdk1.7 javaee最低1.6 -->
<!-- <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version> </dependency> -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- google提供的json解析包 spring4.x需要它 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.3.1</version>
</dependency>
<!-- 图片验证码 使用 kaptcha -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
</dependencies>
2 spring配置文件
<!--主数据源 支持读写 -->
<bean id="dbSourceRW" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!--初始化连接大小-->
<property name="initialSize" value="10"></property>
<!--连接池最大数量-->
<property name="maxTotal" value="30"></property>
<!--连接池最大空闲-->
<property name="maxIdle" value="10"></property>
<!--连接池最小空闲-->
<property name="minIdle" value="1"></property>
<!--获取连接最大等待时间-->
<property name="maxWaitMillis" value="60000"></property>
</bean>
<!--附属数据源 只用来读-->
<bean id="dbSourceR" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver2}" />
<property name="url" value="${jdbc.url2}" />
<property name="username" value="${jdbc.username2}" />
<property name="password" value="${jdbc.password2}" />
<!--初始化连接大小-->
<property name="initialSize" value="10"></property>
<!--连接池最大数量-->
<property name="maxTotal" value="30"></property>
<!--连接池最大空闲-->
<property name="maxIdle" value="10"></property>
<!--连接池最小空闲-->
<property name="minIdle" value="1"></property>
<!--获取连接最大等待时间-->
<property name="maxWaitMillis" value="60000"></property>
</bean>
<!-- 动态数据源 -->
<bean id="dynamicDataSource" class="com.zms.dbhelper.DynamicDataSource">
<!-- 通过key-value关联数据源 -->
<property name="targetDataSources">
<map>
<entry value-ref="dbSourceRW" key="readwritedb"></entry>
<entry value-ref="dbSourceR" key="readdb"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dbSourceRW" />
</bean>
<!-- 会话工厂 完美整合 mybatis, 不需要mybatis配置文件-->
<bean name="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 加载mybatis配置文件 已经不需要 mybatis配置文件了-->
<!-- <property name="configLocation" value="classpath:mybatis.xml"></property> -->
<property name="dataSource" ref="dynamicDataSource" ></property>
<!-- 自动扫描mapping.xml文件 支持通配符 com/zms/hengjinsuo/*/mapping/*.xml -->
<property name="mapperLocations" value="classpath:com/zms/hengjinsuo/mapping/*.xml"></property>
</bean>
3 自定义 数据库切换类
package com.zms.dbhelper;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*
* 功能说明:自己写一个spring 数据库动态路由 实现类
* 创建人:@author [email protected]
* 创建时间:2015年9月15日/下午2:51:20
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.getDbType();
}
}
4 DBHELPER类
package com.zms.dbhelper;
import org.apache.log4j.Logger;
import com.zms.hengjinsuo.user.controller.UserController;
/**
*
* 功能说明:
* 创建人:@author [email protected]
* 创建时间:2015年9月15日/下午2:52:49
*/
public class DBContextHolder {
/**
* 把 数据库类型放到threadLocal中 确保数据源对线程独立
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
private static Logger log = Logger.getLogger(DBContextHolder.class);
public static String DB_RW="readwritedb";
public static String DB_R="readdb";
public static String getDbType() {
String db = contextHolder.get();
if (db == null) {
db = DB_RW;// 默认是读写库
}
log.debug("动态选定的数据库是:"+db);
return db;
//return contextHolder.get();
}
/**
*
* 功能说明:设置本次操作的 数据源 (由dao)来调用
* 创建人:@author [email protected]
* 创建时间:2015年9月15日/下午2:59:23
* @param str
*/
public static void setDbType(String str) {
log.debug("动态设定的数据库为:"+str);
contextHolder.set(str);
}
/**
* clearDBType
*
* @Title: clearDBType
* @Description: 清理连接类型
*/
public static void clearDBType() {
contextHolder.remove();
}
}
5 controller层调用
/**
*
* 功能说明:处理增加用户 创建人:@author [email protected] 创建时间:2015年9月10日/上午11:07:04
*
* @param user
* @return
*/
@RequestMapping("/adduser")
public String adduser(User user, MultipartFile picFile, Model model)
throws ControllerException {
log.debug("需要增加的用户信息:" + user.getUsername() + "生日:"
+ user.getBirthday());
DBContextHolder.setDbType(DBContextHolder.DB_RW);
// 判断 文件控件是否传了文件 一般情况下 字符串长度肯定大于4
if (picFile != null & (picFile.getOriginalFilename().length() > 4)) {
String pic_path = "D:\\tomcat-7.0.63\\webapps\\pic\\";
// 获取图片后缀名
// 获取原始文件名称
String sourceFilename = picFile.getOriginalFilename();
// 取扩展名
String subFixName = sourceFilename.substring(sourceFilename
.lastIndexOf("."));
// 新图片名称
String newFileName = UUID.randomUUID() + subFixName;
// 创建新图片
File newPicFile = new File(pic_path + newFileName);
// 将接收到的图片 写入 硬盘
try {
picFile.transferTo(newPicFile);
// 为用户 图片 属性赋值
user.setPic(newPicFile.getName());
} catch (IllegalStateException e) {
throw new ControllerException(e, "图片上传失败" + e.getMessage());
} catch (IOException e) {
// TODO Auto-generated catch block
throw new ControllerException(e, "图片上传失败" + e.getMessage());
}
}
// 如果没有上传图片也能 保存
userService.insertUser(user);
// return "userlist";
return "redirect:userlist.html ";
}
6 效果