浅谈MyBatis以及MyBatis-Plus
MyBatis
MyBatis的作用,简单来说,就是:
- 提供一种让应用访问数据库的方式(比如CRUD)
    
- 概念:Mapper
 
 - 并且传入传出的都是Java对象。
    
- 概念:ORM
 
 
总的来说就是使应用通过调用Java方法访问数据库。
组成部分
先来个项目结构。

Entity
就是Data Object,i.e. 表里的字段 <==> Java对象里的字段。
@Data
public class UserDO {
    private Integer id;
    private String username;
    private String password;
    private Date createTime;
}
Mapper
在Mapper中自定义访问数据库的方法。
第一次学的时候不知道Mapper为什么叫Mapper。后来的理解是把Java方法Map到SQL语句。只要设置了这些Mappings,之后只要调用Java方法,就相当于调用了相应的SQL语句操纵数据库。
比如下面的insert会map到UserMapper.xml的 insert元素 中的SQL语句。
@Mapper
public interface UserMapper {
    int insert(UserDO user);
    int updateById(UserDO user);
    // 生产请使用标记删除,除非有点想不开,嘿嘿。
    // 标记删除的意思是新开一个字段,标记该行是不是被删除了。
    int deleteById(@Param("id") Integer id); 
    UserDO selectById(@Param("id") Integer id);
    UserDO selectByUsername(@Param("username") String username);
    List<UserDO> selectByIds(@Param("ids")Collection<Integer> ids);
}
<insert id="insert" parameterType="UserDO" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO users (
      username, password, create_time
    ) VALUES (
      #{username}, #{password}, #{createTime}
    )
</insert>
这里#{username}代表参数。我们指定了parameterType=”UserDO”,所以username就会从传入的UserDO里面拿。
配置
application.yaml
mybatis:
  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
  mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
  type-aliases-package: cn.iocoder.springboot.lab12.mybatis.dataobject # 配置数据库实体包路径
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>
    <settings>
        <!-- 使用驼峰命名法转换字段。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer"/>
        <typeAlias alias="Long" type="java.lang.Long"/>
        <typeAlias alias="HashMap" type="java.util.HashMap"/>
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
        <typeAlias alias="ArrayList" type="java.util.ArrayList"/>
        <typeAlias alias="LinkedList" type="java.util.LinkedList"/>
    </typeAliases>
</configuration>
mapper/UserMapper.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="cn.iocoder.springboot.lab12.mybatis.mapper.UserMapper">
    <sql id="FIELDS">
        id, username, password, create_time
    </sql>
    <insert id="insert" parameterType="UserDO" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO users (
          username, password, create_time
        ) VALUES (
          #{username}, #{password}, #{createTime}
        )
    </insert>
    <update id="updateById" parameterType="UserDO">
        UPDATE users
        <set>
            <if test="username != null">
                , username = #{username}
            </if>
            <if test="password != null">
                , password = #{password}
            </if>
        </set>
        WHERE id = #{id}
    </update>
    <delete id="deleteById" parameterType="Integer">
        DELETE FROM users
        WHERE id = #{id}
    </delete>
    <select id="selectById" parameterType="Integer" resultType="UserDO">
        SELECT
            <include refid="FIELDS" />
        FROM users
        WHERE id = #{id}
    </select>
    <select id="selectByUsername" parameterType="String" resultType="UserDO">
        SELECT
            <include refid="FIELDS" />
        FROM users
        WHERE username = #{username}
        LIMIT 1
    </select>
    <select id="selectByIds" resultType="UserDO">
        SELECT
            <include refid="FIELDS" />
        FROM users
        WHERE id IN
            <foreach item="id" collection="ids" separator="," open="(" close=")" index="">
                #{id}
            </foreach>
    </select>
</mapper>
最后一个文件中其实是有一些操作之前没说的,不过嗯背API其实也没啥意思,大家自己看一下也能理解这些语句是什么意思。
MyBatis-Plus
对CRUD的优化
可以将你的Mapper继承MBP提供的BaseMapper<T>,其中T就是你的数据对象DO。
好处就是BaseMapper里面提供了一些常用的CRUD操作,可以直接用而不用自己在xml里写SQL了。
【添加数据:(增)】
    int insert(T entity);              // 插入一条记录
注:
    T         表示任意实体类型
    entity    表示实体对象
【删除数据:(删)】
    int deleteById(Serializable id);    // 根据主键 ID 删除
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);  // 根据 map 定义字段的条件删除
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 根据实体类定义的 条件删除对象
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 进行批量删除
注:
    id        表示 主键 ID
    columnMap 表示表字段的 map 对象
    wrapper   表示实体对象封装操作类,可以为 null。
    idList    表示 主键 ID 集合(列表、数组),不能为 null 或 empty
【修改数据:(改)】
    int updateById(@Param(Constants.ENTITY) T entity); // 根据 ID 修改实体对象。
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); // 根据 updateWrapper 条件修改实体对象
注:
    update 中的 entity 为 set 条件,可以为 null。
    updateWrapper 表示实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
【查询数据:(查)】
    T selectById(Serializable id); // 根据 主键 ID 查询数据
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 进行批量查询
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根据表字段条件查询
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据实体类封装对象 查询一条记录
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询记录的总条数
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 entity 集合)
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 map 集合)
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(但只保存第一个字段的值)
    <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 entity 集合),分页
    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询所有记录(返回 map 集合),分页
注:
    queryWrapper 表示实体对象封装操作类(可以为 null)
    page 表示分页查询条件
使用的时候继承一下就好了。

更改后的Mapper方法长这样:
default UserDO selectByUsername(@Param("username") String username) {
    return selectOne(new QueryWrapper<UserDO>().eq("username", username));
}
等价于select “username” = username这一语句。
上面的QueryWrapper可以将一系列query组合起来,以java对象的形式。用法一看就懂:
// Step1:创建一个 QueryWrapper 对象
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// Step2: 构造查询条件
queryWrapper
        .select("id", "name", "age")
        .eq("age", 20)
        .like("name", "j");
// Step3:执行查询
userService
        .list(queryWrapper)
        .forEach(System.out::println);
这里的userService继承了IService,可以看做是加强版的BaseMapper. 具体可以看这篇
https://www.cnblogs.com/l-y-h/p/12859477.html
一些features
常用注解
【@TableName】
    @TableName               用于定义表名
注:
    常用属性:
        value                用于定义表名
【@TableId】
    @TableId                 用于定义表的主键
注:
    常用属性:
        value           用于定义主键字段名
        type            用于定义主键类型(主键策略 IdType)
   主键策略:
      IdType.AUTO          主键自增,系统分配,不需要手动输入
      IdType.NONE          未设置主键
      IdType.INPUT         需要自己输入 主键值。
      IdType.ASSIGN_ID     系统分配 ID,用于数值型数据(Long,对应 mysql 中 BIGINT 类型)。
      IdType.ASSIGN_UUID   系统分配 UUID,用于字符串型数据(String,对应 mysql 中 varchar(32) 类型)。
【@TableField】  
    @TableField            用于定义表的非主键字段。
注:
    常用属性:
        value                用于定义非主键字段名
        exist                用于指明是否为数据表的字段, true 表示是,false 为不是。
        fill                 用于指定字段填充策略(FieldFill)。
        
    字段填充策略:(一般用于填充 创建时间、修改时间等字段)
        FieldFill.DEFAULT         默认不填充
        FieldFill.INSERT          插入时填充
        FieldFill.UPDATE          更新时填充
        FieldFill.INSERT_UPDATE   插入、更新时填充。
【@TableLogic】
    @TableLogic           用于定义表的字段进行逻辑删除(非物理删除)
注:
    常用属性:
        value            用于定义未删除时字段的值
        delval           用于定义删除时字段的值
        
【@Version】
    @Version             用于字段实现乐观锁
数据自动填充
比如现在每行插入的时候都要填充一个createTime字段,如果每次都手动写User.setCreateTime()有一些没必要。
这时可以在DO上注解一下:
@TableField(fill = FieldFill.INSERT)
private Date createTime;
然后创建一个handler:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
    }
}
就可以自动填充了。
逻辑删除
新建一个字段delete_flags,当删除某行是把该行的flag置为1即可,不需要真的删除。
好处是防止误删。
分页插件
分页的意思其实就是select的时候数据太多了分页显示。一开始我还以为是os里的paging。
实现乐观锁
新建一个字段,在上面用@Version就可以了,每一次更新都会+1
代码生成器
没看懂,感觉很高级的样子。