之前说过,如果一个数据库中要存储的数据量整体比较小,但是其中一个表存储的数据比较多,比如日志表,这时候就要考虑分表存储了;但是如果一个数据库整体存储的容量就比较大,该怎么办呢?这时候就需要考虑分库了,就是建立多个数据库保存数据。这里以答案为例,就算调查对象不是很多,但是参与调查的人数非常多,那么需要保存的数据量就会非常大,怎样将答案以一种规则保存到不同的数据库中就是现在需要考虑的问题(查询分库的问题未解决,先存档)。
一、分库方法
分库分为水平分库和竖直分库两种类型。
(1)水平分库
数据库之间是同构的,但是数据的存储范围不同。比如之后我将使用水平分库的方法保存答案到不同的数据库中。两个数据库中都有答案表,而且字段和约束等完全相同,两者的差异只是保存的数据不同,这样的分库方法就是水平分库。
(2)竖直分库
数据库和数据库之间的结构不相同,比如一个数据库存放一个模块的功能,每个模块的独立性比较强。而且量比较大。
二、实现答案分库的步骤:以2个数据库为例说明
1.创建第二个数据库lsn_surveypark1
2.配置数据源
因为之前已经配置过数据源了,所以这里只需要直接继承上一个数据源并且修改url地址即可
1 <!-- 配置数据源(主库) --> 2 <bean id="dateSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 3 <property name="driverClass" value="${jdbc.driverclass}"></property> 4 <property name="jdbcUrl" value="${jdbc.url}"></property> 5 <property name="user" value="${jdbc.username}"></property> 6 <property name="password" value="${jdbc.password}"></property> 7 8 <!-- 配置c3p0自身的参数 --> 9 <property name="maxPoolSize" value="${c3p0.pool.maxsize}"></property> 10 <property name="minPoolSize" value="${c3p0.pool.minsize}"></property> 11 <property name="initialPoolSize" value="${c3p0.pool.initsieze}"></property> 12 <property name="acquireIncrement" value="${c3p0.pool.increment}"></property> 13 </bean> 14 <!-- (从库) 为了实现分库的功能,必须针对每个数据库配置一个数据源 这里使用了包的继承的特殊属性使用parent属性对dataSource进行了继承 --> 15 <bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource" 16 parent="dateSource"> 17 <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/lsn_surveypark1"></property> 18 </bean>
3.配置数据源路由器
数据源路由器将会根据策略决定使用的数据源。
1 <!-- 配置数据源路由器 --> 2 <bean id="dataSource_router" class="com.kdyzm.datasource.SurveyparkDatasourceRouter"> 3 <property name="targetDataSources"> 4 <map> 5 <!-- 如果id是偶数,保存到主库中 --> 6 <entry key="even" value-ref="dateSource"></entry> 7 <!-- 如果id是奇数,保存到从库中 --> 8 <entry key="odd" value-ref="dataSource1"></entry> 9 </map> 10 </property> 11 <!-- 如果不满足上述规则,则直接使用默认的数据源 --> 12 <property name="defaultTargetDataSource" ref="dateSource"></property> 13 </bean>
这里的策略封装到了一个类中SurveyparkDatasourceRouter,该类必须继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource抽象类并重写determineCurrentLookupKey方法确定策略。
4.自定义路由数据源策略
自定义的方法就是继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource类并重写抽象方法。
1 package com.kdyzm.datasource; 2 3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 4 5 import com.kdyzm.domain.Survey; 6 7 /** 8 * 自定义数据源路由器 9 * 有一个默认的实现类,该类是以传播属性来路由数据的。 10 * @author kdyzm 11 * 12 */ 13 public class SurveyparkDatasourceRouter extends AbstractRoutingDataSource{ 14 15 /** 16 * 该方法实际上确定了一个数据向哪里存放的策略 17 * 在这里使用id的就属性来确定 18 * 如果答案id是偶数,就想lsn_surveypark数据库中的answer表(主表)中存放 19 * 如果答案的id是奇数,就向lsn_surveypark1数据库中的answer表(从表)中存放 20 */ 21 @Override 22 protected Object determineCurrentLookupKey() { 23 SurveyToken surveyToken=SurveyToken.getSurveyToken(); 24 if(surveyToken!=null){ 25 Survey survey=surveyToken.getSurvey(); 26 int surveyId=survey.getSurveyId(); 27 System.out.println("Survey对象不为空,值为:"+surveyId); 28 /** 29 * 在这里必须解除绑定 30 * 如果不在这里解除绑定的话就会将log日志写入到lsn_surveypark1数据库中。 31 * 由于lsn_surveypark1数据库中没有log表,所以一定会报错 32 */ 33 SurveyToken.unbind(); 34 return (surveyId%2)==0?"even":"odd"; //如果是偶数返回even字符串,如果是奇数返回odd字符串 35 } 36 System.out.println("survey对象为空"); 37 return null; 38 } 39 40 }
以上重写的方法中决定了路由数据源的策略:如果调查ID是偶数,就保存到主库lsn_surveypark的answer表中,如果是奇数,就保存到从库lsn_surveypark1中的answer表中。
接下来就是解决怎么拿到Survey对象的问题,上面的黄色背景部分的代码是关键。
注意,如果Survey对象为空,就使用默认的数据源:主库,这是由之前的配置文件中的配置决定的。
5.使用ThreadLocal解决拿到Survey的问题。
(1)分析问题