ORM框架 Mybatis 逆向工程
web项目在国内目前使用最多的对象关系映射(Object Relational Mapping, 简称ORM)是Mybatis,MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射;Mybatis操作数据库数据常用的的就是Mapper接口,Model表映射对象,Mapper.xml文件,当然也可以不需要xml文件,看个人习惯,mybatis的这三个结构就可以很好地操作数据库数据了。
当然这些文件可以自己手写,但是mybatis提供了逆向工程,可以逆向生成这些文件,现在就搭建一个这样的逆向工程。
逆向工程可以单独执行,也可以嵌套与服务中,看个人爱好,主要说一下嵌套服务中的这种方式。
Mybatis generator
构建一个mybatis-demo的一个微服务。结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k7adI3Pf-1596091120517)(media/15960758419051/15960778777723.jpg)]
逆向工程最重要的是三个东西,插件(plugins),数据库链接驱动包,generator.xml配置文件。
generator-maven-plugin插件配置
在项目中的pom文件中在标签内添加如下信息:
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<dependencies>
<dependency>
<groupId> mysql</groupId>
<artifactId> mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.6</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<phase>package</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<verbose>true</verbose>
<!-- 是否覆盖 -->
<overwrite>true</overwrite>
<!-- 自动生成的配置 -->
<configurationFile>
<!-- 定义的generator 文件的路径地址 -->
src/main/resources/mybatis-generator.xml</configurationFile>
</configuration>
</plugin>
</plugins>
</build>
从上述可以看到在标签内引入了plugins插件信息,其中包含mybatis-generator-maven-plugin,该运行插件同时引用了mysql的链接驱动包,mybatis-generator-core包。
mysql-connector-java用来链接mysql数据库的。
mybatis-generator-core用来生成文件内容的核心包(可以下源码自己修改生成规则,或新增内部插件)。
generator 配置文件内容
<?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="mysql" targetRuntime="MyBatis3" >
<commentGenerator>
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!-- connectURL 修改为自己需要生成的表所属库地址,userId是用户名,password是密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/tests"
userId="root"
password="123456"/>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- targetPackage是Mapper接口文件要放的项目包路径 -->
<javaModelGenerator targetPackage="com.test.generator.mapper" targetProject="src/main/java"/>
<!-- targetPackage是MapperXML文件要放的源路径 -->
<sqlMapGenerator targetPackage="sqlmapper" targetProject="src/main/resources"/>
<!-- targetPackage是model文件要放的项目包路径 -->
<javaClientGenerator targetPackage="com.test.generator.model" type="XMLMAPPER" targetProject="src/main/java"/>
<!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素
选择的table会生成一下文件:
1,SQL map文件
2,生成一个主键类;
3,除了BLOB和主键的其他字段的类;
4,包含BLOB的类;
5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选;
6,Mapper接口(可选)
tableName(必要):要生成对象的表名;
注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下,MBG会
根据设置的schema,catalog或tablename去查询数据表,按照下面的流程:
1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询;
2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找;
3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找;
4,否则,使用指定的大小写格式查询;
另外的,如果在创建表的时候,使用的""把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名;
这个时候,请设置delimitIdentifiers="true"即可保留大小写格式;
可选:
1,schema:数据库的schema;
2,catalog:数据库的catalog;
3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias_actualColumnName
4,domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面;
5,enableInsert(默认true):指定是否生成insert语句;
6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get);
7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句;
8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update);
9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete);
10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句;
11,enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询);
12,enableUpdateByExample(默认true):MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性);
13,modelType:参考context元素的defaultModelType,相当于覆盖;
14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性)
15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。默认为false,delimitIdentifiers参考context的属性
注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写;
-->
<table tableName="user_info" domainObjectName="UserInfo" enableSelectByPrimaryKey="true"
enableUpdateByPrimaryKey="true" enableSelectByExample="false" enableDeleteByExample="false" enableCountByExample="false"
enableUpdateByExample="false"/>
</context>
</generatorConfiguration>
该文件内容相对来说比较简洁了。拿过去即用。需要按要求修改的几处,特意标明。
配置信息有疑惑或者要定制,请参考官网配置详情,地址是http://mybatis.org/generator/configreference/xmlconfig.html。
运行方式右键pom.xml文件 Run Maven -》Plugins -》mybatis-generator-maven-plugin -》mybatis-generator:generate 就可以生成对应文件。如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RCFG2ecV-1596091120519)(media/15960758419051/15960792352597.jpg)]
userInfo.java文件内容
package com.test.generator.mapper;
import java.util.Date;
public class UserInfo {
private Long id;
private String name;
private String nickName;
private String idCard;
private String phone;
private Integer customerId;
private Integer chargeType;
private String password;
private Date createTime;
private String creator;
private Date modifyTime;
private String modifier;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Integer getCustomerId() {
return customerId;
}
public void setCustomerId(Integer customerId) {
this.customerId = customerId;
}
public Integer getChargeType() {
return chargeType;
}
public void setChargeType(Integer chargeType) {
this.chargeType = chargeType;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public Date getModifyTime() {
return modifyTime;
}
public void setModifyTime(Date modifyTime) {
this.modifyTime = modifyTime;
}
public String getModifier() {
return modifier;
}
public void setModifier(String modifier) {
this.modifier = modifier;
}
}
UserInfoMapper
package com.test.generator.model;
import com.test.generator.mapper.UserInfo;
public interface UserInfoMapper {
int deleteByPrimaryKey(Long id);
int insert(UserInfo record);
int insertSelective(UserInfo record);
UserInfo selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(UserInfo record);
int updateByPrimaryKey(UserInfo record);
}
UserInfoMapper.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.test.generator.model.UserInfoMapper">
<resultMap id="BaseResultMap" type="com.test.generator.mapper.UserInfo">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="nick_name" jdbcType="VARCHAR" property="nickName" />
<result column="id_card" jdbcType="VARCHAR" property="idCard" />
<result column="phone" jdbcType="VARCHAR" property="phone" />
<result column="customer_id" jdbcType="INTEGER" property="customerId" />
<result column="charge_type" jdbcType="INTEGER" property="chargeType" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="creator" jdbcType="VARCHAR" property="creator" />
<result column="modify_time" jdbcType="TIMESTAMP" property="modifyTime" />
<result column="modifier" jdbcType="VARCHAR" property="modifier" />
</resultMap>
<sql id="Base_Column_List">
id, name, nick_name, id_card, phone, customer_id, charge_type, password, create_time,
creator, modify_time, modifier
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from user_info
where id = #{
id,jdbcType=BIGINT}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from user_info
where id = #{
id,jdbcType=BIGINT}
</delete>
<insert id="insert" parameterType="com.test.generator.mapper.UserInfo">
insert into user_info (id, name, nick_name,
id_card, phone, customer_id,
charge_type, password, create_time,
creator, modify_time, modifier
)
values (#{
id,jdbcType=BIGINT}, #{
name,jdbcType=VARCHAR}, #{
nickName,jdbcType=VARCHAR},
#{
idCard,jdbcType=VARCHAR}, #{
phone,jdbcType=VARCHAR}, #{
customerId,jdbcType=INTEGER},
#{
chargeType,jdbcType=INTEGER}, #{
password,jdbcType=VARCHAR}, #{
createTime,jdbcType=TIMESTAMP},
#{
creator,jdbcType=VARCHAR}, #{
modifyTime,jdbcType=TIMESTAMP}, #{
modifier,jdbcType=VARCHAR}
)
</insert>
<insert id="insertSelective" parameterType="com.test.generator.mapper.UserInfo">
insert into user_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="name != null">
name,
</if>
<if test="nickName != null">
nick_name,
</if>
<if test="idCard != null">
id_card,
</if>
<if test="phone != null">
phone,
</if>
<if test="customerId != null">
customer_id,
</if>
<if test="chargeType != null">
charge_type,
</if>
<if test="password != null">
password,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="creator != null">
creator,
</if>
<if test="modifyTime != null">
modify_time,
</if>
<if test="modifier != null">
modifier,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{
id,jdbcType=BIGINT},
</if>
<if test="name != null">
#{
name,jdbcType=VARCHAR},
</if>
<if test="nickName != null">
#{
nickName,jdbcType=VARCHAR},
</if>
<if test="idCard != null">
#{
idCard,jdbcType=VARCHAR},
</if>
<if test="phone != null">
#{
phone,jdbcType=VARCHAR},
</if>
<if test="customerId != null">
#{
customerId,jdbcType=INTEGER},
</if>
<if test="chargeType != null">
#{
chargeType,jdbcType=INTEGER},
</if>
<if test="password != null">
#{
password,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
#{
createTime,jdbcType=TIMESTAMP},
</if>
<if test="creator != null">
#{
creator,jdbcType=VARCHAR},
</if>
<if test="modifyTime != null">
#{
modifyTime,jdbcType=TIMESTAMP},
</if>
<if test="modifier != null">
#{
modifier,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="com.test.generator.mapper.UserInfo">
update user_info
<set>
<if test="name != null">
name = #{
name,jdbcType=VARCHAR},
</if>
<if test="nickName != null">
nick_name = #{
nickName,jdbcType=VARCHAR},
</if>
<if test="idCard != null">
id_card = #{
idCard,jdbcType=VARCHAR},
</if>
<if test="phone != null">
phone = #{
phone,jdbcType=VARCHAR},
</if>
<if test="customerId != null">
customer_id = #{
customerId,jdbcType=INTEGER},
</if>
<if test="chargeType != null">
charge_type = #{
chargeType,jdbcType=INTEGER},
</if>
<if test="password != null">
password = #{
password,jdbcType=VARCHAR},
</if>
<if test="createTime != null">
create_time = #{
createTime,jdbcType=TIMESTAMP},
</if>
<if test="creator != null">
creator = #{
creator,jdbcType=VARCHAR},
</if>
<if test="modifyTime != null">
modify_time = #{
modifyTime,jdbcType=TIMESTAMP},
</if>
<if test="modifier != null">
modifier = #{
modifier,jdbcType=VARCHAR},
</if>
</set>
where id = #{
id,jdbcType=BIGINT}
</update>
<update id="updateByPrimaryKey" parameterType="com.test.generator.mapper.UserInfo">
update user_info
set name = #{
name,jdbcType=VARCHAR},
nick_name = #{
nickName,jdbcType=VARCHAR},
id_card = #{
idCard,jdbcType=VARCHAR},
phone = #{
phone,jdbcType=VARCHAR},
customer_id = #{
customerId,jdbcType=INTEGER},
charge_type = #{
chargeType,jdbcType=INTEGER},
password = #{
password,jdbcType=VARCHAR},
create_time = #{
createTime,jdbcType=TIMESTAMP},
creator = #{
creator,jdbcType=VARCHAR},
modify_time = #{
modifyTime,jdbcType=TIMESTAMP},
modifier = #{
modifier,jdbcType=VARCHAR}
where id = #{
id,jdbcType=BIGINT}
</update>
</mapper>
上述都是最简洁的,但是还是有可以在精简的地方,如UserInfo中的GetSet方法,可以用lombok的注解代替,UserInfoMapper.java中,除了UserInfo的引用其他都是固定的,可以提取一个BaseMapper,可以生成更简洁的Mapper接口内容。还有如果有表字段的新增,是否可以不需要更改文件直接再运行一遍,就可以将字段内容添加进来,都是可以自定义的,可以修改mybatis-generator-core包里的内容。
当然这个是改源码,然后在maven打包,再通过插件引用该修改后的包,相对于初级者这个还是稍稍有点困难,手动去改也是可以的。
进阶
其实这个也不难,一步一步地来。首先打开github或gitee,找到mybatis-generator-core.直接给地址https://github.com/mybatis/generator
在mybatis-generator-core中plugins包下添加文件LombokPlugin
package org.mybatis.generator.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.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import java.util.*;
/**
* @author fong
* @desc
* @projectname mybatis-generator
* @package org.mybatis.generator.plugins
* @date 2019-06-26 11:33
* @verson 1.0.0
*/
public class LombokPlugin extends PluginAdapter {
private final Collection<Annotations> annotations;
public LombokPlugin() {
annotations = new LinkedHashSet<Annotations>(Annotations.values().length);
}
public boolean validate(List<String> warnings) {
return true;
}
@Override
public boolean modelBaseRecordClassGenerated(
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable
) {
addAnnotations(topLevelClass);
return true;
}
/**
* Intercepts primary key class generation
*
* @param topLevelClass the generated primary key class
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @return always true
*/
@Override
public boolean modelPrimaryKeyClassGenerated(
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable
) {
addAnnotations(topLevelClass);
return true;
}
/**
* Intercepts "record with blob" class generation
*
* @param topLevelClass the generated record with BLOBs class
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @return always true
*/
@Override
public boolean modelRecordWithBLOBsClassGenerated(
TopLevelClass topLevelClass,
IntrospectedTable introspectedTable
) {
addAnnotations(topLevelClass);
return true;
}
/**
* Prevents all getters from being generated.
* See SimpleModelGenerator
*
* @param method the getter, or accessor, method generated for the specified
* column
* @param topLevelClass the partially implemented model class
* @param introspectedColumn The class containing information about the column related
* to this field as introspected from the database
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @param modelClassType the type of class that the field is generated for
*/
@Override
public boolean modelGetterMethodGenerated(
Method method,
TopLevelClass topLevelClass,
IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable,
ModelClassType modelClassType
) {
return false;
}
/**
* Prevents all setters from being generated
* See SimpleModelGenerator
*
* @param method the setter, or mutator, method generated for the specified
* column
* @param topLevelClass the partially implemented model class
* @param introspectedColumn The class containing information about the column related
* to this field as introspected from the database
* @param introspectedTable The class containing information about the table as
* introspected from the database
* @param modelClassType the type of class that the field is generated for
* @return always false
*/
@Override
public boolean modelSetterMethodGenerated(
Method method,
TopLevelClass topLevelClass,
IntrospectedColumn introspectedColumn,
IntrospectedTable introspectedTable,
ModelClassType modelClassType
) {
return false;
}
/**
* Adds the lombok annotations' imports and annotations to the class
*
* @param topLevelClass the partially implemented model class
*/
private void addAnnotations(TopLevelClass topLevelClass) {
for (Annotations annotation : annotations) {
topLevelClass.addImportedType(annotation.javaType);
topLevelClass.addAnnotation(annotation.asAnnotation());
}
}
@Override
public void setProperties(Properties properties) {
super.setProperties(properties);
//@Data is default annotation
annotations.add(Annotations.DATA);
annotations.add(Annotations.NO_ARGS_CONSTRUCTOR);
annotations.add(Annotations.ALL_ARGS_CONSTRUCTOR);
annotations.add(Annotations.BUILDER);
for (String annotationName : properties.stringPropertyNames()) {
if (annotationName.contains(".")) {
// Not an annotation name
continue;
}
String value = properties.getProperty(annotationName);
if (!Boolean.parseBoolean(value)) {
// The annotation is disabled, skip it
continue;
}
Annotations annotation = Annotations.getValueOf(annotationName);
if (annotation == null) {
continue;
}
String optionsPrefix = annotationName + ".";
for (String propertyName : properties.stringPropertyNames()) {
if (!propertyName.startsWith(optionsPrefix)) {
// A property not related to this annotation
continue;
}
String propertyValue = properties.getProperty(propertyName);
annotation.appendOptions(propertyName, propertyValue);
annotations.add(annotation);
annotations.addAll(Annotations.getDependencies(annotation));
}
}
}
private enum Annotations {
DATA("data", "@Data", "lombok.Data"),
BUILDER("builder", "@Builder", "lombok.Builder"),
ALL_ARGS_CONSTRUCTOR("allArgsConstructor", "@AllArgsConstructor", "lombok.AllArgsConstructor"),
NO_ARGS_CONSTRUCTOR("noArgsConstructor", "@NoArgsConstructor", "lombok.NoArgsConstructor"),
ACCESSORS("accessors", "@Accessors", "lombok.experimental.Accessors"),
TO_STRING("toString", "@ToString", "lombok.ToString");
private final String paramName;
private final String name;
private final FullyQualifiedJavaType javaType;
private final List<String> options;
Annotations(String paramName, String name, String className) {
this.paramName = paramName;
this.name = name;
this.javaType = new FullyQualifiedJavaType(className);
this.options = new ArrayList<String>();
}
private static Annotations getValueOf(String paramName) {
for (Annotations annotation : Annotations.values())
if (String.CASE_INSENSITIVE_ORDER.compare(paramName, annotation.paramName) == 0)
return annotation;
return null;
}
private static Collection<Annotations> getDependencies(Annotations annotation) {
if (annotation == ALL_ARGS_CONSTRUCTOR)
return Collections.singleton(NO_ARGS_CONSTRUCTOR);
else
return Collections.emptyList();
}
// A trivial quoting.
// Because Lombok annotation options type is almost String or boolean.
private static String quote(String value) {
if (Boolean.TRUE.toString().equals(value) || Boolean.FALSE.toString().equals(value))
// case of boolean, not passed as an array.
return value;
return value.replaceAll("[\\w]+", "\"$0\"");
}
private void appendOptions(String key, String value) {
String keyPart = key.substring(key.indexOf(".") + 1);
String valuePart = value.contains(",") ? String.format("{%s}", value) : value;
this.options.add(String.format("%s=%s", keyPart, quote(valuePart)));
}
private String asAnnotation() {
if (options.isEmpty()) {
return name;
}
StringBuilder sb = new StringBuilder();
sb.append(name);
sb.append("(");
boolean first = true;
for (String option : options) {
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(option);
}
sb.append(")");
return sb.toString();
}
}
}