I have below entity with custom AttributeConverter
which saves field in the DB as binary data.
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);
}
}
}
So after TaskEntity is selected with SpringData nativeQuery within method with @Transactional
annotation UPDATE queries are fired for all retrieved entities.
After investigation I suppose it is happened because of dirty checking of hibernate, as context field is converted from byte[] and for some reasons it is considered dirty by hibernate.
The interesting thing is that making @Transactional(readOnly=true)
does not help as Postgres is throwing exception "Could not UPDATE in readOnly transaction" but if I remove @Transactional
annotation completely everything works fine and UPDATE queries are not fired after select.
What is the best solution to fix this issue? Maybe it is possible to disable dirty checking for readOnly transactions? Is it possible to rewrite hibernate dirty check for one field? I found that it is possible to overwrite dirty checking completely but I would prefer not to do this.
I faced the same issue when I was using a convertor to convert my JSON field from DB to my custom class.
The Dirty checking policy of Hibernate calls the .equals
method on the entity from the Persistent Context (which is saved as soon as you fetch an object from DB) and your current Entity.
So overriding the .equals method of StateMachineContext
should do it for you. It actually worked for me.
For reference : https://medium.com/@paul.klingelhuber/hibernate-dirty-checking-with-converted-attributes-1b6d1cd27f68