Hibernate / SpringData:使用AttributeConverter对字段进行错误的脏检查

时间:2017-11-14 12:17:11

标签: java hibernate spring-data spring-transactions

我在自定义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);
       }
   }
}

因此,在使用带有@Transactional注释 UPDATE 的方法中的SpringData nativeQuery选择TaskEntity之后,将为所有检索到的实体触发查询。

经过调查我认为它是因为hibernate的脏检查而发生的,因为上下文字段是从byte []转换的,并且由于某些原因它被hibernate认为是脏的。

有趣的是,使@Transactional(readOnly=true)无效,因为Postgres抛出异常“无法在readOnly事务中更新”如果我删除@Transactional注释完全一切正常select后不会触发fine和UPDATE查询。

解决此问题的最佳解决方案是什么?也许有可能禁用readOnly事务的脏检查?是否可以为一个字段重写hibernate脏检查? I found that it is possible to overwrite dirty checking completely但我不想这样做。

1 个答案:

答案 0 :(得分:5)

当我使用转换器将我的JSON字段从DB转换为自定义类时,我遇到了同样的问题。 Hibernate的脏检查策略从持久上下文(从DB中获取对象后立即保存)和当前实体调用实体上的.equals方法。

所以覆盖StateMachineContext的.equals方法应该为你做。它实际上对我有用。

供参考:https://medium.com/@paul.klingelhuber/hibernate-dirty-checking-with-converted-attributes-1b6d1cd27f68