【MyBatis】高级特性(三):MBG、通用Mapper、Mybatis-plus

我们在项目中使用 MyBaits 的时候,针对需要操作的一张表,需要创建实体类、Mapper 映射器、Mapper 接口,里面又有很多的字段和方法的配置,这部分的工作是非常繁琐的。而大部分时候我们对于表的操作是相同的,比如根据主键查询、根据Map查询、单条插入、批量插入、根据主键删除等等等等。

当我们的表很多的时候,意味着有大量的重复工作。所以有没有一种办法,可以根据我们的表,自动生成实体类、Mapper映射器、Mapper接口,里面包含了我们需要用到的这些基本方法和SQL呢?

1.MBG

MyBatis也提供了一个这样的东西,叫做 MyBatis Generator,简称 MBG。我们只需要修改一个配置文件,使用相关的 jar 包命令或者Java 代码就可以帮助我们生成实体类、映射器和接口文件。

1.1 使用步骤

第一步,引入依赖

<plugin>
	<groupId>org.mybatis.generator</groupId>
	<artifactId>mybatis-generator-maven-plugin</artifactId>
	<version>1.3.7</version>

	<dependencies>
		<dependency>
		  <groupId>org.mybatis.generator</groupId>
 		  <artifactId>mybatis-generator-core</artifactId>
          <version>1.3.7</version>
        </dependency>

        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.13</version>
        </dependency>
     </dependencies>

     <executions>
        <execution>
          <id>mybatis generator</id>
          <phase>package</phase>
          <goals>
            <goal>generate</goal>
          </goals>
          <configuration>
            <!--允许移动生成的文件-->
            <verbose>true</verbose>
            <!--允许自动覆盖文件-->
            <overwrite>true</overwrite>
            <configurationFile>
              src/main/resources/generatorConfig.xml
            </configurationFile>
       </configuration>
     </execution>
     </executions>
</plugin>

第二步,配置文件(generator-config.xml)

<?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>  
    <!-- 1.数据库信息 -->
	<!-- 数据库驱动-->  
    <classPathEntry  location="mysql-connector-java-5.1.38.jar"/>  
    <context id="DB2Tables"  targetRuntime="MyBatis3">  
        <commentGenerator>  
            <property name="suppressDate" value="true"/>  
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->  
            <property name="suppressAllComments" value="true"/>  
        </commentGenerator>  
        <!--数据库链接URL,用户名、密码 -->  
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" 
           connectionURL="jdbc:mysql://localhost/mybatis-test" userId="root" password="123456">  
        </jdbcConnection>  
        <javaTypeResolver>  
            <property name="forceBigDecimals" value="false"/>  
        </javaTypeResolver>  
        
        <!-- 2.生成文件位置:实体类(entity),接口(dao),映射文件(mapper)-->
        <!-- 生成模型的包名和位置-->  
        <javaModelGenerator targetPackage="com.my.entity" targetProject="src">  
            <property name="enableSubPackages" value="true"/>  
            <property name="trimStrings" value="true"/>  
        </javaModelGenerator>  
        <!-- 生成映射文件的包名和位置-->  
        <sqlMapGenerator targetPackage="com.my.mapper" targetProject="src">  
            <property name="enableSubPackages" value="true"/>  
        </sqlMapGenerator>  
        <!-- 生成DAO的包名和位置-->  
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.my.dao" 
                             targetProject="src">  
            <property name="enableSubPackages" value="true"/>  
        </javaClientGenerator>  
        
        <!-- 3.要生成的数据库表信息 -->
        <!-- 要生成的表 tableName是数据库中的表名或视图名 domainObjectName是实体类名-->  
        <table tableName="blog" domainObjectName="Blog" enableCountByExample="false" 
               enableUpdateByExample="false" enableDeleteByExample="false" 
               enableSelectByExample="false" selectByExampleQueryId="false"></table>
    </context>  
</generatorConfiguration>  

第三步,执行mybatis-generator插件。生成的类和xml如下图:

1.2 mapper中的函数

我们随便打开一个dao层的接口,看 MBG 给我们生成了什么方法:

public interface UserMapper {
    // 按条件计数
    int countByExample(UserExample example);

	// 插入数据(返回值为ID)
    int insert(User record);
    // 选择性插入,插入值不为null的数据
    int insertSelective(User record);

    // 按主键删除
    int deleteByPrimaryKey(Integer uId);
	// 按条件删除
    int deleteByExample(UserExample example);
    
	// 按主键查询
    User selectByPrimaryKey(Integer uId);
    // 按条件查询
    List<User> selectByExample(UserExample example);
	
	// 按主键更新
    int updateByPrimaryKey(User record);
    // 按条件更新
    int updateByExample(@Param("record") User record, @Param("example") UserExample example);
    // 按主键更新值不为null的字段
    int updateByPrimaryKeySelective(User record);
    // 按条件更新值不为null的字段
    int updateByExampleSelective(@Param("record") User record, @Param("example") UserExample example);
}

那这些作为参数的 Example 是什么呢?

1.2 Example

MBG 的配置文件里面有一个 Example 的开关,这个东西用来构造复杂的筛选条件的,换句话说就是根据我们的代码去生成 where 条件

xxxExample example = new xxxExample();
Criteria criteria = new Example().createCriteria();
方法说明
example.setOrderByClause(“字段名 ASC”);添加升序排列条件,DESC为降序
example.setDistinct(false)去除重复,boolean型,true为选择不重复的记录。
criteria.andXxxIsNull添加字段xxx为null的条件
criteria.andXxxIsNotNull添加字段xxx不为null的条件
criteria.andXxxEqualTo(value)添加xxx字段等于value条件
criteria.andXxxNotEqualTo(value)添加xxx字段不等于value条件
criteria.andXxxGreaterThan(value)添加xxx字段大于value条件
criteria.andXxxGreaterThanOrEqualTo(value)添加xxx字段大于等于value条件
criteria.andXxxLessThan(value)添加xxx字段小于value条件
criteria.andXxxLessThanOrEqualTo(value)添加xxx字段小于等于value条件
criteria.andXxxIn(List<?>)添加xxx字段值在List<?>条件
criteria.andXxxNotIn(List<?>)添加xxx字段值不在List<?>条件
criteria.andXxxLike(“%”+value+”%”)添加xxx字段值为value的模糊查询条件
criteria.andXxxNotLike(“%”+value+”%”)添加xxx字段值不为value的模糊查询条件
criteria.andXxxBetween(value1,value2)添加xxx字段值在value1和value2之间条件
criteria.andXxxNotBetween(value1,value2)添加xxx字段值不在value1和value2之间条件

原理:在实体类中包含了两个有继承关系的Criteria,用其中自动生成的方法来构建查询条件。把这个包含了 Criteria 的实体类作为参数传到查询参数中,在解析 Mapper映射器的时候会转换成SQL条件。

UserMapper mapper = session.getMapper(UserMapper.class); 
UserExample example = new UserExample (); 
UserExample.Criteria criteria = example.createCriteria();
criteria.andUidEqualTo(1);
List<Blog> list = mapper.selectByExample(example)

生成的sql语句

select 'true' as QUERYID, uid, username, phone from t_user WHERE ( uid = ? )

1.4 MBG 存在的问题

当我们使用 MBG 时,如果表字段发生了变化,我们需要修改实体类和 Mapper 文件定义的字段和方法。如果是增量维护,那么一个个文件去修改。如果是全量替换,我们还要去对比用 MBG 生成的文件。字段变动一次就修改一次,维护起来非常麻烦。

解决这个问题,我们有两种思路。

解决方案一

因为 MyBatis 的 Mapper 是支持继承的,所以我们可以把 Mapper.xml 和 Mapper 接口都分成两个文件。

  • 一个是MBG生成的,这部分是固定不变的。
  • 一个是自己手动创建的DAO类,并且继承生成的接口

所以以后发生变化的部分就在手动创建的DAO里面维护。具体操作步骤如下:

第一步,BlogMapperExt 继承generator生成的 Mapper

public interface BlogMapperExt extends BlogMapper {
    public Blog selectBlogByName(String name); 
}

第二步,编写扩展的 mapper 文件 – BlogMapperExt.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.my.mapper.BlogMapperExt">
    <!-- 只能继承 statement,不能继承sql、resultMap等标签 -->
    <!-- 所以,如果收数据库字段名发生了变化,可以在这里重新定义 resultMap -->
    <!-- 但是,对于那些没有发生变化的字段或逻辑,仍然可以复用 -->
    <resultMap id="BaseResultMap" type="blog">
        <id column="bid" property="bid" jdbcType="INTEGER"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="author_id" property="authorId" jdbcType="INTEGER"/>
    </resultMap>

    <!-- 在 parent xml 和 child xml 的 statement id 相同的情况下,会使用  child xml 的 statement id -->
    <select id="selectBlogByName" resultMap="BaseResultMap" statementType="PREPARED">
        select * from blog where name = #{name}
    </select>
</mapper>

3.配置mybatis-conf,扫描新的 BlogMapperExt.xml

<mappers>
    <mapper resource="BlogMapper.xml"/>
    <mapper resource="BlogMapperExt.xml"/>
</mappers> 

以后只要修改Ext的文件就可以了。但是这么做有一个缺点 – 文件会增多冗余。

解决方案二

既然针对每张表生成的基本方法都是一样的,也就是公共的方法部分代码都是一样的,那么就可以把这部分合并成一个文件,通过泛型来实现多种类型。

public interface BlogMapper extends Mapper<Blog>

除了配置文件变动的问题之外,通用Mapper还可以解决:

  • 每个Mapper接口中大量的重复方法的定义
  • 屏蔽数据库的差异
  • 提供批量操作的方法
  • 实现分页

这下就引出了我们接下来要说的通用Mapper…

多提一句,通用Mapper和PageHelper作者是同一个人(刘增辉)

2.通用Mapper

这里就不再过多介绍通用Mapper了,下面说一下在 SpringBoot 中如何使用:

第一步,引入依赖:

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>

第二步,手动创建实体类

public class User {

    private Long uid;
    private String username;
    private String phone;
    private String password;
	
	// getter和setter这里就不列出来了
}

第三步,创建 dao 层的接口并继承 Mapper

@Mapper
public interface UserDao extends tk.mybatis.mapper.common.Mapper<User> {
	
	// 如果自动生成的sql不够我们用或者性能差,我们可以直接是使用注解写在接口中
	// @Insert、@Delete、@Update、@Select
	
    @Select("SELECT * from `user` WHERE uid = #{uid}")
    User selectUserByUid(long uid);

    @Update("UPDATE `user` set `password` = #{pwd} WHERE uid = #{uid}")
    int updatePwdByUid(String pwd, long uid);
}

使用方式跟上面 MBG 一样,都是提供了那些基本的函数和 Example。

3.Mybatis-plus

MyBatis-Plus 是原生 MyBatis 的一个增强工具,可以在使用原生 MyBatis 的所有功能的基础上,使用plus特有的功能。

MyBatis-Plus的核心功能:

  • 通用 CRUD:定义好Mapper接口后,只需要继承BaseMapper 接口即可获得通用的增删改查功能,无需编写任何接口方法与配置文件。
  • 条件构造器:通过EntityWrapper(实体包装类),可以用于拼接 SQL 语句,并且支持排序、分组查询等复杂的SQL。
  • 代码生成器:支持一系列的策略配置与全局配置,比MyBatis的代码生成更好用。

另外,MyBatis-Plus 还自带了分页的功能

这里还是简单说一下 mybatis-plus 如何在 SpringBoot 中使用:

第一步,引入依赖

<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
		<version>3.1.1</version>
</dependency>

第二步,手动创建实体类

public class User {

    private Long uid;
    private String username;
    private String phone;
    private String password;
	
	// getter和setter这里就不列出来了
}

第三步,创建 dao 层的接口并继承 BaseMapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

第四步,配置启用分页功能

@Configuration
public class MybatisPlusConfig {

    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

在使用上,mybatis-plus 与上面两个略有不同。我们下面就来看看继承的 BaseMapper 到底是什么样子把。

/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * <p>这个 Mapper 支持 id 泛型</p>
 */
public interface BaseMapper<T> extends Mapper<T> {

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);

    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,删除记录
     *
     * @param wrapper 实体对象封装操作类(可以为 null)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

    /**
     * 删除(根据ID 批量删除)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap 表字段 map 对象
     */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A minor

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值