- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
王有志 ,一个分享硬核Java技术的互金摸鱼侠 加入Java人的提桶跑路群: 共同富裕的Java人 。
去年我们在重构项目中落地了DDD,当时花了点时间研究了下阿里巴巴大淘宝技术发布的《阿里技术专家详解DDD系列》,其中第三讲《 阿里技术专家详解DDD系列 第三讲 - Repository模式 》中提到了一项技术-- 变更追踪 .
简单来说,变更追踪是记录对象进行业务操作后发生的改变,通过这些改变来决定如何更新数据库,文章中提到了两种实现变更追踪方案:
- 基于Snapshot的方案: 当数据从DB里取出来后,在内存中保存一份snapshot,然后在数据写入时和snapshot比较。常见的实现如Hibernate。
- 基于Proxy的方案: 当数据从DB里取出来后,通过weaving的方式将所有setter都增加一个切面来判断setter是否被调用以及值是否变更,如果变更则标记为Dirty。在保存时根据Dirty判断是否需要更新。常见的实现如Entity Framework。
不过由于只给出了Snapshot方案的部分实现代码,导致很多读者对产生了疑惑.
我们在工程实践中借鉴了Snapshot方案的设计,并根据自身的业务情况做出了一些调整,下面就和大家分享我们在工程中的实践.
叠“BUFF” :
正式开始前,我们先做一些简单的准备工作,主要是DDD设计中的接口定义,首先是定义接口Aggregate和Identifier:
public interface Aggregate<ID extends Identifier> extends Serializable {
ID getId();
}
public interface Identifier extends Serializable {
Serializable value();
}
接着定义Repository接口并提供3个基础能力:
public interface Repository<T extends Aggregate<ID>, ID extends Identifier> {
/**
* 保存
* @param aggregateRoot
* @throws IllegalAccessException
*/
void save(T aggregateRoot) throws IllegalAccessException;
/**
* 删除
* @param aggregateRoot
*/
void remove(T aggregateRoot);
/**
* 查询
* @param identifier
* @return
*/
T find(ID identifier);
}
Repository是Service(业务逻辑)与DAO(Data Access Object,数据访问对象)间的“桥梁”,用于隔离业务逻辑与数据库之间的依赖,帮助我们屏蔽在数据库发生变更时对业务逻辑产生的影响,这点是DDD设计相关的内容,我们在这里不过多的讨论.
我们定义一个简单书籍和图片的实体:
@Getter
@Setter
public class Book implements Aggregate<BookId> {
private BookId bookId;
private String bookName;
private String bookDesc;
private Long words;
private List<Image> images;
private List<String> contents;
@Override
public BookId getId() {
return this.bookId;
}
}
@Getter
@Setter
public class BookId implements Identifier {
private Long bookId;
@Override
public Serializable value() {
return this.bookId;
}
}
@Getter
@Setter
public class Image implements Aggregate<ImageId> {
private ImageId imageId;
private String imageUrl;
@Override
public ImageId getId() {
return this.imageId;
}
}
@Getter
@Setter
public class ImageId implements Identifier {
private long imageId;
@Override
public Serializable value() {
return this.imageId;
}
}
在有些DDD的实践规范中,实体中是不允许出现Getter方法和Setter方法的,这里为了方便提供测试数据,直接使用了lombok的注解添加Getter方法和Setter方法.
最后我们来定义实体Book的Repository服务:
public interface BookRepository extends Repository<Book, BookId> {
}
public class BookRepositoryImpl implements BookRepository {
@Override
public void save(Book aggregateRoot) {
// 实现保存逻辑
}
@Override
public void remove(Book aggregateRoot) {
// 实现删除逻辑
}
@Override
public Book find(BookId identifier) {
Book book = new Book();
// 实现查询逻辑
return book;
}
}
BookRepository接口的意义是方便自定义Repository方法,BookRepositoryImpl是BookRepository具体的实现,这里我们只使用3个基础功能即可,具体的实现逻辑是调用DAO实现增删改查,并借助Convert工具实现DO与实体的转换,我们这里就省略这部分内容了, 实际上是我懒得写了 .
变更追踪的核心是在调用Repository的基础能力时进行实体对象的追踪,并在保存时对比实体对象的变化,具体的执行逻辑如下:
Repository#find
时,复制实体对象的快照,添加的变更追踪的容器中; Repository#save
时,对比当前实体对象与快照,返回两者间的差异; Repository#remove
时,删除变更追踪容器中实体对象的快照。 在我们的工程实践中,核心设计采用了阿里巴巴在《 阿里技术专家详解DDD系列 第三讲 - Repository模式 》给出的方案,但在具体的实现细节上,我们做了一些调整,接下来就和大家分享下我们的设计.
首先来实现通用支撑类RepositorySupport,提供可复用的变更追踪能力:
public abstract class RepositorySupport<T extends Aggregate<ID>, ID extends Identifier> implements Repository<T, ID> {
private final AggregateTracingManager<T, ID> aggregateTracingManager;
public RepositorySupport() {
this.aggregateTracingManager = new ThreadLocalTracingManager<>();
}
/**
* 由继承RepositorySupport的子类实现
*/
protected abstract T onSelect(ID id);
protected abstract void onInsert(T aggregate);
protected abstract void onUpdate(T aggregate, AggregateDifference<T, ID> aggregateDifference);
protected abstract void onDelete(T aggregate);
/**
* 主动追踪
* @param id
* @return
*/
public void attach(T aggregate) {
this.aggregateTracingManager.attach(aggregate);
}
/**
* 差异对比
* @param aggregate
* @return
* @throws IllegalAccessException
*/
protected AggregateDifference<T, ID> different(T aggregate) throws IllegalAccessException {
return this.aggregateTracingManager.different(aggregate);
}
/**
* 解除追踪
* @param id
* @return
*/
public void detach(T aggregate) {
this.aggregateTracingManager.detach(aggregate);
}
@Override
public T find(ID identifier) {
T aggregate = this.onSelect(identifier);
if (aggregate != null) {
this.aggregateTracingManager.attach(aggregate);
}
return aggregate;
}
@Override
public void save(T aggregate) throws IllegalAccessException {
AggregateDifference<T, ID> aggregateDifference = this.aggregateTracingManager.different(aggregate);
if (DifferenceTypeEnum.ADDED.equals(aggregateDifference.getDifferentType())) {
this.onInsert(aggregate);
} else {
this.onUpdate(aggregate, aggregateDifference);
}
this.aggregateTracingManager.merge(aggregate);
}
@Override
public void remove(T aggregate) {
this.onDelete(aggregate);
this.aggregateTracingManager.detach(aggregate);
}
}
我们依次对通用支撑类RepositorySupport中的成员变量和方法进行说明.
首先是RepositorySupport中唯一的成员变量AggregateTracingManager,该类的功能是完成 变更追踪快照的管理,包括对象追踪,差异对比和解除追踪等 .
接着是继承RepositorySupport的实现类需要重写的方法:
RepositorySupport#onSelect
,由RepositorySupport中实现的 Repository#find
调用,与直接实现 Repository#find
相同,通过DAO查询数据,并转换为实体对象; RepositorySupport#onInsert
,由RepositorySupport中实现的 Repository#save
调用,与直接实现 Repository#save
类似,通过DAO保存数据,此时为新增数据的保存; RepositorySupport#onUpdate
,由RepositorySupport中实现的 Repository#save
调用,与直接实现 Repository#save
类似,通过DAO保存数据,此时为修改数据的保存; RepositorySupport#onDelete
,由RepositorySupport中实现的 Repository#remove
调用,与直接实现 Repository#remove
相同,通过DAO删除数据。 接着是Repository中定义的提供变更追踪能力的方法:
RepositorySupport#attach
,主动追踪,当实体的Repository接口中自定义查询方法时,实现类可以通过该方法实现对象的变更追踪; RepositorySupport#different
,差异对比,当实体的Repository接口中自定义保存方法时,实现类可以通过该方法获取当前实体对象与快照的差异; RepositorySupport#detach
,解除追踪,当实体的Repository接口中自定义删除方法时,实现类可以通过该方法解除对象的变更追踪。 最后是RepositorySupport中对Repository接口的实现,实现中确定了 RepositorySupport#onSelect , RepositorySupport#onInsert , RepositorySupport#onUpdate 和 RepositorySupport#onDelete 方法的调用时机,并通过AggregateTracingManager来管理追踪对象:
RepositorySupport#find
的实现中,通过 RepositorySupport#onSelect
查询实体对象,并决定是否调用 AggregateTracingManager#attach
进行变更追踪; RepositorySupport#save
的实现中,调用 AggregateTracingManager#different
获取当前实体对象与快照间的差异,并根据差异的类型选择执行 RepositorySupport#onInsert
或 RepositorySupport#onUpdate
,最后调用 AggregateTracingManager#merge
将变更后的对象合并到变更追踪容器中; RepositorySupport#remove
的实现中,调用 RepositorySupport#onDelete
删除数据,并调用 AggregateTracingManager#detach
解除对象的追踪。 AggregateTracingManager提供了管理变更追踪的能力,接口设计如下:
public interface AggregateTracingManager<T extends Aggregate<ID>, ID extends Identifier> {
/**
* 变更追踪
* @param aggregate
*/
void attach(T aggregate);
/**
* 解除追踪
* @param aggregate
*/
void detach(T aggregate);
/**
* 对比差异
* @param aggregate
* @return
*/
AggregateDifference<T, ID> different(T aggregate) throws IllegalAccessException;
/**
* 合并变更
* @param aggregate
*/
void merge(T aggregate);
}
接着提供一个AggregateTracingManager的实现类,我们的工程中同样选择了ThreadLocal来实现线程隔离:
public class ThreadLocalTracingManager<T extends Aggregate<ID>, ID extends Identifier> implements AggregateTracingManager<T, ID> {
private final ThreadLocal<TraceContext<T, ID>> context;
public ThreadLocalTracingManager() {
this.context = ThreadLocal.withInitial(MapContext::new);
}
@Override
public void attach(T aggregate) {
this.context.get().tracing(aggregate.getId(), aggregate);
}
@Override
public void detach(T aggregate) {
this.context.get().remove(aggregate.getId());
}
@Override
public AggregateDifference<T, ID> different(T aggregate) throws IllegalAccessException {
T snapshot = this.context.get().find(aggregate.getId());
return DifferentUtils.different(snapshot, aggregate);
}
@Override
public void merge(T aggregate) {
attach(aggregate);
}
}
最后是定义变更追踪中用于存储快照的容器TraceContext接口:
public interface TraceContext<T extends Aggregate<ID>, ID extends Identifier> {
void add(ID id, T aggregate);
T find(ID id);
void remove(ID id);
}
TraceContext的功能比较简单,提供了3个方法:
void add(ID id, T aggregate)
,添加追踪对象; T find(ID id)
,获取追踪对象的快照; void remove(ID id)
,删除追踪对象。 这里我提供一个使用HashMap做存储容器的简单实现:
public class MapContext<T extends Aggregate<ID>, ID extends Identifier> implements TraceContext<T, ID> {
private final Map<ID, T> snapshots;
public MapContext() {
this.snapshots = new HashMap<>();
}
@Override
public void add(ID id, T aggregate) {
T snapshot = SnapshotUtils.snapshot(aggregate);
this.snapshots.put(aggregate.getId(), snapshot);
}
@Override
public T find(ID id) {
for (Map.Entry<ID, T> entry : this.snapshots.entrySet()) {
ID entryId = entry.getKey();
if (id.getClass().equals(entryId.getClass()) && entryId.value().equals(id.value())) {
return entry.getValue();
}
}
return snapshots.get(id);
}
@Override
public void remove(ID id) {
this.snapshots.remove(id);
}
}
至此,我们已经完成了变更追踪的整体框架。实际上我们在工程中实现的AggregateTracingManager和TraceContext会更加复杂,并添加了一些具有我司特色的功能,这里大家可以根据各自的情况做出不同的实现.
由于《 阿里技术专家详解DDD系列 第三讲 - Repository模式 》文中的重点是介绍变更追踪这项技术,因此忽略了几个较为关键的工具类的实现,导致很多人在落地这项技术上遇到了困境,这里我结合工程中的实践,结合我个人的思考,给大家提供一个设计思路.
SnapshotUtils用于实现Aggregate的拷贝,因为在 MapContext#find 方法的实现中是通过类型与值的对比来获取对象,因此我们在SnapshotUtils的实现中只需要实现深拷贝即可:
public class SnapshotUtils {
@SuppressWarnings("unchecked")
public static <T extends Aggregate<ID>, ID extends Identifier> T snapshot(T aggregate) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(aggregate);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
T snapshot = (T) objectInputStream.readObject();
objectOutputStream.close();
byteArrayOutputStream.close();
objectInputStream.close();
byteArrayInputStream.close();
return snapshot;
}
}
据我推测阿里巴巴大淘宝技术在文中使用的SnapshotUtils中除了Identifier外的其余字段是深拷贝,我们的实践中允许Identifier也进行深拷贝,所以可以通过序列化与反序列化的方式进行深拷贝.
除了序列化的方式外,还有很多其他的方式可以实现深拷贝,我见过使用JSON工具来回倒腾实现深拷贝,或者可以使用BeanUtil等等.
Tips :有些工具的使用是有前提的,比如需要Getter和Setter方法,又或者使用序列化的方式需要继承Serializable接口.
DiffUtils用于实现两个Java对象间的对比,因为此类需求较少所以市面上可供使用的开源工具并不是很多,相对来说Java Objec Diff是使用较为广泛的开源项目,不过该项目最新版本是2018年更新的0.95版本,作者应该是停止维护Java Object Diff了,或是由于该项目属于工具类项目,目前已经达到了较为完备的状态,不需要进行太多的维护工作了.
我们先来使用Java Objec Diff项目实现一个简单的Java对象对比工具,引入Java Objec Diff的依赖:
<dependency>
<groupId>de.danielbechler</groupId>
<artifactId>java-object-diff</artifactId>
<version>0.95</version>
</dependency>
基于Java Objec Diff项目构建DiffUtils,这里给出一个简单的实现:
public class DiffUtils {
public static EntityDiff diff(Object snapshot, Object obj) {
DiffNode diffNode = ObjectDifferBuilder.buildDefault().compare(obj, snapshot);
if (!diffNode.hasChanges()) {
return EntityDiff.EMPTY;
}
EntityDiff entityDiff = new EntityDiff();
entityDiff.setHasChanges(true);
diffNode.visit((node, visit) -> {
boolean hasChanges = node.hasChanges();
Object objValue = node.canonicalGet(obj);
Object snapshotValue = node.canonicalGet(snapshot);
// 处理其他的逻辑和构建EntityDiff对象
});
return entityDiff;
}
}
@Getter
@Setter
public class EntityDiff {
public static final EntityDiff EMPTY = new EntityDiff();
private boolean hasChanges;
// 省略其余属性的实现
public EntityDiff() {
}
}
EntityDiff的结构可以根据自身工程的需求进行定制化,我这里只是为了展示如何通过Java Objec Diff项目构建DiffUtils.
接下来就该我来献丑了.
因为我们有一些定制化的需求(具体原因已经记不得了),所以当时没有选择使用Java Objec Diff项目而是实现了具有我司特色的Java对象的对比工具类DifferentUtils.
首先是我们定义的4种差异状态:
public enum DifferenceType {
/**
* 新增
*/
ADDED(),
/**
* 删除
*/
REMOVED(),
/**
* 修改
*/
MODIFIED(),
/**
* 无变化
*/
UNTOUCHED()
}
接着我们对结果进行了封装,分为两层,第一层是标记Aggregate差异的AggregateDifference:
@Getter
@Setter
public class AggregateDifference<T extends Aggregate<ID>, ID extends Identifier> {
/**
* 快照对象
*/
private T snapshot;
/**
* 追踪对象
*/
private T aggregate;
/**
* 差异类型
*/
private DifferenceType differentType;
/**
* 字段差异
*/
private Map<String, FieldDifference> fieldDifferences;
}
第二层是比较Aggregate字段差异的FieldDifference:
@Getter
@Setter
public class FieldDifference {
/**
* 字段名
*/
private String name;
/**
* 字段类型
*/
private Type type;
/**
* 快照值
*/
private Object snapshotValue;
/**
* 当前值
*/
private Object tracValue;
/**
* 差异类型
*/
private DifferenceType differenceType;
}
以及3个实现类,标记Java中原生类型的JavaTypeFieldDifference,标记集合类型的CollectionFieldDifference,以及标记实现Aggregate接口的AggregareFieldDifference:
public class JavaTypeFieldDifference extends FieldDifference {
}
@Getter
@Setter
public class CollectionFieldDifference extends FieldDifference {
/**
* 集合元素差异
*/
private List<FieldDifference> elementDifference;
public CollectionFieldDifference(String name, Type type, Object snapshotValue, Object tracValue) {
super(name, type, snapshotValue, tracValue);
this.elementDifference = new ArrayList<>();
}
public CollectionFieldDifference(String name, Type type, Object snapshotValue, Object tracValue, DifferenceType differenceType) {
super(name, type, snapshotValue, tracValue, differenceType);
this.elementDifference = new ArrayList<>();
}
}
@Getter
@Setter
public class AggregareFieldDifference extends FieldDifference {
private Map<String, FieldDifference> fieldDifferences;
private final Identifier identifier;
public AggregareFieldDifference(String name, Type type, Object snapshotValue, Object tracValue, DifferenceType differenceType, Identifier identifier) {
super(name, type, snapshotValue, tracValue, differenceType);
this.identifier = identifier;
this.fieldDifferences = new HashMap<>();
}
}
可以看到,我们在工程实践中并不支持Map类型的字段进行对比,这是因为在我们落地的DDD工程规范中,实现Aggregate接口的类中不允许出现Map类型的字段,只允许Java的8种基础类型(包装类型),String,List,值对象以及实体.
准备工作完成后,我们开始实现DifferentUtils,首先定义方法声明,与上面的 DiffUtils#diff 存在一些差异,主要在泛型的使用上:
public class DifferentUtils {
public static <T extends Aggregate<ID>, ID extends Identifier> AggregateDifference<T, ID> different(T snapshot, T aggregate) throws IllegalAccessException {
// 待实现
}
}
接着我们处理两个入参可能为null的情况进行处理,总计有4种情况:
snapsho == null && aggregate == null
,此时认为是 DifferenceType.UNTOUCHED
; snapshot == null && aggregate != null
,此时认为是 DifferenceType.ADDED
; snapshot != null && aggregate == null
,此时认为是 DifferenceType.REMOVED
; snapshot != null && aggregate != null
,这种情况需要对比字段的差异。 此时我们可以得到用于入参为null时,返回DifferenceType的方法:
private static DifferenceType basicDifferentType(Object snapshot, Object aggregate) {
if (snapshot == null && aggregate == null) {
return DifferenceType.UNTOUCHED;
}
if (snapshot == null) {
return DifferenceType.ADDED;
}
if (aggregate == null) {
return DifferenceType.REMOVED;
}
return null;
}
我们直接在 DifferentUtils#different 中调用 DifferentUtils#basicDifferentType ,并补充snapshot和aggregate均不为null时的处理:
public static <T extends Aggregate<ID>, ID extends Identifier> AggregateDifference<T, ID> different(T snapshot, T aggregate) throws IllegalAccessException {
DifferenceType basicDifferenceType = basicDifferentType(snapshot, aggregate);
if (basicDifferenceType != null) {
return new AggregateDifference<>(snapshot, aggregate, basicDifferenceType);
}
Field[] fields = ReflectionUtils.getFields(aggregate);
// 标记Aggregate
DifferenceType aggregateDifferentType = aggregateDifferentType(fields, snapshot, aggregate);
// 构建AggregateDifference对象
AggregateDifference<T, ID> aggregateDifference = new AggregateDifference<>(snapshot, aggregate, aggregateDifferentType);
Map<String, FieldDifference> fieldDifferences = aggregateDifference.getFieldDifferences();
// 对比字段差异
setDifferences(snapshot, aggregate, fields, fieldDifferences);
return aggregateDifference
}
DifferentUtils#aggregateDifferentType 方法,该方法用于对Aggregate进行标记:
public static <T extends Aggregate<ID>, ID extends Identifier> DifferenceType aggregateDifferentType(Field[] fields, T snapshot, T aggregate) throws IllegalAccessException {
DifferenceType differenceType = basicDifferentType(snapshot, aggregate);
if (differenceType != null) {
return differenceType;
}
boolean unchanged = true;
for (Field field : fields) {
field.setAccessible(true);
// 处理需要跳过的情形
if (shouldSkipClass(field.getType())) {
continue;
}
if (Collection.class.isAssignableFrom(field.getType())) {
ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
Class<?> parameterizedClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
if (Aggregate.class.isAssignableFrom(parameterizedClass) || Map.class.isAssignableFrom(parameterizedClass)) {
continue;
}
}
// 对比字段差异
Object aggregateValue = field.get(aggregate);
Object snapshotValue = field.get(snapshot);
if (snapshotValue == null && aggregateValue == null) {
continue;
} else if (snapshotValue == null) {
unchanged = false;
continue;
}
unchanged = snapshotValue.equals(aggregateValue) & unchanged;
}
return unchanged ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED;
}
private static boolean shouldSkipClass(Class<?> clazz) {
return Identifier.class.isAssignableFrom(clazz) || Aggregate.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz);
}
因为该方法需要在其它位置复用,所以开始时先调用了 DifferentUtils#aggregateDifferentType 处理null的状态;接着是跳过需要特殊处理的类型,这些类型要么是单独处理,要么是不需要处理,以及当字段的类型为Collection时,某些泛型类型也不需要处理;最后是通过 Object#equals 方法进行对比,并返回相应的修改状态.
DifferentUtils#setDifferences 的实现,该方法遍历Aggregate的字段,并对比每个字段的差异:
private static <T extends Aggregate<ID>, ID extends Identifier> void setDifferences(T snapshot, T aggregate, Field[] fields, Map<String, FieldDifference> fieldDifferences) throws IllegalAccessException {
for (Field field : fields) {
if (Identifier.class.isAssignableFrom(field.getType())) {
continue;
}
String filedName = ReflectionUtils.getFieldName(field);
field.setAccessible(true);
Object snapshotValue = snapshot == null ? null : field.get(snapshot);
Object aggregateValue = aggregate == null ? null : field.get(aggregate);
if (snapshotValue == null && aggregateValue == null) {
continue;
}
// 对比每个字段的差异
FieldDifference fieldDifference = compareFiled(field, snapshotValue, aggregateValue);
fieldDifferences.put(filedName, fieldDifference);
}
}
DifferentUtils#compareFiled 的实现,该方法将字段进行分类对比:
@SuppressWarnings("unchecked")
private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareFiled(Field field, Object snapshotValue, Object aggregateValue) throws IllegalAccessException {
ComparableType comparableType = ComparableType.comparableType(aggregateValue == null ? snapshotValue : aggregateValue);
if (ComparableType.AGGREGATE_TYPE.equals(comparableType)) {
return compareAggregateType(field, (T) snapshotValue, (T) aggregateValue);
} else if (ComparableType.COLLECTION_TYPE.equals(comparableType)) {
return compareCollectionType(field, snapshotValue, aggregateValue);
} else if (ComparableType.JAVA_TYPE.equals(comparableType)) {
return compareJavaType(field, snapshotValue, aggregateValue);
} else {
throw new UnsupportedOperationException();
}
}
/**
* 可比较的字段类型
*/
enum ComparableType {
AGGREGATE_TYPE(),
COLLECTION_TYPE(),
JAVA_TYPE(),
OTHER_TYPE();
public static ComparableType comparableType(@NonNull Object obj) {
if (obj instanceof Aggregate) {
return AGGREGATE_TYPE;
} else if (obj instanceof Collection) {
return COLLECTION_TYPE;
} else if (obj instanceof Map) {
return OTHER_TYPE;
} else {
return JAVA_TYPE;
}
}
}
DifferentUtils#compareJavaType 的实现,该方法对比了Java类型字段的差异:
private static FieldDifference compareJavaType(Field field, Object snapshotValue, Object aggregateValue) {
String filedName = ReflectionUtils.getFieldName(field);
Type type = field.getGenericType();
DifferenceType differenceType = javaDifferentType(snapshotValue, aggregateValue);
return new JavaTypeFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType);
}
public static DifferenceType javaDifferentType(Object snapshot, Object aggregate) {
DifferenceType differenceType = basicDifferentType(snapshot, aggregate);
if (differenceType != null) {
return differenceType;
}
if (snapshot.equals(aggregate)) {
return DifferenceType.UNTOUCHED;
} else {
return DifferenceType.MODIFIED;
}
}
DifferentUtils#compareAggregateType 的实现,该方法对比实现Aggregate接口的类型的字段进行对比,通过递归不断向下深入直到类型为Java类型:
private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareAggregateType(Field field, T snapshotValue, T aggregateValue) throws IllegalAccessException {
String filedName = ReflectionUtils.getFieldName(field);
Type type = field.getGenericType();
Aggregate<?> notNullValue = snapshotValue == null ? aggregateValue : snapshotValue;
Field[] entityFields = ReflectionUtils.getFields(notNullValue);
Identifier id = notNullValue.getId();
DifferenceType differenceType = aggregateDifferentType(entityFields, snapshotValue, aggregateValue);
AggregareFieldDifference aggregareFieldDifference = new AggregareFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType, id);
Map<String, FieldDifference> fieldDifferences = aggregareFieldDifference.getFieldDifferences();
setDifferences(snapshotValue, aggregateValue, entityFields, fieldDifferences);
return aggregareFieldDifference;
}
DifferentUtils#compareCollectionType 的实现,该方法用于对比集合类型的 。
@SuppressWarnings("unchecked")
private static <T extends Aggregate<ID>, ID extends Identifier> FieldDifference compareCollectionType(Field field, Object snapshotValue, Object aggregateValue) throws IllegalAccessException {
String filedName = ReflectionUtils.getFieldName(field);
Type type = field.getGenericType();
ParameterizedType parameterizedType = (ParameterizedType) type;
Class<?> genericityClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
// 处理泛型为Java类型的集合
if (!Aggregate.class.isAssignableFrom(genericityClass) && !Map.class.isAssignableFrom(genericityClass)) {
Collection<?> snapshotValues = (Collection<?>) snapshotValue;
Collection<?> aggregateValues = (Collection<?>) aggregateValue;
DifferenceType differenceType = collectionDifferentType(genericityClass, snapshotValues, aggregateValues);
return new CollectionFieldDifference(filedName, type, snapshotValue, aggregateValue, differenceType);
}
// 处理泛型为实现Aggreagte接口的类型的集合
Collection<T> snapshotValues = (Collection<T>) snapshotValue;
Collection<T> aggregateValues = (Collection<T>) aggregateValue;
Map<Serializable, T> snapshotMap = snapshotValues.stream().collect(Collectors.toMap(snapshot -> snapshot.getId().value(), snapshot -> snapshot));
Map<Serializable, T> aggregateMap = aggregateValues.stream().collect(Collectors.toMap(aggregate -> aggregate.getId().value(), aggregate -> aggregate));
CollectionFieldDifference collectionFieldDifference = new CollectionFieldDifference(filedName, type, snapshotValue, aggregateValue);
boolean unchanged = true;
// snapshotMap与aggregateMap的交集,snapshotMap对aggregateMap的补集
for (Serializable key : snapshotMap.keySet()) {
T snapshotElement = snapshotMap.get(key);
T aggregateElement = aggregateMap.get(key);
FieldDifference fieldDifferent = compareFiled(field, snapshotElement, aggregateElement);
unchanged = DifferenceType.UNTOUCHED.equals(fieldDifferent.getDifferenceType()) & unchanged;
collectionFieldDifference.getElementDifference().add(fieldDifferent);
}
// aggregateMap对snapshotMap的补集
for (Serializable key : aggregateMap.keySet()) {
if (snapshotMap.get(key) != null) {
continue;
}
T aggregateElement = aggregateMap.get(key);
FieldDifference fieldDifferent = compareFiled(field, null, aggregateElement);
unchanged = DifferenceType.UNTOUCHED.equals(fieldDifferent.getDifferenceType()) & unchanged;
collectionFieldDifference.getElementDifference().add(fieldDifferent);
}
if (unchanged) {
collectionFieldDifference.setDifferenceType(DifferenceType.UNTOUCHED);
} else {
collectionFieldDifference.setDifferenceType(DifferenceType.MODIFIED);
}
return collectionFieldDifference;
}
public static DifferenceType collectionDifferentType(Class<?> typeArguments, Collection<?> snapshot, Collection<?> aggregate) {
if (CollectionUtils.isEmpty(snapshot) && CollectionUtils.isEmpty(aggregate)) {
return DifferenceType.UNTOUCHED;
}
if (CollectionUtils.isEmpty(snapshot)) {
return DifferenceType.ADDED;
}
if (CollectionUtils.isEmpty(aggregate)) {
return DifferenceType.REMOVED;
}
if (specialHandingClass(typeArguments)) {
return snapshot.size() == aggregate.size() ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED;
}
return snapshot.equals(aggregate) ? DifferenceType.UNTOUCHED : DifferenceType.MODIFIED;
}
private static boolean specialHandingClass(Class<?> clazz) {
return shouldSkipClass(clazz) || Collection.class.isAssignableFrom(clazz);
}
我们将Collection类型的字段分为两类,泛型为Java类型的和泛型为实现Aggregate接口的。当集合的泛型为Java类型时,只需要使用 Object#equals 方法进行对比即可;当集合的泛型为Collection或Aggregate时(集合的泛型不应该出现Map或Identifier),先对数量进行对比,标记整体的变化,接着来对比每个Aggregate的差异,并进行标记.
我的想法是,先将 List<T> 转换为 Map<Serializable, T> ,Map的key存储Id,value存储对象本身,这样可以得到两个Map:
Map<Serializable, T> snapshotMap
Map<Serializable, T> aggregateMap
先遍历snapshotMap,取出aggregateMap中Id与之对应的对象进行比较,并一一标记,这里处理的是snapshotMap与aggregateMap的交集,以及snapshotMap对aggregateMap的补集(即snapshotMap中有而aggregateMap中无的),实际上,我们这里处理的是snapshotMap的全集;再遍历aggregateMap,跳过snapshotMap中Id与之对应的对象,这里我们处理的是aggregateMap对snapshotMap的补集(即aggregateMap中有而snapshotMap中无的);这样,我们就处理完了两个集合中的元素,最后再根据每个元素对比的结果标记集合的差异类型即可.
好了,以上就是具有我司特色的DifferentUtils工具类的实现,因为没有研究过Java Object Diff的源码,因此不太清楚自己与大佬的差距究竟有多远,欢迎大家提出自己的想法一起讨论.
Tips :鉴于保密的原因,DifferentUtils及相关类都经过不同程度的修改,且修改后的实现并没有经过严格的评审和测试,可能会出现各种各样的BUG~~ 。
变更追踪的实现中还有一个反射相关的工具类ReflectionUtils,该工具类的实现可大可小,往小了可以像我下面实现的这样:
public class ReflectionUtils {
public static Field[] getFields(Object obj) {
return obj.getClass().getDeclaredFields();
}
public static String getFieldName(Field field) {
return field.getName();
}
}
往大了可以加入缓存等优化措施,例如 ReflectionUtils#getFields 加入缓存 Map<Class<?>, Field[]> fieldMap ,将首次获取到的结果添加到缓存中,以此来提高反射工具的性能.
好了,到这里我们就一起实现了基于快照机制的变更追踪,文章中的代码还比较潦草,像是毛坯房,目的是和大家分享实现过程和设计,如果要真正的在生产环境中落地,还需要做“精装修”,这里举几个我们的“精装修”例子:
AggregateTracingManager#detach
; 好了,今天就到这里了,Bye~~ 。
如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。 最后欢迎大家关注分享硬核Java技术的金融摸鱼侠 王有志 ,我们下次再见! 。
最后此篇关于DDD实践:实现基于快照机制的变更追踪的文章就讲到这里了,如果你想了解更多关于DDD实践:实现基于快照机制的变更追踪的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
背景: 我最近一直在使用 JPA,我为相当大的关系数据库项目生成持久层的轻松程度给我留下了深刻的印象。 我们公司使用大量非 SQL 数据库,特别是面向列的数据库。我对可能对这些数据库使用 JPA 有一
我已经在我的 maven pom 中添加了这些构建配置,因为我希望将 Apache Solr 依赖项与 Jar 捆绑在一起。否则我得到了 SolarServerException: ClassNotF
interface ITurtle { void Fight(); void EatPizza(); } interface ILeonardo : ITurtle {
我希望可用于 Java 的对象/关系映射 (ORM) 工具之一能够满足这些要求: 使用 JPA 或 native SQL 查询获取大量行并将其作为实体对象返回。 允许在行(实体)中进行迭代,并在对当前
好像没有,因为我有实现From for 的代码, 我可以转换 A到 B与 .into() , 但同样的事情不适用于 Vec .into()一个Vec . 要么我搞砸了阻止实现派生的事情,要么这不应该发
在 C# 中,如果 A 实现 IX 并且 B 继承自 A ,是否必然遵循 B 实现 IX?如果是,是因为 LSP 吗?之间有什么区别吗: 1. Interface IX; Class A : IX;
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在阅读标准haskell库的(^)的实现代码: (^) :: (Num a, Integral b) => a -> b -> a x0 ^ y0 | y0 a -> b ->a expo x0
我将把国际象棋游戏表示为 C++ 结构。我认为,最好的选择是树结构(因为在每个深度我们都有几个可能的移动)。 这是一个好的方法吗? struct TreeElement{ SomeMoveType
我正在为用户名数据库实现字符串匹配算法。我的方法采用现有的用户名数据库和用户想要的新用户名,然后检查用户名是否已被占用。如果采用该方法,则该方法应该返回带有数据库中未采用的数字的用户名。 例子: “贾
我正在尝试实现 Breadth-first search algorithm , 为了找到两个顶点之间的最短距离。我开发了一个 Queue 对象来保存和检索对象,并且我有一个二维数组来保存两个给定顶点
我目前正在 ika 中开发我的 Python 游戏,它使用 python 2.5 我决定为 AI 使用 A* 寻路。然而,我发现它对我的需要来说太慢了(3-4 个敌人可能会落后于游戏,但我想供应 4-
我正在寻找 Kademlia 的开源实现C/C++ 中的分布式哈希表。它必须是轻量级和跨平台的(win/linux/mac)。 它必须能够将信息发布到 DHT 并检索它。 最佳答案 OpenDHT是
我在一本书中读到这一行:-“当我们要求 C++ 实现运行程序时,它会通过调用此函数来实现。” 而且我想知道“C++ 实现”是什么意思或具体是什么。帮忙!? 最佳答案 “C++ 实现”是指编译器加上链接
我正在尝试使用分支定界的 C++ 实现这个背包问题。此网站上有一个 Java 版本:Implementing branch and bound for knapsack 我试图让我的 C++ 版本打印
在很多情况下,我需要在 C# 中访问合适的哈希算法,从重写 GetHashCode 到对数据执行快速比较/查找。 我发现 FNV 哈希是一种非常简单/好/快速的哈希算法。但是,我从未见过 C# 实现的
目录 LRU缓存替换策略 核心思想 不适用场景 算法基本实现 算法优化
1. 绪论 在前面文章中提到 空间直角坐标系相互转换 ,测绘坐标转换时,一般涉及到的情况是:两个直角坐标系的小角度转换。这个就是我们经常在测绘数据处理中,WGS-84坐标系、54北京坐标系
在软件开发过程中,有时候我们需要定时地检查数据库中的数据,并在发现新增数据时触发一个动作。为了实现这个需求,我们在 .Net 7 下进行一次简单的演示. PeriodicTimer .
二分查找 二分查找算法,说白了就是在有序的数组里面给予一个存在数组里面的值key,然后将其先和数组中间的比较,如果key大于中间值,进行下一次mid后面的比较,直到找到相等的,就可以得到它的位置。
我是一名优秀的程序员,十分优秀!