如何在使用Hibernate映射的类中实现toString()方法?

6
我有一个类的实例,它是从Hibernate会话中获取的。那个会话已经结束了。现在,我正在调用toString(),并且得到了预期的LazyInitializationException: could not initialize proxy - no Session,因为我试图访问Hibernate在加载实例时没有解析的引用(延迟加载)。
我不想使加载急切,因为它会将查询从大约120个字符变成超过4KB(带有八个连接)。而且我也不需要:我只想在toString()中显示引用对象的ID;也就是说,Hibernate此时需要知道的东西(否则它无法进行延迟加载)。
所以我的问题是:如何处理这种情况?永远不要尝试在toString()中使用引用吗?还是在加载代码中调用toString()以防万一?或者在Hibernate中是否有一些实用函数,当我传递一个可能是懒惰的引用时,它会返回一些有用的东西?还是完全避免在toString()中使用引用?

如果Java有闭包,你可以这样做:String x = lazyToString({ => this.getY() }) + lazyToString({ => this.getZ() }); 并在lazyToString方法中捕获异常。使用内部类(或try/catch)的开销太高了,无法实现这一点。 - Thomas Jung
是的,但那也不会给我一个session。 - Aaron Digulla
没错。你可以只打印值未加载的消息。我认为这就是意图。你将无法在toString方法调用时启动会话并关联对象。 - Thomas Jung
我的观点是这些信息一定可以在某个地方找到(请看下面我的回答) :) - Aaron Digulla
4个回答

5

可以通过将ID字段的accesstype设置为“property”来实现此操作。例如:

@Entity
public class Foo {
    // the id field is set to be property accessed
    @Id @GeneratedValue @AccessType("property")
    private long id;
    // other fields can use the field access type
    @Column private String stuff;
    public long getId() { return id; }
    public void setId(long id) { this.id = id; }
    String getStuff() { return stuff; }
    // NOTE: we don't need a setStuff method
}

这里有解释。这个链接。 这样,当代理被创建时,id字段总是被填充。

+1 我喜欢它;只有一个小问题:我正在使用DSL语法,所以我的getter被称为"id()"而不是"getId()"。我想我可以为这种特殊情况添加第二个getter,但也许可以告诉Hibernate getter的名称? - Aaron Digulla
可以通过创建自己的org.hibernate.property.PropertyAccessor实现来实现,将完全限定名称声明为@AccessType的值。另一方面,您可以创建setter(您也需要它)和getter并使它们私有,这样您就看不到它们从应用程序的其他部分。 - EJB
@EJB: 真的有效吗? 我有一个情况,其中类A ----->(具有一对多关系)与类B。 A和B都有多个属性。因此,当我为类A调用toString()方法时,尽管为类A的Id字段设置了@AccessType(“property”),但它仍会失败并出现LazyInitialization异常(与上述相同)。 - raikumardipak

1
我发现最符合良好实践的方法是对这篇博客中找到的解决方案进行修改:http://www.nonfunc.com/2016/02/05/jpa-performance-gotcha-tostring-really/。需要针对可空字段和集合对象进行修改。
public toString() {
  return String.format("FuBar [id=%d" +  
      + ", fu=%s" // fu is a lazy non-nullable field
      + ", bar=%s" // bar is a nullable lazy field
      + ", borks=%s]", // borks is a lazy collection of Bork objects
      id,
      fu instanceof HibernateProxy ? "[null]" : fu.toString(),
      bar == null || bar instanceof HibernateProxy ? "[null]" : bar.toString(),
      borks instanceof PersistentSet ? "[null]" : borks.toString());
}

1
我看到你在做什么。一些评论:混合使用String.format()+会影响性能,应该用<lazy>替换[null],因为代理不是null,只是没有加载。更好的方法是返回<lazy TYPE ID>,其中TYPE是实体类型,ID是主键。 - Aaron Digulla
编辑过的代码以提高性能。示例并不完全符合我的情况,并且变量和字符串的串联确实会影响性能(格式化字符串应编译为静态字符串)。但是,我认为,在生产环境中,对于 toString 的性能过度优化并不是非常重要,因为它将用于调试或错误报告,并且在生产环境中不应该被频繁调用。我喜欢指定延迟与空值的建议。 - Nielsvh

1

我找到了一个解决方法:

public static String getId (DBObject dbo)
{
    if (dbo == null)
        return "null";

    if (dbo instanceof HibernateProxy)
    {
        HibernateProxy proxy = (HibernateProxy)dbo;
        LazyInitializer li = proxy.getHibernateLazyInitializer();
        return li.getIdentifier ().toString ();
    }

    try
    {
        return Long.toString (dbo.id ());
    }
    catch (RuntimeException e)
    {
        return "???";
    }
}

所以这段代码的作用是从对象中获取ID(一个64位数字)。DBObject是一个定义了long id()的接口。如果该对象是Hibernate代理,则我会与其LazyInitializer交互以获取ID。否则,我调用id()方法。用法:
class Parent {
    DBObject child;
    public String toString () {
        return "Parent (id=..., child=" + getId(child)+")");
    }
}

0
如果您只想返回对象的ID,我想调用getID(),然后在您想要显示它的时候将int/long解析为字符串值应该可以正常工作。至少根据问题的描述是这样的。
编辑 如何使用JPA和Hibernate解决LazyInitializationException 查看评论并进行一些搜索后,我认为这可能对您的情况最有益。

这将抛出LazyInitializationException异常,因为引用尚未解析。 - Aaron Digulla
Aaron,在阅读了这条评论后,我编辑了我的帖子。请查看新信息,并让我知道是否解决了问题。 - Woot4Moo
@Woot - 这些答案都没有帮助。懒加载的值永远不会被读取。事务已提交。连接已关闭。 - Thomas Jung
是的,我看到这个事务已经提交了。然而,在帖子中有可能重构现有的代码,使用在这里找到的OpenSessionInView模式:http://www.hibernate.org/43.html如果在Aaron的环境中无法实现,那就算了。 - Woot4Moo
@Woot:我必须同意Thomas的看法。 - Aaron Digulla
当我调用toString()时,我没有会话,也不想创建一个。问题实际上是:如何使toString()在会话内外都能正常工作? - Aaron Digulla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接