目录
0. 使用方法
1. 配置文件定义
2. Generator框架解析过程分析
2.1 ConfigurationParser配置文件解析
2.2 MyBatisGenerator自动生成过程分析
我们都知道mybatis对于Dao接口中的方法具体实现方式有两种:
其中基于xml实现的方式,自己手写麻烦复杂,很容易出问题,因此mybaits提供了一个generator的自动生成框架,对于最常用的基本方法(增、删、改、查)可以自动生成dao、domain、mapper文件,应用简单方便,大大提升了开发效率,下面主要介绍下generator框架的处理过程;
generator框架的使用方法,在框架内部的messages.properties文件中给出了说明,如下:
支持用java命令运行,是因为包中ShellRunner存在一个main主方法,这里需要指定一个配置文件,以及其它的几个可选参数;
当然,实际项目使用一般都是通过mybatis-generator-maven-plugin插件完成的,可以做到一键编译,自动生成;
典型的配置文件如下,其中主要包含了数据库链接配置、JavaModelGenerator配置(Domain类)、SqlMapGenerator配置(Xml Sql文件)、JavaClientGenerator(Dao接口)配置以及需要生成的数据表table配置等;
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);
解析过程的代码如下:
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的解析:
下面主要看一下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 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 pluginConfigurations;
private String targetRuntime;
private String introspectedColumnImpl;
private Boolean autoDelimitKeywords;
private JavaFormatter javaFormatter;
private XmlFormatter xmlFormatter;
}
这部分具体过程在实现源码中通过注释可以看得很清楚,这里直接给出源码实现:
/**
* 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
* null
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 contextIds,
Set fullyQualifiedTableNames) throws SQLException,
IOException, InterruptedException {
if (callback == null) {
callback = new NullProgressCallback();
}
generatedJavaFiles.clear();
generatedXmlFiles.clear();
// calculate the contexts to run
List contextsToRun;
if (contextIds == null || contextIds.size() == 0) {
contextsToRun = configuration.getContexts();
} else {
contextsToRun = new ArrayList();
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();
}
如上,主要包含的处理过程有:
另外,在messages.properties的过程过程提示信息中,也可以看出执行过程的一些细节,如下: