在工作中通常需要根據表結構來生成對應的model、mapper、xml。以下是介紹基於maven的mybatis-generator-plugin來完成相關工作,以及遇見的相關問題。
一、基本配置
使用maven配置mybatis generator來根據表結構生成model、mapper及xml文件的基本配置如下:
1、pom.xml添加如下配置
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
說明:<verbose>true</verbose>是爲了運行mvn mybatis-generator:generate -e時顯示具體過程。顯示如下:
[INFO] Introspecting table user
[INFO] Generating Example class for table user
[INFO] Generating Record class for table user
[INFO] Generating Mapper Interface for table user
[INFO] Generating SQL Map for table user
[INFO] Saving file UserMapper.xml
[INFO] Saving file UserDTOExample.java
[INFO] Saving file UserDTO.java
[INFO] Saving file UserMapper.java
<overwrite>true</overwrite>的設置是爲了生成文件時後一次生成的文件覆蓋前一次生成的同名文件。
如果不設置,默認爲false,那麼,多次生成的文件效果如下:
2、generatorConfig.xml 的配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MBG" targetRuntime="MyBatis3" defaultModelType="conditional">
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!-- 配置插件 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/test" userId="root" password="12345678" />
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
<!-- 類型解析器 -->
</javaTypeResolver>
<javaModelGenerator targetPackage="com.iwill.model" targetProject="src/main/java">
<!-- 實體類 -->
<property name="enableSubPackages" value="true" />
<property name="" value=""/>
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.iwill.mapper" targetProject="src/main/resources" >
<!-- 實體類SQL映射文件 -->
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.iwill.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 基礎表 -->
<table tableName="user" domainObjectName="UserDTO" mapperName="UserMapper" />
</context>
</generatorConfiguration>
這裏<property name="suppressAllComments" value="true" />設置的目的是取消生成代碼自動添加的comment,如果不添加這一行,生成的代碼片段如下:
public interface UserMapper {
/**
* This method was generated by MyBatis Generator.
* This method corresponds to the database table user
*
* @mbg.generated Sun Jun 24 23:02:36 CST 2018
*/
long countByExample(UserDTOExample example);
這些註釋毫無價值,發現這個問題後,查看源代碼,有如下發現:
2處爲添加的註冊,1處爲判斷條件,所以只要suppressAllComments設置爲true即可去掉自動添加的註釋。
二、遇見問題
多次運行生成過程,可以發現,UserMapper.xml裏面的內容會append到上一次生成的內容後面,即出現多個相同的resultMap id及sql id在一個文件裏面,這就生成錯誤的xml文件了。運行兩次後的文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.iwill.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.iwill.model.UserDTO">
<result column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause">
<where>
<foreach collection="example.oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
id, name
</sql>
<select id="selectByExample" parameterType="com.iwill.model.UserDTOExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from user
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<delete id="deleteByExample" parameterType="com.iwill.model.UserDTOExample">
delete from user
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="com.iwill.model.UserDTO">
insert into user (id, name)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="com.iwill.model.UserDTO">
insert into user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="name != null">
name,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="name != null">
#{name,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="com.iwill.model.UserDTOExample" resultType="java.lang.Long">
select count(*) from user
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByExampleSelective" parameterType="map">
update user
<set>
<if test="record.id != null">
id = #{record.id,jdbcType=INTEGER},
</if>
<if test="record.name != null">
name = #{record.name,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update user
set id = #{record.id,jdbcType=INTEGER},
name = #{record.name,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<resultMap id="BaseResultMap" type="com.iwill.model.UserDTO">
<result column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
<foreach collection="oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Update_By_Example_Where_Clause">
<where>
<foreach collection="example.oredCriteria" item="criteria" separator="or">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
<when test="criterion.betweenValue">
and ${criterion.condition} #{criterion.value} and #{criterion.secondValue}
</when>
<when test="criterion.listValue">
and ${criterion.condition}
<foreach close=")" collection="criterion.value" item="listItem" open="(" separator=",">
#{listItem}
</foreach>
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
<sql id="Base_Column_List">
id, name
</sql>
<select id="selectByExample" parameterType="com.iwill.model.UserDTOExample" resultMap="BaseResultMap">
select
<if test="distinct">
distinct
</if>
<include refid="Base_Column_List" />
from user
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
</select>
<delete id="deleteByExample" parameterType="com.iwill.model.UserDTOExample">
delete from user
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</delete>
<insert id="insert" parameterType="com.iwill.model.UserDTO">
insert into user (id, name)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" parameterType="com.iwill.model.UserDTO">
insert into user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="name != null">
name,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id,jdbcType=INTEGER},
</if>
<if test="name != null">
#{name,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="com.iwill.model.UserDTOExample" resultType="java.lang.Long">
select count(*) from user
<if test="_parameter != null">
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByExampleSelective" parameterType="map">
update user
<set>
<if test="record.id != null">
id = #{record.id,jdbcType=INTEGER},
</if>
<if test="record.name != null">
name = #{record.name,jdbcType=VARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
<update id="updateByExample" parameterType="map">
update user
set id = #{record.id,jdbcType=INTEGER},
name = #{record.name,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
</update>
</mapper>
爲了解決這個問題,查看源碼:
private void writeGeneratedXmlFile(GeneratedXmlFile gxf, ProgressCallback callback)
throws InterruptedException, IOException {
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();
}
callback.checkCancel();
callback.startTask(getString(
"Progress.15", targetFile.getName())); //$NON-NLS-1$
writeFile(targetFile, source, "UTF-8"); //$NON-NLS-1$
} catch (ShellException e) {
warnings.add(e.getMessage());
}
}
三個if分支首先是判斷gxf.isMergeable(),這個值默認是true,因此會執行第一個分支的邏輯。參照如下:
@Override
public List<GeneratedXmlFile> getGeneratedXmlFiles() {
List<GeneratedXmlFile> answer = new ArrayList<GeneratedXmlFile>();
if (xmlMapperGenerator != null) {
Document document = xmlMapperGenerator.getDocument();
GeneratedXmlFile gxf = new GeneratedXmlFile(document,
getMyBatis3XmlMapperFileName(), getMyBatis3XmlMapperPackage(),
context.getSqlMapGeneratorConfiguration().getTargetProject(),
true, context.getXmlFormatter());
if (context.getPlugins().sqlMapGenerated(gxf, this)) {
answer.add(gxf);
}
}
return answer;
}
這是xml文件對象生成時mergeable的值被初始化爲true。
如果需要讓後一次生成的xml文件覆蓋上一次生成的xml文件,可選的方式有兩種:將mergeable的值設置爲false,或者讓程序執行第二個if分支:修改源代碼將第二個if分支和第一個if分支換個位置:在
if (shellCallback.isOverwriteEnabled()) {
source = gxf.getFormattedContent();
warnings.add(getString("Warning.11", targetFile.getAbsolutePath()));
} else if (gxf.isMergeable()) {
source = XmlFileMergerJaxp.getMergedSource(gxf,targetFile);
} else {
source = gxf.getFormattedContent();
targetFile = getUniqueFileName(directory, gxf.getFileName());
warnings.add(getString("Warning.2", targetFile.getAbsolutePath())); //$NON-NLS-1$
}
在tag爲mybatis-generator-1.3.6的基礎上修改,將版本升級爲1.3.6.1,本地執行,mvn package install。在項目中引入新版本的mybatis-generator-core,代碼如下:
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.6.1</version>
<scope>system</scope>
<systemPath>/Users/iwill/.m2/repository/org/mybatis/generator/mybatis-generator-core/1.3.6.1/mybatis-generator-core-1.3.6.1.jar</systemPath>
</dependency>
重新多次運行 mvn mybatis-generator:generate -e ,發現xml文件每次都是重新生成,上一次生成的內容不會保留。
還有網友使用插件的形式來解決這個問題,即將mergeable的值置爲false。如下:
package com.iwill.plugin;
import org.mybatis.generator.api.GeneratedXmlFile;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import java.lang.reflect.Field;
import java.util.List;
public class OverIsMergeablePlugin extends PluginAdapter {
public boolean validate(List<String> warnings) {
return true;
}
@Override
public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
try {
Field field = sqlMap.getClass().getDeclaredField("isMergeable");
field.setAccessible(true);
field.setBoolean(sqlMap, false);
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
在generatorConfig.xml中添加插件:
<plugin type="com.iwill.plugin.OverIsMergeablePlugin"/>
這種方式是運行本地的main方法的形式生成代碼,如果使用maven的話,會找不到OverIsMergeablePlugin這個類。
三、擴展
其實,maven的插件來利用mybatis-generator生成代碼的本質就是調用org.mybatis.generator.api.MyBatisGenerator#generate()。代碼如下:
ConfigurationParser cp = new ConfigurationParser(
project.getProperties(), warnings);
Configuration config = cp.parseConfiguration(configurationFile);
ShellCallback callback = new MavenShellCallback(this, overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(new MavenProgressCallback(getLog(),
verbose), contextsToRun, fullyqualifiedTables);
MyBatisGenerator.generate的主體如下:
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();
ObjectFactory.reset();
RootClassInfo.reset();
// 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();
}
可以看出,首先是生成文件,然後是寫文件。
四、後續
我們發現,生成Model和Example都是在同一個目錄,目前mybatis-generator-core不支持將這兩份設置不同的package,爲了更好的配置,可以修改源碼,以及dtd文件來提供支持。