Hibernate3.6から4.1.3Finalへのアップグレードを完了したところですが、最初はすべてうまくいったようです。ただし、最近、私の同僚の1人がこれをテストしました。あるシナリオでは、Hibernate内からNullPointerがスローされます(まったく同じDBにアップグレードする前は、この例外はスローされませんでした)。それは信じられないほど奇妙なシナリオです。以下のようなBlogPostというエンティティがあり、マップされたスーパークラスを拡張します(これも含まれています)。
@Entity
@Table(name = "blog_post")
public class BlogPost extends CommunityModelObject implements HasFeedPost {
@Lob
private String title;
@Lob
private String content;
@Enumerated
@Column(nullable = false)
private CBlogPost.Status status = CBlogPost.Status.UNPUBLISHED;
// Reference to the feed post that indicates that this blog post has been published
@OneToOne
@JoinColumn(name = "feed_post_id")
private FeedPost feedPost;
@ManyToOne
@JoinColumn(name = "posted_by_employee_id")
private Employee postedBy;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public CBlogPost.Status getStatus() {
return status;
}
public void setStatus(CBlogPost.Status status) {
this.status = status;
}
@Override
public FeedPost getFeedPost() {
return feedPost;
}
@Override
public void setFeedPost(FeedPost feedPost) {
this.feedPost = feedPost;
}
public Employee getPostedBy() {
return postedBy;
}
public void setPostedBy(Employee postedBy) {
this.postedBy = postedBy;
}
}
@Filter(name = "tenantFilter", condition = "(tenant_id = :tenantId or tenant_id is null)")
@MappedSuperclass
public abstract class CommunityModelObject extends ModelObject {
@IndexedEmbedded(prefix = "tenant", indexNullAs = IndexedEmbedded.DEFAULT_NULL_TOKEN)
@ManyToOne
@JoinColumn(name = "tenant_id")
protected Tenant tenant;
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
/**
* If the Tenant is null then it can be accessed / viewed by the entire "community" / user base
*/
public boolean isCommunityObject() {
return tenant == null;
}
}
@MappedSuperclass
public abstract class ModelObject extends BaseModelObject {
@Id
@GeneratedValue
private Long id;
@Override
public long getId() {
return (id == null ? 0 : id);
}
public void setId(long id) {
this.id = (id == 0 ? null : id);
}
}
@MappedSuperclass
public abstract class BaseModelObject implements java.io.Serializable {
// This annotation ensures that a column is not associated with this member (simply omitting the @Column annotation is not enough since
// that annotation is completely optional)
@Transient
private boolean doNotAutoUpdateDateUpdated = false;
@Version
protected int version;
@Column(name = "date_created")
protected Date dateCreated;
@Column(name = "date_updated")
protected Date dateUpdated;
public abstract long getId();
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public Date getDateCreated() {
return dateCreated;
}
public Date getDateUpdated() {
return dateUpdated;
}
/**
* This will set the dateUpdated to whatever is passed through and it will cause the auto update (pre-update) to NOT occur
*
* @param dateUpdated
*/
public void setDateUpdated(Date dateUpdated) {
doNotAutoUpdateDateUpdated = true;
this.dateUpdated = dateUpdated;
}
public void touch() {
// By setting date updated to null this triggers an update which results in onUpdate being called and the nett
// result is dateUpdated = new Date()
dateUpdated = null;
}
@PrePersist
protected void onCreate() {
dateCreated = new Date();
}
@PreUpdate
protected void onUpdate() {
if (!doNotAutoUpdateDateUpdated) {
dateUpdated = new Date();
}
}
@Override
public boolean equals(Object obj) {
long id = getId();
if (id == 0) {
return this == obj;
}
//Use Hibernate.getClass() because objects might be proxies
return obj != null &&
obj instanceof BaseModelObject &&
Hibernate.getClass(this) == Hibernate.getClass(obj) &&
getId() == ((BaseModelObject)obj).getId();
}
@Override
public int hashCode() {
Long id = getId();
return id == 0 ? super.hashCode() : id.intValue();
}
@Override
public String toString() {
return getClass().getSimpleName() + "-" + getId();
}
}
いくつかのシナリオでBlogPostにクエリを実行すると、最も奇妙なことが起こります。たとえば、以下のクエリを単独で実行すると正常に機能しますが、他の一連のクエリの中で実行すると、以下の例外が発生します。
select b from BlogPost b
java.lang.NullPointerException
at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:240)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:163)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:55)
at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1153)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1208)
at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101)
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:256)
ここでキッカーは、上記でリストしたすべてのマップされたスーパークラスからすべてのフィールドを取得し、それらをBlogPostに直接配置して、BlogPostに何も拡張せず、java.io.Serializableを実装すると、すべてが完全に機能することです。これにより、バグはマップされたスーパークラスまたはCommunityModelObjectに適用しているHibernateフィルターのいずれかに関連していると思います。
これを解決する方法について何かアイデアはありますか?これはHibernateで新しく導入されたバグだと思いますが、間違っている可能性があります。重大なバグ修正のために行う必要のあるHibernateSearchをアップグレードするには、できるだけ早くアップグレードする必要があるため、これが大きな問題を引き起こしています。
また、使用しているDBはMySQLであり、BIT列を処理するためにこのアップグレードを実行するときに作成した次のカスタムダイアレクトを使用していることにも注意してください。
public class MySQL5InnoDBDialectExt extends MySQL5InnoDBDialect {
private static final String BIT_STRING = "bit";
public MySQL5InnoDBDialectExt() {
super();
registerColumnType(Types.BOOLEAN, BIT_STRING);
}
}
ありがとう、ブレント