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

  • 本文关键字:JPA bean 实例 表示 数据库 jpa
  • 更新时间 :
  • 英文 :


今天我被EclipseLink的一些意想不到的行为绊倒了。(我不知道这是否绑定到EclipseLink或如果这是相同的所有JPA提供商。)

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

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

@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();
}

标记为"FAILS"的语句抛出以下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@Converter注释是eclipselink特有的。与JPA 2.1转换器不同,您可以将它们放在ID字段上。

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

我想我必须为此提交一个bug

我找到了问题的原因和解决办法。

我们正在为Product使用一个自定义ID类(ProductId),以及一个自定义的(eclipselink特定的)转换器类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 Driver将列值映射为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实例。

最新更新