文章目录
背景
在一些应用程序开发中,我们往往只能获取到当前对象的值,而无法获取到之前对象的值,以及当前对象的值那些值是修改的。如果我们知道了,我们就可以对这些修改操作作统一的封装,而不是一个对象往往要通过自己的应用程序去判断,然后调用不同的add、update、delete方法。这种做法就好比hibernate,我们在操作一个对象的时候,无需关注对象本身,我们只需要将修改后的对象丢到一个封装好的方法中,由框架自行给我们判断该对象是新增还是修改删除等。我们在领域驱动设计中,新增了聚合根这一角色,我们也希望借鉴hibernate类似的方式,将聚合根的所有crud封装起来,让我们的crud都无需关注过多的细节,更多的关注我们自己的业务操作。为了实现这种效果,我们引入了JAVERS
这个框架,所以接下来我们先来一起学习一下JAVERS
这个框架
说明
JAVERS
主要用于两个对象的比对,简化ORM操作
官网
Github
构造diff
如果我们要比较两个对象,我们需要先构建一个比较器出来,即diff
构造方式如下:
Javers javers = JaversBuilder.javers()
.withListCompareAlgorithm(LEVENSHTEIN_DISTANCE)
.build();
这里需要注意的是 List comparing algorithms这个参数,即比较算法,官方推荐使用的是LEVENSHTEIN_DISTANCE
.通过看源码我们可以看到有如下三种比较器
- Levenshtein: 是最智能的比较算法,一般推荐使用。但是需要注意的是如果比较元素超过300个以上效率就会变得很慢
- Simple:主要优点是速度快,计算复杂度线性,主要缺点是输出过于冗长
- Set: 如果您不关心集合的顺序,请选择 Set 算法。在进行比较之前,JaVers 会将所有 list 转换为 set。此算法产生最简洁的输出(只有 ValueAdded 和 ValueRemoved)
这里虽然有三种,但是我们最为常用的还是
Levenshtein
测试
这里我们用一个例子测试看看
- Student.class
@Data
@ToString
public class Student {
@Id
private int id;
private String name;
private int age;
List<Teacher> teachers;
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
这里需要注意的是
Studetn.class
中有一个@Id
注解,如果没有id,就没法区分对象比对的时候是否为新增对象了,只能比对出是修改和删除,即使我们想要比较出的是新增,实际也比较不出。所以这里需要注意,所以如果我们在新增对象比较的时候,如果没有id,框架又会报错,所以新增的对象我们可以给他一个随机的负数id
- Teacher.class
@Data
public class Teacher {
private String name;
private String age;
}
测试修改
@Test
public void changeTest() {
Javers javers = JaversBuilder.javers().withListCompareAlgorithm(LEVENSHTEIN_DISTANCE).build();
Student old = new Student(1, "韩信", 22);
Teacher teacher = new Teacher();
teacher.setName("韩信的老师");
teacher.setAge("33");
old.setTeachers(Lists.newArrayList(teacher));
Student newObj = new Student(1, "阿离", 32);
Teacher teacher1 = new Teacher();
teacher1.setName("阿离的老师");
teacher1.setAge("44");
newObj.setTeachers(Lists.newArrayList(teacher1));
Diff diff = javers.compare(old, newObj);
System.out.println(diff.prettyPrint());
Changes changes = diff.getChanges();
for (Change change : changes) {
if ((change instanceof NewObject)) {
System.out.println("新增改动: " + change);
// change.getAffectedObject().ifPresent(System.out::println);
System.out.println("新增");
}
if ((change instanceof ObjectRemoved)) {
System.out.println("删除改动: " + change);
// change.getAffectedObject().ifPresent(System.out::println);
}
if ((change instanceof ValueChange)) {
System.out.println("修改改动: " + change);
// change.getAffectedObject().ifPresent(System.out::println);
}
}
可以看到我们拿到了所有的变化值
这里我们改变的对象一边有如下三种
NewObject
新增ObjectRemoved
删除PropertyChange
修改
其中PropertyChange
又细分为:
ContainerChange
集合修改MapChange
map修改ReferenceChange
实体引用修改ValueChange
普通值的修改
测试新增
我们将上面的代码作一行改动
Diff diff = javers.compare(old, null);
运行结果:
集合比较
对于集合的比对,我们一般使用
javers.compareCollections()
@Test
public void compareCollections() {
Javers javers = JaversBuilder.javers().withListCompareAlgorithm(LEVENSHTEIN_DISTANCE).build();
Student old = new Student(1, "韩信", 22);
Teacher teacher = new Teacher();
teacher.setName("韩信的老师");
teacher.setAge("33");
old.setTeachers(Lists.newArrayList(teacher));
Student newObj = new Student(1, "阿离", 32);
Teacher teacher1 = new Teacher();
teacher1.setName("阿离的老师");
teacher1.setAge("44");
newObj.setTeachers(Lists.newArrayList(teacher1));
Student newObj2 = new Student(2, "奏", 18);
Teacher teacher2 = new Teacher();
teacher2.setName("奏的老师");
teacher2.setAge("22");
newObj2.setTeachers(Lists.newArrayList(teacher2));
Diff diff = javers.compareCollections(Lists.newArrayList(old),
Lists.newArrayList(newObj, newObj2), Student.class);
System.out.println(diff.prettyPrint());
Changes changes = diff.getChanges();
for (Change change : changes) {
if ((change instanceof NewObject)) {
System.out.println("新增改动: " + change);
// change.getAffectedObject().ifPresent(System.out::println);
}
if ((change instanceof ObjectRemoved)) {
System.out.println("删除改动: " + change);
// change.getAffectedObject().ifPresent(System.out::println);
}
if ((change instanceof ValueChange)) {
System.out.println("修改改动: " + change);
// change.getAffectedObject().ifPresent(System.out::println);
}
}
}
有了上面的这些方法,我们就可以基于上面的改动对我们聚合根CRUD的一些封装。
封装CRUD
public <T> void objectChangeFunction(Consumer<T> addConsume,
Consumer<T> updateConsume,
Consumer<T> deleteConsume,
Class<T> clazz) {
Changes changes = this.diff.getChanges();
for (Change change : changes) {
if ((change instanceof NewObject && Objects.nonNull(addConsume))) {
change.getAffectedObject().ifPresent(item -> addConsume.accept((T) item));
return;
}
if ((change instanceof ObjectRemoved && Objects.nonNull(deleteConsume))) {
change.getAffectedObject().ifPresent(item -> deleteConsume.accept((T) item));
return;
}
if ((change instanceof ValueChange && Objects.nonNull(updateConsume))) {
change.getAffectedObject().ifPresent(item -> updateConsume.accept((T) item));
return;
}
}
}
使用也非常简单
Diff plutusDiff = javers.compare(old, new);
objectChangeFunction(add -> qualityCheckDAO.saveStudent(student),
update -> qualityCheckDAO.updateStudent(student),
null, Student.class);
这里我们主要是介绍
JAVERS
的基本使用,所以对封装成为工具类这一块就不作过多说明,自己可以基于JAVERS
去为自己项目作一些工具类的封装
自定义比较器
有时候我们需要自定义一些字段类型的比较器,JAVERS
也提供了扩展接口,只需要CustomValueComparator
接口即可
这里我们自定义一个BigDecimal
比较器,来控制比较的小数位数
public class CustomBigDecimalComparator implements CustomValueComparator<BigDecimal> {
private int significantDecimalPlaces;
public CustomBigDecimalComparator(int significantDecimalPlaces) {
this.significantDecimalPlaces = significantDecimalPlaces;
}
@Override
public boolean equals(BigDecimal a, BigDecimal b) {
return round(a).equals(round(b));
}
@Override
public String toString(BigDecimal value) {
return round(value).toString();
}
private BigDecimal round(BigDecimal val) {
return val.setScale(significantDecimalPlaces, RoundingMode.HALF_DOWN);
}
}
使用
@Test
public void test() {
Javers javers = JaversBuilder.javers()
.registerValue(BigDecimal.class,
new CustomBigDecimalComparator(2)).build();
int size = javers.compare(new ValueObject(new BigDecimal("1.123")),
new ValueObject(new BigDecimal("1.124"))).getChanges().size();
System.out.println(size == 0);
}
static class ValueObject {
BigDecimal value;
List<BigDecimal> values;
public ValueObject(BigDecimal value) {
this.value = value;
}
}
这里的CustomValueComparator的设计有点类似于Mybatis的类型转换Handler。
javaer
还可以自定义属性比较器,实现接口CustomPropertyComparator
即可,这里感兴趣自己去了解,就不做过多讲解
注解
JAVERS
提供的一些注解总共有如下几种
类级别
@Entity
将给定的类(及其所有子类)声明为 Entity 类型
@ValueObject
将给定的类(及其所有子类)声明为 Value Object 类型。
@Value
@DiffIgnore
@ShallowReference
@IgnoreDeclaredProperties
@TypeName
属性
@Id
声明一个实体的 id 属性,没有id无法比对出是否为新增对象
@DiffIgnore
比对时可以忽略的属性
@DiffInclude
@ShallowReference
声明一个属性为浅引用。只能用于实体类型属性。目标 Entity 实例的所有属性(Id 除外)都被忽略。
@PropertyName
官网提供了这么多注解,实际我也没用过这么多,用的比较多的还是
@Id
和`@DiffIgnore,限于篇幅,就不作过多解释,在实际中需要使用时可参考官方文档