在工作中经常会有关联性问题:例如 在用户表修改了用户名称 所有冗余了用户名称的表都需要进行修改 故产出了这篇文章 希望对大家有所帮助
1.准备工作
我们需要新增注解 DomainSource ,DomainTarget,DomainProperty,DomainTargets
@ DomainSource注解实体
import java.lang.annotation.*;
/**
* @author : LvLiang
* @effect : 源实体注解,定义在源实体类上
* @date : 2023/6/26 13:57
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DomainSource {
/**
* 属性数组
*/
String[] keys();
}
@DomainTarget注解实体
import java.lang.annotation.*;
/**
* @author : LvLiang
* @effect :目标实体注解,定义在目标有冗余的实体类上
* @date : 2023/6/26 14:16
*
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DomainTarget {
/**
* 包名
*
* @return
*/
String packages();
/**
* 源实体名称
*
*/
String entityName();
/**
* 外键 表字段
*
* @return
*/
String foreignKey();
/**
* 属性数组
*
*/
DomainProperty[] keys();
}
@DomainProperty 注解实体
import java.lang.annotation.*;
/**
* @author : LvLiang
* @effect :修改字段映射关系
* @date : 2023/6/26 14:17
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DomainProperty {
/**
* 源实体中的属性名
*
*/
String source();
/**
* 目标实体中的属性名
*
*/
String target();
}
@DomainTargets注解实体
import java.lang.annotation.*;
/**
* @author : LvLiang
* @effect :
* @date : 2023/8/1 16:51
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DomainTargets {
DomainTarget[] value();
}
消息封装
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
import java.util.List;
/**
*
* @author : LvLiang
* @effect :领域消息事件对象,用于领域数据变动后数据的封装
* @package: com.fastsun.platform.core.bean
* @date : 2023/6/26 14:22
*/
@Data
@SuperBuilder
@NoArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
public class DomainMessageEvent extends BaseEvent {
private static final long serialVersionUID = 5354621263610168362L;
// /**
// * 应用ID
// *
// */
// private String appId;
//
// /**
// * 租户代码
// *
// */
// private String tenantCode;
/**
* 包名
*/
private String packages;
/**
* 源服务实体名
*
*/
private String entityName;
/**
* 修改的数据的主键ID
*/
private Serializable key;
/**
* 源实体中修改的属性名及值
*
*/
private List<Modified> modifiedList;
}
修改属性及值类
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
*
* @author : LvLiang
* @effect : 修改的属性
* @package: com.fastsun.platform.core.bean
* @date : 2023/6/26 14:23
*/
@Data
@ToString
@Builder
public class Modified implements Serializable{
private static final long serialVersionUID = 5353367948265675298L;
/**
* 修改属性名
*
*/
private String property;
/**
* 修改属性名的值
*
*/
private Object value;
}
消息发送类
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fastsun.platform.core.annotation.DomainSource;
import com.fastsun.platform.core.bean.DomainMessageEvent;
import com.fastsun.platform.core.bean.Modified;
import com.fastsun.platform.core.configurer.ApplicationContextProvider;
import com.fastsun.platform.core.dto.TenantDTO;
import com.fastsun.platform.core.enums.FastsunEventType;
import com.fastsun.platform.core.event.IMessagePublisher;
import com.fastsun.platform.core.integrate.IEntityCommitListener;
import com.fastsun.platform.core.integrate.LocalApplicationContext;
import com.fastsun.platform.core.integrate.LocalTenantContext;
import com.fastsun.platform.core.utils.JsonUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.io.Serializable;
import java.util.*;
import static com.fastsun.platform.core.common.FastsunSettings.PLATFORM_MASTER_TENANT;
/**
* @author : LvLiang
* @effect : 数据修改发送类
* @package: com.fastsun.platform.core.listeners
* @date : 2023/6/26 15:51
*/
@Component
@RequiredArgsConstructor
public class DomainDataModificationSending implements IEntityCommitListener {
@Autowired
private IMessagePublisher messagePublisher;
@Override
public void update(String appId, TenantDTO tenant, Object entity, Serializable id, String[] names, Object[] oldState, Object[] state) {
Class<?> clazz = entity.getClass();
DomainSource annotation = clazz.getAnnotation(DomainSource.class);
if (ObjectUtils.isEmpty(annotation)) {
return;
}
String[] keys = annotation.keys();
if (keys.length > 0){
List<Modified> modifiedList = new ArrayList<>();
for (String key : keys) {
for (int i = 0 ; i < names.length; i++) {
String name = names[i];
if (Objects.equals(name, key)) {
if (!Objects.equals(oldState[i], state[i])) {
Modified value = Modified.builder().property(key).value(state[i]).build();
modifiedList.add(value);
}
break;
}
}
}
if (modifiedList.size()>0){
String name = ClassUtil.getClassName(clazz,true);
DomainMessageEvent build = DomainMessageEvent.builder()
.type("update")
.key(id)
.appId(LocalApplicationContext.get())
.tenant(ObjectUtils.isEmpty(tenant) ? null : tenant.getTenantCode())
.entityName(name)
.packages(clazz.getPackage().getName())
.modifiedList(modifiedList)
.build();
messagePublisher.publisher(FastsunEventType.REDUNDANCE, build);
}
}
}
@Override
public void delete(String appId, TenantDTO tenant, Object entity, Serializable id) {
Class<?> clazz = entity.getClass();
DomainSource annotation = clazz.getAnnotation(DomainSource.class);
if (ObjectUtils.isEmpty(annotation)) {
return;
}
String code = tenant == null
? ApplicationContextProvider.getProperty("fastsun.platform.master.tenant")
: tenant.getTenantCode();
String name = ClassUtil.getClassName(clazz,true);
DomainMessageEvent build = DomainMessageEvent.builder()
.type("delete")
.key(id)
.tenant(code)
.entityName(name)
.modifiedList(new ArrayList<>())
.build();
messagePublisher.publisher(FastsunEventType.REDUNDANCE, build);
}
}
消息处理类
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fastsun.platform.core.annotation.DomainProperty;
import com.fastsun.platform.core.annotation.DomainTarget;
import com.fastsun.platform.core.annotation.DomainTargets;
import com.fastsun.platform.core.bean.DomainMessageEvent;
import com.fastsun.platform.core.bean.Modified;
import com.fastsun.platform.core.configurer.ApplicationContextProvider;
import com.fastsun.platform.core.dto.TenantDTO;
import com.fastsun.platform.core.event.IMessageListener;
import com.fastsun.platform.core.integrate.ICacheService;
import com.fastsun.platform.core.integrate.LocalApplicationContext;
import com.fastsun.platform.core.integrate.LocalTenantContext;
import com.fastsun.platform.core.utils.ClassUtils;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
import static com.fastsun.platform.core.common.FastsunSettings.PLATFORM_BASE_PACKAGES;
import static com.fastsun.platform.core.common.SystemConstant.TENANT_CACHE_KEY_NAME;
/**
* @author : LvLiang
* @effect : 数据修改消费类
* @package: com.fastsun.platform.core.listeners
* @date : 2023/6/28 13:42
*/
@Slf4j
@Component("redundance")
public class DomainDataSyncMessageListener implements IMessageListener {
@Autowired
private ICacheService cache;
@Autowired
private JdbcTemplate jdbc;
@Override
public void execute(String topic, String json) {
log.error("消费topic【" + topic + "】!,更新json【"+json+"】");
DomainMessageEvent event = establishEvent(json);
if (!ObjectUtils.isEmpty(event.getAppId())) {
LocalApplicationContext.set(event.getAppId());
}
if (!ObjectUtils.isEmpty(event.getTenant())) {
TenantDTO tenantDTO = (TenantDTO) cache.getMapValue(TENANT_CACHE_KEY_NAME, event.getTenant());
LocalTenantContext.set(tenantDTO);
}
String packages = ApplicationContextProvider.getProperty(PLATFORM_BASE_PACKAGES, "com.fastsun");
Reflections reflections = new Reflections(packages);
Set<Class<?>> domainTargetSet = reflections.getTypesAnnotatedWith(DomainTarget.class);
domainTargetSet.addAll(reflections.getTypesAnnotatedWith(DomainTargets.class));
if (Objects.equals(event.getType(), "update")) {
doUpdate(event, domainTargetSet);
} else if (Objects.equals(event.getType(), "delete")) {
doDelete(event, domainTargetSet);
} else {
log.error("未知的操作类型【" + event.getType() + "】!");
}
}
private DomainMessageEvent establishEvent(String json) {
DomainMessageEvent domainMessageEvent = new DomainMessageEvent();
JSONObject obj = JSONUtil.parseObj(json);
if (ObjectUtil.equals(obj.get("type"), "update")) {
domainMessageEvent.setKey(obj.get("key", Serializable.class));
domainMessageEvent.setEntityName(obj.get("entityName", String.class));
domainMessageEvent.setTenant(obj.get("tenantCode", String.class));
domainMessageEvent.setType(obj.get("type", String.class));
ArrayList modifiedList = obj.get("modifiedList", ArrayList.class);
List<Modified> modifies = new ArrayList<>();
modifiedList.forEach(o -> {
JSONObject jsonObject1 = JSONUtil.parseObj(o);
Modified build = Modified.builder().property(jsonObject1.get("property", String.class)).value(jsonObject1.get("value", Object.class)).build();
modifies.add(build);
});
domainMessageEvent.setModifiedList(modifies);
}
return domainMessageEvent;
}
private void doUpdate(DomainMessageEvent event, Set<Class<?>> domainTargetSet) {
log.error("更新domainTargetSet【"+domainTargetSet+"】");
log.error("更新event【"+event+"】");
try {
domainTargetSet.forEach(o -> {
DomainTarget domainTarget = o.getAnnotation(DomainTarget.class);
DomainTargets domainTargets = o.getAnnotation(DomainTargets.class);
if (ObjectUtil.isNotEmpty(domainTarget)){
cascadeModification(domainTarget,o,event);
}
if (ObjectUtil.isNotEmpty(domainTargets)){
DomainTarget[] value = domainTargets.value();
for (DomainTarget target : value) {
cascadeModification(target,o,event);
}
}
});
} catch (Exception e) {
log.error("同步冗余数据失败!", e);
}
}
private void cascadeModification(DomainTarget domainTarget, Class<?> o, DomainMessageEvent event) {
if (ObjectUtil.equal(domainTarget.entityName(),event.getEntityName()) && ObjectUtil.equal(domainTarget.packages(),o.getPackage().getName())){
Table table = o.getAnnotation(Table.class);
List<DomainProperty> keys = Arrays.asList(domainTarget.keys());
List<DomainProperty> domainProperties = keys.stream()
.filter(p -> event.getModifiedList().stream()
.map(Modified::getProperty)
.collect(Collectors.toList())
.contains(p.source()))
.collect(Collectors.toList());
StringBuilder sql = new StringBuilder();
sql.append("update ");
sql.append(table.name());
sql.append(" set ");
for (int i = domainProperties.size() - 1; i >= 0; i--) {
DomainProperty d = domainProperties.get(i);
Object value = event.getModifiedList().stream()
.filter(modified -> Objects.equals(modified.getProperty(), d.source()))
.map(Modified::getValue)
.collect(Collectors.toList()).stream().findFirst().orElse(null);
String columnForField = ClassUtils.getColumnForField(o, d.target());
sql.append(columnForField);
sql.append("=");
sql.append(Objects.isNull(value) ? "" : value instanceof Boolean ? (Boolean) value : "'" + value + "'");
if (i > 0) {
sql.append(",");
}
}
sql.append(" where ");
sql.append(domainTarget.foreignKey());
sql.append("=");
sql.append("'" + event.getKey() + "'");
jdbc.update(sql.toString());
}else {
log.error("sql未执行,具体原因为:实体名比对:{},包名比对:{}" ,ObjectUtil.equal(domainTarget.entityName(),event.getEntityName()),ObjectUtil.equal(domainTarget.packages(),o.getPackage().getName()));
}
}
}
使用源数据注解
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fastsun.platform.core.annotation.Comment;
import com.fastsun.platform.core.annotation.DBTable;
import com.fastsun.platform.core.annotation.DomainSource;
import com.fastsun.platform.core.bean.BaseEntity;
import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@DBTable(name = "商品分类")
@Table(name = "classification")
@Data
@DomainSource(keys = {"fullPath","classificationName"})
public class Classification extends BaseEntity {
@Column(name = "classification_code",length = 80)
@Comment(name = "分类代码")
private String classificationCode;
@Column(name = "classification_name",length = 150)
@Comment(name = "分类名称")
private String classificationName;
@Column(name = "parent_category_id")
@Comment(name = "父分类ID")
@JsonSerialize(using = ToStringSerializer.class)
private Long parentCategoryID;
@Column(name = "parent_category_name",length = 150)
@Comment(name = "父分类名称")
private String parentCategoryName;
@Column(name = "full_path",length = 300)
@Comment(name = "全路径")
private String fullPath;
@Column(name = "full_path_id",length = 300)
@Comment(name = "全路径Id")
private String fullPathId;
}
修改类
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import org.hibernate.annotations.Where;
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;
@Data
@Entity
@DBTable(name = "商品")
@Table(name = "Product")
@DomainTarget(entityName = "Classification",foreignKey = "CLASSIFICATION_ID",
keys = {
@DomainProperty(target = "classificationName",source = "classificationName")
,@DomainProperty(target = "fullPath",source = "fullPath")
,@DomainProperty(target = "fullPathId",source = "fullPathId")
},
packages = "com.fastsun.platform.centralized.purchasing.domain.entity"
)
public class Product extends BaseAuditEntity {
@Column(name = "PRODUCT_CODE", nullable = false, length = 20)
@Comment(name = "商品编码")
private String code;
@Comment(name = "商品名称", type = DataType.VARCHAR, length = 200)
@Column(name = "PRODUCT_NAME", length = 200, nullable = false)
private String name;
@Comment(name = "分类ID", type = DataType.LONG)
@Column(name = "CLASSIFICATION_ID")
private Long categoryId;
@Column(name = "classification_name",length = 150)
@Comment(name = "分类名称")
private String classificationName;
@Column(name = "full_path",length = 300)
@Comment(name = "分类全路径")
private String fullPath;
@Column(name = "full_path_id",length = 300)
@Comment(name = "分类全路径ID")
private String fullPathId;
}
增加该注解后 编辑分类后会触发修改商品分类信息
希望本文档可以给各位提到帮助