休眠4 ClassCastException在LAZY加载而EAGER正常工作 [英] Hibernate 4 ClassCastException on LAZY loading while EAGER works fine

查看:125
本文介绍了休眠4 ClassCastException在LAZY加载而EAGER正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于地理区域(如大洲,国家,州等),我有以下JOINED继承根实体:

  @实体
@Table(name =GeoAreas)
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class GeoArea implements Serializable
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
protected Integer id;

@Column
受保护的字符串名称;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name =parent_id,referencedColumnName =id)
protected GeoArea parent;

...
}

正如您所见,地理区域有一个简单的自动ID作为PK,名称,并与其父(自我参考)的关系。请注意映射为​​ FetchType.LAZY 地理区域关系。在DB中,FK的 parent_id NOT NULL 使关系成为可选的。 @ManyToOne 的默认值是 optional = true ,所以映射似乎是正确的。



子类定义了额外的属性,并且实际上并不重要。数据库中的数据链接正确,因为可以通过JPQL毫无问题地列出地理区域( FetchType.EAGER 映射稍微不同于 parent ,见文本结尾):



每行代表一个实例:

  public class ArenaListViewLine 
{
private final Integer arenaId;
private final String arenaName;
private final String arenaLabel;

private final布尔hasPostAddress;

...

public ArenaListViewLine(Arena arena,Boolean hasPostAddress)
{
// init字段
...

列表< String> continentNames = new ArrayList< String>();
列表< String> countryNames = new ArrayList< String>();
列表< String> regionNames = new ArrayList< String>();
列表< String> stateNames = new ArrayList< String>();
列表< String> districtNames = new ArrayList< String>();
列表< String> clubShorthands = new ArrayList< String>();

//将每个地理区域添加到列出其俱乐部是竞技场用户(竞技场有几种用法)的列表
//在州的边界区域,来自不同州的两个俱乐部可以使用竞技场(罕见!)
//这可能会在竞技场列表中添加多个状态(非数据库中)
(用法us:用法)
{
Club cl = us.getClub();

//一个俱乐部在任何时候都位于一个地区(必填,非空)
区di = cl.getDistrict();

System.out.println(arena =+ arenaName +:using club's district parent =+ di.getParent());

State st =(State)di.getParent(); // ClassCastException here!

...
}

...
}

运行父映射为LAZY的列表查询时,出现以下异常:

  ... 
引起:org.hibernate.QueryException:无法在org.hibernate.transform.AliasToBeanConstructorResultTransformer.transformTuple()中从元组
实例化类[com.kawoolutions.bbstats.view.ArenaListViewLine] AliasToBeanConstructorResultTransformer.java:57)[hibernate-core-4.0.0.Final.jar:4.0.0.Final]
at org.hibernate.hql.internal.HolderInstantiator.instantiate(HolderInstantiator.java:95)[hibernate -core-4.0.0.Final.jar:4.0.0.Final]
在org.hibernate.loader.hql.QueryLoader.getResultList(QueryLoader.java:438)[hibernate-core-4.0.0.Final .jar:4.0.0.Final]
在org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2279)[hibernate-core-4.0.0.Final.jar:4.0.0.Final]
在org.hibernate.loader.Loader.list(Loader.java:2274)[hib ernate-core-4.0.0.Final.jar:4.0.0.Final]
在org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:470)[hibernate-core-4.0.0。 Final.jar:4.0.0.Final]
在org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:355)[hibernate-core-4.0.0.Final.jar:4.0。 0.Final]
在org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:196)[hibernate-core-4.0.0.Final.jar:4.0.0.Final]
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1115)[hibernate-core-4.0.0.Final.jar:4.0.0.Final]
at org.hibernate.internal.QueryImpl .list(QueryImpl.java:101)[hibernate-core-4.0.0.Final.jar:4.0.0.Final]
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:252)[ hibernate-entitymanager-4.0.0.Final.jar:4.0.0.Final]
... 96 more
引起:java.lang.reflect.InvocationTargetException $ b $在sun.reflect。 NativeConstructorAccessorImpl.newInstance0(本地方法)[:1.7.0_02]
at sun.refle ct.NativeConstructorAccessorImpl.newInstance(Unknown Source)[:1.7.0_02]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)[:1.7.0_02]
在java.lang.reflect.Constructor。 newInstance(Unknown Source)[:1.7.0_02]
at org.hibernate.transform.AliasToBeanConstructorResultTransformer.transformTuple(AliasToBeanConstructorResultTransformer.java:54)[hibernate-core-4.0.0.Final.jar:4.0.0.Final ]
... 106 more
导致:java.lang.ClassCastException:com.kawoolutions.bbstats.model.GeoArea _ $$ _ javassist_273无法转换为com.kawoolutions.bbstats.model.State
at com.kawoolutions.bbstats.view.ArenaListViewLine。< init>(ArenaListViewLine.java:92)[classes:]
... 111 more

堆栈跟踪之前的println是:

  13 :57:47,245 INFO [stdout](http  -  127.0.0.1-8080-4)竞技场= Joachim-Schumann-Schule:使用俱乐部的区域父= com.kawoolutions.bbstats.mode l.State@2b60693e [ID = 258,名称=黑森,isoCode = HE,国家= com.kawoolutions.bbstats.model.Country @ 17f9976b [ID = 88,姓名=德国,isoCode = DE,isoNbr = 276,dialCode = < null>]] 

println清楚地表明父代是State的一个实例,但它不能被投到国家?我不知道...



将GeoArea.parent的FetchType更改为EAGER时,一切正常(请参阅上面的图片)。



我做错了什么? LAZY提示有什么问题?



谢谢

PS:我正在使用 Hibernate 4.0.0.Final ,映射都是标准JPA,服务器是JBoss AS 7. 解决方案

延迟加载的问题在于,Hibernate会动态生成延迟加载对象的代理。虽然起初这似乎是一个好的想法来实现延迟加载,但开发人员必须意识到对象可以是Hibernate代理的事实。我认为这是一个漏洞抽象的好例子。在你的情况下,调用 di.getParent()的返回值是一个代理对象,通过Hibernate使用的javassist库生成。



这样的Hibernate代理会导致与casts,instanceof和 equals() hashCode )如果您还没有为您的实体实现这些方法。



阅读这个问题和答案,它显示了如何使用标记接口 HibernateProxy 来转换代表真实的物体。另外一种选择是在这种情况下进行热切加载。



顺便说一下,请考虑削减匈牙利符号; - )



编辑来解决评论中的问题:

是否有便携式(JPA)方式来实现这一点?



不是我所知道的。我将其他QA中的转换逻辑从接口后面抽象出来,并提供一个替代的noop实现。使用JBoss 7和CDI,您可以切换到那个,而无需重新编译。



它可以自动化吗?

可能使用AspectJ。你可以试着写一个围绕的建议,用于所有调用你的实体的getter方法,这些方法返回其他实体。建议将检查底层字段是否为Hibernate代理,如果是,则应用转换,然后返回转换结果。因此,当getter被调用但你仍然可以从延迟加载中受益时,你总能得到真实的对象。你应该彻底地测试这个,尽管...

I have the following JOINED inheritance root entity for geographic areas (like continents, countries, states etc.):

@Entity
@Table(name = "GeoAreas")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class GeoArea implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    protected Integer id;

    @Column
    protected String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id", referencedColumnName = "id")
    protected GeoArea parent;

    ...
}

As you can see a geo area has a simple auto-ID as PK, a name, and a relationship to its parent (self reference). Please pay attention to the parent geo area relationship mapped as FetchType.LAZY. In the DB the FK's parent_id is NOT NULL making the relationship optional. The default for @ManyToOne is optional = true, so the mappings seem to be correct.

The sub classes define additional properties and aren't of interest really. The data in the DB is linked correctly as the geo areas can be listed without problems via JPQL (with a slightly different FetchType.EAGER mapping on parent, see end of text):

Each line is an instance of:

public class ArenaListViewLine
{
    private final Integer arenaId;
    private final String arenaName;
    private final String arenaLabel;

    private final Boolean hasPostAddress;

    ...

    public ArenaListViewLine(Arena arena, Boolean hasPostAddress)
    {
        // init fields
        ...

        List<String> continentNames = new ArrayList<String>();
        List<String> countryNames = new ArrayList<String>();
        List<String> regionNames = new ArrayList<String>();
        List<String> stateNames = new ArrayList<String>();
        List<String> districtNames = new ArrayList<String>();
        List<String> clubShorthands = new ArrayList<String>();

        // add each geo area to list whose club is a user of an arena (an arena has several usages)
        // in border areas of states two clubs from different states might use an arena (rare!)
        // this potentially adds several states to an entry in the arena list (non in DB yet)
        for ( Usage us : usages )
        {
            Club cl = us.getClub();

            // a club is located in one district at all times (required, NOT NULL)
            District di = cl.getDistrict();

            System.out.println("arena = " + arenaName + ": using club's district parent = " + di.getParent());

            State st = (State)di.getParent(); // ClassCastException here!

            ...
    }

    ...
}

When running the list query with parent mapped as LAZY, I get the following exception:

...
Caused by: org.hibernate.QueryException: could not instantiate class [com.kawoolutions.bbstats.view.ArenaListViewLine] from tuple
    at org.hibernate.transform.AliasToBeanConstructorResultTransformer.transformTuple(AliasToBeanConstructorResultTransformer.java:57) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.hql.internal.HolderInstantiator.instantiate(HolderInstantiator.java:95) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.loader.hql.QueryLoader.getResultList(QueryLoader.java:438) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2279) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.loader.Loader.list(Loader.java:2274) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:470) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:355) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:196) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1115) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:252) [hibernate-entitymanager-4.0.0.Final.jar:4.0.0.Final]
    ... 96 more
Caused by: java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) [:1.7.0_02]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) [:1.7.0_02]
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) [:1.7.0_02]
    at java.lang.reflect.Constructor.newInstance(Unknown Source) [:1.7.0_02]
    at org.hibernate.transform.AliasToBeanConstructorResultTransformer.transformTuple(AliasToBeanConstructorResultTransformer.java:54) [hibernate-core-4.0.0.Final.jar:4.0.0.Final]
    ... 106 more
Caused by: java.lang.ClassCastException: com.kawoolutions.bbstats.model.GeoArea_$$_javassist_273 cannot be cast to com.kawoolutions.bbstats.model.State
    at com.kawoolutions.bbstats.view.ArenaListViewLine.<init>(ArenaListViewLine.java:92) [classes:]
    ... 111 more

The println prior to the stack trace is:

13:57:47,245 INFO  [stdout] (http--127.0.0.1-8080-4) arena = Joachim-Schumann-Schule: using club's district parent = com.kawoolutions.bbstats.model.State@2b60693e[id=258,name=Hesse,isoCode=HE,country=com.kawoolutions.bbstats.model.Country@17f9976b[id=88,name=Germany,isoCode=DE,isoNbr=276,dialCode=<null>]]

The println clearly says the parent is an instance of State, but it cannot be cast to State? I have no idea...

When changing the FetchType of GeoArea.parent to EAGER everything works fine (see image above).

What am I doing wrong? What's wrong with the LAZY hint?

Thanks

PS: I'm using Hibernate 4.0.0.Final, the mappings are all standard JPA, server is JBoss AS 7.

解决方案

The problem with lazy loading is that Hibernate will dynamically generate proxies for objects that are lazily loaded. While at first this seems like a good idea to implement lazy loading, a developer has to be aware of the fact that an object can be a Hibernate Proxy. I think it's a good example of a leaky abstraction. In your case, the return value of the call di.getParent() is such a proxy object, generated throuhg the javassist library which is used by Hibernate.

Such Hibernate proxies will cause problems in connection with casts, instanceof and calls to equals() and hashCode() if you haven't implemented those methods for your entities.

Read this question and answer which shows how to use the marker interface HibernateProxy in order to convert a proxy to the real object. The other option is to go with eager loading in this case.

By the way, please think about ditching that "Hungarian notation" ;-)

Edited to address the questions from the comment:

Is there a portable (JPA) way to achieve this?

Not that I know of. I'd abstract the conversion logic from the other QA away behind an interface and provide an alternative noop implementation Using JBoss 7 and CDI you could switch to that one without even recompiling.

Can it be automated?

It might be possible using AspectJ. You could try to write an around advice for all calls to getters on your entities which return other entities. The advice will check if the underlying field is a Hibernate Proxy, and if so, apply the conversion, then return the result from the conversion. Thus, you would always get the real object when the getter is called but could still benefit from lazy loading. You should test this thoroughly, though...

这篇关于休眠4 ClassCastException在LAZY加载而EAGER正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆