2013-07-18 34 views
9

我对JPA和Hibernate相当陌生(尽管我正在努力学习!),我正在努力解决一个问题,我似乎无法找到一个简单的解决方案,所以在这里。休眠:懒惰初始化vs破碎哈希码/等于难题

我有一个看起来有点像一个实体如下:

@Entity 
@Table(name = "mytable1") 
public class EntityOne { 
    // surrogate key, database generated 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Column(name = "id") 
    private Long id; 

    // business key 
    @Column(name = "identifier", nullable = false, unique = true) 
    private String identifier; 

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH) 
    @JoinColumn(name = "twoId", nullable = false) 
    private EntityTwo two; 

    @OneToMany(mappedBy = "entityOne", fetch = FetchType.EAGER, 
    cascade = {CascadeType.ALL}, orphanRemoval = true) 
    private Set<EntityThree> resources = new HashSet<>(); 

    // getters/setters omitted 

    @Override 
    public int hashCode() { 
    // the business key should always be defined (through constructor/query) 
    // if this is null the class violates the general hashcode contract 
    // that the integer value returned must always be the same 
    Assert.notNull(identifier); 
    // a dirty alternative would be: 
    // if(identifier==null) return 0; 
    return identifier.hashCode(); 
    } 

    @Override 
    public boolean equals(Object o) { 
    return o instanceof ResourceGroup 
     && ((ResourceGroup) o).identifier.equals(identifier); 
    } 
} 

我的项目是建立与Spring JPA,所以我有我的CrudRepository<EntityOne,Long>在具有几@Transactional方法和我扫描服务类注射我的域名/服务包分别用于JPA和交易。

其中一个服务方法调用存储库的findAll()方法并返回一个EntityOne s的列表。

org.hibernate.LazyInitializationException: could not initialize proxy - no Session 

我想这可能是有这个对象初始化非常有用,所以我切换取类型从懒渴望:除非我尝试访问getter方法two,这显然会抛出一切工作正常。但是,如果我这样做,我得到如下:

java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null 
    at org.springframework.util.Assert.notNull(Assert.java:112) 
    at org.springframework.util.Assert.notNull(Assert.java:123) 
    at my.pkg.domain.EntityOne.hashCode(ResourceGroup.java:74) 
    at java.util.HashMap.hash(HashMap.java:351) 
    at java.util.HashMap.put(HashMap.java:471) 
    at java.util.HashSet.add(HashSet.java:217) 
    at java.util.AbstractCollection.addAll(AbstractCollection.java:334) 
    at org.hibernate.collection.internal.PersistentSet.endRead(PersistentSet.java:346) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection(CollectionLoadContext.java:243) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:233) 
    at org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections(CollectionLoadContext.java:209) 
    at org.hibernate.loader.Loader.endCollectionLoad(Loader.java:1149) 
//... 

我简单看了一下Hibernate的源代码,它看起来像它试图把我EntityOne对象在一组其业务重点初始化之前。我的解释是否正确?有没有解决的办法?我做了一件令人难以置信的事情吗?

我感谢你的帮助

编辑:我只是想澄清,我想在这里了解什么是最好的做法是专门相对于JPA和Hibernate。如果这是一个普通的POJO,我可以使标识符字段最终(我实际上使整个类不可变)并且是安全的。我无法这样做,因为我使用的是JPA。所以问题:你是否违反了hashCode合同,并以哪种方式? Hibernate如何处理这种违规行为? JPA推荐的做法是什么?我应该完全摆脱基于哈希的集合,并使用列表来代替?

Giovanni

+0

我用'new LinkedHashMap()'代替'new HashMap()'代替了我所有的关系'Set'字段'初始值,它开始工作。这不奇怪吗? –

回答

0

我相信我实际上找到了一种方法来使这项工作更好一些,即强制Hibernate(或任何JPA提供者)在将对象粘贴到集合中之前具有可用的键。在这种情况下,该对象将被正确初始化,并且我们可以确定该商业密钥不会为空。

例如,这里是如何类EntityTwo将不得不看:

@Entity 
@Table(name = "mytable2") 
public class EntityTwo { 
    // other code omitted ... 
    @OneToMany(mappedBy = "entityTwo", fetch = FetchType.EAGER, 
    cascade = {CascadeType.ALL}, orphanRemoval = true) 
    @MapKey(name = "identifier") 
    private Map<String, EntityOne> entityOnes = new HashMap<>(); 
} 

我没有测试过这种特定的代码,但我有其他工作的例子,它应该根据JPA docs做工精细。在这种情况下,JPA提供者是有道理的:它必须先知道identifier的值,然后才能将对象置于集合中。此外,该对象的hashCodeequals甚至没有被调用,因为该映射由JPA提供程序显式处理。

这种情况下,明确强制工具了解事物建模和相互关联的方式会带来巨大好处。

0

您的解释是正确的。作为第一步,你的hashCode()equals()与你的id字段 - 你告诉Hibernate的那个是你的ID。

作为第二步实施一个正确的hashCode()equals()以免您将来的麻烦。如果你google的话,有很多资源。 Here是一个在这个网站上

8

不,你没有做任何愚蠢的事情。在JPA实体上实现equals和hashCode是一个非常热门的问题,我知道的所有方法都有明显的缺点。没有明显的,简单的解决方案,你只是缺少。

然而,你有一个案件,因为某些原因没有讨论太多。使用业务密钥的hibernate wiki recommends与你正在做的一样,第398页的“Java Persistence with Hibernate”(Bauer/King,2007,广泛被认为是标准的Hibernate参考工作)推荐同样的事情。但是在某些情况下,正如您所看到的,Hibernate可以在其字段初始化之前将实体添加到Set中,因此基于业务密钥的hashCode不起作用,就像您指出的那样。讨论这种情况请参见休眠问题HHH-3799。 Hibernate源代码中有一个预期失败的代码test case,它在2010年添加了这个问题,所以至少有一位Hibernate开发人员认为它是一个bug并且想要修复它,但自2010年以来没有任何活动请考虑为此问题投票。

您可能考虑的一个解决方案是扩大会话范围,以便您在同一个会话中访问实体。然后你可以让你的Set<EntityThree>被懒惰取代而不是急于取回,并且你将避免HHH-3799中的急切取回问题。我所处理的大多数应用程序仅使用分离状态中的对象。这听起来像你正在加载你的实体,然后在会话结束后使用它一段时间;这是我推荐的一种模式。如果您正在编写Web应用程序,请参阅“打开会话视图”模式以及Spring的OpenSessionInViewFilter,以获取有关如何执行此操作的想法。

顺便说一句,我喜欢在未初始化业务密钥时抛出异常;这样你可以快速捕捉编码错误。我们的应用程序有一个由HHH-3799引起的令人讨厌的错误,如果我们使用了非空断言,那么我们可能会在开发中遇到这种错误。

+0

不幸的是,使它工作,我不得不删除断言。即使在同一笔交易中,我认为它也会失败,我必须仔细检查。感谢您的回复! –