为什么多个JPA bean实例表示相同的数据库条目?

时间:2016-11-09 12:28:46

标签: jpa

今天我偶然发现了EclipseLink的一些意外行为。 (我不知道这是否与EclipseLink绑定,或者对于所有JPA提供程序是否相同。)

我假设在同一个事务中发出托管JPA bean的检索总是返回对同一个对象实例的引用(使用相同的EntityManager)。

如果这是正确的,我不知道为什么在执行以下测试用例时收到错误:

@Test
public void test_1() {
  EntityManager em = newEntityManager();
  em.getTransaction().begin();

  // Given:
  Product prod = newProduct();

  // When:
  em.persist(prod);
  em.flush();      
  Product actual =
    em.createQuery("SELECT x from Product x where x.id = " 
    + prod.getId(), Product.class).getSingleResult();

  // Then:
  assertThat(actual).isSameAs(prod); // <-- FAILS

  em.getTransaction().commit();
}

标有&#34; FAILS&#34;抛出以下AssertionError:

java.lang.AssertionError: 
Expecting:
  <demo.Product@35dece42>
and actual:
  <demo.Product@385dfb63>
to refer to the same object

有趣的是,以下略有修改的测试成功:

@Test
public void test_2() {
  EntityManager em = newEntityManager();
  em.getTransaction().begin();

  // Given:
  Product prod = newProduct();

  // When:
  em.persist(prod);
  em.flush();      
  Product actual = em.find(Product.class, prod.getId());

  // Then:
  assertThat(actual).isSameAs(prod); // <-- SUCCEEDS

  em.getTransaction().commit();
}

显然查找查询对象之间存在差异。

这是预期的行为吗?为什么?

- 编辑 -

我认为我找到了问题的根源:Product的ID为ProductId

以下是相关代码:

@Entity
@Table(name = "PRODUCT")
public class Product implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @Column(name = "ID", nullable = false)
  @Converter(name = "productIdConverter", converterClass = ProductIdConverter.class)
  @Convert("productIdConverter")
  private ProductId id;

  @Column(name = "NAME", nullable = false)
  private String name;

[...]
}

@Convert and @Converter annotations是EclipseLink特有的。 与JPA 2.1转换器不同,您可以将它们放在ID字段上。

但似乎在某些情况下,如果该bean的ID字段使用自定义类型,则EclipseLink在其会话缓存中找到托管bean时会遇到问题。

我想我必须为此提交一个错误。

1 个答案:

答案 0 :(得分:0)

我找到了问题的原因和解决方案。

我们正在为ProductId使用自定义ID类(Product),以及一个错误的自定义(EclipseLink特定)Converter-Class ProductIdConverter convertObjectValueToDataValue(...)方法的实现

以下是相关代码:

  /**
   * Convert the object's representation of the value to the databases' data representation.
   */
  @Override
  public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
    if (objectValue == null) {
      return null;
    }    
    Long longValue = ((ProductId) objectValue).getLong();
    return longValue;
  }

请注意,该方法返回Long个实例(或null)。

但由于我们使用Oracle作为数据库后端,并将产品的ID列声明为NUMBER,因此JDBC驱动程序将列值映射为BigDecimal。这意味着,我们必须确保我们的convertObjectValueToDataValue(...)也返回BigDecimal个实例。

所以正确的实现是:

  /**
   * Convert the object's representation of the value to the databases' data representation.
   */
  @Override
  public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
    if (objectValue == null) {
      return null;
    }    
    Long longValue = ((ProductId) objectValue).getLong();
    return BigDecimal.valueOf(longValue);
  }

现在此方法仅返回BigDecimal个实例。