boot-start-ssm模块提供可选的分表支持

This commit is contained in:
lsy
2018-04-09 17:30:54 +08:00
parent 0202e29135
commit 9be56061eb
18 changed files with 363 additions and 12 deletions
+2 -3
View File
@@ -11,8 +11,7 @@
### 本地部署文档详见[ms开发文档](./all/ms/deploy.md)
## 项目概览
- `mybatis-generator-extend-maven-plugin`模块是便于快速根据条件生成mybatis-generator-plugin配置文件的maven扩展插件,基本增
删改查、按条件分页查询`dao` `servcie` `controller`层利用了词向量、卷积神经网络、使用人工智能技术进行代码语意分析(好吧,我吹不
下去了)自动生成,自动生成70%代码,降低开发中重复性耗时工作,此插件可在其他项目中单独使用。
删改查、按条件分页查询`dao` `servcie` `controller`层自动生成,自动生成70%代码,降低开发中重复性耗时工作,此插件可在其他项目中单独使用。
- 合理的拆分,拥有众多的可复用模块,开箱即用
- 高可用注册中心
- 高可用配置配置管理中心,通过`rabbitmq`支持配置刷新
@@ -25,7 +24,7 @@
- 支持docker容器部署
- 使用flume收集系统日志,便于日志分析
- 热点数据使用redis缓存
- 多数据源支持
- 多数据库,数据库分表支持
- 完善的项目使用文档
- 用心的开发者
## 整体技术选型
Binary file not shown.
+1 -1
View File
@@ -1,2 +1,2 @@
#!/usr/bin/env bash
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 25672:25672 -p 4369:4369 rabbitmq
docker run -d --name rabbitmq -p 5671:5671 -p 5672:5672 -p 25672:25672 -p 4369:4369 -p 15672:15672 -p 15671:15671 rabbitmq:3-management
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
cd /opt/tools/kafka_2.11-1.1.0
bin/kafka-server-start.sh config/server.properties
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
cd /opt/tools/kafka_2.11-1.1.0
bin/zookeeper-server-start.sh config/zookeeper.properties
+5 -1
View File
@@ -27,4 +27,8 @@ spring-boot提供了默认的启动时执行数据库脚本功能,指定`sprin
更多的数据源,像增加多个druid数据源一样增加`com.micro.fast.common.init.properties.SqlInitProperties`类的配置bean,然后满足上述
两个数据源时同样要求即可。
### 启用基于shardbatis的分表功能
- 通过设置shardbatis.configPath(shardbatis的xml配置文件的classpath路径)属性来激活分表功能.使用`@IdShard`来在mapper接口参数中指定分表策略字段
或者`@IdShard('filedName')`来指定参数对象中哪个字段是分表策略所需的字段.
- shardbatis.tableCount 指定分表的数量
- 注意,使用默认的分表策略的时候mapper接口中不允许使用只有一个map传递参数.
+10
View File
@@ -55,6 +55,16 @@
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</dependency>
<!--分表插件-->
<dependency>
<groupId>org.shardbatis</groupId>
<artifactId>shardbatis</artifactId>
<version>2.0.0B</version>
</dependency>
<dependency>
<groupId>net.sf.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</dependency>
<!--数据库连接-->
<dependency>
<groupId>mysql</groupId>
@@ -1,6 +1,7 @@
package com.micro.fast.common.config;
import com.github.pagehelper.PageHelper;
import com.google.code.shardbatis.plugin.ShardPlugin;
import com.micro.fast.common.config.param.MybatisParamConfig;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
@@ -8,12 +9,12 @@ import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@@ -49,6 +50,13 @@ public class SqlSessionFactoryConfig implements TransactionManagementConfigurer{
@Autowired
private MybatisParamConfig mybatisParamConfig;
/**
* 分表插件的配置
*/
@Autowired(required = false)
@Qualifier("shardPlugin")
private ShardPlugin shardPlugin;
/**
* mybatis连接工厂bean
* @return sqlSessionFactory
@@ -97,7 +105,12 @@ public class SqlSessionFactoryConfig implements TransactionManagementConfigurer{
}
}
pageHelper.setProperties(properties);
sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageHelper});
// 选择性启用shardbatisf分表插件
if (shardPlugin == null){
sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageHelper});
} else {
sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageHelper,shardPlugin});
}
//设置详细的配置
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
//开启下划线转驼峰命名
@@ -0,0 +1,13 @@
package com.micro.fast.common.shardbatis;
import java.lang.annotation.*;
/**
* 指定分表依据的参数的注解,分表的参数上必须有这个注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@Documented
public @interface IdShard{
String value() default "id";
}
@@ -0,0 +1,158 @@
package com.micro.fast.common.shardbatis;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* 根据idshard注解判断哪个参数是分表的依据,以及根据反射获取分表的依据参数的值
*
* @author lsy
*/
@Slf4j
public class IdShardUtils {
public static final String PARAM = "param";
/**
* 工具类私有构造防止被实例化
*/
private IdShardUtils() {
}
/**
* 根据statementId反射出方法,查找方法中带有IdShard朱姐标记的参数,获取其值用于分表策略
*
* @param params
* @param statementId
* @return
*/
public static Object getShardIdValue(Object params, String statementId) throws Exception {
int lastIndexOfD = statementId.lastIndexOf(".");
String methodName = statementId.substring(lastIndexOfD + 1);
String className = statementId.substring(0, lastIndexOfD);
Class<?> aClass = null;
try {
aClass = Class.forName(className);
Method[] methods = aClass.getMethods();
for (Method method : methods) {
String name = method.getName();
int parameterIndex = 0;
// 方法名相同就找到了语句
if (name.equals(methodName)) {
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
parameterIndex++;
// 参数是否包含IdShard.class注解
Annotation[] annotations = parameter.getAnnotations();
for (Annotation annotation : annotations) {
// 找到带有分表标记符号的参数
if (annotation.annotationType() == IdShard.class) {
Class<?> type = parameter.getType();
// statement是不是只有一个参数,有一个的参数的话参数类型应该相同.但是这一个参数不能是map类型
if (params.getClass() == type) {
return getValueByFieldName((IdShard) annotation,params);
} else {
// 如果有多个参数的话,mybatis会把参数封装成map类型,key是param+参数的顺序
String key = PARAM + (parameterIndex + 1);
Map<String,Object> map = (HashMap<String,Object>)params;
Object o = map.get(key);
return getValueByFieldName((IdShard) annotation,o);
}
}
}
}
}
}
} catch (ClassNotFoundException e) {
log.info("sql语句对应的类不存在:{}", e);
}
// 找不到就返回null
return null;
}
/**
* 根据注解和mybatis的参数
* @param idShard
* @param params
* @return
* @throws Exception
*/
private static Object getValueByFieldName(IdShard idShard, Object params) throws Exception {
// 如果是基础类型(表明是主键)就直接返回
if (isBasicType(params)) {
return params;
} else {
// 如果不是基础类型,就使用IdShard的value属性指定对象的哪个属性是分表策略需要的值
String shardFieldName = idShard.value();
if (StringUtils.isBlank(shardFieldName)) {
throw new Exception("the shardFieldName was not annotated");
}
Field field = null;
try {
// 根据字段名反射获取字段的类类型
field = params.getClass().getDeclaredField(shardFieldName);
} catch (NoSuchFieldException e) {
field = params.getClass().getSuperclass().getDeclaredField(shardFieldName);
}
// 设置属性的访问权限
field.setAccessible(true);
// 获取对象中属性的值
return field.get(params);
}
}
/**
*  检查参数是不是基础类型
*
* @param param
* @return
*/
private static boolean isBasicType(Object param) {
if (param == null) {
return false;
}
if (param instanceof String) {
return true;
}
if (param instanceof BigDecimal) {
return true;
}
if (param instanceof Integer) {
return true;
}
if (param instanceof Long) {
return true;
}
if (param instanceof Double) {
return true;
}
if (param instanceof Float) {
return true;
}
if (param instanceof Character) {
return true;
}
if (param instanceof Byte) {
return true;
}
if (param instanceof Short) {
return true;
}
if (param instanceof Boolean) {
return true;
}
return false;
}
}
@@ -0,0 +1,32 @@
package com.micro.fast.common.shardbatis.config;
import com.google.code.shardbatis.plugin.ShardPlugin;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* shardbatis的配置
* @author lsy
*/
@Configuration
public class ShardbatisConfig {
/**
* shardPlugin的配置类,当这个配置属性不存在的时候分表插件不生效
* @param xmlClassPath shard
* @return
*/
@Bean(name = "shardPlugin")
@ConditionalOnProperty(prefix = "shardbatis",name = "configPath",matchIfMissing = false)
public ShardPlugin shardPlugin(@Value("${shardbatis.configPath}") String xmlClassPath){
ShardPlugin shardPlugin = new ShardPlugin();
Properties properties = new Properties();
properties.setProperty(ShardPlugin.SHARDING_CONFIG,xmlClassPath);
shardPlugin.setProperties(properties);
return shardPlugin;
}
}
@@ -0,0 +1,4 @@
/**
* shardbatis的java config 包
*/
package com.micro.fast.common.shardbatis.config;
@@ -0,0 +1,4 @@
/**
* 利用shardbatis实现分表
*/
package com.micro.fast.common.shardbatis;
@@ -0,0 +1,4 @@
/**
* shardbatis插件配置参数
*/
package com.micro.fast.common.shardbatis.properties;
@@ -0,0 +1,66 @@
package com.micro.fast.common.shardbatis.shardstrategy;
import com.google.code.shardbatis.strategy.ShardStrategy;
import com.micro.fast.common.config.SqlSessionFactoryConfig;
import com.micro.fast.common.shardbatis.IdShardUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Configuration;
/**
* 提供默认的分表策略
* @author lsy
*/
@Slf4j
@Configuration
@AutoConfigureBefore(SqlSessionFactoryConfig.class)
public class SsmDefaultShardStrategy implements ShardStrategy{
private static Integer tableCount;
/**
* 分表策略的实现
* @param baseTableName 逻辑表名
* @param params mybatis语句的参数
* @param statementId 语句的id,一般是类名.方法名
* @return 
*/
@Override
public String getTargetTableName(String baseTableName, Object params, String statementId) {
StringBuilder shardTableName = new StringBuilder(baseTableName);
shardTableName.append("_");
try {
Object shardIdValue = IdShardUtils.getShardIdValue(params, statementId);
if (shardIdValue == null){
shardTableName.append("000");
}else {
String s = shardIdValue.toString();
Integer integer = Integer.valueOf(s);
Integer index = integer % tableCount;
if (index<10){
shardTableName.append("00"+index);
} else if (index<100){
shardTableName.append("0"+index);
} else {
shardTableName.append(index);
}
}
} catch (Exception e) {
log.info("分表失败:{}",e);
throw new RuntimeException("分表失败");
}
return shardTableName.toString();
}
/**
* springBoot不允许直接给静态变量赋值,但是可以通过非静态方法给静态变量赋值
* @param tableCount
*/
@Value("${shardbatis.tableCount}")
public void setTableCount(Integer tableCount){
SsmDefaultShardStrategy.tableCount = tableCount;
}
}
@@ -0,0 +1,4 @@
/**
*提供默认的分表策略
*/
package com.micro.fast.common.shardbatis.shardstrategy;
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE shardingConfig PUBLIC
"-//shardbatis.googlecode.com//DTD Shardbatis 2.0//EN"
"http://shardbatis.googlecode.com/dtd/shardbatis-config.dtd">
<!-- shardbatis的示例配置-->
<shardingConfig>
<ignoreList>
<value></value>
</ignoreList>
<!-- parseList可选配置 如果配置了parseList,只有在parseList范围内并且不再ignoreList内的sql才会被解析和修改 -->
<parseList>
<value>xxx.dao.UserMapper.insertSelective</value>
<value>xxx.dao.UserMapper.selectByPrimaryKey</value>
<value>xxx.UserMapper.updateByPrimaryKeySelective</value>
</parseList>
<!-- 配置分表策略 tableName指的是实际的表名,strategyClass对应的分表策略实现类 -->
<strategy tableName="user" strategyClass="xxx.UserShardStrategyImpl"/>
</shardingConfig>
+18 -5
View File
@@ -85,6 +85,8 @@
<spring-boot-devtools.optional>true</spring-boot-devtools.optional>
<spring-boot-admin.version>1.5.7</spring-boot-admin.version>
<quartz.version>2.2.1</quartz.version>
<shardbatis.version>2.0.0B</shardbatis.version>
<net.sf.jsqlparser.jsqlparser.version>0.7.0</net.sf.jsqlparser.jsqlparser.version>
</properties>
<dependencies>
@@ -318,6 +320,17 @@
<artifactId>gatling-charts-highcharts</artifactId>
<version>${gatling.version}</version>
</dependency>
<!--分表依赖-->
<dependency>
<groupId>org.shardbatis</groupId>
<artifactId>shardbatis</artifactId>
<version>${shardbatis.version}</version>
</dependency>
<dependency>
<groupId>net.sf.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>${net.sf.jsqlparser.jsqlparser.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@@ -405,11 +418,11 @@
<registryUrl>registry.cn-hangzhou.aliyuncs.com</registryUrl>
<!--构建镜像所有的资源,这些资源将被复制到dockerFile的目录-->
<!--<resources>-->
<!--<resource>-->
<!--<targetPath>/</targetPath>-->
<!--<directory>${project.build.directory}</directory>-->
<!--<include>${project.build.finalName}.jar</include>-->
<!--</resource>-->
<!--<resource>-->
<!--<targetPath>/</targetPath>-->
<!--<directory>${project.build.directory}</directory>-->
<!--<include>${project.build.finalName}.jar</include>-->
<!--</resource>-->
<!--</resources>-->
</configuration>
</plugin>