您现在的位置:首页 >> 基础 >> Java编程 >> 内容

利用多数据源实现分库存储--Java学习网

时间:2016-12-4 22:19:12

  核心提示:介绍一些更美观的办法:spring中有一个AbstractRoutingDataSource的抽象类可以很好的支持多数据源,我们只需要继续它即可。123456789101112packagecom.c...

介绍一些更美观的办法:

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的配置文件:

关键的是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="${jdbc-driver}"/>
 4         <property name="url" value="${jdbc-url-1}"/>
 5         <property name="username" value="${jdbc-user-1}"/>
 6         <property name="password" value="${jdbc-password-1}"/>
 7         <property name="filters" value="stat"/>
 8         <property name="maxActive" value="20"/>
 9         <property name="initialSize" value="1"/>
10         <property name="maxWait" value="60000"/>
11         <property name="minIdle" value="1"/>
12         <property name="timeBetweenEvictionRunsMillis" value="3000"/>
13         <property name="minEvictableIdleTimeMillis" value="300000"/>
14         <property name="validationQuery" value="SELECT 'x'"/>
15         <property name="testWhileIdle" value="true"/>
16         <property name="testOnBorrow" value="false"/>
17         <property name="testOnReturn" value="false"/>
18         <property name="poolPreparedStatements" value="true"/>
19         <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
20         <property name="connectionInitSqls" value="set names utf8mb4;"/>
21     </bean>
22 
23     <bean id="dataSource1" parent="parentDataSource">
24         <property name="url" value="${jdbc-url-1}"/>
25         <property name="username" value="${jdbc-user-1}"/>
26         <property name="password" value="${jdbc-password-1}"/>
27     </bean>
28 
29     <bean id="dataSource2" parent="parentDataSource">
30         <property name="url" value="${jdbc-url-2}"/>
31         <property name="username" value="${jdbc-user-2}"/>
32         <property name="password" value="${jdbc-password-2}"/>
33     </bean>
34 
35     <bean id="dataSourceMain" parent="parentDataSource">
36         <property name="url" value="${jdbc-url-main}"/>
37         <property name="username" value="${jdbc-user-main}"/>
38         <property name="password" value="${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="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="com.cnblogs.yjmyzz.mapper.user"/>
64         <property name="sqlSessionFactoryBeanName" value="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="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="com.cnblogs.yjmyzz.mapper.order"/>
80         <property name="sqlSessionFactoryBeanName" value="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);
    }
 
}

Java免费学习   Java自学网 http://www.javalearns.com

关注微信号:javalearns   随时随地学Java

或扫一扫

随时随地学Java

作者:不详 来源:网络
    你是从哪里知道本网站的?
  • 网友介绍的
  • 百度搜索的
  • Google搜索的
  • 其它搜索过来的
  • 网址输错了进来的
  • 太忙了不记得了
共有评论 0相关评论
发表我的评论
  • 大名:
  • 内容:
  • java学习网(www.javalearns.com) © 2014 版权所有 All Rights Reserved.
  • Email:javalearns@163.com 站长QQ:1356121699 晋ICP备14003680号-3
  • java学习网部分内容来自网络或网友发布,如侵犯了您利益,请发邮件至:javalearns@126.com,我们尽快处理!
  • Java学习网
  • 网站统计
  • 晋公网安备 14042902000001号