Spring MVC +Spring + Mybatis 構建分庫分表總結 SSM搭建以及分庫分表的實現
分庫分表在小型公司很少能遇到也很少使用,畢竟資料量沒有那麼大,當資料量大,所有資料都壓在一張表時,如果單從資料庫的角度考慮是可以分庫分表處理來儲存資料。分庫分表 顧名思義就是根據查詢條件動態的去獲取資料所在的庫和表的位置.例如一個系統有唯一的標識userNum,所有路由規則都可以根據userNum做庫表的定位工作.本文測試用3個庫5個表做測試.
三個庫book_00,book_01,book_02 每個庫裡的表 t_user_0000,t_user_0001,t_user_0002,t_user_0003,t_user_0004, 根據userNum尋找匹配庫的_00字尾,匹配表的_0000 字尾 即可定位到是幾庫幾表.
本人會建立一個新的專案,一步一步講解如果簡單的搭建一個分庫分表的系統並且用頁面展示從庫裡查詢出來的資料.
1,首先需要建立一個新的maven的webApp專案
使用框架版本:
Spring 4.0.2 RELEASE
Spring MVC 4.0.2 RELEASE
MyBatis 3.2.6
2,pom裡引入jar包
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.subdt</groupId> <artifactId>subDbTable</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>subDbTable Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <!-- spring版本號 --> <spring.version>4.0.2.RELEASE</spring.version> <!-- mybatis版本號 --> <mybatis.version>3.2.6</mybatis.version> <!-- log4j日誌檔案管理包版本 --> <slf4j.version>1.7.7</slf4j.version> <log4j.version>1.2.17</log4j.version> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <!-- 表示開發的時候引入,釋出的時候不會載入此包 --> <scope>test</scope> </dependency> <!-- spring核心包 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency> <!-- mybatis核心包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <!-- mybatis/spring包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency> <!-- 匯入java ee jar 包 --> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>7.0</version> </dependency> <!-- 匯入Mysql資料庫連結jar包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.30</version> </dependency> <!-- 匯入dbcp的jar包,用來在applicationContext.xml中配置資料庫 --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.2.2</version> </dependency> <!-- JSTL標籤類 --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 日誌檔案管理包 --> <!-- log start --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <!-- 格式化物件,方便輸出日誌 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.1.41</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!-- log end --> <!-- 映入JSON --> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.13</version> </dependency> <!-- 上傳元件包 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.9</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>1.7.1</version> </dependency> </dependencies> <build> <finalName>subDbTable</finalName> <resources> <!--編譯之後包含xml--> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
首先看下目錄結構
3,Spring與MyBatis的整合
首先建立 jdbc.properties 配置資料庫資訊
driver=com.mysql.jdbc.Driver #定義初始連線數 initialSize=0 #定義最大連線數 maxActive=20 #定義最大空閒 maxIdle=20 #定義最小空閒 minIdle=1 #定義最長等待時間 maxWait=60000 jdbc.mysql.url0=jdbc:mysql://localhost:3306/book_02?createDatabaseIfNotExist=true&characterEncoding=utf-8&useUnicode=true jdbc.mysql.username0=root jdbc.mysql.password0=123456 jdbc.mysql.url1=jdbc:mysql://localhost:3306/book_00?createDatabaseIfNotExist=true&characterEncoding=utf-8&useUnicode=true jdbc.mysql.username1=root jdbc.mysql.password1=123456 jdbc.mysql.url2=jdbc:mysql://localhost:3306/book_01?createDatabaseIfNotExist=true&characterEncoding=utf-8&useUnicode=true jdbc.mysql.username2=root jdbc.mysql.password2=123456
建立Spring配置檔案 spring-config.xml
<?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"
default-autowire="byName">
<context:component-scan base-package="com.sub.dt"/>
<aop:aspectj-autoproxy/>
<!-- 屬性檔案讀入 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:props/*.properties</value>
</list>
</property>
</bean>
<import resource="classpath:spring/spring-*.xml"/>
</beans>
建立spring與mybatis結合的整合配置檔案. spring-config-datasource-dbcp.xml
這個檔案主要是多資料來源配置,自動掃描mybatis的xml配置檔案.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 引入配置檔案 -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:props/jdbc.properties"/>
</bean>
<bean id="dataSource0" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${jdbc.mysql.url0}"/>
<property name="username" value="${jdbc.mysql.username0}"/>
<property name="password" value="${jdbc.mysql.password0}"/>
<!-- 初始化連線大小 -->
<property name="initialSize" value="${initialSize}"></property>
<!-- 連線池最大數量 -->
<property name="maxActive" value="${maxActive}"></property>
<!-- 連線池最大空閒 -->
<property name="maxIdle" value="${maxIdle}"></property>
<!-- 連線池最小空閒 -->
<property name="minIdle" value="${minIdle}"></property>
<!-- 獲取連線最大等待時間 -->
<property name="maxWait" value="${maxWait}"></property>
</bean>
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${jdbc.mysql.url1}"/>
<property name="username" value="${jdbc.mysql.username1}"/>
<property name="password" value="${jdbc.mysql.password1}"/>
<!-- 初始化連線大小 -->
<property name="initialSize" value="${initialSize}"></property>
<!-- 連線池最大數量 -->
<property name="maxActive" value="${maxActive}"></property>
<!-- 連線池最大空閒 -->
<property name="maxIdle" value="${maxIdle}"></property>
<!-- 連線池最小空閒 -->
<property name="minIdle" value="${minIdle}"></property>
<!-- 獲取連線最大等待時間 -->
<property name="maxWait" value="${maxWait}"></property>
</bean>
<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${jdbc.mysql.url2}"/>
<property name="username" value="${jdbc.mysql.username2}"/>
<property name="password" value="${jdbc.mysql.password2}"/>
<!-- 初始化連線大小 -->
<property name="initialSize" value="${initialSize}"></property>
<!-- 連線池最大數量 -->
<property name="maxActive" value="${maxActive}"></property>
<!-- 連線池最大空閒 -->
<property name="maxIdle" value="${maxIdle}"></property>
<!-- 連線池最小空閒 -->
<property name="minIdle" value="${minIdle}"></property>
<!-- 獲取連線最大等待時間 -->
<property name="maxWait" value="${maxWait}"></property>
</bean>
<bean id="mysqlDynamicDataSource" class="com.sub.dt.dbRouting.db.DynamicDataSource">
<property name="targetDataSources">
<!-- 識別符號型別 -->
<map>
<entry key="db0" value-ref="dataSource0"/>
<entry key="db1" value-ref="dataSource1"/>
<entry key="db2" value-ref="dataSource2"/>
</map>
</property>
</bean>
<bean id="dbRuleSet" class="com.sub.dt.dbRouting.bean.RouterSet">
<property name="routeFieldStart" value="0"></property>
<property name="routeFieldEnd" value="9200000000000000000"></property>
<property name="dbNumber" value="3"></property>
<property name="routeType" value="2"></property>
<property name="ruleType" value="3"></property>
<property name="tableNumber" value="5"></property>
<property name="dbKeyArray">
<list>
<value>db0</value>
<value>db1</value>
<value>db2</value>
</list>
</property>
</bean>
<bean id="dBRouter" class="com.sub.dt.dbRouting.router.DBRouterImpl">
<property name="routerSetList">
<!-- 識別符號型別 -->
<list>
<ref bean="dbRuleSet"/>
</list>
</property>
</bean>
<!--事務-->
<bean id="baiTiaoTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="mysqlDynamicDataSource"></property>
</bean>
<bean id="btTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="baiTiaoTransactionManager"></property>
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property>
</bean>
<!-- spring和MyBatis完美整合,不需要mybatis的配置對映檔案 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="mysqlDynamicDataSource"/>
<!-- 自動掃描mapping.xml檔案 -->
<property name="mapperLocations" value="classpath:com/sub/dt/mapping/*.xml"></property>
</bean>
<!-- DAO介面所在包名,Spring會自動查詢其下的類 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.sub.dt.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
</beans>
程式使用spring aop切面+自定義註解的方式去在每個方法呼叫時根據userNum定位庫表, 關鍵類已標紅(程式碼會上傳附件自行下載檢視)
spring-config-datasource-dbcp.xml中配置的com.sub.dt.dbRouting.db.DynamicDataSource 繼承 AbstractRoutingDataSource方法,單表配置mybatis是使用的是BasicDataSource
BasicDataSourBasicDataSource
BasicDataSource 自定義的AbstractRoutingDataSource與spring的BasicDataSource都是一個道理 管理資料來源 實現了DataSource介面.
package com.sub.dt.dbRouting.db;
import com.sub.dt.dbRouting.DbContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.logging.Logger;
/**
* @Description SPring 的動態資料來源的實現
* @Autohr supers【weChat:13031016567】
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public static final Logger logger = Logger.getLogger(DynamicDataSource.class.toString());
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbKey();//獲取當前資料來源
}
}
UserMapper.xml建立庫表對應的xml檔案<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sub.dt.dao.IUserDao">
<resultMap id="BaseResultMap" type="com.sub.dt.pojo.User">
<id column="id" property="id" jdbcType="INTEGER"/>
<result column="user_name" property="userName" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
<result column="age" property="age" jdbcType="INTEGER"/>
<result column="user_num" property="userNum" jdbcType="VARCHAR"/>
</resultMap>
<sql id="Base_Column_List">
id,user_num, user_name, password, age
</sql>
<insert id="insertUser" parameterType="com.sub.dt.pojo.User">
insert into t_user${tableIndex} (id,user_num,user_name, password,age)
values (#{id,jdbcType=INTEGER},#{userNum,jdbcType=VARCHAR}, #{userName,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR},#{age,jdbcType=INTEGER})
</insert>
<insert id="deleteByuserNum" parameterType="com.sub.dt.pojo.User">
delete from t_user${tableIndex}
where user_num = #{userNum,jdbcType=VARCHAR}
</insert>
<update id="updateByUserNum" parameterType="com.sub.dt.pojo.User">
update t_user${tableIndex}
<set>
<if test="userName != null">
user_name = #{userName,jdbcType=VARCHAR},
</if>
<if test="password != null">
password = #{password,jdbcType=VARCHAR},
</if>
<if test="age != null">
age = #{age,jdbcType=INTEGER},
</if>
</set>
where user_num = #{userNum,jdbcType=VARCHAR}
</update>
<select id="selectByUserNum" resultMap="BaseResultMap" parameterType="com.sub.dt.pojo.User">
select
<include refid="Base_Column_List"/>
from t_user${tableIndex}
where user_num = #{userNum,jdbcType=VARCHAR}
</select>
</mapper>
IUserDao.java
package com.sub.dt.dao;
import com.sub.dt.pojo.User;
public interface IUserDao {
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
int insertUser(User user);
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
int deleteByuserNum(User user);
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
int updateByUserNum(User user);
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
User selectByUserNum(User user);
}
IUserService.java
package com.sub.dt.service;
import com.sub.dt.pojo.User;
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
public interface IUserService {
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
public int insertUser(User user);
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
public int deleteByuserNum(User user);
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
public int updateByUserNum(User user);
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
public User selectByUserNum(User user);
}
UserServiceImpl.java
package com.sub.dt.service.impl;
import com.sub.dt.dao.IUserDao;
import com.sub.dt.dbRouting.annotation.Router;
import com.sub.dt.pojo.User;
import com.sub.dt.service.IUserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
@Service("userService")
public class UserServiceImpl implements IUserService {
@Resource
private IUserDao userDao;
@Router
public int insertUser(User user) {
return this.userDao.insertUser(user);
}
@Router
public int deleteByuserNum(User user) {
return this.userDao.deleteByuserNum(user);
}
@Router
public int updateByUserNum(User user) {
return this.userDao.updateByUserNum(user);
}
@Router
public User selectByUserNum(User user) {
return this.userDao.selectByUserNum(user);
}
}
UserController.java
package com.sub.dt.controller;
import com.sub.dt.pojo.User;
import com.sub.dt.service.IUserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/user")
public class UserController {
@Resource
private IUserService userService;
@RequestMapping("/queryUser")
public String toIndex(HttpServletRequest request,Model model,User user){
User userDb = this.userService.selectByUserNum(user);
model.addAttribute("user", userDb);
return "queryUser";
}
}
4,核心分庫分表包都在dbRouting資料夾下
package com.sub.dt.dbRouting.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Router {
String routerField() default RouterConstants.ROUTER_FIELD_DEFAULT;
String tableStyle() default RouterConstants.ROUTER_TABLE_SUFFIX_DEFAULT;
}
DBRouterInterceptor.java 定義切點為方法上使用,@Router註解的方法,執行註解方法前前執行doRoute方法,根據引數中的userNum設定是幾庫幾表
package com.sub.dt.dbRouting;
import com.sub.dt.dbRouting.annotation.Router;
import com.sub.dt.dbRouting.annotation.RouterConstants;
import com.sub.dt.dbRouting.router.RouterUtils;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @Description 切面切點 在Router註解的方法執行前執行 切點織入
* @Autohr supers【weChat:13031016567】
*/
@Aspect
@Component
public class DBRouterInterceptor {
private static final Logger log = LoggerFactory.getLogger(DBRouterInterceptor.class);
private DBRouter dBRouter;
@Pointcut("@annotation( com.sub.dt.dbRouting.annotation.Router)")
public void aopPoint() {
}
@Before("aopPoint()")
public Object doRoute(JoinPoint jp) throws Throwable {
long t1 = System.currentTimeMillis();
boolean result = true;
Method method = getMethod(jp);
Router router = method.getAnnotation(Router.class);
String routeField = router.routerField();
Object[] args = jp.getArgs();
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
long t2 = System.currentTimeMillis();
String routeFieldValue = BeanUtils.getProperty(args[i],
routeField);
log.debug("routeFieldValue{}" + (System.currentTimeMillis() - t2));
if (StringUtils.isNotEmpty(routeFieldValue)) {
if (RouterConstants.ROUTER_FIELD_DEFAULT.equals(routeField)) {
dBRouter.doRouteByResource("" + RouterUtils.getResourceCode(routeFieldValue));
break;
} else {
this.searchParamCheck(routeFieldValue);
String resource = routeFieldValue.substring(routeFieldValue.length() - 4);
dBRouter.doRouteByResource(resource);
break;
}
}
}
}
log.debug("doRouteTime{}" + (System.currentTimeMillis() - t1));
return result;
}
private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
Signature sig = jp.getSignature();
MethodSignature msig = (MethodSignature) sig;
return getClass(jp).getMethod(msig.getName(), msig.getParameterTypes());
}
private Class<? extends Object> getClass(JoinPoint jp)
throws NoSuchMethodException {
return jp.getTarget().getClass();
}
/**
* 查詢支付結構引數檢查
*
* @param payId
*/
private void searchParamCheck(String payId) {
if (payId.trim().equals("")) {
throw new IllegalArgumentException("payId is empty");
}
}
public DBRouter getdBRouter() {
return dBRouter;
}
public void setdBRouter(DBRouter dBRouter) {
this.dBRouter = dBRouter;
}
}
RouterSet.java 主要是 配置在spring-config-datasource-dbcp.xml中 儲存庫的數量,表的數量,庫表相關資訊
package com.sub.dt.dbRouting.bean;
import java.util.List;
/**
* @Description
* @Autohr supers【weChat:13031016567】
*/
public class RouterSet {
/**根據字串*/
public final static int RULE_TYPE_STR=3;
public final static int ROUTER_TYPE_DB=0;
public final static int ROUTER_TYPE_TABLE =1;
public final static int ROUTER_TYPE_DBANDTABLE=2;
/**資料庫表的邏輯KEY,與資料來源MAP配置中的key一致*/
private List<String> dbKeyArray;
/**資料庫數量*/
private int dbNumber;
/**資料表數量*/
private int tableNumber;
/**資料表index樣式*/
private String tableIndexStyle;
/**Id開始*/
private String routeFieldStart;
/**Id結束*/
private String routeFieldEnd;
/**規則型別*/
private int ruleType;
/**路由型別型別*/
private int routeType;
public static int getRULE_TYPE_STR() {
return RULE_TYPE_STR;
}
public static int getROUTER_TYPE_DB() {
return ROUTER_TYPE_DB;
}
public static int getROUTER_TYPE_TABLE() {
return ROUTER_TYPE_TABLE;
}
public static int getROUTER_TYPE_DBANDTABLE() {
return ROUTER_TYPE_DBANDTABLE;
}
public List<String> getDbKeyArray() {
return dbKeyArray;
}
public void setDbKeyArray(List<String> dbKeyArray) {
this.dbKeyArray = dbKeyArray;
}
public int getDbNumber() {
return dbNumber;
}
public void setDbNumber(int dbNumber) {
this.dbNumber = dbNumber;
}
public int getTableNumber() {
return tableNumber;
}
public void setTableNumber(int tableNumber) {
this.tableNumber = tableNumber;
}
public String getTableIndexStyle() {
return tableIndexStyle;
}
public void setTableIndexStyle(String tableIndexStyle) {
this.tableIndexStyle = tableIndexStyle;
}
public String getRouteFieldStart() {
return routeFieldStart;
}
public void setRouteFieldStart(String routeFieldStart) {
this.routeFieldStart = routeFieldStart;
}
public String getRouteFieldEnd() {
return routeFieldEnd;
}
public void setRouteFieldEnd(String routeFieldEnd) {
this.routeFieldEnd = routeFieldEnd;
}
public int getRuleType() {
return ruleType;
}
public void setRuleType(int ruleType) {
this.ruleType = ruleType;
}
public int getRouteType() {
return routeType;
}
public void setRouteType(int routeType) {
this.routeType = routeType;
}
}
DBRouterImpl.java getDbKey方法根據userNum定位庫表演算法
package com.sub.dt.dbRouting.router;
import com.sub.dt.dbRouting.DBRouter;
import com.sub.dt.dbRouting.DbContextHolder;
import com.sub.dt.dbRouting.annotation.RouterConstants;
import com.sub.dt.dbRouting.bean.RouterSet;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.List;
/**
* @Description 根據指定變數動態切 庫和表
* @Autohr supers【weChat:13031016567】
*/
public class DBRouterImpl implements DBRouter {
private static final Logger log = LoggerFactory.getLogger(DBRouterImpl.class);
/**
* 配置列表
*/
private List<RouterSet> routerSetList;
@Override
public String doRoute(String fieldId) {
if (StringUtils.isEmpty(fieldId)) {
throw new IllegalArgumentException("dbsCount and tablesCount must be both positive!");
}
int routeFieldInt = RouterUtils.getResourceCode(fieldId);
String dbKey = getDbKey(routerSetList, routeFieldInt);
return dbKey;
}
@Override
public String doRouteByResource(String resourceCode) {
if (StringUtils.isEmpty(resourceCode)) {
throw new IllegalArgumentException("dbsCount and tablesCount must be both positive!");
}
int routeFieldInt = Integer.valueOf(resourceCode);
String dbKey = getDbKey(routerSetList, routeFieldInt);
return dbKey;
}
/**
* @Description 根據資料欄位來判斷屬於哪個段的規則,獲得資料庫key
* @Autohr supers【weChat:13031016567】
*/
private String getDbKey(List<RouterSet> routerSets, int routeFieldInt) {
RouterSet routerSet = null;
if (routerSets == null || routerSets.size() <= 0) {
throw new IllegalArgumentException("dbsCount and tablesCount must be both positive!");
}
String dbKey = null;
for (RouterSet item : routerSets) {
if (item.getRuleType() == routerSet.RULE_TYPE_STR) {
routerSet = item;
if (routerSet.getDbKeyArray() != null && routerSet.getDbNumber() != 0) {
long dbIndex = 0;
long tbIndex = 0;
//預設按照分庫進行計算
long mode = routerSet.getDbNumber();
//如果是按照分庫分表的話,計算
if (item.getRouteType() == RouterSet.ROUTER_TYPE_DBANDTABLE && item.getTableNumber() != 0) {
mode = routerSet.getDbNumber() * item.getTableNumber();
dbIndex = routeFieldInt % mode / item.getTableNumber();
tbIndex = routeFieldInt % item.getTableNumber();
String tableIndex = getFormateTableIndex(item.getTableIndexStyle(), tbIndex);
DbContextHolder.setTableIndex(tableIndex);
} else if (item.getRouteType() == RouterSet.ROUTER_TYPE_DB) {
mode = routerSet.getDbNumber();
dbIndex = routeFieldInt % mode;
} else if (item.getRouteType() == RouterSet.ROUTER_TYPE_TABLE) {
tbIndex = routeFieldInt % item.getTableNumber();
String tableIndex = getFormateTableIndex(item.getTableIndexStyle(), tbIndex);
DbContextHolder.setTableIndex(tableIndex);
}
dbKey = routerSet.getDbKeyArray().get(Long.valueOf(dbIndex).intValue());
log.debug("getDbKey resource:{}------->dbkey:{},tableIndex:{},", new Object[]{routeFieldInt, dbKey, tbIndex});
DbContextHolder.setDbKey(dbKey);
}
break;
}
}
return dbKey;
}
/**
* @Description 此方法是將例如+++0000根式的字串替換成傳引數字例如44 變成+++0044
* @Autohr supers【weChat:13031016567】
*/
private static String getFormateTableIndex(String style, long tbIndex) {
String tableIndex = null;
DecimalFormat df = new DecimalFormat();
if (StringUtils.isEmpty(style)) {
style = RouterConstants.ROUTER_TABLE_SUFFIX_DEFAULT;//在格式後新增諸如單位等字元
}
df.applyPattern(style);
tableIndex = df.format(tbIndex);
return tableIndex;
}
public List<RouterSet> getRouterSetList() {
return routerSetList;
}
public void setRouterSetList(List<RouterSet> routerSetList) {
this.routerSetList = routerSetList;
}
}
spring-mvc.xml這個很簡單了 就是前段與後端controller層的結合,不多說.
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- 自動掃描該包,使SpringMVC認為包下用了@controller註解的類是控制器 -->
<context:component-scan base-package="com.sub.dt.controller"/>
<!--避免IE執行AJAX時,返回JSON出現下載檔案 -->
<bean id="mappingJacksonHttpMessageConverter"
class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/html;charset=UTF-8</value>
</list>
</property>
</bean>
<!-- 啟動SpringMVC的註解功能,完成請求和註解POJO的對映 -->
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="mappingJacksonHttpMessageConverter"/>
<!-- JSON轉換器 -->
</list>
</property>
</bean>
<!-- 定義跳轉的檔案的前後綴 ,檢視模式配置-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 這裡的配置我的理解是自動給後面action的方法return的字串加上字首和字尾,變成一個 可用的url地址 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置檔案上傳,如果沒有使用檔案上傳可以不用配置,當然如果不配,那麼配置檔案中也不必引入上傳元件包 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 預設編碼 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 檔案大小最大值 -->
<property name="maxUploadSize" value="10485760000"/>
<!-- 記憶體中的最大值 -->
<property name="maxInMemorySize" value="40960"/>
</bean>
</beans>
web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Archetype Created Web Application</display-name>
<!-- Spring和mybatis的配置檔案 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-config.xml</param-value>
</context-param>
<!-- 編碼過濾器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Spring監聽器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 防止Spring記憶體溢位監聽器 -->
<listener>
<listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
</listener>
<!-- Spring MVC servlet -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<!-- 此處可以可以配置成*.do,對應struts的字尾習慣 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>/index.jsp</welcome-file>
</welcome-file-list>
</web-app>
奉上 mysql建立表的sql語句,首先自行建立三個庫book_00,book_01,book_02 每個庫都執行以下sql即可
/*
Navicat MySQL Data Transfer
Source Server : local
Source Server Version : 50173
Source Host : localhost:3306
Source Database : book_00
Target Server Type : MYSQL
Target Server Version : 50173
File Encoding : 65001
Date: 2017-03-10 09:48:12
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `t_user_0000`
-- ----------------------------
DROP TABLE IF EXISTS `t_user_0000`;
CREATE TABLE `t_user_0000` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_num` varchar(64) NOT NULL,
`user_name` varchar(16) NOT NULL,
`password` varchar(64) NOT NULL,
`age` int(4) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_num` (`user_num`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user_0000
-- ----------------------------
-- ----------------------------
-- Table structure for `t_user_0001`
-- ----------------------------
DROP TABLE IF EXISTS `t_user_0001`;
CREATE TABLE `t_user_0001` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_num` varchar(64) NOT NULL,
`user_name` varchar(16) NOT NULL,
`password` varchar(64) NOT NULL,
`age` int(4) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_num` (`user_num`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user_0001
-- ----------------------------
-- ----------------------------
-- Table structure for `t_user_0002`
-- ----------------------------
DROP TABLE IF EXISTS `t_user_0002`;
CREATE TABLE `t_user_0002` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_num` varchar(64) NOT NULL,
`user_name` varchar(16) NOT NULL,
`password` varchar(64) NOT NULL,
`age` int(4) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_num` (`user_num`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user_0002
-- ----------------------------
-- ----------------------------
-- Table structure for `t_user_0003`
-- ----------------------------
DROP TABLE IF EXISTS `t_user_0003`;
CREATE TABLE `t_user_0003` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_num` varchar(64) NOT NULL,
`user_name` varchar(16) NOT NULL,
`password` varchar(64) NOT NULL,
`age` int(4) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_num` (`user_num`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user_0003
-- ----------------------------
-- ----------------------------
-- Table structure for `t_user_0004`
-- ----------------------------
DROP TABLE IF EXISTS `t_user_0004`;
CREATE TABLE `t_user_0004` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_num` varchar(64) NOT NULL,
`user_name` varchar(16) NOT NULL,
`password` varchar(64) NOT NULL,
`age` int(4) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_num` (`user_num`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of t_user_0004
所有程式碼建立完畢
測試類 TestUserService.java
package com.sub.dt.service;
import com.sub.common.GsonUtils;
import com.sub.dt.dbRouting.DbContextHolder;
import com.sub.dt.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Created by supers on 2017/3/9.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/spring-config.xml")
public class TestUserService {
@Autowired
private IUserService userService;
/**
* @Description 測試分庫分表插入
* @Autohr supers【weChat:13031016567】
*/
@Test
public void testInsertUser(){
User user = new User();
user.setUserNum("wergsgdf3243");
user.setUserName("admin");
user.setAge(23);
user.setPassword("adf23");
int re = userService.insertUser(user);
System.out.println(DbContextHolder.getDbKey()+"庫 "+DbContextHolder.getTableIndex()+"表 的插入結果:"+GsonUtils.toJson(re));
}
/**
* @Description 測試分庫分表刪除
* @Autohr supers【weChat:13031016567】
*/
@Test
public void testDeleteByuserNum(){
User user = new User();
user.setUserNum("wergsgdf3243");
int re = userService.deleteByuserNum(user);
System.out.println(DbContextHolder.getDbKey()+"庫 "+DbContextHolder.getTableIndex()+"表 的刪除結果:"+GsonUtils.toJson(re));
}
/**
* @Description 測試分庫分表修改
* @Autohr supers【weChat:13031016567】
*/
@Test
public void testupdateByUserNum(){
User user = new User();
user.setUserNum("wergsgdf3243");
user.setAge(34);
int re = userService.updateByUserNum(user);
System.out.println(DbContextHolder.getDbKey()+"庫 "+DbContextHolder.getTableIndex()+"表 的更新結果:"+GsonUtils.toJson(re));
}
/**
* @Description 測試分庫分表查詢
* @Autohr supers【weChat:13031016567】
*/
@Test
public void testQueryUserByNum(){
User user = new User();
user.setId(1);
user.setUserNum("wergsgdf3243");
User userDb = userService.selectByUserNum(user);
System.out.println(DbContextHolder.getDbKey()+"庫 "+DbContextHolder.getTableIndex()+"表 的查詢結果:"+GsonUtils.toJson(userDb));
}
}
最後奉上 log4j.properties配置
#定義LOG輸出級別
log4j.rootLogger=DEBUG,Console,File
#定義日誌輸出目的地為控制檯
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
#可以靈活地指定日誌輸出格式,下面一行是指定具體的格式
log4j.appender.Console.layout = org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%c] - %m%n
#檔案大小到達指定尺寸的時候產生一個新的檔案
log4j.appender.File = org.apache.log4j.RollingFileAppender
#指定輸出目錄
log4j.appender.File.File = logs/ssm.log
#定義檔案最大大小
log4j.appender.File.MaxFileSize = 10MB
# 輸出所以日誌,如果換成DEBUG表示輸出DEBUG以上級別日誌
log4j.appender.File.Threshold = ALL
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c]%m%n