Mybatis处理数据的代码逐步优化

前言:

最近在工作的过程当中,发现数据库有一个字段 假设为 A 字段存储数据的格式是text类型,但目前有一个需求,那就是我需要查数据的检索条件里面有关于A字段的检索

这就开始让人颇感无奈了,因为text类型的数据,并不好处理,而且他的text类型的字段里面的数据格式长这样

[{"userId": "xxx","userName":"xxx"}]

这比较直观的感受就是,这是json数组,可为什么要用text去存储,让我有些疑惑,但该做还是要做的

正文开始

既然这存储的数据已经是json数组了,那不如直接将其转为json格式

说干就干,把其数据刷一遍转为json格式即可

前端传过来的条件长这样 [{"userId": "xxx","userName":"xxx"},{"userId": "xxx","userName":"xxx"}]

假设条件为处理人,则表示前端传过来的处理人筛选条件可能是多个,既然现在该字段已经转成json格式的字段,那么数据查询的动态sql就简单多了

做json条件查询代码如下:

<if test="assignedAccounts != null and assignedAccounts.size() > 0 ">
    and    <foreach collection="assignedAccounts" item="assignedAccount"  separator="or">
       JSON_CONTAINS(assigned_account, JSON_OBJECT('userId', #{assignedAccount.userId}))
    </foreach>
</if>

这里我是用动态sql去拼接的,在尾部拼接一个or 
相当于 where id = 0 or id = 1
         

对应的实体类字段条件:

@ApiModelProperty(value = "处理人")
private List<UserDto> assignedAccounts;


由于目前前端要求统一将json字符串转为对象给前端

那么我是如何去将查询出来的数据,转成对象呢?

初始处理数据转换

一开始,我用的就是最简单的方式 直接将json转为对象

pageInfo是我的分页对象,其中的

item.getAssignedAccount() 就是获取上面我所说的json格式数据

代码如下:

List<CommonFieldWorkItemVO> collect = pageInfo.getList().stream().map(item -> {
    CommonFieldWorkItemVO itemVO = BeanUtil.copyProperties(item, CommonFieldWorkItemVO.class);
    itemVO.setUserDto(UserDto.buildUserFirst(item.getAssignedAccount()));
    itemVO.setUpdater(UserDto.builder()
            .userId(item.getUpdaterAccount())
            .userName(item.getUpdaterName())
            .build());
    itemVO.setCreator(UserDto.builder()
            .userId(item.getCreatorAccount())
            .userName(item.getCreatorName())
            .build());
    return itemVO;
}).collect(Collectors.toList());//封装前端的user对象



UserDto.buildUserFirst 方法实现
public static UserDto buildUserFirst(String json) {
    if (!StringUtils.hasText(json)) {
        return new UserDto();
    }
    try {
        List<UserDto> list = JSONUtil.toList(json, UserDto.class);
        return list.isEmpty() ? new UserDto() : list.get(0);
    } catch (Exception e) {
        log.error("", e);
        return new UserDto();
    }
}



就是上面一段最简单的方式去映射对象,没错,就是遍历去处理数据

我自己都感觉这样的写法好low , 我也是参考了别人的写法,被带坑了哈哈哈

真的是不得不吐槽一下自己,还是不够追求极致

那该怎么去优化呢,我就在思考这个问题

优化处理数据转换

我忽然想起来 mybatis有一个 自定义类型的数据转换处理机制

TypeHandler类型转换器

在mybatis中用于实现java类型和JDBC类型的相互转换.

mybatis使用prepareStatement来进行参数设置的时候,需要通过typeHandler将传入的java参数设置成合适的jdbc类型参数

这个过程实际上是通过调用PrepareStatement不同的set方法实现的

接下来就是优化代码了

首先定义一个通用的类型转换器,因为以后可能需要做不同的数据转换

ListTypeHandler 通用JSONList处理

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 描述
 * @author qinshujin
 * @date 2022/12/04
 */
@MappedJdbcTypes(JdbcType.VARBINARY)
@MappedTypes({List.class})
public abstract class ListTypeHandler<T> extends BaseTypeHandler<List<T>> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType)
            throws SQLException {
        String content = ObjectUtil.isEmpty(parameter) ? null : JSON.toJSONString(parameter);
        ps.setString(i, content);
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return this.getListByJsonArrayString(rs.getString(columnName));
    }

    @Override
    public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return this.getListByJsonArrayString(rs.getString(columnIndex));
    }

    @Override
    public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return this.getListByJsonArrayString(cs.getString(columnIndex));
    }

    private List<T> getListByJsonArrayString(String content) {
        return ObjectUtil.isEmpty(content) ? new ArrayList<>() : JSON.parseObject(content, this.specificType());
    }

    /**
     * 具体类型,由子类提供
     * @return 具体类型
     */
    protected abstract TypeReference<List<T>> specificType();
}



再来定义一个这次我们需要做具体转换的处理器

UserListTypeHandler  处理JSONList 转为 List<UserDto> 对象

代码实现

import com.alibaba.fastjson.TypeReference;
import com.oppo.softwareservice.common.dto.UserDto;

import java.util.List;

/**
 * UserListTypeHandler 处理
 * @author qinshujin
 * @date 2022/12/04
 */
public class UserListTypeHandler extends ListTypeHandler<UserDto> {
    @Override
    protected TypeReference<List<UserDto>> specificType() {
        return new TypeReference<>() {
        };
    }
}




处理器已经定义好了,接下来就直接使用

我这里是自己去维护了一个字段映射,因为个人原因,不太喜欢用mybatisdplus的自动映射

具体的映射代码

    <resultMap type="com.oppo.softwareservice.demandpool.vo.CommonFieldWorkItemVO" id="CommonFieldWorkItemResult">
        <result property="userDto" column="userDto"  typeHandler="com.oppo.softwareservice.common.mybatis.handler.UserListTypeHandler"/>
        <!--关联映射-->
        <association property="creator" javaType="com.oppo.softwareservice.common.dto.UserDto">
            <result property="userId" column="creator_account"/>
            <result property="userName" column="creator_name"/>
        </association>
        <association property="updater" javaType="com.oppo.softwareservice.common.dto.UserDto">
            <result property="userId" column="updater_account"/>
            <result property="userName" column="updater_name"/>
        </association>
    </resultMap>

这个映射的userDto字段也就是刚刚所说的jsonlist类型字段,可以看见 后面指定了一个 typeHandler就是刚刚自定义的Handler

而关联映射,细心的小伙伴 则可以发现 是 这一段代码的优化

itemVO.setUpdater(UserDto.builder()
        .userId(item.getUpdaterAccount())
        .userName(item.getUpdaterName())
        .build());itemVO.setCreator(UserDto.builder()
        .userId(item.getCreatorAccount())
        .userName(item.getCreatorName())
        .build());

而具体的查询sql是这样的

    <sql id="selectCommonFieldWorkItemVo">
        SELECT
            cfwi.url,
            cfwi.assigned_account as userDto,
            cfwi.creator_name,
            cfwi.creator_account,
            cfwi.updater_name,
            cfwi.updater_account,
        FROM
            common_field_work_item as cfwi
    </sql>


   <select id="selectCommonFieldWorkItemList" resultMap="CommonFieldWorkItemResult" parameterType="com.oppo.softwareservice.demandpool.reqeust.feature.FeatureReq" >
        <include refid="selectCommonFieldWorkItemVo"/>
        <where>
            1 = 1
            <if test="assignedAccounts != null and assignedAccounts.size() > 0 ">
                and
                <foreach collection="assignedAccounts" item="assignedAccount"  separator="or">
                   JSON_CONTAINS(assigned_account, JSON_OBJECT('userId', #{assignedAccount.userId}))
                </foreach>
            </if>
        </where>
    </select>

结语

至此代码优化完毕

从原来的遍历处理数据

@Override
public Page<CommonFieldWorkItemVO> selectCommonFieldWorkItemList(FeatureReq req) {
    PageHelper.startPage(req.getPageNum(),req.getPageSize());
    List<CommonFieldWorkItemPO> commonFieldWorkItemPOS = commonFieldWorkItemMapper.selectCommonFieldWorkItemList(req);
    PageInfo<CommonFieldWorkItemPO> pageInfo = PageInfo.of(commonFieldWorkItemPOS);
    Page<CommonFieldWorkItemVO> page = BeanUtil.copyProperties(pageInfo, Page.class);
    //封装前端的user对象
    List<CommonFieldWorkItemVO> collect = pageInfo.getList().stream().map(item -> {
        CommonFieldWorkItemVO itemVO = BeanUtil.copyProperties(item, CommonFieldWorkItemVO.class);
        itemVO.setUserDto(UserDto.buildUserFirst(item.getAssignedAccount()));
        itemVO.setUpdater(UserDto.builder()
                .userId(item.getUpdaterAccount())
                .userName(item.getUpdaterName())
                .build());
        itemVO.setCreator(UserDto.builder()
                .userId(item.getCreatorAccount())
                .userName(item.getCreatorName())
                .build());
        return itemVO;
    }).collect(Collectors.toList());
    page.setRecords(collect);
    return page;
}


优化之后,变成了这样

@Override
public Page<CommonFieldWorkItemVO> selectCommonFieldWorkItemList(FeatureReq req) {
    PageHelper.startPage(req.getPageNum(),req.getPageSize());
    List<CommonFieldWorkItemVO> commonFieldWorkItemVOList = commonFieldWorkItemMapper.selectCommonFieldWorkItemList(req);
    PageInfo<CommonFieldWorkItemVO> pageInfo = PageInfo.of(commonFieldWorkItemVOList);
    return PageUtil.PageInfoToPage(pageInfo);
}


是不是代码一看上去就感觉简洁了呢?

再附上处理普通json数据的handler

代码如下

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.oppo.softwareservice.common.dto.UserDto;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;


/**
 * 处理UserHandler
 * @author qinshujin
 * @date 2022/12/04
 */
@MappedJdbcTypes(JdbcType.VARCHAR)//这里对应着数据库中对应的类型,数据库中存的json格式,可看作json串,使用varchar去对应
@MappedTypes({UserDto.class})//这里map表示你最终要封装到的对象,这两个注解一定要有并且类型要指定好。
public class UserTypeHandler implements TypeHandler<UserDto> {
    private final String suffix = "("; //处理特殊格式
    @Override
    public void setParameter(PreparedStatement preparedStatement, int i, UserDto UserDto, JdbcType jdbcType) throws SQLException {
        Object o = JSON.toJSON(UserDto);
        preparedStatement.setObject(i,o);
    }

    @Override
    public UserDto getResult(ResultSet resultSet, String s) throws SQLException {
        return handlerUserDto(resultSet.getString(s));
    }

    @Override
    public UserDto getResult(ResultSet resultSet, int i) throws SQLException {
        return handlerUserDto(resultSet.getString(i));
    }

    @Override
    public UserDto getResult(CallableStatement callableStatement, int i) throws SQLException {
        return handlerUserDto(callableStatement.getString(i));
    }

    //处理userDto
    public UserDto handlerUserDto(String param){
        if (ObjectUtil.isEmpty(param)){
            return new UserDto();
        }
        if (param.contains(suffix)){
            String userId = param.substring(param.indexOf("(") + 1, param.indexOf(")")).trim();
            String userName = param.substring(0, param.indexOf("(")).trim();
            return new UserDto(userId, userName);
        }
        return JSONObject.parseObject(param,UserDto.class);
    }
}


使用方法一样
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有马大树

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

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

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

打赏作者

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

抵扣说明:

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

余额充值