项目地址:https://gitee.com/chuyunfei/mybatis-generator-plugin.git
下面的是教程,上面的是成品,但是需要改东西才能在你的电脑上跑,下面教程有,希望可以帮到你。
在使用mybatis进行开发的时候,所有的SQL语句是需要我们自己手动去写的,哪怕是:select * from table_name; 这样子简单的语句。这些简单、繁复、枯燥、无聊的工作占据了我们很大的一部分开发时间,所以我不想写这些简单的SQL,想让别人把这些简单的给完成了,我再去写那些关键的SQL语句。
很少有什么项目的需求可以在开始开发前就直接确定完的,难免会在开发的过程中进行需求的更改而导致数据表的更改,而mybatis的映射文件是与结构表高度相关的,如果在大量的映射文件中找到指定的配置难免会出现不必要的错误,所以对于数据表的映射文件的管理最好是自动化的,减少人为造成bug。在我开发的过程中,项目的数据表习惯于写在一个SQL文件里面,变换数据表结构时修改这个文件而不是直接修改数据库,可以保证数据库里面的数据表结构和这个SQL文件里面所呈现的一致。
在自己使用mybatis进行开发时,mapper的接口及其xml配置都是个人进行命名的,很难保证数据表结构或者需求更改后仍然保持稳定,特别是在没有一个强制规范的时候,mapper的接口会写的乱七八糟,带有很强烈的个性,这种个性对于非第一代开发人员的程序维护将造成很大的障碍,所以最好可以将Dao层的API接口稳定,便于程序的维护,也可以明确API含义,避免API含糊其辞,比如这个方法名在一个mapper标识的是一个意思,在另外一个mapper里面标识的却是完全风马牛不相及的意思,这会增加理解的成本,增加维护的难度。
就是懒,不想做哪些明明别人都已经给我解决的问题,不想动,躺着不好吗?
最开始我在想到底要不要写这个,毕竟网上一搜一大堆,但是根据我搭建的惨痛教训,我觉得还是写一下,但是事先声明环境,不是这个环境的后面的各个阶段都要找到对应环境的相应解决方案,好了,我的开发环境如下:
- JDK8
- mybatis:3.4.5
- mybatis-generator-core:1.3.2
- maven 3 (IDEA插件)
- IDEA 企业版
- MySQL 5.7 (高版本MySQL这里需要注意配置 jdbc.url 的附带参数的问题,比如什么useSSL=false,什么时区,什么字符编码什么的)
- mybatis-generator-maven-plugin:1.3.2
记得改maven的三坐标,后面关于maven的坐标的信息记得换成你自己的坐标
4.0.0
cqupt.mislab
generator
1.0-SNAPSHOT
org.mybatis
mybatis
3.4.5
org.mybatis.generator
mybatis-generator-core
1.3.2
maven-compiler-plugin
3.8.0
1.8
UTF-8
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.2
true
true
依赖添加后会出现一个maven插件,也就是我们的依赖,如下:
再来一个逆向工程的配置文件:generatorConfig.xml
当然前提是你正确配置了相关信息,在这里有一篇我不知道哪里偷来的详细解释,在此感谢那位匿名的仁兄!(后来我找到了,在这里:https://www.cnblogs.com/xiaocao1434/p/8797636.html ,感谢 小草1434!)
配置文件完整的解释:generatorConfigComment.xml
为什么纠结这个插件开发,当然不是头脑发热什么追求技术什么的,当然是开发了插件可以让我更舒服、更快、更灵活、更有效的开发。
每一次我搜索 mybatis-generator 插件开发 的时候,出来的都是教我怎么用mybatis-generator的,对,没错,以插件的方式使用mybatis-generator,也就是我们到目前为止已经实现的部分,嗯…,每次我都想换Google再来搜一次。
没办法,查不到资料只有自己动手丰衣足食了,想到mybatis的插件开发,我试着在刚才的项目中 double shift and input plugin !!!!一个大大的惊喜:
秉承着打破砂锅问到底的小强精神,我点了进去,于是彻底对其宣判死刑:
没错,真相只有一个,就是这个关键类。
到目前为止我发现自己其实是走了弯路的,回想起配置文件里面是不是有可以配置插件的配置?还说不是有默认的几个插件嘛?不就是我瞎猫碰到死耗子的那几个插件吗?然后顺藤摸瓜,依然可以找到这个关键接口。
先选一个插件来进行研究,就拿带toString那个插件嘛,就决定是它了:
private void generateToString(IntrospectedTable introspectedTable,
TopLevelClass topLevelClass) {
//创建了一个方法,叫toString: method.setName("toString"); //$NON-NLS-1$
Method method = new Method();
//public访问权限
method.setVisibility(JavaVisibility.PUBLIC);
//返回值为String
method.setReturnType(FullyQualifiedJavaType.getStringInstance());
method.setName("toString"); //$NON-NLS-1$
//如果支持Java5就加一个注解@Override
if (introspectedTable.isJava5Targeted()) {
method.addAnnotation("@Override"); //$NON-NLS-1$
}
context.getCommentGenerator().addGeneralMethodComment(method,
introspectedTable);
//为方法添加一行代码体
method.addBodyLine("StringBuilder sb = new StringBuilder();"); //$NON-NLS-1$
method.addBodyLine("sb.append(getClass().getSimpleName());"); //$NON-NLS-1$
method.addBodyLine("sb.append(\" [\");"); //$NON-NLS-1$
method.addBodyLine("sb.append(\"Hash = \").append(hashCode());"); //$NON-NLS-1$
StringBuilder sb = new StringBuilder();
//将类里面的字段都给拼凑起来
for (Field field : topLevelClass.getFields()) {
String property = field.getName();
sb.setLength(0);
sb.append("sb.append(\"").append(", ").append(property) //$NON-NLS-1$ //$NON-NLS-2$
.append("=\")").append(".append(").append(property) //$NON-NLS-1$ //$NON-NLS-2$
.append(");"); //$NON-NLS-1$
method.addBodyLine(sb.toString());
}
method.addBodyLine("sb.append(\"]\");"); //$NON-NLS-1$
method.addBodyLine("return sb.toString();"); //$NON-NLS-1$
//给这个类添加这个toString方法
topLevelClass.addMethod(method);
}
en…,感觉这个也太…简单了吧?咋这么简单呢?还以为要代理要探针要什么高深莫测的技术,结果我裤子都脱了,给我看这个!!!
其实仔细、冷静、慢慢的分析还是有道理的,逆向工程生成文件其实就是根据数据库和配置文件里面的元数据,生成对需要生成的文件的元数据描述,最后才会通过这些元数据描述来进行文件的生成。也就是说我们现在修改的其实只是这个类的描述数据而已,也就解释了为什么这么简单的原因,因为到插件这里压根就还没有生成文件!!
先查看插件接口里面有哪些方法:
仔细分析上面的接口和查看接口注释可以发现接口的方法被分为五个部分:
看着这些接口的名字再看看配置文件的结构:
是不是突然恍然大悟!!!原来一切都是安排。
特别注意:虽然我很想把我知道的都给你说,都是事实是根本就没有办法说清楚,但是如果你按照下面的步骤做,你将能够为你的应用开发无论是xml、mapper、model那个方面的。
- 大胆猜测,信心求证
- 多看源码,有一点点想法就去看看源码再去实践。
- 不要怂。
由于是以插件的方式运行,所以需要把自己开发的插件加入插件依赖,更改pom文件,将本项目添加到插件依赖:
maven-compiler-plugin
3.8.0
1.8
UTF-8
org.mybatis.generator
mybatis-generator-maven-plugin
1.3.2
true
true
cqupt.mislab
generator
1.0-SNAPSHOT
为了方便测试,写一个maven命令:
clean:clean
compiler:compile
jar:jar
install:install-file -Dfile=F:\JAVA\Projects\Main\generator\target\generator-1.0-SNAPSHOT.jar
-DgroupId=cqupt.mislab
-DartifactId=generator
-Dversion=1.0-SNAPSHOT
-Dpackaging=jar
mybatis-generator:generate
注释:
注意更改你的jar包位置和maven三坐标,然后将这个命令搞到这里来:
记得点右下角的apply,然后将看见这个:
现在点击右边那个绿色三角形执行一下,可以看见和刚刚一样的结果就是成功了。
根据我们前面的分析和自带插件的分析,首先一个插件需要事先Plugin接口,当然为了更方便都是直接继承PluginAdapter,然后由于我们是想写修改Mapper接口行为的插件(为mapper填加注解),所以挑选以client*开头的方法,最终得到:
package plugins;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import java.util.List;
public class MapperAnnotationPlugin extends PluginAdapter {
@Override
public boolean clientGenerated(Interface interfaze,TopLevelClass topLevelClass,IntrospectedTable introspectedTable){
//首先要导入这个类型撒,addImportedType,生动形象,还有一些什么:addMethod、addAnnotation什么的,见名知意
interfaze.addImportedType(new FullyQualifiedJavaType("org.springframework.stereotype.Repository"));
//给它加一顶注解
interfaze.addAnnotation("@Repository");
return true;
}
@Override//每个插件必须实现,具体的就是插件执行中的警告信息,该插件的配置信息是否完备等
public boolean validate(List warnings){
return true;
}
}
再把我们的插件配置进去:
执行,点击配置的那个maven命令:
现在我们要让这个插件更加智能,我可以让他在某些表里面生效,某些表不失效,可以添加多个注解,撸起袖子加油干:
首先为插件配置属性:
其次为表配置是否启用插件:
现在去改造我们原来的简单的插件:
package plugins;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import java.util.List;
import java.util.Properties;
/**
* 0、全局在插件配置属性里面使用:globalEnable = true 开启
* 1、实现为Mapper文件添加指定注解的功能
* 2、单表开启参数:enableMapperAnnotation = true
* 3、单表注解的分隔符,配置参数为:annotationSeparator,默认为:,
* 4、单表注解类,使用配置:annotationClasses
* 5、全局分隔符使用:globalAnnotationSeparator 配置
* 6、全局注解使用:globalAnnotationClasses 配置
*/
public class MapperAnnotationPlugin extends PluginAdapter {
@Override
public boolean clientGenerated(Interface interfaze,TopLevelClass topLevelClass,IntrospectedTable introspectedTable){
//判断插件是否启用
if(!isPluginEnable(introspectedTable)){
return true;
}
final Properties tableProperties = getTableProperties(introspectedTable);
//获取表里面配置的分隔符,优先级比全局的高,默认为:,
String separator = tableProperties.getProperty("annotationSeparator");
if(separator == null){
//获取全局分隔符
separator = properties.getProperty("globalAnnotationSeparator",",");
}
String annotationClass = null;
//获取这张表需要添加的注解
String tableAnnotationClass = tableProperties.getProperty("annotationClasses");
if(tableAnnotationClass!=null){
annotationClass = tableAnnotationClass;
}
//全局需要配置注解
String globalAnnotationClass = properties.getProperty("globalAnnotationClasses");
if(globalAnnotationClass != null){
if(tableAnnotationClass == null){
annotationClass = globalAnnotationClass;
}else {
annotationClass = tableAnnotationClass + separator + globalAnnotationClass;
}
}
if(annotationClass != null){
String[] annotationClazz = annotationClass.split(separator);
if(annotationClazz.length > 0){
for(String clazz : annotationClazz){
//导入注解
interfaze.addImportedType(new FullyQualifiedJavaType(clazz));
//添加注解
interfaze.addAnnotation("@" + clazz.substring(clazz.lastIndexOf(".")+1));
}
}
}
return true;
}
@Override
public boolean validate(List warnings){
return true;
}
/**
* 获取表的配置数据
*/
private Properties getTableProperties(IntrospectedTable introspectedTable){
return introspectedTable.getTableConfiguration().getProperties();
}
/**
* 判断一个表是否开启这个插件
*/
private boolean isPluginEnable(IntrospectedTable introspectedTable){
//获取配置在插件里面的属性,全局开关
final String globalEnable = properties.getProperty("globalEnable");
//如果没有1配置,默认为全局开启
if(globalEnable == null || Boolean.valueOf(globalEnable)){
//获取配置在表里面的属性
final String tableEnable = introspectedTable.getTableConfigurationProperty("enableMapperAnnotation");
if(tableEnable != null){
return Boolean.valueOf(tableEnable);
}
return true;
}
return false;
}
}
注意重新测试的时候把你上一次测试的生成文件删除掉
在上一张图片中你一定看见了那些令人感到厌烦的自带的注释,现在继续自定义的注释生成器,也是最常用的,可以将SQL里面的comment自动的生成到model的字段上面去,但是这个有专门的方式,但是可以思考怎么样用插件实现,现在来进行常规的方式:自定义注释生成器。
package plugins;
import org.mybatis.generator.api.*;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.MergeConstants;
import org.mybatis.generator.config.PropertyRegistry;
import java.io.Serializable;
import java.util.Date;
import java.util.Properties;
import static org.mybatis.generator.internal.util.StringUtility.isTrue;
/**
* 1、将SQL的comment转移到实体类里面
*/
public class ConsumerCommentGenerator implements CommentGenerator , Serializable {
private Properties properties;
private boolean suppressDate;
private boolean suppressAllComments;
public ConsumerCommentGenerator() {
super();
properties = new Properties();
suppressDate = false;
suppressAllComments = false;
}
public void addJavaFileComment(CompilationUnit compilationUnit) {
return;
}
/**
* Adds a suitable comment to warn users that the element was generated, and
* when it was generated.
*/
public void addComment(XmlElement xmlElement) {
if (suppressAllComments) {
return;
}
xmlElement.addElement(new TextElement(""));
}
public void addRootComment(XmlElement rootElement) {
rootElement.addElement(new TextElement(
""));
}
@Override
public void addConfigurationProperties(Properties properties) {
this.properties.putAll(properties);
suppressDate = isTrue(properties
.getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_DATE));
suppressAllComments = isTrue(properties
.getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_ALL_COMMENTS));
}
/**
* This method adds the custom javadoc tag for. You may do nothing if you do
* not wish to include the Javadoc tag - however, if you do not include the
* Javadoc tag then the Java merge capability of the eclipse plugin will
* break.
*
* @param javaElement
* the java element
*/
protected void addJavadocTag(JavaElement javaElement,boolean markAsDoNotDelete) {
javaElement.addJavaDocLine(" *");
StringBuilder sb = new StringBuilder();
sb.append(" * ");
sb.append(MergeConstants.NEW_ELEMENT_TAG);
if (markAsDoNotDelete) {
sb.append(" do_not_delete_during_merge");
}
String s = getDateString();
if (s != null) {
sb.append(' ');
sb.append(s);
}
javaElement.addJavaDocLine(sb.toString());
}
/**
* This method returns a formated date string to include in the Javadoc tag
* and XML comments. You may return null if you do not want the date in
* these documentation elements.
*
* @return a string representing the current timestamp, or null
*/
protected String getDateString() {
if (suppressDate) {
return null;
} else {
return new Date().toString();
}
}
public void addClassComment(InnerClass innerClass,IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
StringBuilder sb = new StringBuilder();
innerClass.addJavaDocLine("/**");
innerClass.addJavaDocLine(" * This class was generated by MyBatis Generator."); //$NON-NLS-1$
sb.append(" * This class corresponds to the database table "); //$NON-NLS-1$
sb.append(introspectedTable.getFullyQualifiedTable());
innerClass.addJavaDocLine(sb.toString());
addJavadocTag(innerClass, false);
innerClass.addJavaDocLine(" */"); //$NON-NLS-1$
}
public void addEnumComment(InnerEnum innerEnum,
IntrospectedTable introspectedTable) {
return;
}
public void addFieldComment(Field field,
IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
if (suppressAllComments) {
return;
}
field.addJavaDocLine("/**");
String remarkLine = introspectedColumn.getRemarks();
if(remarkLine != null){
String[] remarks = remarkLine.split(System.getProperty("line.separator"));
for(String remark : remarks){
field.addJavaDocLine(" * " + remark);
}
}
field.addJavaDocLine("*/");
}
public void addFieldComment(Field field, IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
field.addJavaDocLine("/**");
field.addJavaDocLine(" * 类静态字段");
field.addJavaDocLine(" */");
}
public void addGeneralMethodComment(Method method,IntrospectedTable introspectedTable) {
if (suppressAllComments) {
return;
}
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 切勿修改");
method.addJavaDocLine(" */");
}
public void addGetterComment(Method method,IntrospectedTable introspectedTable,IntrospectedColumn introspectedColumn) {
if (suppressAllComments) {
return;
}
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 切勿修改");
method.addJavaDocLine(" */");
}
public void addSetterComment(Method method,
IntrospectedTable introspectedTable,
IntrospectedColumn introspectedColumn) {
if (suppressAllComments) {
return;
}
method.addJavaDocLine("/**");
method.addJavaDocLine(" * 切勿修改");
method.addJavaDocLine(" */");
}
public void addClassComment(InnerClass innerClass,
IntrospectedTable introspectedTable, boolean markAsDoNotDelete) {
if (suppressAllComments) {
return;
}
innerClass.addJavaDocLine("/**");
innerClass.addJavaDocLine(" * 切勿修改");
innerClass.addJavaDocLine(" * 数据表:" + introspectedTable.getFullyQualifiedTable());
innerClass.addJavaDocLine(" */");
}
}
现在来配置这个自定义的注释生成器:
这个是我当前项目的问题,我持久层采用JPA,但是IDEA自带的JPA工具无法生成字段注释,而且定数据表也是SQL,虽然最后都需要转换为JPA的ERP模型,但是自己写Model和注释简直要把我逼疯,所以想到了逆向工程插件来实现这个问题,具体的转换思路是:
按照这个思路可以解决将数据表转换为model所花的大量时间(因为数据表多的令人绝望),还可以把数据表上面的注释也给转换过来,一举多得。
在此之间,依赖 lombok 来保持Model的类定义的清爽。
由于我们修改的是model,所以选择覆盖model*的接口方法
package plugins;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import java.util.List;
public class JpaEntityPlugin extends PluginAdapter {
/**
* 为JpaEntity的类级别增加注解的方法
*/
@Override
public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass,IntrospectedTable introspectedTable){
//清空里面所有的方法,所有的方法由lombok自动生成,保持文件的干爽和清晰
topLevelClass.getMethods().clear();
//导入需要导入的类
topLevelClass.addImportedType("lombok.*");
topLevelClass.addImportedType("javax.persistence.*");
final List annotations = topLevelClass.getAnnotations();
//添加lombok的相关注解
annotations.add("@Data");
annotations.add("@Builder");
annotations.add("@NoArgsConstructor");
annotations.add("@AllArgsConstructor");
//添加JPA的相关注解
annotations.add("@Entity");
final String tableName = introspectedTable.getFullyQualifiedTable().getIntrospectedTableName();
annotations.add("@Table(name = \"" + tableName + "\")");
return super.modelBaseRecordClassGenerated(topLevelClass,introspectedTable);
}
/**
* 为JpaEntity的字段添加Jpa,注意,必须要有一个字段为id的自增主键
*/
@Override
public boolean modelFieldGenerated(Field field,TopLevelClass topLevelClass,IntrospectedColumn introspectedColumn,IntrospectedTable introspectedTable,ModelClassType modelClassType){
final String columnName = introspectedColumn.getActualColumnName();
final List annotations = field.getAnnotations();
//添加Jpa的相关注解
if(field.getName().equalsIgnoreCase("id")){
annotations.add("@Id");
annotations.add("@GeneratedValue(strategy = GenerationType.AUTO)");
}else {
annotations.add("@Basic");
}
annotations.add("@Column(name = \"" + columnName + "\")");
return super.modelFieldGenerated(field,topLevelClass,introspectedColumn,introspectedTable,modelClassType);
}
@Override
public boolean validate(List warnings){
return true;
}
}
我演示了model和mapper的插件开发,但是为mapper增加方法的话需要修改xml配置文件,所以我分享一下我自己的工程:
https://gitee.com/chuyunfei/mybatis-generator-plugin.git
上面有这三个插件的源码,还有其他三个自定义的mapper方法,上面演示的工程是我在写博客的过程中一步步建立起来的,当然你可以直接运行我的git项目,不过记得改maven命令和那个jdbc驱动的位置。
如果有问题的话可以留言讨论哦,万一我不小心那个配置没有正确而对你造成困扰就很不好了,欢迎留言,么么哒。
——伤心流泪的不一定就是受害者,也许是加害者。