mybatis-generator生成報錯The specified target project directory xxx does not exist及源碼分析
背景
參考macro大神的mall-learning項目搭建初始化框架的時候,在自己的項目里搭建了一個module來寫代碼,搭建generator代碼的時候報錯,The specified target project directory xxx does not exist
參考了一篇博客
mybatis逆向工程 The specified target project directory src\main\resources does not exist
然后加上模塊名還是不對,還是報相同的錯誤
問題分析
這個報錯的意思是指定的包不存在
百度了一下配置文件里targetProject的含義:
targetProject:目標項目,指定一個存在的目錄下,生成的內容會放到指定目錄中,如果目錄不存在,MBG不會自動建目錄
但是文章中有句話給了我提示,targetProject是相對路徑,為了搞清楚,我們看源碼
實現類Generator
public class Generator {
public static void main(String[] args) throws Exception {
//MBG 執行過程中的警告信息
List<String> warnings = new ArrayList<String>();
//當生成的代碼重復時,覆蓋原代碼
boolean overwrite = true;
//讀取我們的 MBG 配置文件
InputStream is = Generator.class.getResourceAsStream("/generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(is);
is.close();
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
//創建 MBG
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
//執行生成代碼
myBatisGenerator.generate(null);
//輸出警告信息
for (String warning : warnings) {
System.out.println(warning);
}
}
}
異常是myBatisGenerator.generate(null);
這句產生的,往下找
/**
* 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.
* @param writeFiles
* if true, then the generated files will be written to disk. If false,
* then the generator runs but nothing is written
* @throws SQLException
* the SQL exception
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws InterruptedException
* if the method is canceled through the ProgressCallback
*/
public void generate(ProgressCallback callback, Set<String> contextIds,
Set<String> fullyQualifiedTableNames, boolean writeFiles) 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
if (writeFiles) {
callback.saveStarted(generatedXmlFiles.size()
+ generatedJavaFiles.size());
for (GeneratedXmlFile gxf : generatedXmlFiles) {
projects.add(gxf.getTargetProject());
writeGeneratedXmlFile(gxf, callback);
}
for (GeneratedJavaFile gjf : generatedJavaFiles) {
projects.add(gjf.getTargetProject());
writeGeneratedJavaFile(gjf, callback);
}
for (String project : projects) {
shellCallback.refreshProject(project);
}
}
callback.done();
}
打斷點發現主要是這里使用了配置文件里的target
for (GeneratedJavaFile gjf : generatedJavaFiles) {
projects.add(gjf.getTargetProject());
writeGeneratedJavaFile(gjf, callback);
}
我們進去查看這個方法的實現
private void writeGeneratedJavaFile(GeneratedJavaFile gjf, ProgressCallback callback)
throws InterruptedException, IOException {
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());
}
}
斷點發現主要是 File directory = shellCallback.getDirectory(gjf .getTargetProject(), gjf.getTargetPackage());
拋出的異常
我們繼續往里面看
public File getDirectory(String targetProject, String targetPackage)
throws ShellException {
// targetProject is interpreted as a directory that must exist
//
// targetPackage is interpreted as a sub directory, but in package
// format (with dots instead of slashes). The sub directory will be
// created
// if it does not already exist
File project = new File(targetProject);
if (!project.isDirectory()) {
throw new ShellException(getString("Warning.9", //$NON-NLS-1$
targetProject));
}
StringBuilder sb = new StringBuilder();
StringTokenizer st = new StringTokenizer(targetPackage, "."); //$NON-NLS-1$
while (st.hasMoreTokens()) {
sb.append(st.nextToken());
sb.append(File.separatorChar);
}
File directory = new File(project, sb.toString());
if (!directory.isDirectory()) {
boolean rc = directory.mkdirs();
if (!rc) {
throw new ShellException(getString("Warning.10", //$NON-NLS-1$
directory.getAbsolutePath()));
}
}
return directory;
}
這里發現,我們generatorConfig.xml
的targetProject
主要是在這個方法里生成File對象
又因為File創建可以根據絕對路徑,也可以根據相對路徑,當我們在generatorConfig.xml
配置相對路徑的時候,File對象會以項目路徑(即user.dir目錄)
去尋找這個FILE
對象,由於相對路徑不對,找不到對應的文件夾,project.isDirectory()
就為false
,就會爆出The specified target project directory xxx does not exist
的異常,也因為這個問題,我們就能理解這句話 targetProject:目標項目,指定一個存在的目錄下,生成的內容會放到指定目錄中,如果目錄不存在,MBG不會自動建目錄
的意義,當文件夾不存在的時候,他會拋出異常,而不會創建相關文件
解決方法
- 使用絕對路徑
- 使用相對路徑的時候找准相對路徑
- 介紹一個簡單的找相對路徑方法,idea左側目錄找到你的module,右鍵,
copy relative path
就可以得到這個module相對於usr.dir
的相對路徑,如(module-one
),然后拼接原來的src/main/java
,組成module-one/src/main/java
即可
- 介紹一個簡單的找相對路徑方法,idea左側目錄找到你的module,右鍵,