讓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等配置文件或者對如何自動產生代碼有疑問,可以參考一下。

工程結構: