创建元数据实体
按需创建元数据实体类,实现 Serializable 接口,用来存储元数据并放入缓存。

元数据解析器 MetaDataParse
定义元数据解析接口,获取资源表元数据实体。依据自身业务创建 DefaultMetaDataParse 实现 MetaDataParse 接口。
public interface MetaDataParse {
/**
* 获取所有表元数据
*
* @return List<Table>
*/
List<Table> tables();
/**
* 获取指定表元数据
*
* @return List<Table>
*/
List<Table> tables(List<String> tableCodes);
/**
* 获取指定表元数据
*
* @param tableCode 表名
* @return Table
*/
Table table(String tableCode);
/**
* 设置解析器数据源
*
* @param dataSource 数据源
*/
void setDataSource(DataSource dataSource);
}
元数据缓存管理 MetaDataCacheManager
定义元数据缓存管理接口。依据自身业务创建 DefaultMetaDataCacheManager 实现 MetaDataCacheManager 接口。
public interface MetaDataCacheManager {
/**
* 资源表数据加入缓存
*
* @param table 资源表数据
*/
void put(Table table);
/**
* 删除资源表缓存
*
* @param code 资源表编码
*/
void remove(String code);
/**
* 缓存中获取资源表数据
*
* @param code 资源表编码
* @return 资源表数据
*/
Table get(String code);
/**
* 清空全部缓存
*/
void clear();
/**
* 设置 configuration
*
* @param configuration mybatis配置
*/
void setConfiguration(CustomConfiguration configuration);
}
元数据构造器 MetaStatementBuilder
创建 MetaStatementBuilder 实现对元数据对象的构建及缓存,使用 CustomConfiguration、MetaDataParse、MetaDataCacheManager 创建 元数据构造器对象。如果未传入MetaDataParse和MetaDataCacheManager时,使用默认的 DefaultMetaDataParse 和 DefaultMetaDataCacheManager。
public class MetaStatementBuilder {
private static final Log LOGGER = LogFactory.getLog(MetaStatementBuilder.class);
protected final CustomConfiguration configuration;
protected final MetaDataParse parse;
private final MetaDataCacheManager cacheManager;
public static MetaStatementBuilder getInstance(CustomConfiguration configuration, MetaDataParse parse, MetaDataCacheManager cacheManager) {
return new MetaStatementBuilder(configuration,
Optional.ofNullable(parse).orElse(new DefaultMetaDataParse()),
Optional.ofNullable(cacheManager).orElse(new DefaultMetaDataCacheManager())
);
}
public MetaStatementBuilder(CustomConfiguration configuration, MetaDataParse parse, MetaDataCacheManager cacheManager) {
this.configuration = configuration;
//元数据解析器
this.parse = parse;
//元数据缓存管理器
this.cacheManager = cacheManager;
//设置解析器数据源
this.parse.setDataSource(configuration.getEnvironment().getDataSource());
//设置解析器数据源
this.cacheManager.setConfiguration(configuration);
}
/**
* 重新获取 table 元数据
*
* @param tableCode 表名
* @return Table
*/
public Table builderTable(String tableCode) {
cacheManager.removeTable(tableCode);
return table(tableCode);
}
/**
* 获取 table 元数据
*
* @param tableCode 表名
* @return Table
*/
public Table table(String tableCode) {
//校验是否为null
Optional.ofNullable(tableCode).orElseThrow(() -> new CacheException("Meta tableCode is null."));
//缓存中获取
Table table = cacheManager.getTable(tableCode);
if (table == null) {
LOGGER.debug("Meta table[" + tableCode + "] cache init.");
//查询元数据
table = parse.table(tableCode);
Optional.ofNullable(table).orElseThrow(() -> new CacheException("Meta table[" + tableCode + "] parse fail."));
//put缓存
cacheManager.put(table);
//生成主键策略
keyGen(table);
}
return table;
}
/**
* 生成主键策略
*
* @param table 资源表表元数据
* @return org.apache.ibatis.executor.keygen.KeyGenerator
*/
private KeyGenerator keyGen(Table table) {
// TODO 生成自定义主键策略
}
public CustomConfiguration getConfiguration() {
return configuration;
}
public MetaDataCacheManager getCacheManager() {
return cacheManager;
}
public MetaDataParse getParse() {
return parse;
}
}
修改 SqlSessionFactoryBean
在 SqlSessionFactoryBean 中加入元数据解析器、元数据缓存属性。方便配置自定义的元数据解析器和元数据缓存实现。
/**
* 元数据解析器
*/
private MetaDataParse metaDataParse;
/**
* 元数据缓存
*/
private MetaDataCacheManager cacheManager;
在 buildSqlSessionFactory 方法中使用 metaDataParse 和 cacheManager 创建元数据构造器 MetaStatementBuilder,并将 MetaStatementBuilder 设置到 CustomConfiguration 中。
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
······
// 元数据操作相关拓展
if (targetConfiguration instanceof CustomConfiguration) {
//强制类型转换
CustomConfiguration customConfiguration = (CustomConfiguration) targetConfiguration;
//创建 MetaStatementBuilder 并添加到 Configuration
customConfiguration.setMetaStatementBuilder(MetaStatementBuilder.getInstance(customConfiguration, metaDataParse, cacheManager)
.builder());
}
······
}
MetaSqlNode 使用 MetaStatementBuilder 生成语句
MetaSqlNode 继承了 MyBatis 的 SqlNode 接口。添加自定义语句不是通过 DynamicContext.appendSql() ,而是拼装动态 XML 脚本,然后通过 CustomXMLScriptBuilder 将 XML转换为SqlNode,最后调用转换获取的 SqlNode.apply(DynamicContext) 将sql添加到 DynamicContext。
下面以修改逻辑举例
public class MetaSqlNode implements SqlNode {
······
@Override
public boolean apply(DynamicContext context) {
//支持Test语句
if (!StringUtils.isEmpty(test) && !evaluator.evaluateBoolean(test, context.getBindings())) {
return false;
}
//获取资源表
table = configuration.getMetaStatementBuilder().table(table);
//默认空语句
SqlNode sqlNode = new TextSqlNode("");
if (type == TypeEnum.update) {
//更新语句
sqlNode = updateScript(table);
}
return sqlNode.apply(context);
}
/**
* 动态更新语句节点,需指定ID
*
* @param table Table对象
* @param hasWhere 是否包含条件
* @return org.apache.ibatis.scripting.xmltags.SqlNode
*/
private SqlNode updateScript(Table table, boolean hasWhere) {
Id id = table.getId();
if (id == null || id.getCode() == null) {
throw new RuntimeException(String.format("TableCode[%s] id not find!", table.getCode()));
}
//选取需要插入的列,忽略主键字段
List<Column> columns = table.getColumnList().stream()
.filter(p -> !table.getId().getCode().equalsIgnoreCase(p.getCode())).collect(Collectors.toList());
//拼接语句
StringBuilder sqlScript = new StringBuilder("<script> UPDATE ").append(table.getCode());
sqlScript.append(" <trim prefix=\"set\" suffixOverrides=\",\"> ");
columns.forEach(p -> {
sqlScript.append("<if test=\"").append(String.format(MAP_PARAMETER, p.getCode())).append("!=null\">").append(p.getCode()).append("=#{").append(String.format(MAP_PARAMETER, p.getCode())).append("},</if>");
});
sqlScript.append("</trim>");
sqlScript.append("WHERE ");
sqlScript.append(id.getCode()).append("=#{").append(String.format(MAP_PARAMETER, id.getCode())).append("}");
sqlScript.append("</script>");
return scriptToSqlNode(sqlScript);
}
/**
* 动态sql语句转SqlNode节点
*
* @param sqlScript 动态sql语句
* @return org.apache.ibatis.scripting.xmltags.SqlNode
*/
private SqlNode scriptToSqlNode(StringBuilder sqlScript) {
XPathParser parser = new XPathParser(sqlScript.toString(), false, configuration.getVariables(), new CustomXMLMapperEntityResolver());
CustomXMLScriptBuilder builder = new CustomXMLScriptBuilder(configuration, parser.evalNode("/script"), null);
return builder.parseDynamicTags();
}
······
}
实际使用
Spring 按如下配置自定义元数据解析器与元数据缓存管理实现,
<!-- Mybatis SessionFactory-->
<bean id="sqlSessionFactory" class="com.my.ibatis.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configurationProperties" >
<bean class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations" value="classpath*:mybatis.properties"/>
</bean>
</property>
<!-- 元数据解析器 -->
<property name="metaDataParse">
<bean class=" Class extends MetaDataParse "/>
</property>
<!-- 元数据缓存管理 -->
<property name="metaDataParse">
<bean class=" Class extends MetaDataCacheManager "/>
</property>
</bean>
元数据解析器 MetaDataParse 可以通过查询数据库中存储的表结构数据来构建 metadata,也可以扫描实体类的注解来构建 metadata。缓存管理 MetaDataCacheManager 可以使用简单的 ConcurrentHashMap 来实现,也可以是 Ehcache等缓存框架实现。