这个bug是在使用hibernate annotation过程中发现的,开始以为是hibenrate annotation的bug,后来又使用xml文件来配置,还是存在同样的问题。
问题场景:我需要使用省份与城市,他们的关系是:一个省有个多城市,很简单的OneToMany关系,在改变province中的cities的lazy特性时,遇到问题
由于我采用把所有的资源都是放在一个表中的策略,所有这里有一个基类Resource
基类
@Entity
@Inheritance(strategy
=
InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name
=
"
CATEGORY_ID
"
,discriminatorType
=
DiscriminatorType.STRING)
@Table(name
=
"
T_RESOURCE
"
)
public
class
Resource
implements
Serializable
...
{
privateStringid;
privateStringname;
privateResourceCategoryresourceCategory;
@Id
@GenericGenerator(name="generator",strategy="uuid")
@GeneratedValue(generator="generator")
@Column(name="ID")
publicStringgetId()...{
returnid;
}
@Column(name="NAME")
publicStringgetName()...{
returnname;
}
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="CATEGORY_ID",updatable=false,insertable=false)
publicResourceCategorygetResourceCategory()...{
returnresourceCategory;
}
@Override
publicbooleanequals(finalObjectother)...{
if(!(otherinstanceofResource))
returnfalse;
ResourcecastOther=(Resource)other;
returnnewEqualsBuilder().append(id,castOther.id).isEquals();
}
@Override
publicinthashCode()...{
returnnewHashCodeBuilder().append(id).toHashCode();
}
}
Province子类
@Entity
@DiscriminatorValue(
"
province
"
)
public
class
Province
extends
Resource
...
{
privateSet<City>cities=newHashSet<City>();
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER,mappedBy="province")
publicSet<City>getCities()...{
returncities;
}
}
子类City
@Entity
@DiscriminatorValue(
"
city
"
)
public
class
City
extends
Resource
...
{
privateProvinceprovince;
publicCity(Stringname,ResourceCategoryresourceCategory)...{
super(name,resourceCategory);
}
@ManyToOne
@JoinColumn(name="PARENT_ID")
publicProvincegetProvince()...{
returnprovince;
}
}
hibernate.cfg.xml
<!--
<mappingresource="ResourceCategory.hbm.xml"/>
<mappingresource="Resource.hbm.xml"/>
-->
<
mapping
class
="ResourceCategory"
/>
<
mapping
class
="Resource"
/>
hibernate助手类
public
class
HibernateUtil
...
{
privatestaticSessionFactorysessionFactory;
static...{
//Configurationconfiguration=newConfiguration();
AnnotationConfigurationconfiguration=newAnnotationConfiguration();
configuration.configure();
sessionFactory=configuration.buildSessionFactory();
}
publicstaticSessionFactorygetSessionFactory()...{
returnsessionFactory;
}
publicSessiongetSession()...{
returngetSessionFactory().openSession();
}
}
单元测试类
public
class
HibnerateTest
extends
TestCase
...
{
publicvoidtestListProvinceByHQL()...{
SessionFactorysessionFactory=HibernateUtil.getSessionFactory();
Sessionsession=sessionFactory.openSession();
Transactionctx=session.beginTransaction();
Queryquery=session.createQuery("fromProvince");
List<Province>provinces=query.list();
for(Provinceprovince:provinces)...{
System.out.println(province.getName());
}
ctx.commit();
}
publicvoidtestListProvinceByCriteria()...{
SessionFactorysessionFactory=HibernateUtil.getSessionFactory();
Sessionsession=sessionFactory.openSession();
Transactionctx=session.beginTransaction();
Criteriacriteria=session.createCriteria(Province.class);
List<Province>provinces=criteria.list();
for(Provinceprovince:provinces)...{
System.out.println(province.getName());
}
ctx.commit();
}
}
分别执行单元测试方法,他们输出的sql语句是一样的,但是输出的结果是不一样的。
select this_.ID as ID1_1_, this_.NAME as NAME1_1_, this_.COMMENTS as COMMENTS1_1_, this_.CATEGORY_ID as CATEGORY2_1_1_, cities2_.PARENT_ID as PARENT5_3_, cities2_.ID as ID3_, cities2_.ID as ID1_0_, cities2_.NAME as NAME1_0_, cities2_.COMMENTS as COMMENTS1_0_, cities2_.CATEGORY_ID as CATEGORY2_1_0_, cities2_.PARENT_ID as PARENT5_1_0_ from T_RESOURCE this_ left outer join T_RESOURCE cities2_ on this_.ID=cities2_.PARENT_ID where this_.CATEGORY_ID='province'
testListProvinceByHQL方式输出的是
上海
上海
上海
上海
江苏
江苏
江苏
江苏
testListProvinceByCriteria方式输出的是
上海
江苏
若把public Set<City> getCities()的映射改为
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "province")
@Fetch(FetchMode.SELECT)
或者
@OneToMany(cascade = CascadeType.ALL, mappedBy = "province")
@LazyCollection(LazyCollectionOption.FALSE)
则两个方法输出sql语句是一样的,且结果也一样
SQL:
select this_.ID as ID1_0_, this_.COMMENTS as COMMENTS1_0_, this_.NAME as NAME1_0_, this_.CATEGORY_ID as CATEGORY1_1_0_ from T_RESOURCE this_ where this_.CATEGORY_ID='province'
select cities0_.PARENT_ID as PARENT5_1_, cities0_.ID as ID1_, cities0_.ID as ID1_0_, cities0_.COMMENTS as COMMENTS1_0_, cities0_.NAME as NAME1_0_, cities0_.CATEGORY_ID as CATEGORY1_1_0_, cities0_.PARENT_ID as PARENT5_1_0_ from T_RESOURCE cities0_ where cities0_.PARENT_ID=?
select cities0_.PARENT_ID as PARENT5_1_, cities0_.ID as ID1_, cities0_.ID as ID1_0_, cities0_.COMMENTS as COMMENTS1_0_, cities0_.NAME as NAME1_0_, cities0_.CATEGORY_ID as CATEGORY1_1_0_, cities0_.PARENT_ID as PARENT5_1_0_ from T_RESOURCE cities0_ where cities0_.PARENT_ID=?
输出:
上海
江苏
从上面的现象可是看出,不论配置文件怎么变化,通过hql语句获得结果一直不变,而Criteria会变化,这就证明了Criteria是有bug的。
据此我得出结论:hibernate的Criteria在把关联的lazy属性设置为"false",且把fetch设置为join时,查找数据就存在bug
后来我使用xml配置文件的方式,也存在同样的问题,也证明了我的这个结论,
Resource.hbm.xml
<?
xmlversion="1.0"
?>
<!
DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/HibernateMappingDTD3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"
>
<
hibernate-mapping
>
<
class
name
="Resource"
table
="T_RESOURCE"
>
<
id
name
="id"
type
="string"
>
<
column
name
="ID"
/>
</
id
>
<
discriminator
column
="CATEGORY_ID"
type
="string"
/>
<
property
name
="name"
type
="string"
column
="NAME"
/>
<
many-to-one
name
="resourceCategory"
insert
="false"
update
="false"
class
="ResourceCategory"
column
="CATEGORY_ID"
/>
<
subclass
name
="Province"
discriminator-value
="province"
>
<
set
name
="cities"
lazy
="false"
fetch
="join"
>
<
key
>
<
column
name
="PARENT_ID"
/>
</
key
>
<
one-to-many
class
="City"
/>
</
set
>
</
subclass
>
<
subclass
name
="City"
discriminator-value
="city"
>
<
many-to-one
name
="province"
class
="Province"
column
="PARENT_ID"
/>
</
subclass
>
</
class
>
</
hibernate-mapping
>
ResourceCategory.hbm.xml
<?
xmlversion
=
"
1.0
"
?>
<!
DOCTYPEhibernate
-
mappingPUBLIC
"
-//Hibernate/HibernateMappingDTD3.0//EN
"
"
http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd
"
>
<
hibernate
-
mapping
>
<
class
name
=
"
ResourceCategory
"
table
=
"
T_RESOURCE_CATEGORY
"
>
<
idname
=
"
id
"
type
=
"
string
"
>
<
columnname
=
"
ID
"
/>
</
id
>
<
propertyname
=
"
name
"
type
=
"
string
"
column
=
"
NAME
"
/>
</
class
>
</
hibernate
-
mapping
>