我们在项目中使用 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);
}