利用多数据源实现分库存储

介绍一些更美观的办法:

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="${jdbc-driver}"/>
28         <property name="url" value="${jdbc-url-1}"/>
29         <property name="username" value="${jdbc-user-1}"/>
30         <property name="password" value="${jdbc-password-1}"/>
31         <property name="filters" value="stat"/>
32         <property name="maxActive" value="20"/>
33         <property name="initialSize" value="1"/>
34         <property name="maxWait" value="60000"/>
35         <property name="minIdle" value="1"/>
36         <property name="timeBetweenEvictionRunsMillis" value="3000"/>
37         <property name="minEvictableIdleTimeMillis" value="300000"/>
38         <property name="validationQuery" value="SELECT ‘x‘"/>
39         <property name="testWhileIdle" value="true"/>
40         <property name="testOnBorrow" value="false"/>
41         <property name="testOnReturn" value="false"/>
42         <property name="poolPreparedStatements" value="true"/>
43         <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
44         <property name="connectionInitSqls" value="set names utf8mb4;"/>
45     </bean>
46
47     <bean id="dataSource1" parent="parentDataSource">
48         <property name="url" value="${jdbc-url-1}"/>
49         <property name="username" value="${jdbc-user-1}"/>
50         <property name="password" value="${jdbc-password-1}"/>
51     </bean>
52
53     <bean id="dataSource2" parent="parentDataSource">
54         <property name="url" value="${jdbc-url-2}"/>
55         <property name="username" value="${jdbc-user-2}"/>
56         <property name="password" value="${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="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="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: [email protected]

 */

@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);

    }

}

时间: 2024-09-30 16:08:55

利用多数据源实现分库存储的相关文章

报表应用结构优化之数据分库存储

报表项目中,可能会出现报表源数据来自于不同数据库的情况. 这是由于同一张报表可能会从多个业务系统取数据.比如:员工信息从人力资源系统中取出,销售数据从销售系统中取出.另一种可能是,同一应用系统的数据库负载太大,不得已分成多个数据库的情况.比如:销售系统数据分成当前库和历史库. 报表工具须要连接的可能是相同类型的数据库.比方都是oracle或者db2:也可能是不同类型的数据库. 报表应用中,数据分库存储的解决的方法有:1.建设专门的数据仓库.2.利用跨库訪问的技术. 专门数据仓库的建设和管理比較复

润乾集算报表优化应用结构之数据分库存储

报表项目中,可能会出现报表源数据来自于不同数据库的情况.这是因为同一张报表可能会从多个业务系统取数据.例如:员工信息从人力资源系统中取出,销售数据从销售系统中取出.还有一种可能是,同一应用系统的数据库负载太大,不得已分成多个数据库的情况.例如:销售系统数据分成当前库和历史库. 报表工具需要连接的可能是同样类型的数据库,比如都是oracle或者db2:也可能是不同类型的数据库. 报表应用中,数据分库存储的解决办法有:1.建设专门的数据仓库:2.利用跨库访问的技术. 专门数据仓库的建设和管理比较复杂

实现报表数据分库存储

报表项目中,常常会出现报表源数据来自不同数据库的情况,也就是同一张报表可能会从多个业务系统读取数据.例如:员工信息从人力资源系统中取出,销售数据从销售系统中取出.当然,还有一种可能,同一应用系统的数据库负载太大,不得已分成多个数据库,例如:销售系统数据分成当前库和历史库. 在数据库类型方面,报表工具可能连接同样类型的数据库,比如都是 oracle 或者 db2:也可能是不同的类型. 报表应用中针对这种数据分库存储的解决办法有两种:1.建设专门的数据仓库:2.利用跨库访问的技术. 专门数据仓库的建

利用IOS模拟器将数据存储在本地沙盒中以及从沙盒中读取详细步骤

使用IO模拟器,应用沙盒的根路径为:/Users/apple/Library/Application Support/iPhone Simulator/6.0/Applications(6.0为模拟器的版本) 1.获取应用沙盒目录 <1>利用沙盒根目录拼接"Documents"字符串 NSString * home = NSHomeDirectory(); NSString * documents = [home stringByAppendingPathComponent

利用samba给mysql提供数据存储服务

利用samba部署wordpress (1) samba server导出/data/application/web,在目录中提供wordpress; (2) samba  client挂载nfs server导出的文件系统至/var/www/html: (3) 客户端(lamp)部署wordpress,并让其正常访问:要确保能正常发文章,上传图片: (4) 客户端2(lamp),挂载samba  server导出的文件系统至/var/www/html:验正其wordpress是否可被访问: 要

利用AWS boto实现EC2 存储卷的自动快照

boto是AWS的Python SDK,可以利用boto自动生成ec2的存储卷快照,实现ec2数据的备份 1.安装boot pip install boto ,如果没有安装pip,需要提前安装 2.配置boto配置文件 ~/.aws/credentials  #设置aws认证凭据   [default]  aws_access_key_id = AAAAAAAAAAAAAAA  aws_secret_access_key = TNAAAAXXXXXXXXXXXXXXXXXXXXXX ~/.aws

Android开发笔记(一百零九)利用网盘实现云存储

网盘存储 个人开发者往往没有自己的后台服务器,但同时又想获取app的运行信息,这就要借助于第三方的网络存储(也叫网盘.云盘.微盘等等).通过让app自动在网盘上存取文件,可以间接实现后台服务器的存储功能,同时开发者也能及时找到app的用户信息. 曾几何时,各大公司纷纷推出免费的个人网盘服务,还开放了文件管理api给开发者调用,一时间涌现了网盘提供商的八大金刚:百度网盘.阿里云.华为网盘.腾讯微云.新浪微盘.360云盘.金山快盘.115网盘.可是好景不长,出于盈利.监管等等因素,各大网盘开放平台要

centos 6.5环境利用iscsi搭建SAN网络存储服务及服务端target和客户端initiator配置详解

一.简介 iSCSI(internet SCSI)技术由IBM公司研究开发,是一个供硬件设备使用的.可以在IP协议的上层运行的SCSI指令集,这种指令集合可以实现在IP网络上运行SCSI协议,使其能够在诸如高速千兆以太网上进行路由选择.iSCSI技术是一种新储存技术,该技术是将现有SCSI接口与以太网络(Ethernet)技术结合,使服务器可与使用IP网络的储存装置互相交换资料. iSCSI是一种基于TCP/IP 的协议,用来建立和管理IP存储设备.主机和客户机等之间的相互连接,并创建存储区域网

ThinkPHP5.0下,利用Cookie和Session来存储用户信息

利用tp5框架封装好的Cookie类和Session类.若发现过期时间没有生效,可以试试清除缓存. 登录页面Login.php <?php/** * Created by PhpStorm. * User: zjl * Date: 2018/11/1 * Time: 15:21 */namespace app\admin\controller; use think\Controller;use think\Request;use think\Session;use think\Cookie; c