由于 JPA AttributeConverter 导致数据丢失?

Mar*_*etz 5 java hibernate jpa

我使用 JPA 将我的数据保存到数据库中。具体来说,我使用 Hibernate 4.3.5 作为 JPA 实现。由于性能和保持表结构简单,我不会直接从对象 1:1 映射到表,而是一些我只保留为对象但不为其创建实体类的数据列表。取而代之的是,我将对象结构作为 JSON 序列化到数据库中。这种序列化/反序列化是用@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);
  }

  ...
}
Run Code Online (Sandbox Code Playgroud)

如上所述,它主要是有效的!我在单元测试中检测到以下问题:

// 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();
Run Code Online (Sandbox Code Playgroud)

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

有人知道这个错误或知道解决方法吗?还是说到底是我的bug?

WPo*_*ier 2

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

我曾经在使用 Hibernate+@Converter 时遇到过同样的问题。一段时间后,我意识到这是因为 Hibernate 不知道左侧何时AttributeConverter<left,right>变脏(也许其他一些 JPA 实现会这样做),这就是为什么它从不调用convertToDatabaseColumn(),因此从不更新数据库。

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

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();
Run Code Online (Sandbox Code Playgroud)

这里clone()返回实例的浅表副本List。(它们自身的实例SampleObject不会被复制。)

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