前言:
最近在工作的过程当中,发现数据库有一个字段 假设为 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);
}
}
使用方法一样