今天我偶然发现了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时会遇到问题。
我想我必须为此提交一个错误。
答案 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
个实例。