Spring Boot项目通用功能第三讲之《通用属性》

前言

接着上两篇(《通用Service》《通用树结构操作》)通用服务的功能,我们继续说下我们的第三篇,关于通用的属性服务,先来说说为什么会想着抽离这么个服务,想必工作时间久的开发人员肯定会遇到这种问题,随着业务的增长,会对原有业务表上增加各种字段,但是有的字段并不应该归属于主表上,而是应该放在其扩展属性表上,因为有些字段仅仅是主业务其中某一种特定业务才具有的(比如:在order表中,总是有一些并不是所有订单都会有的属性,这时我们可能会将这些属性存储在一个叫ext的字段中,格式可能是json格式的字符串也可能是key:value这种数据结构,但是想想这种方式的存储是不是很不利于结构化搜索,而且还要在主表上维护扩展属性的增删改查功能,抽离下公共功能的附加表是个很好的方式),那如果我们把这种key->value结构关系单独抽离一个功能,来简化我们对这种附加属性的使用,是不是会更好呢?

实现思路

实现思路很简单,就是写一个通用的属性服务mapper和service,让任何主表的属性扩展表仅需要简单的配置就可以去使用该功能,那么现在来看下通用的扩展表结构和需要定义几个核心类:

表结构

DROP TABLE IF EXISTS `XXX_attr`;
CREATE TABLE `XXX_attr` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `object_id` varchar(64) NOT NULL COMMENT '对象ID',-- 该字段类型需与主表的主键一致
  `key` varchar(64) NOT NULL COMMENT '键',
  `value` varchar(1024) DEFAULT NULL COMMENT '值',
  `type` varchar(32) NOT NULL COMMENT '类型',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE INDEX uq_object_id_key(`object_id`, `key`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='XXX属性表';

核心类
Attribute:属性PO类
AttributeMapper:通用属性MAPPER类
InsertAttributeService、UpdateAttributeService、DeleteAttributeService、SelectAttributeService:通用属性API接口
AttributeServiceImpl:通用属性实现逻辑
AttributesChangedEvent:属性变更事件

注意

  • key就是我们的属性字段名,value就是属性字段的值,字段type用来存储value字段的类型
  • XXX 就代表你的主表喽,其中object_id存储你的主表的主键关联
  • 值得注意的是object_id字段的类型是根据主表的主键类型一致的,防止因类型不一致导致程序走不上索引问题

具体实现

Attribute对象

package com.zm.zhuma.commons.attributes.model;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Attribute<OID> implements Serializable{

	private static final long serialVersionUID = 1L;
	
	private Long id;
	
	private OID objectId;
	
	private String key;
	
	private String value;
	
	private String type;

}

AttributeMapper类(通用属性操作mapper)

package com.zm.zhuma.commons.attributes.mapper;

import java.util.List;

import com.zm.zhuma.commons.attributes.model.Attribute;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface AttributeMapper<OID> {

	void addAttributes(@Param(value = "tableName") String tableName, @Param(value = "attributes") List<Attribute<OID>> attributes);

	void deleteAttributes(@Param(value = "tableName") String tableName, @Param(value = "objectId") OID objectId, @Param(value = "keys") List<String> keys);

	void updateAttributes(@Param(value = "tableName") String tableName, @Param("attr") Attribute<OID> attribute);

	List<Attribute<OID>> getAttributeMapByKeys(@Param(value = "tableName") String tableName, @Param(value = "objectIds") List<OID> objectIds, @Param(value = "keys") List<String> keys);

	List<Attribute<OID>> getAttributeMapByKeyAndValue(@Param(value = "tableName") String tableName, @Param(value = "objectIds") List<OID> objectIds, @Param(value = "key") String key, @Param(value = "value") Object value);

	List<Attribute<OID>> getAttributeMapByKeyAndValues(@Param(value = "tableName") String tableName, @Param(value = "key") String key, @Param(value = "values") List<Object> values);

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mapper.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zm.zhuma.commons.attributes.mapper.AttributeMapper">

	<insert id="addAttributes" useGeneratedKeys="true">
		insert into ${tableName} (`object_id`,`key`,`value`,`type`,`create_time`) values
		<foreach collection="attributes" item="item" index="index" separator=",">
			(#{item.objectId},#{item.key},#{item.value},#{item.type},now())
		</foreach>
	</insert>

	<delete id="deleteAttributes">
		delete from ${tableName} where `object_id`=#{objectId}
		<if test="keys != null and keys.size() > 0">
			and `key` in
			<foreach collection="keys" item="key" open="(" separator="," close=")">
				#{key}
			</foreach>
		</if>
	</delete>

	<update id="updateAttributes">
		update ${tableName} set `value` = #{attr.value},type = #{attr.type}
		<where>
			<if test="attr.id != null">id = #{attr.id}</if>
			<if test="attr.objectId != null and attr.objectId != ''"> and `object_id` = #{attr.objectId}</if>
			<if test="attr.key != null and attr.key != '' "> and `key` = #{attr.key}</if>
		</where>
	</update>

	<select id="getAttributeMapByKeys" resultType="com.zm.zhuma.commons.attributes.model.Attribute">
		select * from ${tableName}
		<where>
			<if test="objectIds != null and objectIds.size() > 0">
				`object_id` in
				<foreach collection="objectIds" item="id" open="(" separator="," close=")">
					#{id}
				</foreach>
			</if>
			<if test="keys != null and keys.size() > 0">
				and `key` in 
		        <foreach collection="keys" item="key" open="(" separator="," close=")">
		            #{key}
		        </foreach>
			</if>
		</where>
	</select>
	
	<select id="getAttributeMapByKeyAndValue" resultType="com.zm.zhuma.commons.attributes.model.Attribute">
		select * from ${tableName}
		<where>
			<if test="objectIds != null and objectIds.size() > 0">
				`object_id` in
				<foreach collection="objectIds" item="id" open="(" separator="," close=")">
					#{id}
				</foreach>
			</if>
			<if test="key != null and key != ''">
				and `key` = #{key}
			</if>
			<if test="value != null and value != ''">
				and `value` = #{value}
			</if>
		</where>
	</select>

	<select id="getAttributeMapByKeyAndValues" resultType="com.zm.zhuma.commons.attributes.model.Attribute">
		select * from ${tableName}
		<where>
			<if test="key != null and key != ''">
				`key` = #{key}
			</if>
			<if test="values != null and values.size() > 0">
				and `value` in
				<foreach collection="values" item="value" open="(" separator="," close=")">
					#{value}
				</foreach>
			</if>
		</where>
	</select>

</mapper>

备注

  • 可以看到我们其实是把表名字(${tableName})作为参数传递进来,以达到通用属性mapper的目的,有没有又学到一招的感觉呢?O(∩_∩)O

增加属性接口定义

package com.zm.zhuma.commons.attributes.service;

import com.zm.zhuma.commons.attributes.model.AttributesChange;

import java.util.Map;

/**
 * @desc 插入属性服务
 *
 * @author zhuamer
 * @since 7/9/2018 11:13
 */
public interface InsertAttributeService<OID> {

	/**
	 * 添加对象属性
	 * 该操作将保存attributes中的属性,不存在于attributes中的属性不做任何操作
	 * @param objectId 对象id
	 * @param attributes 属性集合
	 */
	AttributesChange<OID> addAttributes(OID objectId, Map<String, Object> attributes);

}

修改属性接口定义

package com.zm.zhuma.commons.attributes.service;

import com.zm.zhuma.commons.attributes.model.AttributesChange;

import java.util.Map;

/**
 * @desc 更新属性服务
 *
 * @author zhuamer
 * @since 7/9/2018 11:13
 */
public interface UpdateAttributeService<OID> {

	/**
	 * 设置对象属性
	 * @param objectId 对象id
	 * @param key 属性key
	 * @param value 属性值
	 */
	AttributesChange<OID> setAttribute(OID objectId, String key, Object value);

	/**
	 * 设置对象属性
	 * 该操作将保存attributes中的属性,不存在于attributes中的属性将删除
	 * @param objectId 对象id
	 * @param attributes 属性map,key:属性key,value:属性值
	 */
	AttributesChange<OID> setAttributes(OID objectId, Map<String, Object> attributes);

}

删除属性接口定义

package com.zm.zhuma.commons.attributes.service;

import com.zm.zhuma.commons.attributes.model.AttributesChange;

import java.util.Map;

/**
 * @desc 删除属性服务
 *
 * @author zhuamer
 * @since 7/9/2018 11:13
 */
public interface DeleteAttributeService<OID> {

	/**
	 * 删除单个属性
	 * @param objectId 对象id
	 * @param key 属性key
	 */
	AttributesChange<OID> deleteAttribute(OID objectId, String key);

	/**
	 * 删除对象属性
	 * @param objectId 对象id
	 */
	AttributesChange<OID> deleteAttributes(OID objectId);

	/**
	 * 删除对象属性
	 * @param objectId 对象id
	 * @param keys 属性keys
	 */
	AttributesChange<OID> deleteAttributes(OID objectId, Iterable<String> keys);

}

查看属性接口定义

package com.zm.zhuma.commons.attributes.service;

import java.util.Map;

/**
 * @desc 查询属性服务
 *
 * @author zhuamer
 * @since 7/9/2018 11:13
 */
public interface SelectAttributeService<OID> {

	/**
	 * 获取对象所有属性
	 * @param objectId 对象id
	 * @return 属性map,key:属性key,value:属性值
	 */
	Map<String, Object> getAttributes(OID objectId);

	/**
	 * 获取对象所有属性
	 * @param objectId 对象id
	 * @param objectClass 属性对应的类
	 * @return 所有属性对应转化的类对象
	 */
	<T> T getAttributes(OID objectId, Class<T> objectClass);

	/**
	 * 获取对象某一个属性
	 * @param objectId 对象id
	 * @param key 属性key
	 * @return 属性值
	 */
	Object getAttribute(OID objectId, String key);

	/**
	 * 获取对象某一个属性
	 * @param objectId 对象id
	 * @param key 属性key
	 * @param valueClass 属性value类
	 * @return 属性值
	 */
	<V> V getAttribute(OID objectId, String key, Class<V> valueClass);

	/**
	 * 获取对象某一批属性
	 * @param objectId 对象id
	 * @param keys 属性keys
	 * @return 属性map,key:属性key,value:属性值
	 */
	Map<String, Object> getAttributes(OID objectId, Iterable<String> keys);

	/**
	 * 批量获取多个对象的属性
	 * @param objectIds 对象ids
	 * @param keys 属性keys
	 * @return map,key:对象id,value:对象属性map(key:属性key,value:属性值)
	 */
	Map<OID, Map<String, Object>> getAttributes(Iterable<OID> objectIds, Iterable<String> keys);

	/**
	 * 批量获取多个对象的属性
	 * @param objectIds 对象ids
	 * @return map,key:对象id,value:对象属性map(key:属性key,value:属性值)
	 */
	Map<OID, Map<String, Object>> getAttributes(Iterable<OID> objectIds);

	/**
	 * 批量获取多个对象的属性
	 * @param objectIds 对象ids
	 * @param key 属性key
	 * @return
	 */
	Map<OID, Object> getAttributes(Iterable<OID> objectIds, String key);

	/**
	 * 批量获取多个对象ID对应属性信息
	 * @param objectIds 对象ids
	 * @param key 属性key
	 * @return
	 */
	Map<OID, Object> getAttributes(Iterable<OID> objectIds, String key, Object value);

	/**
	 * 一个key,不同value值
	 * @param key
	 * @param values
	 * @return
	 */
	Map<OID, Object> getAttributes(String key, Iterable<Object> values);

}

备注

  • 获取属性的方法还是比较多的,所以应该基本能满足平时开发中的业务场景了

AttributeServiceImpl实现逻辑

package com.zm.zhuma.commons.attributes.service.impl;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.google.common.collect.Maps;
import com.zm.zhuma.commons.attributes.mapper.AttributeMapper;
import com.zm.zhuma.commons.attributes.event.publisher.AttributeEventPublisher;
import com.zm.zhuma.commons.attributes.model.Attribute;
import com.zm.zhuma.commons.attributes.model.AttributeChange;
import com.zm.zhuma.commons.attributes.model.AttributesChangedEvent;
import com.zm.zhuma.commons.attributes.model.AttributesChange;
import com.zm.zhuma.commons.attributes.service.AttributeService;

import com.google.common.collect.Lists;

import com.zm.zhuma.commons.util.BeanUtil;
import com.zm.zhuma.commons.util.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.TooManyResultsException;
import org.springframework.util.Assert;

@Slf4j
public class AttributeServiceImpl<OID> implements AttributeService<OID> {

	private String table = null;

	private AttributeMapper<OID> attributeDao;

	private AttributeEventPublisher<OID> eventPublisher;

	public AttributeServiceImpl(String table, AttributeMapper<OID> attributeDao, AttributeEventPublisher<OID> eventPublisher) {
		this.table = table;
		this.attributeDao = attributeDao;
		this.eventPublisher = eventPublisher;
	}

	@Override
	public Map<String, Object> getAttributes(OID objectId) {
		Assert.notNull(objectId, "objectId is not null");

		List<Attribute<OID>> list = attributeDao.getAttributeMapByKeys(table, Lists.newArrayList(objectId), null);

		return list.stream().collect(Collectors.toMap(Attribute::getKey, this::convertType,(key1, key2) -> key2));
	}

	@Override
	public <T> T getAttributes(OID objectId, Class<T> objectClass) {
		Map<String, Object> attrMap = getAttributes(objectId);
		return BeanUtil.mapToObject(attrMap, objectClass);
	}

	@Override
	public Object getAttribute(OID objectId, String key) {
		Assert.notNull(objectId, "objectId is not null");
		Assert.notNull(key, "key is not null");

		List<Attribute<OID>> list = attributeDao.getAttributeMapByKeys(table, Lists.newArrayList(objectId), Lists.newArrayList(key));

		if (list.size() == 0) {
			return null;
		} else if (list.size() == 1) {
			return convertType(list.get(0));
		} else {
			throw new TooManyResultsException();
		}
	}

	@Override
	public <V> V getAttribute(OID objectId, String key, Class<V> valueClass) {
		return (V) getAttribute(objectId, key);
	}

	@Override
	public Map<String, Object> getAttributes(OID objectId, Iterable<String> keys) {
		Assert.notNull(objectId, "objectId is not null");
		Assert.notNull(keys, "keys is not null");

		List<Attribute<OID>> list = attributeDao.getAttributeMapByKeys(table, Lists.newArrayList(objectId), Lists.newArrayList(keys));

		return list.stream().collect(Collectors.toMap(Attribute::getKey, this::convertType,(key1, key2) -> key2));
	}

	@Override
	public Map<OID, Map<String, Object>> getAttributes(Iterable<OID> objectIds, Iterable<String> keys) {
		Assert.notNull(objectIds, "objectIds is not null");
		Assert.notNull(keys, "objectId is not null");

		Map<OID, Map<String, Object>> map = Maps.newHashMap();

		objectIds.forEach(objectId -> {
			Map<String, Object> partMap = getAttributes(objectId, keys);
			map.put(objectId, partMap);
		});

		return map;
	}

	@Override
	public Map<OID, Map<String, Object>> getAttributes(Iterable<OID> objectIds) {
		Assert.notNull(objectIds, "objectIds is not null");

		Map<OID, Map<String, Object>> map = Maps.newHashMap();

		objectIds.forEach(objectId -> {
			Map<String, Object> partMap = getAttributes(objectId);
			map.put(objectId, partMap);
		});

		return map;
	}

	@Override
	public Map<OID, Object> getAttributes(Iterable<OID> objectIds, String key) {
		Assert.notNull(objectIds, "objectIds is not null");
		Assert.notNull(key, "key is not null");

		List<Attribute<OID>> list = attributeDao.getAttributeMapByKeys(table, Lists.newArrayList(objectIds), Lists.newArrayList(key));

		return list.stream().collect(Collectors.toMap(Attribute::getObjectId, this::convertType,(key1, key2) -> key2));
	}

	@Override
	public Map<OID, Object> getAttributes(Iterable<OID> objectIds, String key, Object value) {
		Assert.notNull(objectIds, "objectIds is not null");
		Assert.notNull(key, "key is not null");

		List<Attribute<OID>> list = attributeDao.getAttributeMapByKeyAndValue(table, Lists.newArrayList(objectIds), key, value);

		return list.stream().collect(Collectors.toMap(Attribute::getObjectId, this::convertType,(key1, key2) -> key2));
	}

	@Override
	public Map<OID, Object> getAttributes(String key, Iterable<Object> values) {
		Assert.notNull(key, "key is not null");
		Assert.notNull(values, "values is not null");

		List<Attribute<OID>> list = attributeDao.getAttributeMapByKeyAndValues(table, key, Lists.newArrayList(values));

		Map<OID, Object> map = new HashMap<>();
		for (Attribute<OID> attribute : list) {
			map.put(attribute.getObjectId(), convertType(attribute));
		}

		return map;
	}

	@Override
	public AttributesChange<OID> setAttribute(OID objectId, String key, Object value) {
		Map<String, Object> attributes = Maps.newHashMap();
		attributes.put(key, value);
		return this.setAttributes(objectId, attributes);
	}


	@Override
	public AttributesChange<OID> setAttributes(OID objectId, Map<String, Object> attributes) {
		Assert.notNull(objectId, "objectId is not null");
		Assert.notNull(attributes, "attributes is not null");

		Map<String, AttributeChange> added = Maps.newHashMap();
		Map<String, AttributeChange> updated = Maps.newHashMap();
		Map<String, AttributeChange> removed = Maps.newHashMap();

		Map<String, Object> previousMap = getAttributes(objectId);

		List<String> previousKeyList = Lists.newArrayList(previousMap.keySet());
		List<String> currentKeyList = Lists.newArrayList(attributes.keySet());

		List<String> addKeyList = CollectionUtil.subtract(currentKeyList, previousKeyList);
		List<String> updateKeyList = CollectionUtil.intersection(currentKeyList, previousKeyList);
		List<String> removeKeyList = CollectionUtil.subtract(previousKeyList, currentKeyList);

		List<Attribute<OID>> addAttrList = addKeyList.stream().map(c -> {
			Attribute<OID> attribute = new Attribute<>();
			attribute.setKey(c);
			attribute.setObjectId(objectId);
			convertType(attributes.get(c), attribute);

			added.put(c, AttributeChange.builder().previous(null).current(attributes.get(c)).build());

			return attribute;
		}).collect(Collectors.toList());

		if (!CollectionUtil.isEmpty(addAttrList)) {
			attributeDao.addAttributes(table, addAttrList);
		}

		updateKeyList.forEach(c -> {
			Attribute<OID> attribute = new Attribute<>();
			attribute.setKey(c);
			attribute.setObjectId(objectId);
			convertType(attributes.get(c), attribute);

			attributeDao.updateAttributes(table, attribute);

			updated.put(c, AttributeChange.builder().previous(previousMap.get(c)).current(attributes.get(c)).build());
		});

		if (!CollectionUtil.isEmpty(removeKeyList)) {
			removeKeyList.forEach(c -> removed.put(c, AttributeChange.builder().previous(previousMap.get(c)).current(null).build()));
			attributeDao.deleteAttributes(table, objectId, removeKeyList);
		}

		return buildResult(objectId, added, updated, removed);
	}

	@Override
	public AttributesChange<OID> addAttributes(OID objectId, Map<String, Object> attributes) {
		Assert.notNull(objectId, "objectId is not null");
		Assert.notNull(attributes, "attributes is not null");

		Map<String, AttributeChange> added = Maps.newHashMap();
		Map<String, AttributeChange> updated = Maps.newHashMap();
		Map<String, AttributeChange> removed = Maps.newHashMap();

		if (!CollectionUtil.isEmpty(attributes)) {
			Map<String, Object> previousMap = getAttributes(objectId);

			List<String> previousKeyList = Lists.newArrayList(previousMap.keySet());

			List<String> currentKeyList = Lists.newArrayList(attributes.keySet());

			List<String> addKeyList = CollectionUtil.subtract(currentKeyList, previousKeyList);
			List<String> updateKeyList = CollectionUtil.intersection(currentKeyList, previousKeyList);

			List<Attribute<OID>> addAttrList = addKeyList.stream().map(c -> {
				Attribute<OID> attribute = new Attribute<>();
				attribute.setKey(c);
				attribute.setObjectId(objectId);
				convertType(attributes.get(c), attribute);

				added.put(c, AttributeChange.builder().previous(null).current(attributes.get(c)).build());

				return attribute;
			}).collect(Collectors.toList());

			if (!CollectionUtil.isEmpty(addAttrList)) {
				attributeDao.addAttributes(table, addAttrList);
			}

			updateKeyList.forEach(c -> {
				Attribute<OID> attribute = new Attribute<>();
				attribute.setKey(c);
				attribute.setObjectId(objectId);
				convertType(attributes.get(c), attribute);

				attributeDao.updateAttributes(table, attribute);

				updated.put(c, AttributeChange.builder().previous(previousMap.get(c)).current(attributes.get(c)).build());
			});
		}

		return buildResult(objectId, added, updated, removed);
	}

	@Override
	public AttributesChange<OID> deleteAttribute(OID objectId, String key) {
		return this.deleteAttributes(objectId, Lists.newArrayList(key));
	}

	@Override
	public AttributesChange<OID> deleteAttributes(OID objectId) {
		Assert.notNull(objectId, "objectId is not null");

		Map<String, AttributeChange> added = Maps.newHashMap();
		Map<String, AttributeChange> updated = Maps.newHashMap();
		Map<String, AttributeChange> removed = Maps.newHashMap();

		List<String> removeKeyList = Lists.newArrayList();

		Map<String, Object> previousMap = getAttributes(objectId);

		if (!CollectionUtil.isEmpty(previousMap)) {
			for (Map.Entry<String, Object> entry : previousMap.entrySet()) {
				removed.put(entry.getKey(), AttributeChange.builder().previous(entry.getValue()).current(null).build());
				removeKeyList.add(entry.getKey());
			}

			if (!CollectionUtil.isEmpty(removeKeyList)) {
				attributeDao.deleteAttributes(table, objectId, removeKeyList);
			}
		}

		return buildResult(objectId, added, updated, removed);
	}

	@Override
	public AttributesChange<OID> deleteAttributes(OID objectId, Iterable<String> keys) {
		Assert.notNull(objectId, "objectId is not null");
		Assert.notNull(keys, "keys is not null");

		Map<String, AttributeChange> added = Maps.newHashMap();
		Map<String, AttributeChange> updated = Maps.newHashMap();
		Map<String, AttributeChange> removed = Maps.newHashMap();

		Map<String, Object> previousMap = getAttributes(objectId);

		List<String> previousKeyList = Lists.newArrayList(previousMap.keySet());
		List<String> currentKeyList =  Lists.newArrayList(keys);

		List<String> removeKeyList = CollectionUtil.intersection(previousKeyList, currentKeyList);

		if (!CollectionUtil.isEmpty(previousMap) && !CollectionUtil.isEmpty(removeKeyList)) {
			for (String key : removeKeyList) {
				removed.put(key, AttributeChange.builder().previous(previousMap.get(key)).current(null).build());
			}
			attributeDao.deleteAttributes(table, objectId, removeKeyList);
		}

		return buildResult(objectId, added, updated, removed);
	}

	/** 保存扩展属性时对象类型转换 */
	private void convertType(Object obj, Attribute<OID> attribute) {
		String type = null;
		String value = null;

		if (obj instanceof Integer) {
			type = Integer.class.getSimpleName();
			value = obj.toString();
		} else if (obj instanceof Float) {
			type = Float.class.getSimpleName();
			value = obj.toString();
		}  else if (obj instanceof Double) {
			type = Double.class.getSimpleName();
			value = obj.toString();
		} else if (obj instanceof BigDecimal) {
			type = BigDecimal.class.getSimpleName();
			value = obj.toString();
		} else if (obj instanceof Long) {
			type = Long.class.getSimpleName();
			value = obj.toString();
		} else if (obj instanceof Date) {
			type = Date.class.getSimpleName();
			Date date = (Date) obj;
			value = String.valueOf(date.getTime());
		} else if (obj instanceof Boolean) {
			type = Boolean.class.getSimpleName();
			value = obj.toString();
		} else if (obj instanceof String) {
			type = String.class.getSimpleName();
			value = obj.toString();
		} else {
			if (null != obj )  {
				type = String.class.getSimpleName();
				value = obj.toString();
			}
		}

		attribute.setType(type);
		attribute.setValue(value);
	}

	/**
	 * 返回值类型转换
	 */
	private Object convertType(Attribute<OID> attribute) {
		String type = attribute.getType();
		String value = attribute.getValue();
		Object result = null;

		if (null != type && null != value) {
			if (Integer.class.getSimpleName().equals(type)) {
				result = Integer.valueOf(value);
			} else if (Float.class.getSimpleName().equals(type)) {
				result = Float.valueOf(value);
			} else if (Double.class.getSimpleName().equals(type)) {
				result = Double.valueOf(value);
			} else if (BigDecimal.class.getSimpleName().equals(type)) {
				result = BigDecimal.valueOf(Double.valueOf(value));
			} else if (Long.class.getSimpleName().equals(type)) {
				result = Long.valueOf(value);
			} else if (Date.class.getSimpleName().equals(type)) {
				result = new Date(Long.valueOf(value));
			} else if (Boolean.class.getSimpleName().equals(type)) {
				result = Boolean.valueOf(value);
			} else if (String.class.getSimpleName().equals(type)) {
				result = String.valueOf(value);
			} else {
				result = value;
			}
		}

		return result;
	}

	private AttributesChange<OID> buildResult(OID objectId, Map<String, AttributeChange> added,  Map<String, AttributeChange> updated, Map<String, AttributeChange> removed) {
		AttributesChange<OID> result = AttributesChange.<OID>builder().objectId(objectId).added(added).updated(updated).removed(removed).build();

		if (!CollectionUtil.isEmpty(added) || !CollectionUtil.isEmpty(updated) || !CollectionUtil.isEmpty(removed)) {
			sendAttributesChangeEvent(result);
		}

		return result;
	}

	/**
	 * 发送属性变更消息
	 */
	private void sendAttributesChangeEvent(AttributesChange<OID> attributesChange) {
		if (eventPublisher == null) {
			return;
		}

		AttributesChangedEvent<OID> event = AttributesChangedEvent.<OID>builder()
				.data(attributesChange)
				.occurredTime(new Date())
				.build();


		eventPublisher.publishAttributesChangedEvent(event, table);
		log.debug("{} is published.", event);
	}

}

备注

  • 实现逻辑代码时比较冗长,本不想粘贴出来的,但是为了代码的完整性,还是粘贴出来吧_,我们看到,代码中最后会统一封装一个属性变更事件,并将事件提用spring cloud stream 抛出,具体事件类请看下面

AttributesChangedEvent属性事件变更

package com.zm.zhuma.commons.attributes.model;

import java.io.Serializable;
import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttributesChangedEvent<OID> implements Serializable {
	private static final long serialVersionUID = -5098574719305009319L;

	private AttributesChange<OID> data;

	private Date occurredTime;

}

package com.zm.zhuma.commons.attributes.model;

import java.io.Serializable;
import java.util.Map;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttributesChange<OID> implements Serializable {
	private static final long serialVersionUID = -5008407345712737581L;

	private String objectType;

	private OID objectId;

	private Map<String, AttributeChange> added;

	private Map<String, AttributeChange> updated;

	private Map<String, AttributeChange> removed;

}

package com.zm.zhuma.commons.attributes.model;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttributeChange implements Serializable {
	private static final long serialVersionUID = -662090239071614840L;

	private Object previous;

	private Object current;

}

备注

  • 可以看到所有的能影响属性表的方法都统一的返回了属性变更事件,其实它是用added、update、remove的map类型接收,返回了增加、修改、删除属性时,属性的变更前字段和变更后的字段,为什么这么做呢?原因有两点,一个是这有利于我们对属性修改后对变更情况的的把控,有利于调用端对结果的使用,其实还有一点,就是我们会在通用属性服务实现逻辑中加入stream rabbitMQ的消息抛出,也会公用该对象
测试

属性测试控制器

package com.zhuma.demo.web.demo5;


import com.zm.zhuma.commons.attributes.model.AttributesChange;
import com.zm.zhuma.commons.attributes.service.AttributeService;
import com.zm.zhuma.commons.model.bo.Node;
import com.zm.zhuma.commons.web.annotations.ResponseResult;
import com.zm.zhuma.user.client.OrgClient;
import com.zm.zhuma.user.model.po.Org;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * @desc 用户管理控制器
 * 
 * @author zhumaer
 * @since 7/16/2018 16:37 PM
 */
@ResponseResult
@RestController("demo4UserAttrController")
@RequestMapping("demo4/users/{userId}/attrs")
public class UserAttrController {

    @Autowired
    private AttributeService<String> userAttributeService;

    @PostMapping
    AttributesChange<String> add(@PathVariable("userId") String userId,@RequestBody Map<String, Object> attrMap) {
        return userAttributeService.addAttributes(userId, attrMap);
    }

    @GetMapping
    Map<String, Object>  get(@PathVariable("userId") String userId) {
        return userAttributeService.getAttributes(userId);
    }

    @PutMapping
    AttributesChange<String> put(@PathVariable("userId") String userId,@RequestBody Map<String, Object> attrMap) {
        return userAttributeService.setAttributes(userId, attrMap);
    }

    @DeleteMapping
    AttributesChange<String> delete(@PathVariable("userId") String userId) {
        return userAttributeService.deleteAttributes(userId);
    }
}

POST MAN截图
这里写图片描述

数据库截图
这里写图片描述

备注

  • 我们仅测试了一个新增属性的场景,课后,大家试一下修改删除和获取等场景吧_
结束语

通用属性服务的介绍到此结束,该服务运用到平时开发中,一定会大大增加开发效率,但是我最最担心的一点就是,因为属性表的增加,使用人会把后续业务新增字段都归结为扩展属性,都扔到属性表中,这并不是一种好的方法,还是建议主表字段和扩展属性字段分离清楚。祝好 ~~~

附上本实例代码github地址(本实例在zhuma-demo项目下,demo5模块):https://github.com/zhumaer/zhuma

源码github地址:https://github.com/zhumaer/zhuma
QQ群号:629446754(欢迎加群)

欢迎关注我们的公众号或加群,等你哦!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring项目Spring Boot项目搭建和配置方面有一些区别。 在纯Spring项目中,开发者需要手动进行大量的配置工作,包括配置XML文件、配置依赖、配置Web容器等。这使得项目搭建过程相对繁琐,需要花费较多的时间和精力。 而在Spring Boot项目中,通过使用自动化配置,大部分的配置工作都可以由Spring Boot来完成。开发者只需要很少的Spring配置,就能够快速搭建一个基于Spring的独立应用程序。Spring Boot内置了许多常用的第三方库的默认自动化配置方案,使得开发者能够更加方便地集成各种功能和组件。 此外,Spring Boot还提供了内嵌服务器的功能,可以快速部署应用程序。开发者可以选择将Spring Boot项目打包成可执行的JAR包,通过简单的java -jar命令就能够启动应用。这种方式避免了繁琐的部署过程,提高了开发和运行效率。 总体而言,相比于纯Spring项目Spring Boot具有以下优势: - 提供一个快速的Spring项目搭建渠道。 - 开箱即用,很少的Spring配置就能运行一个JavaEE项目。 - 提供了生产级的服务监控方案。 - 内嵌服务器,可以快速部署。 - 提供了一系列非功能性的通用配置。 - 纯Java配置,没有代码生成,也不需要XML配置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [首次公开,基于SpringCloud+SpringBoot+Vue电子版项目实战教程,附完整源码](https://blog.csdn.net/m0_49496327/article/details/124695547)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值