一、什么是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 工程。工程结构如下:
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:
这些子标签有严格的配置顺序
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个或者多个)
/**
*
* 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(" */");
}
}
具体 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() 是我们需要的类型。
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 方法进行测试:
左边是启动前的结构,右边是生成后的结构
查看具体内容:
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会依据表的结构产生一些不同的实体类。
这个属性支持以下三个值:
因为我们的配置并没有配置属性值,所以会生成主键实体类。
问题二:出现异常 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