目錄
2.2 AdditionalXMLMapperGenerator Document生成器實現
2.3 BatchInsertElementGenerator 動態SQL生成
0. 現實背景
在項目開發中,業務數據落庫時,爲了減少和數據庫之間的通信,頻繁佔用數據庫鏈接資源,我們一般都會需要一個批量插入的mapper接口方法;但遺憾的是,mybatis generator框架中沒有提供這樣的方法,目前一般的解決方案是,我們手動在mapper接口中添加批量插入的方法,然後再手工添加一個批量插入的動態SQL與之關聯,但這樣的問題在於:
-
每個table手寫批量插入的動態SQL要花費一定的時間,手寫完也不能保證是沒有問題的,一次通過;
-
每次table 新增字段時,其他的mapper接口,都可以重新編譯生成,然後覆蓋,但批量插入的動態SQL還需要我們手動去添加維護,有時也很容易遺漏,出現問題;
爲了解決上述問題,批量插入mapper接口方法以及動態SQL xml文件 也需要做到一次編譯,自動生成;
1. 方案調研
1.1 自動添加mapper接口方法
在上篇文章中Mybatis Generator源碼:Generator自動生成框架,我們知道在JavaClientGenerator配置中有一個rootInterface的屬性配置,該配置可以使生成的Domain對象自動實現某個接口方法,這裏的實現源碼如下(JavaMapperGenerator方法getCompilationUnits中):
@Override
public List<CompilationUnit> getCompilationUnits() {
progressCallback.startTask(getString("Progress.17", //$NON-NLS-1$
introspectedTable.getFullyQualifiedTable().toString()));
CommentGenerator commentGenerator = context.getCommentGenerator();
FullyQualifiedJavaType type = new FullyQualifiedJavaType(
introspectedTable.getMyBatis3JavaMapperType());
Interface interfaze = new Interface(type);
interfaze.setVisibility(JavaVisibility.PUBLIC);
commentGenerator.addJavaFileComment(interfaze);
String rootInterface = introspectedTable
.getTableConfigurationProperty(PropertyRegistry.ANY_ROOT_INTERFACE);
if (!stringHasValue(rootInterface)) {
rootInterface = context.getJavaClientGeneratorConfiguration()
.getProperty(PropertyRegistry.ANY_ROOT_INTERFACE);
}
if (stringHasValue(rootInterface)) {
FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType(
rootInterface);
interfaze.addSuperInterface(fqjt);
interfaze.addImportedType(fqjt);
}
addCountByExampleMethod(interfaze);
addDeleteByExampleMethod(interfaze);
addDeleteByPrimaryKeyMethod(interfaze);
addInsertMethod(interfaze);
addInsertSelectiveMethod(interfaze);
addSelectByExampleWithBLOBsMethod(interfaze);
addSelectByExampleWithoutBLOBsMethod(interfaze);
addSelectByPrimaryKeyMethod(interfaze);
addUpdateByExampleSelectiveMethod(interfaze);
addUpdateByExampleWithBLOBsMethod(interfaze);
addUpdateByExampleWithoutBLOBsMethod(interfaze);
addUpdateByPrimaryKeySelectiveMethod(interfaze);
addUpdateByPrimaryKeyWithBLOBsMethod(interfaze);
addUpdateByPrimaryKeyWithoutBLOBsMethod(interfaze);
List<CompilationUnit> answer = new ArrayList<CompilationUnit>();
if (context.getPlugins().clientGenerated(interfaze, null,
introspectedTable)) {
answer.add(interfaze);
}
List<CompilationUnit> extraCompilationUnits = getExtraCompilationUnits();
if (extraCompilationUnits != null) {
answer.addAll(extraCompilationUnits);
}
return answer;
}
因此,可以把batchInsert方法加入到某個接口中,然後rootInterface指定該接口即可;
1.2 自動生成xml文件
在上篇文章Mybatis Generator源碼:Generator自動生成框架中生成xml文件階段調用了context的generateFiles方法,下面看一下該方法的內部實現:
public void generateFiles(ProgressCallback callback,
List<GeneratedJavaFile> generatedJavaFiles,
List<GeneratedXmlFile> generatedXmlFiles, List<String> warnings)
throws InterruptedException {
pluginAggregator = new PluginAggregator();
for (PluginConfiguration pluginConfiguration : pluginConfigurations) {
Plugin plugin = ObjectFactory.createPlugin(this,
pluginConfiguration);
if (plugin.validate(warnings)) {
pluginAggregator.addPlugin(plugin);
} else {
warnings.add(getString("Warning.24", //$NON-NLS-1$
pluginConfiguration.getConfigurationType(), id));
}
}
if (introspectedTables != null) {
for (IntrospectedTable introspectedTable : introspectedTables) {
callback.checkCancel();
introspectedTable.initialize();
introspectedTable.calculateGenerators(warnings, callback);
generatedJavaFiles.addAll(introspectedTable
.getGeneratedJavaFiles());
generatedXmlFiles.addAll(introspectedTable
.getGeneratedXmlFiles());
generatedJavaFiles.addAll(pluginAggregator
.contextGenerateAdditionalJavaFiles(introspectedTable));
generatedXmlFiles.addAll(pluginAggregator
.contextGenerateAdditionalXmlFiles(introspectedTable));
}
}
generatedJavaFiles.addAll(pluginAggregator
.contextGenerateAdditionalJavaFiles());
generatedXmlFiles.addAll(pluginAggregator
.contextGenerateAdditionalXmlFiles());
}
這裏同時完成了java文件和xml文件的生成,其中pluginAggregator.contextGenerateAdditionalXmlFiles(introspectedTable)提供了應用generator插件生成其它xml文件的擴展接口,這樣,我們就可以自定義一個generator插件來完成 batchInsert xml動態Sql的自動生成;
2. 方案實現
自動添加mapper接口batchInsert方法比較簡單,這裏不再進行說明,下面主要介紹xml動態SQL自動生成的設計實現:
2.1 批量插入插件BatchInsertPlugin實現
public class BatchInsertPlugin extends PluginAdapter {
@Override
public boolean validate(List<String> warnings) {
return true;
}
@Override
public List<GeneratedXmlFile> contextGenerateAdditionalXmlFiles(IntrospectedTable introspectedTable) {
AbstractXmlGenerator xmlGenerator = new AdditionalXMLMapperGenerator();
xmlGenerator.setContext(introspectedTable.getContext());
xmlGenerator.setIntrospectedTable(introspectedTable);
Document document = xmlGenerator.getDocument();
List<GeneratedXmlFile> answer = Lists.newArrayListWithCapacity(1);
if (document != null) {
GeneratedXmlFile gxf = new GeneratedXmlFile(document, introspectedTable.getMyBatis3XmlMapperFileName(),
introspectedTable.getMyBatis3XmlMapperPackage(),
context.getSqlMapGeneratorConfiguration().getTargetProject(), true, context.getXmlFormatter());
answer.add(gxf);
}
return answer;
}
@Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
return true;
}
}
這裏通過自定義的AdditionalXMLMapperGenerator完成Document對象的構建,進而完成了批量插入xml動態SQL文件GeneratedXmlFile的自動生成,下面看下AdditionalXMLMapperGenerator Document生成器的實現:
2.2 AdditionalXMLMapperGenerator Document生成器實現
AdditionalXMLMapperGenerator具體實現代碼如下,藉助BatchInsertElementGenerator完成了Document文檔對象的構建:
public class AdditionalXMLMapperGenerator extends AbstractXmlGenerator {
public AdditionalXMLMapperGenerator() {
super();
}
@Override
public Document getDocument() {
Document document =
new Document(XmlConstants.MYBATIS3_MAPPER_PUBLIC_ID, XmlConstants.MYBATIS3_MAPPER_SYSTEM_ID);
document.setRootElement(getSqlMapElement());
if (!context.getPlugins().sqlMapDocumentGenerated(document, introspectedTable)) {
document = null;
}
return document;
}
private XmlElement getSqlMapElement() {
XmlElement root = new XmlElement("mapper"); //$NON-NLS-1$
String namespace = introspectedTable.getMyBatis3SqlMapNamespace();
root.addAttribute(new Attribute("namespace", //$NON-NLS-1$
namespace));
context.getCommentGenerator().addRootComment(root);
root.addElement(new TextElement("<!-- This sql is auto-generated by MyBatis Generator. -->")); //$NON-NLS-1$
addBatchInsertElement(root);
return root;
}
private void addBatchInsertElement(XmlElement parentElement) {
if (introspectedTable.getRules().generateInsert()) {
AbstractXmlElementGenerator elementGenerator = new BatchInsertElementGenerator();
initializeAndExecuteGenerator(elementGenerator, parentElement);
}
}
private void initializeAndExecuteGenerator(AbstractXmlElementGenerator elementGenerator, XmlElement parentElement) {
elementGenerator.setContext(context);
elementGenerator.setIntrospectedTable(introspectedTable);
elementGenerator.addElements(parentElement);
}
}
2.3 BatchInsertElementGenerator 動態SQL生成
如下,BatchInsertElementGenerator完成了批量插入動態SQL的構造,這裏就是我們比較熟悉的動態sql語句了;
public class BatchInsertElementGenerator extends AbstractXmlElementGenerator {
private static final String BATCH_INSERT_STATEMENT_ID = "batchInsert";
private static final int NEW_LINE_CHARACTER_LIMIT = 80;
BatchInsertElementGenerator() {
super();
}
@Override
public void addElements(XmlElement parentElement) {
XmlElement answer = new XmlElement("insert");
answer.addAttribute(new Attribute("id", BATCH_INSERT_STATEMENT_ID));
answer.addAttribute(
new Attribute("parameterType", FullyQualifiedJavaType.getNewListInstance().getFullyQualifiedName()));
context.getCommentGenerator().addComment(answer);
GeneratedKey gk = introspectedTable.getGeneratedKey();
if (gk != null) {
IntrospectedColumn introspectedColumn = introspectedTable.getColumn(gk.getColumn());
if (introspectedColumn != null) {
if (gk.isJdbcStandard()) {
answer.addAttribute(new Attribute("useGeneratedKeys", "true"));
answer.addAttribute(new Attribute("keyProperty", introspectedColumn.getJavaProperty()));
} else {
answer.addElement(getSelectKey(introspectedColumn, gk));
}
}
}
StringBuilder insertClause = new StringBuilder();
StringBuilder valuesClause = new StringBuilder();
insertClause.append("insert into ");
insertClause.append(introspectedTable.getFullyQualifiedTableNameAtRuntime());
insertClause.append(" (");
List<String> valuesClauses = Lists.newArrayList();
valuesClauses.add("values ");
valuesClauses.add("<foreach collection=\"list\" separator=\",\" index=\"index\" item=\"item\">");
valuesClauses.add(" (");
Iterator<IntrospectedColumn> iter = introspectedTable.getAllColumns().iterator();
while (iter.hasNext()) {
IntrospectedColumn introspectedColumn = iter.next();
if (introspectedColumn.isIdentity()) {
// cannot set values on identity fields
continue;
}
insertClause.append(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn));
if (introspectedColumn.getDefaultValue() == null) {
getValueClauseWhenNoDefaultValue(valuesClause, valuesClauses, iter, introspectedColumn, 1);
} else {
getChooseString(valuesClause, valuesClauses, introspectedColumn, iter);
}
appendCommaWhenHasNext(insertClause, iter);
if (insertClause.length() > NEW_LINE_CHARACTER_LIMIT) {
answer.addElement(new TextElement(insertClause.toString()));
insertClause.setLength(0);
OutputUtilities.xmlIndent(insertClause, 1);
}
}
insertClause.append(')');
answer.addElement(new TextElement(insertClause.toString()));
valuesClause.append(')');
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
valuesClause.append("</foreach>");
valuesClauses.add(valuesClause.toString());
for (String clause : valuesClauses) {
answer.addElement(new TextElement(clause));
}
if (context.getPlugins().sqlMapInsertElementGenerated(answer, introspectedTable)) {
parentElement.addElement(answer);
}
}
private void getValueClauseWhenNoDefaultValue(StringBuilder valuesClause, List<String> valuesClauses,
Iterator<IntrospectedColumn> iter, IntrospectedColumn introspectedColumn, int indentLevel) {
OutputUtilities.xmlIndent(valuesClause, indentLevel);
valuesClause.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn));
insertItemAfterLeftBrace(valuesClause);
appendCommaWhenHasNext(valuesClause, iter);
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
}
private void getChooseString(StringBuilder valuesClause, List<String> valuesClauses,
IntrospectedColumn introspectedColumn, Iterator iter) {
OutputUtilities.xmlIndent(valuesClause, 1);
valuesClause.append("<choose>");
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
OutputUtilities.xmlIndent(valuesClause, 2);
valuesClause.append("<when test=\"item.");
valuesClause.append(introspectedColumn.getJavaProperty(null));
valuesClause.append(" != null\" >");
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
getValueClauseWhenNoDefaultValue(valuesClause, valuesClauses, iter, introspectedColumn, 3);
OutputUtilities.xmlIndent(valuesClause, 2);
valuesClause.append("</when>");
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
OutputUtilities.xmlIndent(valuesClause, 2);
valuesClause.append("<otherwise>");
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
OutputUtilities.xmlIndent(valuesClause, 3);
valuesClause.append(wrapQuotaPair(introspectedColumn.getDefaultValue()));
appendCommaWhenHasNext(valuesClause, iter);
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
OutputUtilities.xmlIndent(valuesClause, 2);
valuesClause.append("</otherwise>");
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
OutputUtilities.xmlIndent(valuesClause, 1);
valuesClause.append("</choose>");
valuesClauses.add(valuesClause.toString());
valuesClause.setLength(0);
}
private void appendCommaWhenHasNext(StringBuilder insertClause, Iterator<IntrospectedColumn> iter) {
if (iter.hasNext()) {
insertClause.append(", ");
}
}
private void insertItemAfterLeftBrace(StringBuilder valuesClause) {
int index = valuesClause.indexOf("{");
if (index != -1) {
valuesClause.insert(index + 1, "item.");
}
}
private String wrapQuotaPair(String defaultValue) {
return "'" + defaultValue + "'";
}
}