Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性。而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时的请求及系统状态来动态的决定将数据存储在哪个数据库实例中,以及从哪个数据库提取数据。
Spring配置多数据源的方式和具体使用过程。
Spring对于多数据源,以数据库表为参照,大体上可以分成两大类情况:
一是,表级上的跨数据库。即,对于不同的数据库却有相同的表(表名和表结构完全相同)。
二是,非表级上的跨数据库。即,多个数据源不存在相同的表。
1、根据用户的选择,使用不同的数据源。
2、解决思路锁定:将sessionFactory的属性dataSource设置成不同的数据源,以达到切换数据源的目的。
3、问题产生:因为整个项目用的几乎都是单例模式,当多个用户并发访问数据库的时候,会产生资源争夺的问题。即项目启动时候,所有的bean都被装载到内存,并且每个bean都只有一个对象。正因为只有一个对象,所有的对象属性就如同静态变量(静态变量跟单例很相似,常用静态来实现单例)。整个项目系统的dataSource只有一个,如果很多用户不断的去改变dataSource的值,那必然会出现资源的掠夺问题,造成系统隐患。
4、多资源共享解决思路:同一资源被抢夺的时候,通常有两种做法,a、以时间换空间 b、以空间换时间。
5、线程同步机制就是典型的“以时间换空间”,采用排队稍等的方法,一个个等待,直到前面一个用完,后面的才跟上,多人共用一个变量,用synchronized锁定排队。
6、“ThreadLocal”就是典型的“以空间换时间”,她可以为每一个人提供一份变量,因此可以同时访问并互不干扰。
7、言归正传:sessionFactory的属性dataSource设置成不用的数据源,首先不能在配置文件中写死,我们必须为她单独写一个类,让她来引用这个类,在这个类中再来判断我们到底要选择哪个数据源。
----------------------------------------------------------------------------------------------------------------------------------
假设现在有两个数据库myone和mytwo,读者可以理解为一个写库,一个读库,数据库中都各自有一个表,表的格式都一样,如下:
------------------------------------------------------------
id int(11) PRI auto_increment
username varchar(20)
password varchar(20)
------------------------------------------------------------
数据库脚本如下:
- create database myone;
- use myone;
- create table user(id int auto_increment primary key,username varchar(20),password varchar(20));
- create database mytwo;
- use mytwo;
- create table user(id int auto_increment primary key,username varchar(20),password varchar(20));
- insert into mytwo.user(username,password) value('test','test');
现在想要实现数据库的自动切换,有些mapper操作写库myone,有些mapper操作读库mytwo。
首先,构建maven工程,pom文件如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.aliyun.security</groupId>
- <artifactId>resourcegroup</artifactId>
- <version>1.0-SNAPSHOT</version>
- <dependencies>
- <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-dbcp2</artifactId>
- <version>2.1.1</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis</artifactId>
- <version>3.2.8</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-jdbc</artifactId>
- <version>4.1.6.RELEASE</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-aop</artifactId>
- <version>4.1.6.RELEASE</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>4.1.6.RELEASE</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
- <dependency>
- <groupId>org.aspectj</groupId>
- <artifactId>aspectjweaver</artifactId>
- <version>1.8.6</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis-spring</artifactId>
- <version>1.1.1</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.38</version>
- </dependency>
- </dependencies>
- </project>
接下来是写库myone对应的mapper,如下:
- package dal.mapper.myone;
- import dal.dataobject.myone.User;
- public interface OneUserManageMapper {
- int createUser(User user);
- }
- <?xml version="1.0" encoding="GBK"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="dal.mapper.myone.OneUserManageMapper">
- <insert id="createUser">
- INSERT INTO user(username, password) VALUES(#{username}, #{password})
- </insert>
- </mapper>
接下来是读库mytwo对应的mapper,如下:
- package dal.mapper.mytwo;
- import dal.dataobject.mytwo.User;
- import org.apache.ibatis.annotations.Param;
- public interface TwoUserManageMapper {
- User getUserById(@Param("id") int id);
- }
- <?xml version="1.0" encoding="GBK"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="dal.mapper.mytwo.TwoUserManageMapper">
- <!--<insert id="createUser">-->
- <!--INSERT INTO user(username, password) VALUES(#{username}, #{password})-->
- <!--</insert>-->
- <resultMap id="RM-User" type="dal.dataobject.mytwo.User">
- <result column="id" property="id"/>
- <result column="username" property="username"/>
- <result column="password" property="password"/>
- </resultMap>
- <select id="getUserById" resultMap="RM-User">
- SELECT * from user WHERE id=#{id}
- </select>
- </mapper>
接下来是实现自动切换最主要的代码,这里使用AOP编程来实现:
- package dal.datasourceswitch;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- /**
- * Created by Administrator on 2016/8/22.
- */
- /**
- * Created by rabbit on 14-5-25.
- */
- public class MultipleDataSource extends AbstractRoutingDataSource {
- private static ThreadLocal<String> threadLocalDatasource=new ThreadLocal<String>(){
- @Override
- protected String initialValue() {
- return null;
- }
- };
- public static void setThreadLocalDatasource(String dsName){
- threadLocalDatasource.set(dsName);
- }
- @Override
- protected Object determineCurrentLookupKey() {
- return threadLocalDatasource.get();
- }
- }
- package dal.datasourceswitch;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.springframework.stereotype.Component;
- @Component
- @Aspect
- public class MultipleDataSourceAspectAdvice {
- @Around("execution(* dal.mapper.myone..*.*(*))")
- public Object doAround1(ProceedingJoinPoint jp) throws Throwable {
- MultipleDataSource.setThreadLocalDatasource("myone");
- return jp.proceed();
- }
- @Around("execution(* dal.mapper.mytwo..*.*(*))")
- public Object doAround2(ProceedingJoinPoint jp) throws Throwable {
- MultipleDataSource.setThreadLocalDatasource("mytwo");
- return jp.proceed();
- }
- }
读者可以学习下AbstractRoutingDataSource类,就知道这么做的理由了。
最后,看下spring的配置文件:
- <?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"
- xmlns:aop="http://www.springframework.org/schema/aop"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-3.0.xsd
- http://www.springframework.org/schema/aop
- http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="location">
- <value>jdbc.properties</value>
- </property>
- </bean>
- <bean id="myoneDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
- <property name="driverClassName" value="${jdbc.myone.driver}"/>
- <property name="url" value="${jdbc.myone.url}"/>
- <property name="username" value="${jdbc.myone.username}"/>
- <property name="password" value="${jdbc.myone.password}"/>
- </bean>
- <bean id="mytwoDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
- <property name="driverClassName" value="${jdbc.mytwo.driver}"/>
- <property name="url" value="${jdbc.mytwo.url}"/>
- <property name="username" value="${jdbc.mytwo.username}"/>
- <property name="password" value="${jdbc.mytwo.password}"/>
- </bean>
- <bean id="multipleDataSource" class="dal.datasourceswitch.MultipleDataSource">
- <property name="defaultTargetDataSource" ref="myoneDataSource"/> <!--默认主库-->
- <property name="targetDataSources">
- <map>
- <entry key="myone" value-ref="myoneDataSource"/> <!--辅助aop完成自动数据库切换-->
- <entry key="mytwo" value-ref="mytwoDataSource"/>
- </map>
- </property>
- </bean>
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="dataSource" ref="multipleDataSource"/>
- <property name="mapperLocations">
- <list>
- <value>mapper/OneUserManageMapper.xml</value>
- <value>mapper/TwoUserManageMapper.xml</value>
- </list>
- </property>
- </bean>
- <bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true">
- <property name="sqlSessionFactory" ref="sqlSessionFactory" />
- </bean>
- <bean id="myoneUserManageMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"
- parent="baseMapper">
- <property name="mapperInterface"
- value="dal.mapper.myone.OneUserManageMapper" />
- </bean>
- <bean id="mytwoUserManageMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"
- parent="baseMapper">
- <property name="mapperInterface"
- value="dal.mapper.mytwo.TwoUserManageMapper" />
- </bean>
- <aop:aspectj-autoproxy/>
- <!-- 自动扫描,多个包以 逗号分隔 -->
- <context:component-scan base-package="dal"/> <!--注解自动装配-->
- <context:annotation-config /> <!--组件自动扫描-->
- </beans>
上面是实现多数据源自动切换的主要代码,源码github地址:
https://github.com/ZhenShiErGe/Multi-Datasource-Autoswitch.git
文章修改自http://www.cnblogs.com/lzrabbit/p/3750803.html