我为地理区域(如大洲,国家,州等)提供了以下JOINED继承根实体:
@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;
...
}
正如您所看到的,地理区域有一个简单的自动ID作为PK,名称以及与其父级的关系(自引用)。请注意映射为parent
的{{1}}地理区域关系。在数据库中,FK的FetchType.LAZY
为parent_id
,使关系成为可选的。 <{1}}的默认值为NOT NULL
,因此映射似乎是正确的。
子类定义了其他属性,实际上并不重要。数据库中的数据正确链接,因为可以通过JPQL列出地理区域而没有问题(@ManyToOne
上的optional = true
映射略有不同,请参见文本结尾):
每一行都是:
的实例FetchType.EAGER
当运行列表查询时,父映射为LAZY,我得到以下异常:
parent
堆栈跟踪之前的println是:
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!
...
}
...
}
println清楚地说父母是国家的一个例子,但它不能被投到国家?我不知道......
将GeoArea.parent的FetchType更改为EAGER时,一切正常(参见上图)。
我做错了什么? LAZY提示有什么问题?
由于
PS:我正在使用 Hibernate 4.0.0.Final ,映射都是标准的JPA,服务器是JBoss AS 7.
答案 0 :(得分:16)
延迟加载的问题是Hibernate将动态生成延迟加载的对象的代理。虽然起初这似乎是一个实现延迟加载的好主意,但开发人员必须意识到对象可以是Hibernate代理。我认为这是漏洞抽象的一个很好的例子。在您的情况下,调用di.getParent()
的返回值是一个代理对象,由Hibernate使用的javassist库生成。
如果您没有为实体实现这些方法,此类Hibernate代理会导致与强制转换,instanceof以及对equals()
和hashCode()
的调用相关的问题。
阅读this question and answer,其中显示了如何使用标记接口HibernateProxy
将代理转换为真实对象。另一种选择是在这种情况下采用急切加载。
那么请考虑放弃“匈牙利记谱法”; - )
编辑以解决评论中的问题:
是否有便携式(JPA)方式来实现这一目标?
不是我知道的。我将来自其他QA的转换逻辑从接口后面抽象出来并提供一个替代的noop实现使用JBoss 7和CDI你可以切换到那个甚至没有重新编译。
可以自动化吗?
使用AspectJ可能是可能的。您可以尝试为所有返回其他实体的实体的getter写入around
个建议。该建议将检查基础字段是否为Hibernate代理,如果是,则应用转换,然后返回转换结果。因此,在调用getter时,您总是可以获得真实对象,但仍然可以从延迟加载中获益。你应该彻底测试一下,但是......
答案 1 :(得分:2)
罗伯特·彼得梅尔的回答很少。对于这种结构:
class GeoArea {
@ManyToOne(fetch = FetchType.LAZY)
protected GeoArea parent;
}
class State extends GeoArea {}
因为GeoArea.parent
是一个惰性代理,所以创建这个代理Hibernate并不知道这个字段中加载的实际类是什么。它只知道它将是GeoArea
子类。因此,它会为GeoArea
类创建一个javaassist代理,该代理永远不会转换为State
或任何不同的GeoArea
子类型。
因此,您可以为每个instanceof
用法或类型转换解包代理,但是对我来说它不是一个选项(我在一个大系统中使很多关联来提高数据库性能 - 为100%确定系统是否仍然有效我需要在任何地方添加解包,我可以看到类型转换或instanceof
)。但您可以选择使用它进行自动化
bytecode instrumentation。
最后我找到了一个无缝的解决方案来避免字节码检测 - 在庞大的系统中,我们仍然不知道在添加这个检测之后什么不再起作用,以及在手动添加解包代码之后:)你可以应用一个在getter中手动解包代理的简单技巧。这是我的例子:
@MappedSuperclass
public class BaseEntity {
public BaseEntity getThis() {
return this;
}
}
@Entity
public class B extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
protected A source;
public A getSource() {
return this.source!=null ? (A) this.source.getThis() : null;
}
public void setSource(A source) {
this.source = source;
}
}
here是关于这个问题的一些更详细的解释。
答案 2 :(得分:0)
您正在使用JBoss
模块类加载器。
ClassCastException
代理的主要原因是
代理(由Hibernate
生成)使用状态类的不同实例作为调用者(客户端)。在同一个Java JM中必须有两个状态类实例! JBoss
处理模块之间的模块和隐式/显式导入/导出依赖关系。
因此,您必须检查application modules或使用标准类加载的其他应用程序服务器。