MyBatis逆向工程生成代码以及使用详解

一、什么是MyBatis逆向工程?

MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
当数据库表比较多的时候,重复的创建pojo对象和简单的数据库表的(CRUD)操作的mapper,效率低,官方给出了使用mybatis Generator用来根据数据库表逆向生成pojo和mapper文件,避免了不断进行大量重复的工作,提高了开发效率,极大的方便了开发。

逆向工程:针对数据库单表—->生成代码(mapper.xml、mapper.java、pojo)

二、使用方法

这里我创建的是一个 springboot 工程。工程结构如下:

MyBatis逆向工程生成代码以及使用详解_第1张图片

1、首先导入逆向工程需要的 maven 依赖,要注意mysql驱动包的版本,pom.xml 文件结构如下:



	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.1.6.RELEASE
		 
	
	com.mybatis.generator
	mybatis-generator
	0.0.1-SNAPSHOT
	mybatis-generator
	Demo project for Spring Boot

	
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
		
		
		
			org.mybatis
			mybatis
			3.4.5
		
		
		
			mysql
			mysql-connector-java
			5.1.39
		
		
		
			org.mybatis.generator
			mybatis-generator-core
			1.3.3
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		

	


2、在 resources 下创建生成代码配置文件 generatorConfig.xml:





    
        
            
            
            
            
        

        
        
        
        
        
        

        
        

        
        

        
        

        
        
  • context:子元素

这些子标签有严格的配置顺序
          1. property(0个或者多个)
          2. plugin(0个或者多个)
          3. commentGenerator(0个或者1个)
          4. jdbcConnection(1个)
          5. javaTypeResolver(0个或者1个)
          6. javaModelGenerator(1个)
          7. sqlMapGenerator(0个或者1个)
          8. javaClientGenerator(0个或者1个)
          9. table(1个或者多个)

  • commentGenerator :配置是否自动生成默认注释和时间戳 ,自动生成是注释格式十分不友好。
 /**
     *
     * This field was generated by MyBatis Generator.
     * This field corresponds to the database column course.cno
     *
     * @mbggenerated
     */
    private Integer cno;

所以默认不用自动生成注释,而是我们自己去定义  MyCommentGenerator 就是定义的注释格式,其中要实现 CommentGenerator 接口,在接口中我们可以看到对 field 、set、get、method 、class 注释方法的声明。具体实现如下:

package com.mybatis.generator.web;

import static org.mybatis.generator.internal.util.StringUtility.isTrue;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.MergeConstants;
import org.mybatis.generator.config.PropertyRegistry;

/**
 * 描述:重新定义注释格式
 */
public class MyCommentGenerator implements CommentGenerator{
    private Properties properties;
    private Properties systemPro;
    private boolean suppressDate;
    private boolean suppressAllComments;
    private String currentDateStr;

    public MyCommentGenerator() {
        super();
        properties = new Properties();
        systemPro = System.getProperties();
        suppressDate = false;
        suppressAllComments = false;
        currentDateStr = (new SimpleDateFormat("yyyy-MM-dd")).format(new Date());
    }

    public void addJavaFileComment(CompilationUnit compilationUnit) {
        // add no file level comments by default
        return;
    }

    /**
     * Adds a suitable comment to warn users that the element was generated, and
     * when it was generated.
     */
    public void addComment(XmlElement xmlElement) {
        return;
    }

    public void addRootComment(XmlElement rootElement) {
        // add no document level comments by default
        return;
    }

    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() {
        String result = null;
        if (!suppressDate) {
            result = currentDateStr;
        }
        return result;
    }

    public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable) {
        if (suppressAllComments) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        innerClass.addJavaDocLine("/**");
        sb.append(" * ");
        sb.append(introspectedTable.getFullyQualifiedTable());
        sb.append(" ");
        sb.append(getDateString());
        innerClass.addJavaDocLine(sb.toString().replace("\n", " "));
        innerClass.addJavaDocLine(" */");
    }

    public void addEnumComment(InnerEnum innerEnum, IntrospectedTable introspectedTable) {
        if (suppressAllComments) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        innerEnum.addJavaDocLine("/**");
        sb.append(" * ");
        sb.append(introspectedTable.getFullyQualifiedTable());
        innerEnum.addJavaDocLine(sb.toString().replace("\n", " "));
        innerEnum.addJavaDocLine(" */");
    }

    public void addFieldComment(Field field, IntrospectedTable introspectedTable,
                                IntrospectedColumn introspectedColumn) {
        if (suppressAllComments) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        field.addJavaDocLine("/**");
        sb.append(" * ");
        sb.append(introspectedColumn.getRemarks());
        field.addJavaDocLine(sb.toString().replace("\n", " "));
        field.addJavaDocLine(" */");
    }

    public void addFieldComment(Field field, IntrospectedTable introspectedTable) {
        if (suppressAllComments) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        field.addJavaDocLine("/**");
        sb.append(" * ");
        sb.append(introspectedTable.getFullyQualifiedTable());
        field.addJavaDocLine(sb.toString().replace("\n", " "));
        field.addJavaDocLine(" */");
    }

    @Override
    public void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {

    }

    public void addGeneralMethodComment(Method method, IntrospectedTable introspectedTable) {
        if (suppressAllComments) {
            return;
        }
        method.addJavaDocLine("/**");
        addJavadocTag(method, false);
        method.addJavaDocLine(" */");
    }

    /**
     *  get 方法的注释
     * @param method
     * @param introspectedTable
     * @param introspectedColumn
     */
    public void addGetterComment(Method method, IntrospectedTable introspectedTable,
                                 IntrospectedColumn introspectedColumn) {
        if (suppressAllComments) {
            return;
        }
        method.addJavaDocLine("/**");
        StringBuilder sb = new StringBuilder();
        sb.append(" * ");
        sb.append(introspectedColumn.getRemarks());
        method.addJavaDocLine(sb.toString().replace("\n", " "));
        sb.setLength(0);
        sb.append(" * @return ");
        sb.append(introspectedColumn.getActualColumnName());
        sb.append(" ");
        sb.append(introspectedColumn.getRemarks());
        method.addJavaDocLine(sb.toString().replace("\n", " "));
        method.addJavaDocLine(" */");
    }

    /**
     * 对 set 方法的注释
     * @param method
     * @param introspectedTable
     * @param introspectedColumn
     */
    public void addSetterComment(Method method, IntrospectedTable introspectedTable,
                                 IntrospectedColumn introspectedColumn) {
        if (suppressAllComments) {
            return;
        }
        method.addJavaDocLine("/**");
        StringBuilder sb = new StringBuilder();
        sb.append(" * ");
        sb.append(introspectedColumn.getRemarks());
        method.addJavaDocLine(sb.toString().replace("\n", " "));
        Parameter parm = method.getParameters().get(0);
        sb.setLength(0);
        sb.append(" * @param ");
        sb.append(parm.getName());
        sb.append(" ");
        sb.append(introspectedColumn.getRemarks());
        method.addJavaDocLine(sb.toString().replace("\n", " "));
        method.addJavaDocLine(" */");
    }

    /**
     * 对类的注释
     * @param innerClass
     * @param introspectedTable
     * @param markAsDoNotDelete
     */
    public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable, boolean markAsDoNotDelete) {
        if (suppressAllComments) {
            return;
        }
        StringBuilder sb = new StringBuilder();
        innerClass.addJavaDocLine("/**");
        sb.append(" * ");
        sb.append(introspectedTable.getFullyQualifiedTable());
        innerClass.addJavaDocLine(sb.toString().replace("\n", " "));
        sb.setLength(0);
        sb.append(" * @author ");
        sb.append(systemPro.getProperty("user.name"));
        sb.append(" ");
        sb.append(currentDateStr);
        innerClass.addJavaDocLine(" */");
    }
}
  • jdbcConnection:这里是配置连接数据库的基本信息,按照自己的修改配置即可。
  • javaTypeResolver:数据库的类型和 Model 中 Java 类型的关系是由 JavaTypeResolver 控制的,有些时候我们需要将一些类型转为我们需要的类型,比如:tinyint -> Integer , bigint -> String ,这里我们就可以通过继承和实现 javaTypeResolver 解决。这里我们自己定义的 MyJavaTypeResolver 结构如下图

MyBatis逆向工程生成代码以及使用详解_第2张图片

具体 MyJavaTypeResolver  代码实现:

package com.mybatis.generator.web;

import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;

/**
 *
 */
public class MyJavaTypeResolver extends JavaTypeResolverDefaultImpl {

    /**
     * 将tinyint转换为Integer
     * 将bigint转换为String
     */
    public MyJavaTypeResolver() {
        super();
        this.typeMap.put(-6, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("TINYINT", new FullyQualifiedJavaType(Integer.class.getName())));
        this.typeMap.put(-5, new JavaTypeResolverDefaultImpl.JdbcTypeInformation("BIGINT", new FullyQualifiedJavaType(String.class.getName())));

    }
}

方法中的参数:-5 、-6 是数据类型,在 JavaTypeResolverDefaultImpl 中可以查看具体对应的值。第二个参数内容,

"BIGINT", new FullyQualifiedJavaType(String.class.getName())

BIGINT 即需要转换的类型,String.class.getName() 是我们需要的类型。

  • javaModelGenerator:生成的 pojo 的包名和位置。我在使用的过程中,遇到了 targetProject 无法识别的问题,如果直接写 /src/main/java  无法生成文件,加上项目名  mybatis-generator/src/main/java 可以正常生成,但是在另外一台电脑上不加项目名就可以,很奇怪。
  • sqlMapGenerator:生成的映射文件包名和位置。
  • javaClientGenerator:生成的 DAO 的包名和位置。这里和 javaModelGenerator 上面类似,需要加项目名。
  • table:这里是配置需要生成的数据库表。需要注意的是 tableName = "%" 是包含数据库中所有的表,如果单独生成一个表的映射关系,只需要在 tableName = "表名" 即可。

3、创建数据库和表结构:

MyBatis逆向工程生成代码以及使用详解_第3张图片

这里创建了 test 数据库,并创建了4张表。

4、创建启动类 StartUp,具体代码如下:

package com.mybatis.generator.web;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author admin
 */
public class StartUp {
    public static void main(String[] args) throws URISyntaxException {
        try {
            List warnings = new ArrayList();
            boolean overwrite = true;
            ClassLoader classloader = Thread.currentThread().getContextClassLoader();
            InputStream is = classloader.getResourceAsStream("generatorConfig.xml");
            ConfigurationParser cp = new ConfigurationParser(warnings);
            Configuration config = cp.parseConfiguration(is);
            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
            MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
            myBatisGenerator.generate(null);
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (InvalidConfigurationException e) {
            e.printStackTrace();
        } catch (XMLParserException e) {
            e.printStackTrace();
        }
    }
}

其中 generatorConfig.xml 就是上面我们在resources 目录下创建的。

5、启动 StartUp 中的 main 方法进行测试:

左边是启动前的结构,右边是生成后的结构

MyBatis逆向工程生成代码以及使用详解_第4张图片MyBatis逆向工程生成代码以及使用详解_第5张图片

查看具体内容:

Course:

package com.mybatis.generator.entity;

public class Course {
    /**
     * 
     */
    private Integer cno;

    /**
     * 课程名称
     */
    private String cname;

    /**
     * 教师编号
     */
    private Integer tno;

    /**
     * 
     * @return cno 
     */
    public Integer getCno() {
        return cno;
    }

    /**
     * 
     * @param cno 
     */
    public void setCno(Integer cno) {
        this.cno = cno;
    }

    /**
     * 课程名称
     * @return cname 课程名称
     */
    public String getCname() {
        return cname;
    }

    /**
     * 课程名称
     * @param cname 课程名称
     */
    public void setCname(String cname) {
        this.cname = cname;
    }

    /**
     * 教师编号
     * @return tno 教师编号
     */
    public Integer getTno() {
        return tno;
    }

    /**
     * 教师编号
     * @param tno 教师编号
     */
    public void setTno(Integer tno) {
        this.tno = tno;
    }
}

CourseMapper:

package com.mybatis.generator.mapper;

import com.mybatis.generator.entity.Course;

public interface CourseMapper {
    /**
     *
     * @mbggenerated
     */
    int deleteByPrimaryKey(Integer cno);

    /**
     *
     * @mbggenerated
     */
    int insert(Course record);

    /**
     *
     * @mbggenerated
     */
    int insertSelective(Course record);

    /**
     *
     * @mbggenerated
     */
    Course selectByPrimaryKey(Integer cno);

    /**
     *
     * @mbggenerated
     */
    int updateByPrimaryKeySelective(Course record);

    /**
     *
     * @mbggenerated
     */
    int updateByPrimaryKey(Course record);
}

CourseMapper.xml:




  
    
    
    
  
  
    cno, cname, tno
  
  
  
    delete from course
    where cno = #{cno,jdbcType=INTEGER}
  
  
    insert into course (cno, cname, tno
      )
    values (#{cno,jdbcType=INTEGER}, #{cname,jdbcType=VARCHAR}, #{tno,jdbcType=INTEGER}
      )
  
  
    insert into course
    
      
        cno,
      
      
        cname,
      
      
        tno,
      
    
    
      
        #{cno,jdbcType=INTEGER},
      
      
        #{cname,jdbcType=VARCHAR},
      
      
        #{tno,jdbcType=INTEGER},
      
    
  
  
    update course
    
      
        cname = #{cname,jdbcType=VARCHAR},
      
      
        tno = #{tno,jdbcType=INTEGER},
      
    
    where cno = #{cno,jdbcType=INTEGER}
  
  
    update course
    set cname = #{cname,jdbcType=VARCHAR},
      tno = #{tno,jdbcType=INTEGER}
    where cno = #{cno,jdbcType=INTEGER}
  

这样我们的逆向工程生成就完成了.... 但是有没有发现问题呢?

问题解答:

问题一:数据库中有4张表,为什么生成5个实体?

解析:MyBatis Generator配置文件context元素有一个defaultModelType属性,这个属性的值会影响实体类(或叫domain类,model类)的生成。这个属性用于设置产生的模型类型。模型类型定义了MBG如何去产生模型类。对于一些模型类型,MBG会为每一张表产生单独的实体类,而其他的模型类型,MBG会依据表的结构产生一些不同的实体类。
这个属性支持以下三个值:

  • conditional:这是默认值。但是如果有联合主键,会生成一个主键实体类,这个模型与hierarchical模型相似,如果一个实体类只包含一个字段,则不会单独生成此实体类。因此,如果一个表的主键只有一个字段,那么不会为该字段生成单独的实体类,会将该字段合并到基本实体类中。
  • flat:该模型为每一张表只生成一个实体类。这个实体类包含表中的所有字段。一般使用这个模型就够了。
  • hierarchical:如果表有主键,那么该模型会产生一个单独的主键实体类,如果表还有BLOB字段,则会为表生成一个包含所有BLOB字段的单独的实体类,然后为所有其他的字段生成一个单独的实体类。MBG会在所有生成的实体类之间维护一个继承关系。显然这个模型比较复杂。

因为我们的配置并没有配置属性值,所以会生成主键实体类。

问题二:出现异常 java.sql.SQLException: Unknown system variable 'query_cache_size' 

java.sql.SQLException: Unknown system variable 'query_cache_size'
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:963)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3966)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3902)
	at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2526)
	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2673)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2545)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2503)
    ......

解析:由于 mysql 驱动包的版本过低导致这个问题的发生。意思是 query_cache_size 在MySQL5.7.20就已经过时了,而在MySQL8.0之后就已经被移除了。所以如果数据库使用的  Mysql 8.0之后的,那么依赖包要是对应的版本。

问题三:出现异常 java.sql.SQLException:The server time zone value '???ú±ê×??±??' is unrecognized or represents more than one time zone.

解析:数据库和系统时区存在差异,在低版本的mysql驱动中存在该问题,解决方法:在jdbc url后加上serverTimezone=GMT

问题四:警告 Establishing SSL connection without server's identityverification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+requirements SSL connection must be established by default if explicit optionisn't set. For compliance with existing applications not using SSL theverifyServerCertificate property is set to 'false'. You need either toexplicitly disable SSL by setting useSSL=false, or set useSSL=true and providetruststore for server certificate verification.

解析:原因是MySQL在高版本需要指明是否进行SSL连接。解决方案如下:在mysql连接字符串url中加入ssl=true或者false即可,如下所示。

jdbc:mysql://localhost:3306/test?useSSL=false

 

 

 

 

 

 

 

 

你可能感兴趣的:(Java相关)