Javers ENTITY_INSTANCE_WITH_NULL_ID 使用 2 个数据库时
Javers ENTITY_INSTANCE_WITH_NULL_ID when using 2 databases
当我将数据存储在 Postgres 中(使用 JPA 延迟加载)并将 javers 存储在 MongoDB
中时,我遇到了这个异常 "ENTITY_INSTANCE_WITH_NULL_ID"
Spring 启动:1.4.0.RELEASE
Sprig 数据 JPA:1.4.0.RELEASE
贾弗斯:2.3.0
我调试发现,如果对象是惰性对象,ID 为空:
org.javers.core.metamodel.type.EntityType:88 "Object cdoId = getIdProperty().get(instance);"
当您将一个对象提交给 JaVers 时,它的先前版本将从 JaversRepository 加载并与当前版本(您刚刚传递给 commit() 方法的那个版本)进行比较。
在这种情况下,JaVers 使用 GlobalId
查询找到以前的版本,因此 TypeName + 实体 ID。
这就是实体的 ID 不能为 null 的原因。
有两种可能:
- 如果 null ID 在此 class 中是正常的(根据您的域模型),您应该将其映射为 JaVers 中的 ValueObject。
如果您使用的是 Hibernate,则延迟加载代理存在常见问题。对于某些查询,Hibernate 不会 return 您的真实域对象,而是本质上为空的动态代理对象(因此 ID 为空)。这种技术可能看起来很聪明,但在 Hibernate 初始化它们之前会使您的对象成为垃圾。
JaVers 提供 HibernateUnproxyObjectAccessHook 进行清理:域对象的初始化和取消代理。
JaversBuilder.javers().withObjectAccessHook(
new HibernateUnproxyObjectAccessHook()).build()
此挂钩在 javers-spring-boot-starter-sql
中默认启用,但在 javers-spring-boot-starter-mongo
中未启用。如果您正在使用 Mongo starter,请自行创建一个 JaVers bean,并启用挂钩,请参阅 JaversMongoAutoConfiguration。
我通过制作自己的访问挂钩解决了这个问题,并使用
添加到 Javers
.withObjectAccessHook(new EntityAccessHook()).build()
public class EntityAccessHook<T> extends HibernateUnproxyObjectAccessHook<T> {
@Override
public Optional<ObjectAccessProxy<T>> createAccessor(T entity) {
Optional<ObjectAccessProxy<T>> accessProxy = super.createAccessor(entity);
if (accessProxy.isEmpty() && entity instanceof AbstractUuidEntity) {
return fromEntityInitializer((AbstractUuidEntity) entity);
}
return accessProxy;
}
private Optional<ObjectAccessProxy<T>> fromEntityInitializer(
AbstractUuidEntity abstractUuidEntity) {
return Optional.of(
new ObjectAccessProxy(
() -> abstractUuidEntity,
abstractUuidEntity.getClass(),
abstractUuidEntity.getId() == null ? UUID.randomUUID() : abstractUuidEntity.getId()));
}
}
当我将数据存储在 Postgres 中(使用 JPA 延迟加载)并将 javers 存储在 MongoDB
中时,我遇到了这个异常 "ENTITY_INSTANCE_WITH_NULL_ID"Spring 启动:1.4.0.RELEASE
Sprig 数据 JPA:1.4.0.RELEASE
贾弗斯:2.3.0
我调试发现,如果对象是惰性对象,ID 为空: org.javers.core.metamodel.type.EntityType:88 "Object cdoId = getIdProperty().get(instance);"
当您将一个对象提交给 JaVers 时,它的先前版本将从 JaversRepository 加载并与当前版本(您刚刚传递给 commit() 方法的那个版本)进行比较。
在这种情况下,JaVers 使用 GlobalId
查询找到以前的版本,因此 TypeName + 实体 ID。
这就是实体的 ID 不能为 null 的原因。
有两种可能:
- 如果 null ID 在此 class 中是正常的(根据您的域模型),您应该将其映射为 JaVers 中的 ValueObject。
如果您使用的是 Hibernate,则延迟加载代理存在常见问题。对于某些查询,Hibernate 不会 return 您的真实域对象,而是本质上为空的动态代理对象(因此 ID 为空)。这种技术可能看起来很聪明,但在 Hibernate 初始化它们之前会使您的对象成为垃圾。 JaVers 提供 HibernateUnproxyObjectAccessHook 进行清理:域对象的初始化和取消代理。
JaversBuilder.javers().withObjectAccessHook( new HibernateUnproxyObjectAccessHook()).build()
此挂钩在 javers-spring-boot-starter-sql
中默认启用,但在 javers-spring-boot-starter-mongo
中未启用。如果您正在使用 Mongo starter,请自行创建一个 JaVers bean,并启用挂钩,请参阅 JaversMongoAutoConfiguration。
我通过制作自己的访问挂钩解决了这个问题,并使用
添加到 Javers.withObjectAccessHook(new EntityAccessHook()).build()
public class EntityAccessHook<T> extends HibernateUnproxyObjectAccessHook<T> {
@Override
public Optional<ObjectAccessProxy<T>> createAccessor(T entity) {
Optional<ObjectAccessProxy<T>> accessProxy = super.createAccessor(entity);
if (accessProxy.isEmpty() && entity instanceof AbstractUuidEntity) {
return fromEntityInitializer((AbstractUuidEntity) entity);
}
return accessProxy;
}
private Optional<ObjectAccessProxy<T>> fromEntityInitializer(
AbstractUuidEntity abstractUuidEntity) {
return Optional.of(
new ObjectAccessProxy(
() -> abstractUuidEntity,
abstractUuidEntity.getClass(),
abstractUuidEntity.getId() == null ? UUID.randomUUID() : abstractUuidEntity.getId()));
}
}