我刚刚完成了从 Hibernate 3.6 到 4.1.3 Final 的升级,起初一切似乎都很好。但是,我的一位同事最近对此进行了测试,在一个场景中,他从 Hibernate 中抛出了一个 NullPointer(并且在我们为完全相同的数据库升级之前没有抛出这个异常)。这是一个非常奇怪的场景。我们有一个名为 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 中新引入的错误,但我可能错了。这给我们带来了重大问题,因为我们需要尽快升级以升级 Hibernate Search,我们需要这样做来修复关键错误。
另请注意,我们使用的数据库是 MySQL,具有以下自定义方言,这是我在进行此升级以处理我们的 BIT 列时编写的:
public class MySQL5InnoDBDialectExt extends MySQL5InnoDBDialect {
private static final String BIT_STRING = "bit";
public MySQL5InnoDBDialectExt() {
super();
registerColumnType(Types.BOOLEAN, BIT_STRING);
}
}
谢谢,布伦特