Mybatis操做mysql 8的Json字段類型

Json字段是從mysql 5.7起加進來的全新的字段類型,如今咱們看看在什麼狀況下使用該字段類型,以及用mybatis如何操做該字段類型html

通常來講,在不知道字段的具體數量的時候,使用該字段是很是合適的,好比說——商品的無限屬性。前端

如今咱們來假設這麼一個場景,在商品的二級分類中給商品定義足夠多的屬性,咱們先設計屬性的類mysql

/**
 * 商品自定義屬性
 */
@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爲枚舉類型git

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;
    }
}

咱們來看一下商品分類的部分代碼github

@AllArgsConstructor
@NoArgsConstructor
public class ProviderProductLevel implements Provider,Serializable

其中包含一個商品屬性對象的列表sql

@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("修改爲功");
}

在進行一番增刪改查後,數據庫中的數據大體以下json

咱們查高級項鍊的全部屬性的結果以下mybatis

如今咱們要在屬於該商品分類中添加商品,商品類定義大體以下app

@Data
@NoArgsConstructor
public class ProviderProduct implements Provider {
    private Product product;
    private String code;
    private Brand brand;
    private String details;
    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;
}

}
相關文章
相關標籤/搜索