3 minute read

MyBatis

MyBatis的作用,简单来说,就是:

  1. 提供一种让应用访问数据库的方式(比如CRUD)
    1. 概念:Mapper
  2. 并且传入传出的都是Java对象。
    1. 概念:ORM

总的来说就是使应用通过调用Java方法访问数据库。

组成部分

先来个项目结构。

structure

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 表示分页查询条件

使用的时候继承一下就好了。

basemapper

更改后的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

代码生成器

没看懂,感觉很高级的样子。