让MyBatis Generator产生的Mapper更简洁

  • 2020 年 2 月 14 日
  • 笔记

本文提供一种方法,目标是让MyBatis Generator产生的Mapper更简洁。

主要体现在如下几个方面:

  • 有一个BaseMapper(自己编写)
  • 所有产生的Mapper继承BaseMapper无需每个Mapper都要定义好多接口方法
  • 除了产生的Mapper有改动之外,其余自动产生的Entity、Example、XML文件保持不变

01

背景

  • 不同Mapper的比较

比如,我们建立两个测试表,一个是t_user, 一个是t_news。其建表语句如下:

CREATE TABLE `t_user` (    `user_id` int(11) NOT NULL AUTO_INCREMENT,    `email` varchar(64) DEFAULT NULL,    `name` varchar(30) DEFAULT NULL,    PRIMARY KEY (`user_id`)  ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;      CREATE TABLE `t_news` (    `news_id` int(11) NOT NULL AUTO_INCREMENT,    `title` varchar(150) NOT NULL,    `content` text NOT NULL,    `brief_intro` varchar(255) DEFAULT NULL,    `pic_url` varchar(255) DEFAULT NULL,    `news_from` varchar(100) DEFAULT NULL,    `news_author` varchar(50) DEFAULT NULL,    `news_url` varchar(255) DEFAULT NULL,    `keywords` varchar(150) DEFAULT NULL,    `meta_desc` varchar(150) DEFAULT NULL,    `create_time` datetime DEFAULT NULL,    PRIMARY KEY (`news_id`)  ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

然后,我们比较一下自动产生的Mapper有什么区别

UserMapper.java

NewsMapper.java

从上述两个自动产生的Mapper可以看出,

除了Entity、Entity对应的Example以及Primary Key可能会变化之外,其余所有的方法名都是一样的。

  • 分析

如果是这样自动产生代码,那么各个Mapper势必有很多重复的代码,不直观。

那么,问题来了?

可不可以将这些通用的方法定义在一个BaseMapper中,然后,其余自动产生的Mapper继承自BaseMapper,且与各自的Entity、Example、Primary KEY绑定在一起呢?

比如:

定义一个BaseMapper.java,其中,

  • T表示与table表对应的实体类(Entity)
  • E表示Entity对应的Example类
  • PK表示可能会用到主键 (比如Integer等)

如下所示:

package my.mabatis.example.base;    import java.io.Serializable;  import java.util.List;    import org.apache.ibatis.annotations.Param;    /**   *   * @author wangmengjun   *   */  public interface BaseMapper<T, E, PK extends Serializable> {      long countByExample(E example);      int deleteByExample(E example);      int deleteByPrimaryKey(PK pk);      int insert(T record);      int insertSelective(T record);      List<T> selectByExample(E example);      T selectByPrimaryKey(PK pk);      int updateByExampleSelective(@Param("record") T record,        @Param("example") E example);      int updateByExample(@Param("record") T record, @Param("example") E example);      int updateByPrimaryKeySelective(T record);      int updateByPrimaryKey(T record);  }

那么,

UserMapper.java就变成类似如下的样子了。

public interface NewsMapper extends BaseMapper<News, NewsExample, Integer> {  }

接下来,我们就来看看如何完成去达到这样的目标。Let‘s GO~~~

02

解决方法

  • 方法一:改源代码

改源代码?

如果一个工具,让产生的Dao继承一个BaseMapper,都需要通过源码来完成,那其扩展性可见一般。 不建议使用,这个只能是没有办法的时候才会使用。

因为,上述考虑的都是Mapper,那么,如果改动源代码的话,我们就在org.mybatis.generator.codegen.mybatis3.javamapper.JavaMapperGenerator类中修改即可。

修改包含两个部分,

  • 不在Mapper中添加任何方法,因为这些都在BaseMapper中存在了,只要继承即可。
  • 产生Mapper的时候,指定父类接口BaseMapper, 实体类类型、Example类型、主键类型。

具体在JavaMapperGenerator类的getCompilationUnits方法下进行:

public List<CompilationUnit> getCompilationUnits() {  //省略所有方法内容  }
  • 移除添加方法的代码
          addCountByExampleMethod(interfaze);          addDeleteByExampleMethod(interfaze);          addDeleteByPrimaryKeyMethod(interfaze);          addInsertMethod(interfaze);          addInsertSelectiveMethod(interfaze);          addSelectByExampleWithBLOBsMethod(interfaze);          addSelectByExampleWithoutBLOBsMethod(interfaze);          addSelectByPrimaryKeyMethod(interfaze);          addUpdateByExampleSelectiveMethod(interfaze);          addUpdateByExampleWithBLOBsMethod(interfaze);          addUpdateByExampleWithoutBLOBsMethod(interfaze);          addUpdateByPrimaryKeySelectiveMethod(interfaze);          addUpdateByPrimaryKeyWithBLOBsMethod(interfaze);          addUpdateByPrimaryKeyWithoutBLOBsMethod(interfaze);
  • 指定父类接口BaseMapper, 实体类类型等

在上述移除的代码块中添加类似如下代码块。

    /**       * 主键默认采用java.lang.Integer       */      FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType("BaseMapper<"          + introspectedTable.getBaseRecordType() + ","          + introspectedTable.getExampleType() + ","          + "java.lang.Integer" + ">");      FullyQualifiedJavaType imp = new FullyQualifiedJavaType(          "my.mabatis.example.base.BaseMapper");      /**       * 添加 extends MybatisBaseMapper       */      interfaze.addSuperInterface(fqjt);        /**       * 添加import my.mabatis.example.base.MybatisBaseMapper;       */      interfaze.addImportedType(imp);      /**       * 方法不需要       */      interfaze.getMethods().clear();

然后,然后就搞定了。: )

  • 方法二:不改源码

不改源代码?

尽管修改源代码的方式可行,但是侵入性太强。

其实,MyBatis Generator自动代码产生工具已经提供插件适配扩展的功能,我们只要继承PluginAdapter即可。然后,重写clientGenerated方法即可。

    @Override    public boolean clientGenerated(Interface interfaze,        TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {      }

具体代码如下:

默认采用java.lang.Integer作为主键。

package my.mabatis.example.plugin;    import java.util.List;    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;    /**   * @author wangmengjun   *   */  public class BaseMapperGeneratorPlugin extends PluginAdapter {      public boolean validate(List<String> warnings) {      return true;    }      /**     * 生成dao     */    @Override    public boolean clientGenerated(Interface interfaze,        TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {      /**       * 主键默认采用java.lang.Integer       */      FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType("BaseMapper<"          + introspectedTable.getBaseRecordType() + ","          + introspectedTable.getExampleType() + ","          + "java.lang.Integer" + ">");      FullyQualifiedJavaType imp = new FullyQualifiedJavaType(          "my.mabatis.example.base.BaseMapper");      /**       * 添加 extends MybatisBaseMapper       */      interfaze.addSuperInterface(fqjt);        /**       * 添加import my.mabatis.example.base.MybatisBaseMapper;       */      interfaze.addImportedType(imp);      /**       * 方法不需要       */      interfaze.getMethods().clear();      interfaze.getAnnotations().clear();      return true;    }    }

接着,在用于自动产生代码的配置文件中generatorConfig.xml指定自定义的plugin

如:

   <!-- 配置内置的或者自定义的Plugin -->       <plugin type="my.mabatis.example.plugin.BaseMapperGeneratorPlugin" />

详细配置如:

  <?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>    <!-- 引入配置文件 -->    <properties resource="jdbc.properties" />      <context id="context1" targetRuntime="MyBatis3">      <!-- 配置内置的或者自定义的Plugin -->      <plugin type="my.mabatis.example.plugin.BaseMapperGeneratorPlugin" />        <!-- 注释产生配置 -->      <commentGenerator>        <property name="suppressAllComments" value="false" />        <property name="suppressDate" value="false" />      </commentGenerator>        <!-- 数据库连接信息 -->      <jdbcConnection driverClass="${jdbc.driverClassName}"        connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}" />        <!-- 生成Model对象路径配置 -->      <javaModelGenerator targetPackage="my.mybatis.generator.auto.entity"        targetProject="srcmainjava">        <property name="enableSubPackages" value="true" />        <property name="trimStrings" value="true" />      </javaModelGenerator>        <!-- 生成sqlXML文件路径配置 -->      <sqlMapGenerator targetPackage="my.mybatis.generator.auto.entity.xml"        targetProject="srcmainjava">        <property name="enableSubPackages" value="true" />      </sqlMapGenerator>        <!-- 生成DAO的类文件路径配置 -->      <javaClientGenerator targetPackage="my.mybatis.generator.auto.dao"        targetProject="srcmainjava" type="XMLMAPPER">        <property name="enableSubPackages" value="true" />      </javaClientGenerator>        <!--要生成哪些表 -->      <table tableName="t_user" domainObjectName="User" />      <table tableName="t_news" domainObjectName="News" />    </context>  </generatorConfiguration>

经过上述几个步骤,重新生成代码,就可以看到生成的Mapper,包括UserMapperNewsMapper都已经发生了变化,而这正是我们所期望的。

public interface NewsMapper extends BaseMapper<News, NewsExample, Integer> {  }
  public interface UserMapper extends BaseMapper<User, UserExample, Integer> {  }

自动产生的Mapper继承于BaseMapper,变得相对较为干净。

接下来,我们就来测试一下,是否管用。

03

测试验证

  • 创建mybatis-config.xml文件

在src/main/resource目录下创建mybatis-config.xml配置文件。内容如下:

<?xml version="1.0" encoding="UTF-8" ?>  <!DOCTYPE configuration    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"    "http://mybatis.org/dtd/mybatis-3-config.dtd">  <configuration>    <properties resource="jdbc.properties" />    <typeAliases>      <typeAlias type="my.mybatis.generator.auto.entity.User"        alias="User" />    </typeAliases>    <environments default="development">      <environment id="development">        <transactionManager type="JDBC" />        <dataSource type="POOLED">          <property name="driver" value="${jdbc.driverClassName}" />          <property name="url" value="${jdbc.url}" />          <property name="username" value="${jdbc.username}" />          <property name="password" value="${jdbc.password}" />        </dataSource>      </environment>    </environments>    <mappers>      <mapper resource="my/mybatis/generator/auto/entity/xml/UserMapper.xml" />    </mappers>  </configuration>
  • 创建一个MyBatisUtil工具类
package my.mabatis.example.util;    import java.io.IOException;  import java.io.Reader;  import org.apache.ibatis.io.Resources;  import org.apache.ibatis.session.SqlSessionFactory;  import org.apache.ibatis.session.SqlSessionFactoryBuilder;    /**   *   * @author wangmengjun   *   */  public class MyBatisUtil {      private static SqlSessionFactory factory;      private MyBatisUtil() {    }      static {      Reader reader = null;      try {        reader = Resources.getResourceAsReader("mybatis-config.xml");      } catch (IOException e) {        throw new RuntimeException(e.getMessage());      }      factory = new SqlSessionFactoryBuilder().build(reader);    }      public static SqlSessionFactory getSqlSessionFactory() {      return factory;    }  }
  • 编写Service类

因为只是很简单的操作,所以service就不分接口和实现了,直接上代码。

package my.mabatis.example.service;    import java.util.List;    import my.mabatis.example.util.MyBatisUtil;  import my.mybatis.generator.auto.dao.UserMapper;  import my.mybatis.generator.auto.entity.User;  import my.mybatis.generator.auto.entity.UserExample;  import my.mybatis.generator.auto.entity.UserExample.Criteria;    import org.apache.ibatis.session.SqlSession;    /**   *   * @author wangmengjun   *   */  public class UserService {      /**     * 保存用户     * @param user 待保存用户对象     */    public void insertUser(User user) {      SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory()          .openSession();      try {        UserMapper userDao = sqlSession.getMapper(UserMapper.class);        userDao.insert(user);        sqlSession.commit();      } finally {        sqlSession.close();      }    }      /**     * 按照指定email返回用户     * @param email     * @return 按照指定email返回用户     */    public User findUserByEmail(String email) {      SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory()          .openSession();      try {        UserMapper userDao = sqlSession.getMapper(UserMapper.class);        /**         * 使用Example来操作         */        UserExample example = new UserExample();        Criteria criteria = example.createCriteria();        criteria.andEmailEqualTo(email);        List<User> users = userDao.selectByExample(example);        /**         * 假定email唯一         */        return users.isEmpty() ? null : users.get(0);      } finally {        sqlSession.close();      }    }    }
  • 测试类和运行结果
package my.mabatis.example.runner;    import my.mabatis.example.service.UserService;  import my.mybatis.generator.auto.entity.User;    public class Test {      public static void main(String[] args) {      UserService userService = new UserService();        User userToInsert = new User();      userToInsert.setEmail("[email protected]");      userToInsert.setName("mengjun");      userService.insertUser(userToInsert);        User user = userService.findUserByEmail("[email protected]");      System.out.println(user.getEmail());      System.out.println(user.getName());    }  }

输出结果:

log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).  log4j:WARN Please initialize the log4j system properly.  log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.  [email protected]  mengjun

保存用户信息和查看用户信息都能成功执行,代码可用。至此,整个流程就结束了。

Note: 整个代码都是在上一篇文章<<使用MyBatis Generator自动生成代码>>的基础上改动的,如对generatorConfig.xml等配置文件或者对如何自动产生代码有疑问,可以参考一下。

工程结构: