由于JPA AttributeConverter而导致数据丢失?

时间:2016-01-08 11:38:37

标签: java hibernate jpa

我使用JPA将数据保存到数据库中。详细地说,我使用Hibernate 4.3.5作为JPA实现。 由于性能和保持表结构简单,我不能直接从对象到表格1:1映射,但我只保留一些数据列表作为对象但不创建实体类。而不是这个,我将对象结构序列化为JSON到DB。这个序列化/反序列化是用@Converter完成的,效果很好。

简化代码:

@Entity
public class EntitySample {
  ...
  @Convert(converter=ConverterSample.class)
  private List<SampleObject> sampleList=new ArrayList<>();

  private String name;

  public List<SampleObject> getSampleList() {
    return sampleList;
  }

  public void setName(String newName) {
    name=newName;
  }
  ...
}

@Converter
public class ConverterSample implements AttributeConverter<List,String> {

  @Override
  public String convertToDatabaseColumn(List data) {
    return serializeToJSON(data);
  }

  @Override
  public List convertToEntityAttribute(String data) {
    return deserializeFromJSON(data);
  }

  ...
}

据说,它主要是工作! 我在单元测试中发现了以下问题:

// create a new entity object with list A, B, C:
EntitySample entity=new EntitySample();
entity.getSampleList().add(new SampleObject("A"));
entity.getSampleList().add(new SampleObject("B"));
entity.getSampleList().add(new SampleObject("C"));
entity.setName("init");
startTransaction();
getEM().persist(entity);
commitTransaction();

// change the order to A, C, B:
getEM().clear();
EntitySample loaded=getEM().find(...); // just reload from DB
SampleObject moveObj=loaded.getSampleList().remove(1);
loaded.getSampleList().add(moveObj);
// loaded.setName("changed"); // all works with this change, but not without!

startTransaction();
getEM().merge(loaded);
commitTransaction();

使用上面的代码,将带有带有元素A,B,C的JSON列表的对象写入DB。在此之后,再次加载对象并将列表元素的顺序更改为A,C,B。但是现在保存对象不会更改数据库中的数据数据!对我来说,看起来,Hibernate没有发现任何变化!我没有为合并调用转换器代码convertToDatabaseColumn()。 但是一旦我也改变名称(上面的注释行),一切正常。现在,加载的对象似乎被检测为已更改,因此也会调用转换并将JSON字符串存储到DB。

有人知道这个错误或知道解决方法吗?或者到底是我的虫子?

1 个答案:

答案 0 :(得分:2)

我知道这是一个老问题,但是......

我曾经遇到过Hibernate + @ Converter一样的问题。 过了一会儿,我意识到这是因为Hibernate不知道AttributeConverter<left,right>的左侧何时变脏(可能是其他一些JPA实现),这就是为什么它从不调用{ {1}}因此永远不会更新数据库。

要解决此问题,您必须将实体的属性设置为一个全新的实例(convertToDatabaseColumn()可以解决这个问题。)

clone()

此处EntitySample entity=new EntitySample(); entity.getSampleList().add(new SampleObject("A")); entity.getSampleList().add(new SampleObject("B")); entity.getSampleList().add(new SampleObject("C")); entity.setName("init"); startTransaction(); getEM().persist(entity); commitTransaction(); // change the order to A, C, B: getEM().clear(); startTransaction(); // <-- Transaction should start here EntitySample loaded=getEM().find(...); List<SampleObject> list = loaded.getSampleList(); SampleObject moveObj = list.remove(1); list.add(moveObj); loaded.setSampleList(list.clone()); // <-- Workaround // getEM().merge(loaded); // <-- 'loaded' already is an entity! commitTransaction(); 返回clone()实例的浅表副本。 (List本身的实例不会被复制。)

为避免这种情况,您必须创建一个新的Hibernate类型(将纯JPA放在一边),但这与主要问题无关。