目錄
我們都知道mybatis對於Dao接口中的方法具體實現方式有兩種:
- 一種是基於註解的方式
- 另一種是以及xml 動態SQL的方式
其中基於xml實現的方式,自己手寫麻煩複雜,很容易出問題,因此mybaits提供了一個generator的自動生成框架,對於最常用的基本方法(增、刪、改、查)可以自動生成dao、domain、mapper文件,應用簡單方便,大大提升了開發效率,下面主要介紹下generator框架的處理過程;
0. 使用方法
generator框架的使用方法,在框架內部的messages.properties文件中給出了說明,如下:
支持用java命令運行,是因爲包中ShellRunner存在一個main主方法,這裏需要指定一個配置文件,以及其它的幾個可選參數;
當然,實際項目使用一般都是通過mybatis-generator-maven-plugin插件完成的,可以做到一鍵編譯,自動生成;
1. 配置文件定義
典型的配置文件如下,其中主要包含了數據庫鏈接配置、JavaModelGenerator配置(Domain類)、SqlMapGenerator配置(Xml Sql文件)、JavaClientGenerator(Dao接口)配置以及需要生成的數據表table配置等;
2. Generator框架解析過程分析
Generator框架的入口類ShellRunner的main主方法中,完成了配置文件的解析,以及generator文件自動生成的功能,核心代碼如下,下面分別進行說明:
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configurationFile);
DefaultShellCallback shellCallback = new DefaultShellCallback(
arguments.containsKey(OVERWRITE));
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, shellCallback, warnings);
ProgressCallback progressCallback = arguments.containsKey(VERBOSE) ? new VerboseProgressCallback()
: null;
myBatisGenerator.generate(progressCallback, contexts, fullyqualifiedTables);
2.1 ConfigurationParser配置文件解析
解析過程的代碼如下:
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configurationFile);
追蹤代碼,找到解析配置文件xml的代碼如下:
private Configuration parseConfiguration(InputSource inputSource)
throws IOException, XMLParserException {
parseErrors.clear();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(true);
try {
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new ParserEntityResolver());
ParserErrorHandler handler = new ParserErrorHandler(warnings,
parseErrors);
builder.setErrorHandler(handler);
Document document = null;
try {
document = builder.parse(inputSource);
} catch (SAXParseException e) {
throw new XMLParserException(parseErrors);
} catch (SAXException e) {
if (e.getException() == null) {
parseErrors.add(e.getMessage());
} else {
parseErrors.add(e.getException().getMessage());
}
}
if (parseErrors.size() > 0) {
throw new XMLParserException(parseErrors);
}
Configuration config;
Element rootNode = document.getDocumentElement();
DocumentType docType = document.getDoctype();
if (rootNode.getNodeType() == Node.ELEMENT_NODE
&& docType.getPublicId().equals(
XmlConstants.IBATOR_CONFIG_PUBLIC_ID)) {
config = parseIbatorConfiguration(rootNode);
} else if (rootNode.getNodeType() == Node.ELEMENT_NODE
&& docType.getPublicId().equals(
XmlConstants.MYBATIS_GENERATOR_CONFIG_PUBLIC_ID)) {
config = parseMyBatisGeneratorConfiguration(rootNode);
} else {
throw new XMLParserException(getString("RuntimeError.5")); //$NON-NLS-1$
}
if (parseErrors.size() > 0) {
throw new XMLParserException(parseErrors);
}
return config;
} catch (ParserConfigurationException e) {
parseErrors.add(e.getMessage());
throw new XMLParserException(parseErrors);
}
}
可以看到,這裏用到了DOM Xml文件解析框架,對解析到的Document獲取到rootNode,然後進一步解析generator框架的配置類,代碼如下,解析類爲MyBatisGeneratorConfigurationParser
public Configuration parseConfiguration(Element rootNode)
throws XMLParserException {
Configuration configuration = new Configuration();
NodeList nodeList = rootNode.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if ("properties".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseProperties(configuration, childNode);
} else if ("classPathEntry".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseClassPathEntry(configuration, childNode);
} else if ("context".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseContext(configuration, childNode);
}
}
return configuration;
}
上面分別完成了properties、classPathEntry以及context的解析:
- properties:主要完成外部屬性文件的解析;
- classPathEntry:完成外部自定義類加載器的注入;
- context:generator框架支持解析多個context,每個context可以有不同的數據庫配置等;
下面主要看一下context的解析過程:
private void parseContext(Configuration configuration, Node node) {
Properties attributes = parseAttributes(node);
String defaultModelType = attributes.getProperty("defaultModelType"); //$NON-NLS-1$
String targetRuntime = attributes.getProperty("targetRuntime"); //$NON-NLS-1$
String introspectedColumnImpl = attributes
.getProperty("introspectedColumnImpl"); //$NON-NLS-1$
String id = attributes.getProperty("id"); //$NON-NLS-1$
ModelType mt = defaultModelType == null ? null : ModelType
.getModelType(defaultModelType);
Context context = new Context(mt);
context.setId(id);
if (stringHasValue(introspectedColumnImpl)) {
context.setIntrospectedColumnImpl(introspectedColumnImpl);
}
if (stringHasValue(targetRuntime)) {
context.setTargetRuntime(targetRuntime);
}
configuration.addContext(context);
NodeList nodeList = node.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node childNode = nodeList.item(i);
if (childNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
if ("property".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseProperty(context, childNode);
} else if ("plugin".equals(childNode.getNodeName())) { //$NON-NLS-1$
parsePlugin(context, childNode);
} else if ("commentGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseCommentGenerator(context, childNode);
} else if ("jdbcConnection".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseJdbcConnection(context, childNode);
} else if ("javaModelGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseJavaModelGenerator(context, childNode);
} else if ("javaTypeResolver".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseJavaTypeResolver(context, childNode);
} else if ("sqlMapGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseSqlMapGenerator(context, childNode);
} else if ("javaClientGenerator".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseJavaClientGenerator(context, childNode);
} else if ("table".equals(childNode.getNodeName())) { //$NON-NLS-1$
parseTable(context, childNode);
}
}
}
如上,完成了context中各個元素的具體解析,最終構造了Context對象,並保存到Configuration配置類中,Context域變量如下;至此,配置文件的解析過程完畢。
public class Context extends PropertyHolder {
private String id;
private JDBCConnectionConfiguration jdbcConnectionConfiguration;
private SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration;
private JavaTypeResolverConfiguration javaTypeResolverConfiguration;
private JavaModelGeneratorConfiguration javaModelGeneratorConfiguration;
private JavaClientGeneratorConfiguration javaClientGeneratorConfiguration;
private ArrayList<TableConfiguration> tableConfigurations;
private ModelType defaultModelType;
private String beginningDelimiter = "\""; //$NON-NLS-1$
private String endingDelimiter = "\""; //$NON-NLS-1$
private CommentGeneratorConfiguration commentGeneratorConfiguration;
private CommentGenerator commentGenerator;
private PluginAggregator pluginAggregator;
private List<PluginConfiguration> pluginConfigurations;
private String targetRuntime;
private String introspectedColumnImpl;
private Boolean autoDelimitKeywords;
private JavaFormatter javaFormatter;
private XmlFormatter xmlFormatter;
}
2.2 MyBatisGenerator自動生成過程分析
這部分具體過程在實現源碼中通過註釋可以看得很清楚,這裏直接給出源碼實現:
/**
* This is the main method for generating code. This method is long running,
* but progress can be provided and the method can be cancelled through the
* ProgressCallback interface.
*
* @param callback
* an instance of the ProgressCallback interface, or
* <code>null</code> if you do not require progress information
* @param contextIds
* a set of Strings containing context ids to run. Only the
* contexts with an id specified in this list will be run. If the
* list is null or empty, than all contexts are run.
* @param fullyQualifiedTableNames
* a set of table names to generate. The elements of the set must
* be Strings that exactly match what's specified in the
* configuration. For example, if table name = "foo" and schema =
* "bar", then the fully qualified table name is "foo.bar". If
* the Set is null or empty, then all tables in the configuration
* will be used for code generation.
* @throws InvalidConfigurationException
* @throws SQLException
* @throws IOException
* @throws InterruptedException
* if the method is canceled through the ProgressCallback
*/
public void generate(ProgressCallback callback, Set<String> contextIds,
Set<String> fullyQualifiedTableNames) throws SQLException,
IOException, InterruptedException {
if (callback == null) {
callback = new NullProgressCallback();
}
generatedJavaFiles.clear();
generatedXmlFiles.clear();
// calculate the contexts to run
List<Context> contextsToRun;
if (contextIds == null || contextIds.size() == 0) {
contextsToRun = configuration.getContexts();
} else {
contextsToRun = new ArrayList<Context>();
for (Context context : configuration.getContexts()) {
if (contextIds.contains(context.getId())) {
contextsToRun.add(context);
}
}
}
// setup custom classloader if required
if (configuration.getClassPathEntries().size() > 0) {
ClassLoader classLoader = getCustomClassloader(configuration.getClassPathEntries());
ObjectFactory.addExternalClassLoader(classLoader);
}
// now run the introspections...
int totalSteps = 0;
for (Context context : contextsToRun) {
totalSteps += context.getIntrospectionSteps();
}
callback.introspectionStarted(totalSteps);
for (Context context : contextsToRun) {
context.introspectTables(callback, warnings,
fullyQualifiedTableNames);
}
// now run the generates
totalSteps = 0;
for (Context context : contextsToRun) {
totalSteps += context.getGenerationSteps();
}
callback.generationStarted(totalSteps);
for (Context context : contextsToRun) {
context.generateFiles(callback, generatedJavaFiles,
generatedXmlFiles, warnings);
}
// now save the files
callback.saveStarted(generatedXmlFiles.size()
+ generatedJavaFiles.size());
for (GeneratedXmlFile gxf : generatedXmlFiles) {
projects.add(gxf.getTargetProject());
File targetFile;
String source;
try {
File directory = shellCallback.getDirectory(gxf
.getTargetProject(), gxf.getTargetPackage());
targetFile = new File(directory, gxf.getFileName());
if (targetFile.exists()) {
if (gxf.isMergeable()) {
source = XmlFileMergerJaxp.getMergedSource(gxf,
targetFile);
} else if (shellCallback.isOverwriteEnabled()) {
source = gxf.getFormattedContent();
warnings.add(getString("Warning.11", //$NON-NLS-1$
targetFile.getAbsolutePath()));
} else {
source = gxf.getFormattedContent();
targetFile = getUniqueFileName(directory, gxf
.getFileName());
warnings.add(getString(
"Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
}
} else {
source = gxf.getFormattedContent();
}
} catch (ShellException e) {
warnings.add(e.getMessage());
continue;
}
callback.checkCancel();
callback.startTask(getString(
"Progress.15", targetFile.getName())); //$NON-NLS-1$
writeFile(targetFile, source, "UTF-8"); //$NON-NLS-1$
}
for (GeneratedJavaFile gjf : generatedJavaFiles) {
projects.add(gjf.getTargetProject());
File targetFile;
String source;
try {
File directory = shellCallback.getDirectory(gjf
.getTargetProject(), gjf.getTargetPackage());
targetFile = new File(directory, gjf.getFileName());
if (targetFile.exists()) {
if (shellCallback.isMergeSupported()) {
source = shellCallback.mergeJavaFile(gjf
.getFormattedContent(), targetFile
.getAbsolutePath(),
MergeConstants.OLD_ELEMENT_TAGS,
gjf.getFileEncoding());
} else if (shellCallback.isOverwriteEnabled()) {
source = gjf.getFormattedContent();
warnings.add(getString("Warning.11", //$NON-NLS-1$
targetFile.getAbsolutePath()));
} else {
source = gjf.getFormattedContent();
targetFile = getUniqueFileName(directory, gjf
.getFileName());
warnings.add(getString(
"Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
}
} else {
source = gjf.getFormattedContent();
}
callback.checkCancel();
callback.startTask(getString(
"Progress.15", targetFile.getName())); //$NON-NLS-1$
writeFile(targetFile, source, gjf.getFileEncoding());
} catch (ShellException e) {
warnings.add(e.getMessage());
}
}
for (String project : projects) {
shellCallback.refreshProject(project);
}
callback.done();
}
如上,主要包含的處理過程有:
- 連接數據庫,解析context中配置的各個數據表(context.introspectTables方法中實現)
- 生成java文件和xml文件(context.generateFiles方法中實現)
- 分別保存生成的java文件和xml文件
另外,在messages.properties的過程過程提示信息中,也可以看出執行過程的一些細節,如下: