MybatisPlus中typeHandler的使用以及踩过的一些其他方面的坑

3,362 阅读4分钟

实际场景

当数据库表数据为这样一个结构时(某个字段存储的值为json数组),在java代码中使用这个字段时需要借助fastjson进行手动转化,比较麻烦;

iddata
1[{"id":0,"name":"test0"},{"id":1,"name":"test1"},{"id":2,"name":"test2"},{"id":3,"name":"test3"},{"id":4,"name":"test4"}]
2[{"id":0,"name":"test0"},{"id":1,"name":"test1"},{"id":2,"name":"test2"},{"id":3,"name":"test3"},{"id":4,"name":"test4"}]
3[{"id":0,"name":"test0"},{"id":1,"name":"test1"},{"id":2,"name":"test2"},{"id":3,"name":"test3"},{"id":4,"name":"test4"}]
4[{"id":0,"name":"test0"},{"id":1,"name":"test1"},{"id":2,"name":"test2"},{"id":3,"name":"test3"},{"id":4,"name":"test4"}]
5[{"id":0,"name":"test0"},{"id":1,"name":"test1"},{"id":2,"name":"test2"},{"id":3,"name":"test3"},{"id":4,"name":"test4"}]

解决方案

第一种:自定义TypeHandler

TypeHandler根据字面意思即为类型处理器

引用官方文档的描述: MyBatis在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型

官方文档:mybatis.org/mybatis-3/z…

可以让MybaitsPlus、Mybatis自动帮我们转化

@MappedTypes(List.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class JsonArrayToUserListHandler extends BaseTypeHandler<List<TestData>> {
    
    // 执行插入数据方法 且 实体属性不为空时
    // 属性为空的时候也没必要进行相应的处理
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<TestData> parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, String.valueOf(JSON.toJSON(parameter)));
    }
    
    // 根据列名获取数据
    // 数据的值可能为空
    @Override
    public List<TestData> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String data = rs.getString(columnName);
        if (data != null) {
            return JSON.parseArray(data, TestData.class);
        }
        return null;
    }
    
    // 根据列索引获取数据
    // 数据的值可能为空
    @Override
    public List<TestData> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String data = rs.getString(columnIndex);
        if (data != null) {
            return JSON.parseArray(data, TestData.class);
        }
        return null;
    }
    
    
    @Override
    public List<TestData> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String data = cs.getString(columnIndex);
        if (data != null) {
            return JSON.parseArray(data, TestData.class);
        }
        return null;
    }
}
@Data
@TableName(autoResultMap = true) // 低版本mybatisplus 没有autoResultMap = true这个属性可以不用写
public class Test implements Serializable {

    @TableId(type = IdType.AUTO)
    private Long id;

    @TableField(value = "data", typeHandler = JsonArrayToUserListHandler.class) // 在此处配置后使用mybatisPlus提供的方法就可以使用了
    private List<TestData> data;

    private static final long serialVersionUID = 1L;
}

完成以上配置后即可在mybaitsPlus提供的方法里使用了

如果需要在xml里面写sql还需要增加如下配置:

<?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.practice.mapper.TestMapper">

    <resultMap id="BaseResultMap" type="com.practice.entity.Test">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="data" column="data" typeHandler="com.practice.entity.typehandler.JsonArrayToUserListHandler"/>
    </resultMap>
  
    <select id="getList" resultMap="BaseResultMap">
        select * from test
    </select>
  
    <update>
      update test set data = #{data, typeHandler=com.practice.entity.typehandler.JsonArrayToUserListHandler}
    </update>
</mapper>

第二种:直接使用Mybatis提供的

mybatisPlus提供现成的TypeHandler,可以直接使用

@TableField(value = "data", typeHandler = FastjsonTypeHandler.class)
private List<TestData> data;

@TableField(value = "data", typeHandler = JacksonTypeHandler.class)
private List<TestData> data;

注:使用UpdateWrapper更新时有bug无法更新

@MapKey注解

使用方式

@Mapper
public interface TestMapper extends BaseMapper<Test> {

    @MapKey("id")
    Map<Long, Test> getIdMapKey();
}
<?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.practice.mapper.TestMapper">

    <resultMap id="BaseResultMap" type="com.practice.entity.Test">
            <id property="id" column="id" jdbcType="INTEGER"/>
            <result property="data" column="data" typeHandler="com.practice.entity.typehandler.JsonArrayToUserListHandler"/>
    </resultMap>
  
    <select id="getIdMapKey" resultMap="BaseResultMap">
        select * from test
    </select>
</mapper>

查询结果

注:key需要唯一

@TableLogic

作用

会在mybatis中的各种查询、更新、删除方法中自动拼接deleted = 0,过滤调删除的值

字段加完@TableLogic注解之后使用mybatisPlus提供的一些方法则无法更新该字段;

@TableLogic(value = "0", delval = "1")
private Integer deleted;

无法更新方法(@TableLogic字段)

public interface IService<T> {
    
    boolean updateById(T entity);
        
    @Transactional(rollbackFor = Exception.class)
    default boolean updateBatchById(Collection<T> entityList) {
        return updateBatchById(entityList, 1000);
    }
    
    boolean saveOrUpdate(T entity);
    
    @Transactional(rollbackFor = Exception.class)
    default boolean saveOrUpdateBatch(Collection<T> entityList) {
        return saveOrUpdateBatch(entityList, 1000);
    }
}

public interface BaseMapper<T> {
    
    int updateById(@Param(Constants.ENTITY) T entity);
}

在上述方法中如果说在实体中仅填充了deleted会直接报出语法错误

就是因为直接忽略了标注@TableLogic注解的属性

可更新方法(@TableLogic字段)

  1. 使用UpdateWrapper并且需要调用其set方法

updateWrapper.set("deleted", 0);

public interface IService<T> {
    
    boolean update(T entity, Wrapper<T> updateWrapper);
    
    default boolean update(Wrapper<T> updateWrapper) {
        return update(null, updateWrapper);
    }
}

public interface BaseMapper<T> {
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
}

  1. 使用自己写sql

日期查询时精确度问题

查询时间范围

数据库中大多日期字段是DateTime类型,例 2022-04-25 09:30:00 , 查询传参时日期的精度也要求到秒,否则起止日部分数据可能会丢失。

如果不精确到时分秒,会自动填充为00:00:00

eg: 2022-04-25 -> 2022-04-25 00:00:00

select * from demo where businessDate between '2022-04-25 00:00:00' and '2022-04-25 23:23:59'

按时间排序时

要使用其他字段辅助排序
场景:按照创建日期升序,同一天创建很多条,在翻页时同一条记录在多个页中出现。
分析:在时间相同的情况下,返回数据的顺序会变化。
方案:创建时间(主排序)+ 数据ID(辅助排序),保证查询的幂等性。

Json检索

select * from a1
where 'DT004' member of (JSON_EXTRACT(js,'$[*]'))