Hibernate/SpringData:使用 AttributeConverter 对字段进行不正确的脏检查
Hibernate/SpringData : Incorrect dirty check on field with AttributeConverter
我有以下带有自定义 AttributeConverter
的实体,它将数据库中的字段保存为二进制数据。
TaskEntity.java
@Entity
@Table(name = "task")
public class TaskEntity {
@Id
@GeneratedValue
@Column(name = "id", nullable = false)
private UUID id;
@Column(name = "state_machine_context")
@Convert(converter = StateMachineContextConverter.class)
private StateMachineContext<State, Event> stateMachineContext;
}
StateMachineContextConverter.java
@Converter
public class StateMachineContextConverter
implements AttributeConverter<StateMachineContext, byte[]> {
private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer());
kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer());
kryo.addDefaultSerializer(UUID.class, new UUIDSerializer());
return kryo;
});
private static final int BUFFER_SIZE = 4096;
private static final int MAX_BUFFERED_SIZE = 10240;
@Override
public byte[] convertToDatabaseColumn(final StateMachineContext attribute) {
return serialize(attribute);
}
@Override
public StateMachineContext convertToEntityAttribute(final byte[] dbData) {
return deserialize(dbData);
}
private byte[] serialize(final StateMachineContext context) {
if (context == null) {
return null;
}
try (Output output = new Output(BUFFER_SIZE, MAX_BUFFERED_SIZE)) {
final Kryo kryo = kryoThreadLocal.get();
kryo.writeObject(output, context);
return output.toBytes();
}
}
private StateMachineContext deserialize(final byte[] data) {
if (data == null || data.length == 0) {
return null;
}
final Kryo kryo = kryoThreadLocal.get();
try (Input input = new Input(data)) {
return kryo.readObject(input, StateMachineContext.class);
}
}
}
因此,在 TaskEntity select在带有 @Transactional
注释的方法中使用 SpringData nativeQuery 编辑 UPDATE 查询后,所有检索到的实体都会被触发。
经过调查,我认为这是由于 hibernate 的脏检查而发生的,因为上下文字段是从 byte[] 转换而来的,并且由于某些原因它被 hibernate 认为是脏的。
有趣的是,如果我完全删除 @Transactional
注释,使 @Transactional(readOnly=true)
没有帮助,因为 Postgres 抛出异常 "Could not UPDATE in readOnly transaction" 但是 一切正常,并且在 select 之后不会触发 UPDATE 查询。
解决此问题的最佳解决方案是什么?也许可以禁用只读事务的脏检查?是否可以为一个字段重写休眠脏检查? I found that it is possible to overwrite dirty checking completely 但我不想这样做。
我在使用转换器将 JSON 字段从数据库转换为自定义 class 时遇到了同样的问题。
Hibernate 的脏检查策略从持久性上下文(从数据库中获取对象后立即保存)和当前实体调用实体上的 .equals
方法。
所以重写 StateMachineContext
的 .equals 方法应该为您完成。它实际上对我有用。
我有以下带有自定义 AttributeConverter
的实体,它将数据库中的字段保存为二进制数据。
TaskEntity.java
@Entity
@Table(name = "task")
public class TaskEntity {
@Id
@GeneratedValue
@Column(name = "id", nullable = false)
private UUID id;
@Column(name = "state_machine_context")
@Convert(converter = StateMachineContextConverter.class)
private StateMachineContext<State, Event> stateMachineContext;
}
StateMachineContextConverter.java
@Converter
public class StateMachineContextConverter
implements AttributeConverter<StateMachineContext, byte[]> {
private static final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
Kryo kryo = new Kryo();
kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer());
kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer());
kryo.addDefaultSerializer(UUID.class, new UUIDSerializer());
return kryo;
});
private static final int BUFFER_SIZE = 4096;
private static final int MAX_BUFFERED_SIZE = 10240;
@Override
public byte[] convertToDatabaseColumn(final StateMachineContext attribute) {
return serialize(attribute);
}
@Override
public StateMachineContext convertToEntityAttribute(final byte[] dbData) {
return deserialize(dbData);
}
private byte[] serialize(final StateMachineContext context) {
if (context == null) {
return null;
}
try (Output output = new Output(BUFFER_SIZE, MAX_BUFFERED_SIZE)) {
final Kryo kryo = kryoThreadLocal.get();
kryo.writeObject(output, context);
return output.toBytes();
}
}
private StateMachineContext deserialize(final byte[] data) {
if (data == null || data.length == 0) {
return null;
}
final Kryo kryo = kryoThreadLocal.get();
try (Input input = new Input(data)) {
return kryo.readObject(input, StateMachineContext.class);
}
}
}
因此,在 TaskEntity select在带有 @Transactional
注释的方法中使用 SpringData nativeQuery 编辑 UPDATE 查询后,所有检索到的实体都会被触发。
经过调查,我认为这是由于 hibernate 的脏检查而发生的,因为上下文字段是从 byte[] 转换而来的,并且由于某些原因它被 hibernate 认为是脏的。
有趣的是,如果我完全删除 @Transactional
注释,使 @Transactional(readOnly=true)
没有帮助,因为 Postgres 抛出异常 "Could not UPDATE in readOnly transaction" 但是 一切正常,并且在 select 之后不会触发 UPDATE 查询。
解决此问题的最佳解决方案是什么?也许可以禁用只读事务的脏检查?是否可以为一个字段重写休眠脏检查? I found that it is possible to overwrite dirty checking completely 但我不想这样做。
我在使用转换器将 JSON 字段从数据库转换为自定义 class 时遇到了同样的问题。
Hibernate 的脏检查策略从持久性上下文(从数据库中获取对象后立即保存)和当前实体调用实体上的 .equals
方法。
所以重写 StateMachineContext
的 .equals 方法应该为您完成。它实际上对我有用。