首页 > 代码库 > 利用多数据源实现分库存储
利用多数据源实现分库存储
介绍一些更美观的办法:
spring中有一个AbstractRoutingDataSource的抽象类可以很好的支持多数据源,我们只需要继续它即可。
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.cnblogs.yjmyzz.utils; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class RoutingDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DBContext.getDBKey(); } } |
很简单,就一个方法。其中DBContext的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.cnblogs.yjmyzz.utils; public class DBContext { //define count of database and it must match with resources/properties/jdbc.properties private static final int DB_COUNT = 2 ; private static final ThreadLocal<String> tlDbKey = new ThreadLocal<String>(); public static String getDBKey() { return tlDbKey.get(); } public static void setDBKey(String dbKey) { tlDbKey.set(dbKey); } public static String getDBKeyByUserId( int userId) { int dbIndex = userId % DB_COUNT; return "db_" + (++dbIndex); } } |
主要利用了ThreadLocal这个类在每个线程中保持自己私有的变量。
这里我模拟了一个分库的场景:假设一个应用允许用户注册,但是用户数量太多,全都放在一个数据库里,记录过多,会导致数据库性能瓶颈,比较容易想到的办法,把用户的数据分散到多个数据库中保存(注:可能马上有同学会说了,分开存了,要查询所有用户怎么办?这确实是分库带来的一个弊端,但也有相应的解决方案,本文先不讨论这个,以免跑题)。
假设我们有二个数据库,里面的表结构完全相同,有一张表T_USER用于保存用户数据,问题来了,如果有N个用户要注册,id分别是1、2、3...,服务端接到参数后,怎么知道把这些数据分别插入到这二个库中,必然要有一个规则 ,比较简单的办法就是取模,所以上面的getDBKeyByUserId就是干这个的。
然后是jdbc的属性配置文件:
1
2
3
4
5
6
7
8
9
10
11
|
jdbc-driver=com.mysql.jdbc.Driver jdbc-key- 1 =db_1 jdbc-url- 1 =jdbc:mysql: //default:3306/db_1?useUnicode=true&characterEncoding=utf8 jdbc-user- 1 =test jdbc-password- 1 = 123456 jdbc-key- 2 =db_2 jdbc-url- 2 =jdbc:mysql: //default:3306/db_2?useUnicode=true&characterEncoding=utf8 jdbc-user- 2 =test jdbc-password- 2 = 123456 |
接下来是spring的配置文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 8 9 10 <context:annotation-config/> 11 12 <context:component-scan base-package="com.cnblogs.yjmyzz"/> 13 14 <bean id="propertiesFactoryBean" 15 class="org.springframework.beans.factory.config.PropertiesFactoryBean"> 16 <property name="locations"> 17 <list> 18 <value>classpath:properties/jdbc.properties</value> 19 </list> 20 </property> 21 </bean> 22 23 <context:property-placeholder properties-ref="propertiesFactoryBean" ignore-unresolvable="true"/> 24 25 <bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" 26 destroy-method="close"> 27 <property name="driverClassName" value="http://www.mamicode.com/${jdbc-driver}"/> 28 <property name="url" value="http://www.mamicode.com/${jdbc-url-1}"/> 29 <property name="username" value="http://www.mamicode.com/${jdbc-user-1}"/> 30 <property name="password" value="http://www.mamicode.com/${jdbc-password-1}"/> 31 <property name="filters" value="http://www.mamicode.com/stat"/> 32 <property name="maxActive" value="http://www.mamicode.com/20"/> 33 <property name="initialSize" value="http://www.mamicode.com/1"/> 34 <property name="maxWait" value="http://www.mamicode.com/60000"/> 35 <property name="minIdle" value="http://www.mamicode.com/1"/> 36 <property name="timeBetweenEvictionRunsMillis" value="http://www.mamicode.com/3000"/> 37 <property name="minEvictableIdleTimeMillis" value="http://www.mamicode.com/300000"/> 38 <property name="validationQuery" value="http://www.mamicode.com/SELECT ‘x‘"/> 39 <property name="testWhileIdle" value="http://www.mamicode.com/true"/> 40 <property name="testOnBorrow" value="http://www.mamicode.com/false"/> 41 <property name="testOnReturn" value="http://www.mamicode.com/false"/> 42 <property name="poolPreparedStatements" value="http://www.mamicode.com/true"/> 43 <property name="maxPoolPreparedStatementPerConnectionSize" value="http://www.mamicode.com/20"/> 44 <property name="connectionInitSqls" value="http://www.mamicode.com/set names utf8mb4;"/> 45 </bean> 46 47 <bean id="dataSource1" parent="parentDataSource"> 48 <property name="url" value="http://www.mamicode.com/${jdbc-url-1}"/> 49 <property name="username" value="http://www.mamicode.com/${jdbc-user-1}"/> 50 <property name="password" value="http://www.mamicode.com/${jdbc-password-1}"/> 51 </bean> 52 53 <bean id="dataSource2" parent="parentDataSource"> 54 <property name="url" value="http://www.mamicode.com/${jdbc-url-2}"/> 55 <property name="username" value="http://www.mamicode.com/${jdbc-user-2}"/> 56 <property name="password" value="http://www.mamicode.com/${jdbc-password-2}"/> 57 </bean> 58 59 <!-- config switch routing db --> 60 <bean id="dataSource" class="com.cnblogs.yjmyzz.utils.RoutingDataSource"> 61 <property name="targetDataSources"> 62 <map key-type="java.lang.String"> 63 <entry key="${jdbc-key-1}" value-ref="dataSource1"/> 64 <entry key="${jdbc-key-2}" value-ref="dataSource2"/> 65 </map> 66 </property> 67 </bean> 68 69 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 70 <property name="configLocation" value="http://www.mamicode.com/classpath:mybatis-config.xml"></property> 71 <property name="dataSource" ref="dataSource"/> 72 <property name="mapperLocations"> 73 <array> 74 <value>classpath:mybatis/*.xml</value> 75 </array> 76 </property> 77 </bean> 78 79 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 80 <property name="basePackage" value="http://www.mamicode.com/com.cnblogs.yjmyzz.mapper"/> 81 </bean> 82 83 </beans>
关键的是parentDataSource,dataSource1,dataSource2,dataSource这几个bean的配置,一看就懂。
服务端的核心代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package com.cnblogs.yjmyzz.service.impl; import com.cnblogs.yjmyzz.entity.UserEntity; import com.cnblogs.yjmyzz.mapper.UserEntityMapper; import com.cnblogs.yjmyzz.service.UserService; import com.cnblogs.yjmyzz.utils.DBContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * Created by yangjunming on 2/15/16. * author: yangjunming@huijiame.com */ @Service ( "userService" ) public class UserServiceImpl implements UserService { @Autowired UserEntityMapper userEntityMapper; @Override public void addUser(UserEntity userEntity) { //switch db DBContext.setDBKey(DBContext.getDBKeyByUserId(userEntity.getUserId())); userEntityMapper.insertSelective(userEntity); } @Override public UserEntity getUser( int userId) { //switch db DBContext.setDBKey(DBContext.getDBKeyByUserId(userId)); return userEntityMapper.selectByPrimaryKey(userId); } } |
注意:25,32行在调用mybatis操作数据库前,先根据需要切换到不同的数据库,然后再操作。
运行完成后,可以看下db_1,db_2这二个数据库,确认数据是否已经分散存储到每个库中:
如果不喜欢在代码里手动切换db,也可以用注解的方式自动切换,比如:我们又增加了一个db_main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
jdbc-driver=com.mysql.jdbc.Driver jdbc-key- 1 =db_1 jdbc-url- 1 =jdbc:mysql: //default:3306/db_1?useUnicode=true&characterEncoding=utf8 jdbc-user- 1 =test jdbc-password- 1 = 123456 jdbc-key- 2 =db_2 jdbc-url- 2 =jdbc:mysql: //default:3306/db_2?useUnicode=true&characterEncoding=utf8 jdbc-user- 2 =test jdbc-password- 2 = 123456 jdbc-key-main=db_main jdbc-url-main=jdbc:mysql: //default:3306/db_main?useUnicode=true&characterEncoding=utf8 jdbc-user-main=test jdbc-password-main= 123456 |
然后在spring配置文件里,要做些调整:
1 <bean id="parentDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" 2 destroy-method="close"> 3 <property name="driverClassName" value="http://www.mamicode.com/${jdbc-driver}"/> 4 <property name="url" value="http://www.mamicode.com/${jdbc-url-1}"/> 5 <property name="username" value="http://www.mamicode.com/${jdbc-user-1}"/> 6 <property name="password" value="http://www.mamicode.com/${jdbc-password-1}"/> 7 <property name="filters" value="http://www.mamicode.com/stat"/> 8 <property name="maxActive" value="http://www.mamicode.com/20"/> 9 <property name="initialSize" value="http://www.mamicode.com/1"/> 10 <property name="maxWait" value="http://www.mamicode.com/60000"/> 11 <property name="minIdle" value="http://www.mamicode.com/1"/> 12 <property name="timeBetweenEvictionRunsMillis" value="http://www.mamicode.com/3000"/> 13 <property name="minEvictableIdleTimeMillis" value="http://www.mamicode.com/300000"/> 14 <property name="validationQuery" value="http://www.mamicode.com/SELECT ‘x‘"/> 15 <property name="testWhileIdle" value="http://www.mamicode.com/true"/> 16 <property name="testOnBorrow" value="http://www.mamicode.com/false"/> 17 <property name="testOnReturn" value="http://www.mamicode.com/false"/> 18 <property name="poolPreparedStatements" value="http://www.mamicode.com/true"/> 19 <property name="maxPoolPreparedStatementPerConnectionSize" value="http://www.mamicode.com/20"/> 20 <property name="connectionInitSqls" value="http://www.mamicode.com/set names utf8mb4;"/> 21 </bean> 22 23 <bean id="dataSource1" parent="parentDataSource"> 24 <property name="url" value="http://www.mamicode.com/${jdbc-url-1}"/> 25 <property name="username" value="http://www.mamicode.com/${jdbc-user-1}"/> 26 <property name="password" value="http://www.mamicode.com/${jdbc-password-1}"/> 27 </bean> 28 29 <bean id="dataSource2" parent="parentDataSource"> 30 <property name="url" value="http://www.mamicode.com/${jdbc-url-2}"/> 31 <property name="username" value="http://www.mamicode.com/${jdbc-user-2}"/> 32 <property name="password" value="http://www.mamicode.com/${jdbc-password-2}"/> 33 </bean> 34 35 <bean id="dataSourceMain" parent="parentDataSource"> 36 <property name="url" value="http://www.mamicode.com/${jdbc-url-main}"/> 37 <property name="username" value="http://www.mamicode.com/${jdbc-user-main}"/> 38 <property name="password" value="http://www.mamicode.com/${jdbc-password-main}"/> 39 </bean> 40 41 <!-- method 1: config switch routing db --> 42 <bean id="dataSource" class="com.cnblogs.yjmyzz.utils.RoutingDataSource"> 43 <property name="targetDataSources"> 44 <map key-type="java.lang.String"> 45 <entry key="${jdbc-key-1}" value-ref="dataSource1"/> 46 <entry key="${jdbc-key-2}" value-ref="dataSource2"/> 47 <entry key="${jdbc-key-main}" value-ref="dataSourceMain"/> 48 </map> 49 </property> 50 </bean> 51 52 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> 53 <property name="configLocation" value="http://www.mamicode.com/classpath:mybatis-config.xml"></property> 54 <property name="dataSource" ref="dataSource"/> 55 <property name="mapperLocations"> 56 <array> 57 <value>classpath:mybatis/*.xml</value> 58 </array> 59 </property> 60 </bean> 61 62 <bean id="userScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 63 <property name="basePackage" value="http://www.mamicode.com/com.cnblogs.yjmyzz.mapper.user"/> 64 <property name="sqlSessionFactoryBeanName" value="http://www.mamicode.com/sqlSessionFactory"/> 65 </bean> 66 67 <!-- method 2: config annotation auto switch--> 68 <bean id="sqlSessionFactoryMain" class="org.mybatis.spring.SqlSessionFactoryBean"> 69 <property name="configLocation" value="http://www.mamicode.com/classpath:mybatis-config.xml"></property> 70 <property name="dataSource" ref="dataSourceMain"/> 71 <property name="mapperLocations"> 72 <array> 73 <value>classpath:mybatis/*.xml</value> 74 </array> 75 </property> 76 </bean> 77 78 <bean id="orderScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 79 <property name="basePackage" value="http://www.mamicode.com/com.cnblogs.yjmyzz.mapper.order"/> 80 <property name="sqlSessionFactoryBeanName" value="http://www.mamicode.com/sqlSessionFactoryMain"/> 81 </bean>
注意:67-81行,主要是增加了一个单独的sqlSessionFactoryMain,然后将一个新的MapperScannerConfigurer关联到它。
新库里对应表的Mapper类可以这么写:
1
2
3
|
@Resource (name = "orderScannerConfigurer" ) public interface OrderEntityMapper extends Mapper<OrderEntity> { } |
注解里name对应的值,必须与刚才spring文件里新增的MapperScannerConfigurer对应。
这样,服务层就可以省去手动切换的代码了,即:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class UserServiceImpl implements UserService { @Autowired UserEntityMapper userEntityMapper; @Autowired OrderEntityMapper orderEntityMapper; @Override public void addUser(UserEntity userEntity) { //switch db DBContext.setDBKey(DBContext.getDBKeyByUserId(userEntity.getUserId())); userEntityMapper.insertSelective(userEntity); } @Override public UserEntity getUser( int userId) { //switch db DBContext.setDBKey(DBContext.getDBKeyByUserId(userId)); return userEntityMapper.selectByPrimaryKey(userId); } @Override public void addOrder(OrderEntity orderEntity) { //since orderEntityMapper can auto switch db by annotation //so we don‘t need to switch db manually orderEntityMapper.insertSelective(orderEntity); } @Override public OrderEntity getOrder( int orderId) { //since orderEntityMapper can auto switch db by annotation //so we don‘t need to switch db manually return orderEntityMapper.selectByPrimaryKey(orderId); } } |
利用多数据源实现分库存储