文档章节

Mybatis操作mysql 8的Json字段类型

算法之名
 算法之名
发布于 07/16 19:23
字数 2544
阅读 297
收藏 8

Json字段是从mysql 5.7起加进来的全新的字段类型,现在我们看看在什么情况下使用该字段类型,以及用mybatis如何操作该字段类型

一般来说,在不知道字段的具体数量的时候,使用该字段是非常合适的,比如说——商品的无限属性。

现在我们来假设这么一个场景,在商品的二级分类中给商品定义足够多的属性,我们先设计属性的类

/**
 * 商品自定义属性
 */
@NoArgsConstructor
@AllArgsConstructor
public class OtherProperty implements Serializable {
    @Getter
    @Setter
    private Long id; //属性id
    @Getter
    @Setter
    private FormType formType; //前端使用的表单类型
    @Getter
    @Setter
    private String name; //属性名称
    @Getter
    @Setter
    private String unit; //单位
    @Getter
    @Setter
    private String values; //可选值以@分隔,如配件@车品
    @Getter
    private List<String> valueList = new ArrayList<>(); //对可选值的取值列表
    @Getter
    @Setter
    private String defaultValue; //可选值中的默认值
    @Getter
    @Setter
    private boolean search; //是否可搜索
    @Getter
    @Setter
    private boolean mustWrite; //是否必录
    @Getter
    @Setter
    private Boolean used = false; //是否已经在商品中使用,已使用该属性则不允许修改

    public OtherProperty changeValuesToList() {
        String[] split = this.values.split("@");
        for (String value : split) {
            this.valueList.add(value);
        }
        this.values = null;
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        OtherProperty that = (OtherProperty) o;

        if (!id.equals(that.id)) return false;
        if (search != that.search) return false;
        if (mustWrite != that.mustWrite) return false;
        if (formType != that.formType) return false;
        if (!name.equals(that.name)) return false;
        if (unit != null ? !unit.equals(that.unit) : that.unit != null) return false;
        if (values != null ? !values.equals(that.values) : that.values != null) return false;
        return defaultValue != null ? defaultValue.equals(that.defaultValue) : that.defaultValue == null;
    }

    @Override
    public int hashCode() {
        int result = id.hashCode() + formType.hashCode() + name.hashCode();
        result = result + (unit != null ? unit.hashCode() : 0);
        result = result + (values != null ? values.hashCode() : 0);
        result = result + (defaultValue != null ? defaultValue.hashCode() : 0);
        result = result + (search ? 1 : 0);
        result = result + (mustWrite ? 1 : 0);
        return result;
    }
}

其中formType为枚举类型

public enum FormType implements Localisable {
    TYPE1("文本框"),
    TYPE2("下拉框"),
    TYPE3("单选框"),
    TYPE4("复选框"),
    TYPE5("多行文本框");

    private String value;
    private FormType(String value) {
        this.value = value;
    }
    @Override
    public String getValue() {
        return this.value;
    }
}

我们来看一下商品分类的部分代码

@AllArgsConstructor
@NoArgsConstructor
public class ProviderProductLevel implements Provider,Serializable

其中包含一个商品属性对象的列表

@Getter
@Setter
private List<OtherProperty> otherProperties;

部分操作源码如下

/**
 * 通过二级配件分类id查找其包含的所有其他属性
 * @param
 * @return
 */
public List<OtherProperty> findOtherProperties() {
    if (this.level == 2) {
        LevelDao levelDao = SpringBootUtil.getBean(LevelDao.class);
        String ids = levelDao.findIdsByLevel2Id(this.id);
        return levelDao.findOtherProperties(ids);
    }
    return null;
}

/**
 * 在二级配件分类中删除其他属性的id
 * @param paramIds
 * @return
 */
public boolean deletePropertyIdfromLevel(String paramIds) {
    if (this.level == 2) {
        LevelDao levelDao = SpringBootUtil.getBean(LevelDao.class);
        String ids = levelDao.findIdsByLevel2Id(this.id);
        String[] idsArray = ids.split(",");
        List<String> idsList = Arrays.asList(idsArray);
        List<String> contentIdsList = new ArrayList<>();
        contentIdsList.addAll(idsList);
        String[] paramArray = paramIds.split(",");
        List<String> paramList = Arrays.asList(paramArray);
        if (contentIdsList.containsAll(paramList)) {
            contentIdsList.removeAll(paramList);
        }
        if (contentIdsList.size() > 0) {
            StringBuilder builder = new StringBuilder();
            contentIdsList.stream().forEach(eachId -> builder.append(eachId + ","));
            String newIds = builder.toString().substring(0, builder.toString().length() - 1);
            levelDao.addOtherPropertiesToLevel(new ParamOtherPropertiesId(newIds, this.id));
        }else {
            levelDao.addOtherPropertiesToLevel(new ParamOtherPropertiesId("",this.id));
        }
        return true;
    }
    return false;
}
/**
 * 展示某二级配件分类的所有其他属性
 * @param id
 * @return
 */
@SuppressWarnings("unchecked")
@Transactional
@GetMapping("/productprovider-anon/showproperties")
public Result<List<OtherProperty>> showOtherProperties(@RequestParam("id") Long id) {
    Provider level2 = levelDao.findLevel2(id);
    return Result.success(((ProviderProductLevel)level2).findOtherProperties());
}

/**
 * 修改某二级配件分类的其他属性
 * @param id
 * @param otherProperties
 * @return
 */
@SuppressWarnings("unchecked")
@Transactional
@PostMapping("/productprovider-anon/changeother")
public Result<String> changeOtherProperties(@RequestParam("id") Long id,@RequestBody List<OtherProperty> otherProperties) {
    //获取配件二级分类对象
    Provider level2 = levelDao.findLevel2(id);
    //获取未使用的配件二级分类的其他属性(没有任何商品使用过该属性)
    List<OtherProperty> unUsedList = Optional.ofNullable(((ProviderProductLevel) level2).getOtherProperties()).map(otherProperties1 -> otherProperties1.stream())
            .orElse(new ArrayList<OtherProperty>().stream())
            .filter(otherProperty -> !otherProperty.getUsed())
            .collect(Collectors.toList());
    //获取已使用的配件二级分类的其他属性
    List<Long> usedIdList = Optional.ofNullable(((ProviderProductLevel) level2).getOtherProperties()).map(otherProperties1 -> otherProperties1.stream())
            .orElse(new ArrayList<OtherProperty>().stream())
            .filter(otherProperty -> otherProperty.getUsed())
            .map(OtherProperty::getId)
            .collect(Collectors.toList());
    //在传递回来的配件二级分类其他属性中校对没有修改过的,没有使用过的其他属性,只对修改过的,没有使用过的其他属性进行
    //存储,否则不处理
    List<OtherProperty> changeList = otherProperties.stream().filter(otherProperty -> Optional.ofNullable(otherProperty.getId()).isPresent())
            .filter(otherProperty -> !unUsedList.contains(otherProperty))
            .filter(otherProperty -> !usedIdList.contains(otherProperty.getId()))
            .peek(otherProperty -> otherPropertyDao.deleteOtherPropertiesById(otherProperty.getId()))
            .collect(Collectors.toList());
    if (changeList.size() > 0) {
        StringBuilder builder = new StringBuilder();
        changeList.stream().map(OtherProperty::getId).forEach(eachId -> builder.append(eachId + ","));
        String newIds = builder.toString().substring(0, builder.toString().length() - 1);
        ((ProviderProductLevel) level2).deletePropertyIdfromLevel(newIds);
        ((ProviderProductLevel) level2).addOtherProperties(changeList);
    }
    //获取新增的其他属性进行追加到配件二级分类的其他属性中
    List<OtherProperty> newList = otherProperties.stream().filter(otherProperty -> !Optional.ofNullable(otherProperty.getId()).isPresent())
            .peek(otherProperty -> otherProperty.setId(idService.genId()))
            .collect(Collectors.toList());
    ((ProviderProductLevel) level2).addOtherProperties(newList);
    return Result.success("修改成功");
}

在进行一番增删改查后,数据库中的数据大致如下

我们查高级项链的所有属性的结果如下

现在我们要在属于该商品分类中添加商品,商品类定义大致如下

@Data
@NoArgsConstructor
public class ProviderProduct implements Provider {
    private Product product; //配件元信息对象
    private String code; //配件编码
    private Brand brand; //品牌
    private String details; //配件图文说明
    private String levelName; //二级配件分类名称
    private DefaultProvider provider; //配件商
    private ExtBeanWrapper otherValues; //其他属性集合
}

其中对应于属性列表的字段为otherValues,这个值正是我们要存入数据库的Json字段类型映射。

商品的数据库表结构如下

要使用mybatis的数据对Json字段类型的转换,可以先引用一个网上写好的转换器,当然也可以自己写

pom

<dependency>
   <groupId>com.github.jeffreyning</groupId>
   <artifactId>extcol</artifactId>
   <version>0.0.1-RELEASE</version>
</dependency>

配置文件中添加 type-handlers-package: com.nh.micro.ext.th

mybatis:
  type-aliases-package: com.cloud.model.productprovider
  type-handlers-package: com.nh.micro.ext.th
  mapper-locations: classpath:/mybatis-mappers/*
  configuration:
    mapUnderscoreToCamelCase: true

在mapper文件中写入一段插入语句

<insert id="saveProduct" parameterType="com.cloud.productprovider.composite.ProviderProduct">
    insert into product (id,name,code,model,normal_price,price_begin,product_imgs,details,brand_id,other_property_value)
    values (#{product.id},#{product.name},#{code},#{product.model},#{product.price.normalPrice},
    <choose>
        <when test="product.price.begin">
            1
        </when>
        <otherwise>
            0
        </otherwise>
    </choose>,
    #{product.productImgs},
    #{details},
    #{brand.id},
    #{otherValues,jdbcType=VARCHAR}
    )
</insert>

对应商品分类的每一个自定义属性,我们可以先拿到该自定义属性的id,然后以该id,取值为键值对进行插入

{
    "product":{
        "name":"AAAA",
        "model":"AAAAA",
        "price":{
            "normalPrice":199,
            "begin":false
        },
        "productImgs":"http://123.433.567.988"
    },
    "code":"0001",
    "details":"<html><body><a href='sfasffg'><img url='sdfsgwer' /></a></body></html>",
    "brand":{
        "id":1,
        "name":"飞利浦"
    },
    "otherValues":{
        "innerMap":{
            "2459623566996408120":"10",
            "2459623566996409144":"呼和浩特",
            "2459623566996410168":"飞利浦",
            "2459623566996411192":"国际",
            "2459623566996412216":"包邮"
        }
    }
}

执行之后,数据库的数据如下

该插件的数据类和转换器的源码如下,其实也是很简单的

public class ExtBeanWrapper {

   public ExtBeanWrapper() {
   };

   public ExtBeanWrapper(Object entity) {
      this.setObj(entity);
   }

   private Map innerMap = new HashMap();

   public Map getInnerMap() {
      return innerMap;
   }

   public void setInnerMap(Map innerMap) {
      this.innerMap = innerMap;
   }

   public void setObj(Object entity) {
      if (entity == null) {
         innerMap = null;
      }
      JSON jobj = (JSON) JSON.toJSON(entity);
      innerMap = JSON.toJavaObject(jobj, Map.class);
   }

   public Object getObj() {
      if (innerMap == null) {
         return null;
      }
      JSON jobj = (JSON) JSON.toJSON(innerMap);
      Map entity = JSON.toJavaObject(jobj, Map.class);
      return entity;
   }

   public Object getObj(Class targetClass) {
      if (innerMap == null) {
         return null;
      }
      JSON jobj = (JSON) JSON.toJSON(innerMap);
      Object entity = JSON.toJavaObject(jobj, targetClass);
      return entity;
   }

}
MappedTypes(com.nh.micro.ext.ExtBeanWrapper.class)
@MappedJdbcTypes(JdbcType.VARCHAR)
public class TagToJsonTypeHandler extends BaseTypeHandler<ExtBeanWrapper> {
   private Map jsonToMap(String value) {
      if (value == null || "".equals(value)) {
         return Collections.emptyMap();
      } else {
         return JSON.parseObject(value, new TypeReference<Map<String, Object>>() {
         });
      }
   }

   @Override
   public void setNonNullParameter(PreparedStatement ps, int i, ExtBeanWrapper parameter, JdbcType jdbcType)
         throws SQLException {
         ps.setString(i, JSON.toJSONString(parameter.getInnerMap()));
   }

   public boolean isJson(String value){
      if(value==null || "".equals(value)){
         return false;
      }else{
         if(value.startsWith("{")){
            return true;
         }
      }
      return false;
   }

   @Override
   public ExtBeanWrapper getNullableResult(ResultSet rs, String columnName) throws SQLException {
      String value=rs.getString(columnName);
      Map innerMap=jsonToMap(value);
      ExtBeanWrapper extBeanTag=new ExtBeanWrapper();
      extBeanTag.setInnerMap(innerMap);
      return extBeanTag;
   }

   @Override
   public ExtBeanWrapper getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
      String value=rs.getString(columnIndex);
      Map innerMap=jsonToMap(value);
      ExtBeanWrapper extBeanTag=new ExtBeanWrapper();
      extBeanTag.setInnerMap(innerMap);
      return extBeanTag;
   }

   @Override
   public ExtBeanWrapper getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
      String value=cs.getString(columnIndex);
      Map innerMap=jsonToMap(value);
      ExtBeanWrapper extBeanTag=new ExtBeanWrapper();
      extBeanTag.setInnerMap(innerMap);
      return extBeanTag;
   }

}

现在我们来看一下如何将该Json字段从数据库取出,还是以上面的案例为例,先在mapper文件中定义一组resultMap

<resultMap id="productMap" type="com.cloud.productprovider.composite.ProviderProduct">
    <id property="code" column="code" />
    <result property="details" column="details" />
    <association property="product" javaType="com.cloud.model.productprovider.Product"
        column="id">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="model" column="model" />
        <result property="productImgs" column="product_imgs" />
        <association property="price" javaType="com.cloud.model.serviceprovider.Price">
            <id property="normalPrice" column="normal_price" />
            <result property="secKillPrice" column="seckill_price" />
            <result property="begin" column="price_begin" typeHandler="com.cloud.productprovider.untils.BoolIntTypeHandler" />
        </association>
    </association>
    <association property="brand" javaType="com.cloud.model.productprovider.Brand" column="brand_id" select="findBrandById" />
    <association property="levelName" column="level_id" javaType="java.lang.String" select="findLevelNameById" />
    <association property="provider" column="default_provider_id" javaType="com.cloud.productprovider.composite.DefaultProvider"
                 select="findProviderById" />
    <association property="otherValues" javaType="com.nh.micro.ext.ExtBeanWrapper" column="other_property_value">
        <id property="entry" column="other_property_value" jdbcType="VARCHAR" typeHandler="com.nh.micro.ext.th.TagToJsonTypeHandler" />
    </association>
</resultMap>

这里稍微解释一下,price里的begin是boolean类型,price_begin在数据库中是整形,有一个转换器,代码如下

public class BoolIntTypeHandler extends BaseTypeHandler<Boolean> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType) throws SQLException {
        ps.setBoolean(i,parameter);
    }

    @Override
    public Boolean getNullableResult(ResultSet rs, String columnName) throws SQLException {
        int value = rs.getInt(columnName);
        if (rs.wasNull()) {
            return false;
        }else {
            if (value == 0) {
                return false;
            }else if (value == 1) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Boolean getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int value = rs.getInt(columnIndex);
        if (rs.wasNull()) {
            return false;
        }else {
            if (value == 0) {
                return false;
            }else if (value == 1) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Boolean getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int value = cs.getInt(columnIndex);
        if (cs.wasNull()) {
            return false;
        }else {
            if (value == 0) {
                return false;
            }else if (value == 1) {
                return true;
            }
        }
        return false;
    }
}

品牌这里有一个查找

<select id="findBrandById" parameterType="java.lang.Long" resultType="com.cloud.model.productprovider.Brand">
    select id,code,name,sort,log_url logoUrl from brand
    <where>
        id=#{brand_id}
    </where>
</select>

配件二级分类名称

<select id="findLevelNameById" parameterType="java.lang.Long" resultType="java.lang.String">
    select name from product_level
    <where>
        id=#{level_id}
    </where>
</select>

配件商信息

<resultMap id="defaultProviderMap" type="com.cloud.productprovider.composite.DefaultProvider">
    <id property="code" column="code" />
    <association property="productProvider" javaType="com.cloud.model.productprovider.ProductProvider">
        <id property="id" column="id" />
        <result property="name" column="name" />
    </association>
</resultMap>
<select id="findProviderById" parameterType="java.lang.Long" resultMap="defaultProviderMap" resultType="com.cloud.productprovider.composite.DefaultProvider">
    select a.id,a.name,b.code from product_provider a
    inner join default_provider b on a.id=b.id
    <where>
        a.id=#{default_provider_id}
    </where>
</select>

当然我们的重点还是otherValues这里

<association property="otherValues" javaType="com.nh.micro.ext.ExtBeanWrapper" column="other_property_value">
    <id property="entry" column="other_property_value" jdbcType="VARCHAR" typeHandler="com.nh.micro.ext.th.TagToJsonTypeHandler" />
</association>

获取数据的全部select代码如下

<select id="findProductById" parameterType="java.lang.Long" resultMap="productMap"
        resultType="com.cloud.productprovider.composite.ProviderProduct">
    select id,code,name,model,brand_id,normal_price,level_id,default_provider_id,other_property_value
    from product
    <where>
        id=#{id}
    </where>
</select>

获取出来的数据如下

{
    "code": 200,
    "data": {
        "brand": {
            "code": "001",
            "id": 1,
            "logoUrl": "http://123.456.789",
            "name": "飞利浦",
            "sort": 1
        },
        "code": "0001",
        "levelName": "高级项链",
        "otherValues": {
            "innerMap": {
                "2459623566996411192": "国际",
                "2459623566996408120": "10",
                "2459623566996409144": "呼和浩特",
                "2459623566996410168": "飞利浦",
                "2459623566996412216": "包邮"
            },
            "obj": {
                "2459623566996410168": "飞利浦",
                "2459623566996411192": "国际",
                "2459623566996408120": "10",
                "2459623566996409144": "呼和浩特",
                "2459623566996412216": "包邮"
            }
        },
        "product": {
            "id": 2459722970793247544,
            "model": "AAAAA",
            "name": "AAAA",
            "onShelf": false,
            "price": {
                "begin": false,
                "normalPrice": 199
            }
        },
        "provider": {
            "code": "0001",
            "productProvider": {
                "id": 2459698718186668856,
                "name": "大众4S店",
                "productList": []
            },
            "status": false
        }
    },
    "msg": "操作成功"
}

当然我们这里要把其他属性的id替换成用户能看懂的其他属性的名称

@Override
public Provider findProduct(Long id) {
    ProductDao productDao = SpringBootUtil.getBean(ProductDao.class);
    OtherPropertyDao otherPropertyDao = SpringBootUtil.getBean(OtherPropertyDao.class);
    Provider product = productDao.findProductById(id);
    Map map = ((ProviderProduct) product).getOtherValues().getInnerMap();
    Map<String,String> insteadMap = new HashMap<>();
    for (Object key : map.keySet()) {
        log.info("键名为:" + String.valueOf(key));
        String name = otherPropertyDao.findNameById(Long.parseLong(String.valueOf(key)));
        insteadMap.put(name,(String) map.get(key));
    }
    ((ProviderProduct) product).getOtherValues().setObj(insteadMap);
    return product;
}

最后我们获取的结果为

{
    "code": 200,
    "data": {
        "brand": {
            "code": "001",
            "id": 1,
            "logoUrl": "http://123.456.789",
            "name": "飞利浦",
            "sort": 1
        },
        "code": "0001",
        "levelName": "高级项链",
        "otherValues": {
            "innerMap": {
                "商品等级": "国际",
                "运费设置": "包邮",
                "生产厂家": "飞利浦",
                "包装规格": "10",
                "商品产地": "呼和浩特"
            },
            "obj": {
                "$ref": "$.data.otherValues.innerMap"
            }
        },
        "product": {
            "id": 2459722970793247544,
            "model": "AAAAA",
            "name": "AAAA",
            "onShelf": false,
            "price": {
                "begin": false,
                "normalPrice": 199
            }
        },
        "provider": {
            "code": "0001",
            "productProvider": {
                "id": 2459698718186668856,
                "name": "大众4S店",
                "productList": []
            },
            "status": false
        }
    },
    "msg": "操作成功"
}

© 著作权归作者所有

算法之名
粉丝 40
博文 164
码字总数 245420
作品 0
广州
私信 提问
Mybatis深度整合Mysql的Json字段 - extcol

概述 以前当业务数据结构变化时,往往需要采用的方案是: 修改表结构增加字段 遇到数据结构有list结构时,新建1对多的关联子表 用字典表表示字段的增加 以上方案对代码侵入性很强,同时与旧业...

杰睿宁
05/04
449
0
mysql的json特性的应用

概述 说实话,个人不是很喜欢这种特性,关系型数据库就应该有关系型数据库的样子,而不是为了留住用户而强加给他一些nosql的特性,而且还没有人家做的好,这样反而会造成一种四不像的感觉。s...

whaon
2018/08/10
940
0
Mybatis-Plus 1.4.7,新增验证字段策略枚举类

Mybatis-Plus是一款MyBatis的增强工具包,简化 CURD 操作。启动加载 XML 配置时注入单表 SQL 操作 ,为简化开发工作、提高生产率而生。Mybatis-Plus 启动注入非拦截实现、性能更优。 Demo S...

青苗
2016/09/28
4.1K
6
Mybatis-generator的使用

一、核心文件generator.xml 指定数据库jar包位置、数据库连接信息、生成包的位置、表名等关键信息。该文件放在任意位置。

woshixuye111
2014/06/07
0
0
mybatis 使用Map封装查询的结果

有时我们厌倦了为每个查询写一个Entity类,这时Map开始发挥它的功效。 对于要返回“学号,班级,姓名”结果的查询,可以这样写Mapper: select s.code as sNo , s.name as sName, c.name as c...

诗和_远方
2017/03/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

spring cloud

一、从面试题入手 1.1、什么事微服务 1.2、微服务之间如何独立通讯的 1.3、springCloud和Dubbo有哪些区别 1.通信机制:DUbbo基于RPC远程过程调用;微服务cloud基于http restFUL API 1.4、spr...

榴莲黑芝麻糊
今天
2
0
Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
昨天
6
0
maven 环境隔离

解决问题 即 在 resource 文件夹下面 ,新增对应的资源配置文件夹,对应 开发,测试,生产的不同的配置内容 <resources> <resource> <directory>src/main/resources.${deplo......

之渊
昨天
8
0
详解箭头函数和普通函数的区别以及箭头函数的注意事项、不适用场景

箭头函数是ES6的API,相信很多人都知道,因为其语法上相对于普通函数更简洁,深受大家的喜爱。就是这种我们日常开发中一直在使用的API,大部分同学却对它的了解程度还是不够深... 普通函数和...

OBKoro1
昨天
7
0
轻量级 HTTP(s) 代理 TinyProxy

CentOS 下安装 TinyProxy yum install -y tinyproxy 启动、停止、重启 # 启动service tinyproxy start# 停止service tinyproxy stop# 重启service tinyproxy restart 相关配置 默认...

Anoyi
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部