【问题标题】:Why is the same database entry represented by multiple JPA bean instances?为什么同一个数据库条目由多个 JPA bean 实例表示?
【发布时间】:2016-11-09 12:28:45
【问题描述】:

今天我偶然发现了 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 and @Converter annotations 是 EclipseLink 特定的。 与 JPA 2.1 转换器不同,您可以将它们放在 ID 字段中。

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

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

【问题讨论】:

  • 根据 JPA 规范,EntityManager 应该只为特定的“id”提供一个对象(引用)。该规则适用于查询以及查找......类似地,您可以使用 em.contains 来查看它是否“管理”特定对象。如果 EclipseLink 真的违反了这个规则,那么就提出一个错误。 DataNucleus JPA 在这方面工作得很好。
  • “产品”是否有一些奇怪的 equals/hashCode 方法?
  • 好点。这是自定义 ID 类。谢谢你的提示。我已经分别更新了我的问题。

标签: jpa


【解决方案1】:

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

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

以下是相关代码:

  /**
   * 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 实例。

【讨论】:

    猜你喜欢
    • 2021-06-20
    • 2012-10-16
    • 1970-01-01
    • 2021-01-18
    • 1970-01-01
    • 1970-01-01
    • 2012-04-12
    • 1970-01-01
    • 2017-08-31
    相关资源
    最近更新 更多