今天我被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
实例。