示例
// 示例
@Data
@TableName("test")
@IdShard(num = 2, length=2)
public class TestModel implements Serializable {
private static final long serialVersionUID = 1L;
/** id */
@TableId(type = IdType.NONE)
@SequenceId(interval = 10)
private Integer id;
/** 名称 */
private String name;
/** 类型 */
private Integer type;
/** 更新时间 */
private Date updateTime;
/** 创建时间 */
private Date createTime;
}
实现逻辑
ID自增(AOP)
参考查看
表设计
CREATE TABLE `t_sequence_id` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL COMMENT '表名', `sequence_id` int(20) NOT NULL COMMENT '当前最大id', `update_time` datetime DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='存储表最大ID'; CREATE TABLE `test02` ( `id` int(11) NOT NULL COMMENT 'id', `name` varchar(20) CHARACTER SET latin1 DEFAULT NULL COMMENT '名称', `type` tinyint(3) DEFAULT '0' COMMENT '类型', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='拆分表(test*)';
分表注解
// ID 拆分
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface IdShard {
/**
* < 拆分个数
* @return
*/
int num();
/**
* < 拼接长度
* @return
*/
int length();
}
// ID AOP 自增
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface SequenceId {
/**
* < 批量获取ID数
* @return
*/
int interval() default 1;
}
spring配置和ID默认hash拆分
// 配置
@Slf4j
@Configuration
public class MybatisConfig {
@Bean
public MybatisSqlSessionFactoryBean configMybatisSqlSessionFactoryBean(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
// 配置拦截
sqlSessionFactoryBean.setPlugins(mybatisPlusInterceptor());
return sqlSessionFactoryBean;
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
/**
扫描model获取需要拆分的表
添加拦截和ID拆分(hash)
*/
Map<String, TableNameHandler> map = Maps.newHashMap();
Map<String, IdTableNameHandler> idmap = IdTableNameHandler.getIdMap(MybatisSaveAop.getModelClazzs());
// 表名-处理策略(可自定义)
map.putAll(idmap);
dynamicTableNameInnerInterceptor.setTableNameHandlerMap(map);
interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
return interceptor;
}
}
// Id默认hash拆分
@Data
public class IdTableNameHandler implements TableNameHandler {
private Class<?> clazz;
private int length;
private int num;
@Override
public String dynamicTableName(String sql, String tableName, Object param,
List<ParameterMapping> paramMappings, SqlCommandType sct) {
String n = getNById(clazz, param, paramMappings, num, length);
return tableName + n;
}
/**
获取id hash后的值
*/
private static String getNById(Class<?> clazz, Object param, List<ParameterMapping> paramMappings, int num, int length) {
String n = "";
String idname = MybatisUtil.getIdname(clazz);
if(param.getClass().equals(clazz)) {
Object obj = ReflectMathodUtil.getValue(param, idname);
n = MybatisUtil.getN(obj , num, length);
}
if(param instanceof Map) {
Map<?, ?> map = (Map<?, ?>)param;
if(map.keySet().contains(idname)) {
Object obj = map.get(idname);
n = MybatisUtil.getN(obj, num, length);
}
if(map.keySet().contains("et")) {
Object obj = map.get("et");
return getNById(clazz, obj, paramMappings, num, length);
}
}
if((param instanceof Long || param instanceof Integer) && paramMappings != null) {
ParameterMapping m = paramMappings.get(0);
if(idname.equals(m.getProperty())) {
Object obj = param;
n = MybatisUtil.getN(obj, num, length);
}
}
if(n == null || "".equals(n)) {
n = MybatisUtil.getNByRandom(num, length);
}
return n;
}
public static Map<String, IdTableNameHandler> getIdMap(Set<Class<?>> clazzs) {
Map<String, IdTableNameHandler> idmap = Maps.newHashMap();
if(clazzs != null && clazzs.size() > 0) {
for(Class<?> clazz : clazzs) {
IdShard is = clazz.getAnnotation(IdShard.class);
if(is != null) {
IdTableNameHandler id = new IdTableNameHandler();
id.setClazz(clazz);
id.setLength(is.length());
id.setNum(is.num());
idmap.put(MybatisUtil.getName(clazz), id);
}
}
}
return idmap;
}
}
拦截自定义-解决数据太少或者复杂业务拆分(参考 Mybatsi Plus)
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings({"rawtypes"})
public class DynamicTableNameInnerInterceptor implements InnerInterceptor {
private Map<String, TableNameHandler> tableNameHandlerMap;
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
SqlCommandType sct = ms.getSqlCommandType();
if (InterceptorIgnoreHelper.willIgnoreDynamicTableName(ms.getId())) return;
mpBs.sql(this.changeTable(mpBs.sql(), mpBs, sct));
}
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
MappedStatement ms = mpSh.mappedStatement();
SqlCommandType sct = ms.getSqlCommandType();
if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
if (InterceptorIgnoreHelper.willIgnoreDynamicTableName(ms.getId())) return;
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
mpBs.sql(this.changeTable(mpBs.sql(), mpBs, sct));
}
}
protected String changeTable(String sql, PluginUtils.MPBoundSql mpBs, SqlCommandType sct) {
TableNameParser parser = new TableNameParser(sql);
List<TableNameParser.SqlToken> names = new ArrayList<>();
parser.accept(names::add);
StringBuilder builder = new StringBuilder();
int last = 0;
for (TableNameParser.SqlToken name : names) {
int start = name.getStart();
if (start != last) {
builder.append(sql, last, start);
String value = name.getValue();
TableNameHandler handler = tableNameHandlerMap.get(value);
if (handler != null) {
builder.append(handler.dynamicTableName(sql, value, mpBs.parameterObject(), mpBs.parameterMappings(), sct));
} else {
builder.append(value);
}
}
last = name.getEnd();
}
if (last != sql.length()) {
builder.append(sql.substring(last));
}
return builder.toString();
}
}
/**
* @author miemie
* @since 3.4.0
*/
public interface TableNameHandler {
String dynamicTableName(String sql, String value, Object parameterObject, List<ParameterMapping> parameterMappings,
SqlCommandType sct);
}
util
public class MybatisUtil {
public static <T> String getName(Class<T> clazz) {
TableName table = clazz.getAnnotation(TableName.class);
if (table != null) {
return table.value();
}
return null;
}
public static <T> String getIdname(Class<T> clazz) {
Field[] fs = clazz.getDeclaredFields();
if (fs != null && fs.length > 0) {
for (Field f : fs) {
if (f.getAnnotation(SequenceId.class) != null) {
return f.getName();
}
}
}
return null;
}
public static String getN(Object obj, int num, int length) {
if (obj != null && (obj instanceof Long || obj instanceof Integer)) {
long n = Long.parseLong(obj.toString()) % num;
if (n == 0) {
n = num;
}
String s = String.format("%" + length + "d", n).replace(" ", "0");
return s;
}
return "";
}
}