我正在使用带有Hibernate持久性的Dropwizard。这让我头疼,因为Hibernate在生成SQL以持久化对象之前似乎正在“丢失”对象生成的ID。
我有三个类:Checklist,Checkpoint,ChecklistItem,我把它们建模为双向一对多的关系。
我已经构建了域对象、DAO、公开它的资源以及一系列测试(DAO测试和一个全面集成测试)。
我注意到一些奇怪的(坏的)行为:
Checklist对象(和子对象)。下面是我认为应该在日志中看到的内容(摘自good DAO测试):
[main] DEBUG org.hibernate.engine.spi.ActionQueue - Executing identity-insert immediately
[main] DEBUG org.hibernate.SQL - insert into Checklists (id, description, locked, name, ownerUserId) values (null, ?, ?, ?, ?)
[main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 1
[main] DEBUG org.hibernate.engine.spi.ActionQueue - Executing identity-insert immediately
[main] DEBUG org.hibernate.SQL - insert into Checkpoints (id, name, checklistId) values (null, ?, ?)
[main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 1
[main] DEBUG org.hibernate.engine.spi.ActionQueue - Executing identity-insert immediately
[main] DEBUG org.hibernate.SQL - insert into ChecklistItems (id, description, name, checkpointId, state, url) values (null, ?, ?, ?, ?, ?)
[main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 1
[main] DEBUG org.hibernate.engine.spi.ActionQueue - Executing identity-insert immediately
[main] DEBUG org.hibernate.SQL - insert into ChecklistItems (id, description, name, checkpointId, state, url) values (null, ?, ?, ?, ?, ?)
[main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 2
[main] DEBUG org.hibernate.engine.spi.ActionQueue - Executing identity-insert immediately
[main] DEBUG org.hibernate.SQL - insert into ChecklistItems (id, description, name, checkpointId, state, url) values (null, ?, ?, ?, ?, ?)
[main] DEBUG o.h.id.IdentifierGeneratorHelper - Natively generated identity: 3
[main] DEBUG o.h.e.i.AbstractFlushingEventListener - Processing flush-time cascades下面是我在运行集成测试时得到的(坏):
127.0.0.1 - - "PUT /api/cl/v0.1/users HTTP/1.1" 201 - "-" "checklists-server (integration test client)" 240
DEBUG com.misys.uk.checklists.resources.ChecklistResource: Invoked ChecklistResource#createChecklist(uriInfo, newChecklist)
DEBUG org.hibernate.SQL: /* insert com.misys.uk.checklists.core.Checklist */ insert into Checklists (id, description, locked, name, ownerUserId) values (null, ?, ?, ?, ?)
DEBUG org.hibernate.SQL: /* insert com.misys.uk.checklists.core.Checkpoint */ insert into Checkpoints (id, name, checklistId) values (null, ?, ?)
WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper: SQL Error: 23502, SQLState: 23502
ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper: NULL not allowed for column "CHECKLISTID"; SQL statement:
/* insert com.misys.uk.checklists.core.Checkpoint */ insert into Checkpoints (id, name, checklistId) values (null, ?, ?) [23502-189]
DEBUG org.hibernate.SQL: /* insert com.misys.uk.checklists.core.ChecklistItem */ insert into ChecklistItems (id, description, name, checkpointId, state, url) values (null, ?, ?, ?, ?, ?)
WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper: SQL Error: 23502, SQLState: 23502
ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper: NULL not allowed for column "CHECKPOINTID"; SQL statement:
/* insert com.misys.uk.checklists.core.ChecklistItem */ insert into ChecklistItems (id, description, name, checkpointId, state, url) values (null, ?, ?, ?, ?, ?) [23502-189]
ERROR io.dropwizard.jersey.errors.LoggingExceptionMapper: Error handling a request: 319c21e9a8198202
! org.h2.jdbc.JdbcSQLException: NULL not allowed for column "CHECKPOINTID"; SQL statement:
! /* insert com.misys.uk.checklists.core.ChecklistItem */ insert into ChecklistItems (id, description, name, checkpointId, state, url) values (null, ?, ?, ?, ?, ?) [23502-189]
! at org.h2.message.DbException.getJdbcSQLException(DbException.java:345) ~[h2-1.4.189.jar:1.4.189]以下是我的域对象:
@Entity
@Table(name = "Checklists")
public class Checklist {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(unique = true)
private long id = Constants.UNASSIGNED;
@Column(nullable = false)
private long ownerUserId;
@NotEmpty @Length(max = 64) @Column(nullable = false, length = 64)
private String name;
@Length(max = 2000) @Column(length = 2000)
private String description;
@Column(nullable = false)
private boolean locked;
@OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL}) @OrderColumn(name = "cpOrderWithinCl")
@JoinTable(name = "ChecklistToCheckpoint",
joinColumns = @JoinColumn(name = "checklistId"),
inverseJoinColumns = @JoinColumn(name = "checkpointId"))
private List<Checkpoint> checkpoints;
public Checklist() {
// needed for Jackson
}
@JsonProperty
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
@JsonProperty
public long getOwnerUserId() {
return ownerUserId;
}
public void setOwnerUserId(final long ownerUserId) {
this.ownerUserId = ownerUserId;
}
@JsonProperty
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@JsonProperty @JsonInclude(Include.NON_NULL)
public String getDescription() {
return description;
}
public void setDescription(final String description) {
this.description = description;
}
@JsonProperty
public List<Checkpoint> getCheckpoints() {
return checkpoints;
}
public void setCheckpoints(final List<Checkpoint> checkpoints) {
this.checkpoints = Preconditions.checkNotNull(checkpoints);
}
private void addCheckpoint(final Checkpoint newCheckpoint) {
if (checkpoints == null) {
checkpoints = new LinkedList<>();
}
newCheckpoint.setParent(this);
checkpoints.add(Preconditions.checkNotNull(newCheckpoint));
}
private void removeCheckpoint(final Checkpoint checkpoint) {
if (checkpoints != null) {
checkpoints.remove(Preconditions.checkNotNull(checkpoint));
}
}
@JsonProperty
public boolean isLocked() {
return locked;
}
public void setLocked(final boolean locked) {
this.locked = locked;
}
// equals() and hashCode() ...
}实体Checkpoint,它与ChecklistItem有一个多个关系。
@Entity
@Table(name = "Checkpoints")
public class Checkpoint {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(unique = true)
private long id = Constants.UNASSIGNED;
@NotEmpty @Length(max = 64) @Column(nullable = false, length = 64)
private String name;
@ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "checklistId", nullable = false)
private Checklist parent;
@OneToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL) @OrderColumn(name = "ciOrderWithinCp")
@JoinTable(name = "CheckpointToChecklistItem",
joinColumns = @JoinColumn(name = "checkpointId"),
inverseJoinColumns = @JoinColumn(name = "checklistItemId"))
private List<ChecklistItem> items;
public Checkpoint() {
// Needed for Jackson
}
@JsonProperty
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
@JsonProperty
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@JsonIgnore
public Checklist getParent() {
return parent;
}
public void setParent(final Checklist parent) {
this.parent = parent;
}
@JsonProperty
public List<ChecklistItem> getItems() {
return items;
}
public void setItems(final List<ChecklistItem> items) {
this.items = Preconditions.checkNotNull(items);
}
public void addItem(final ChecklistItem newItem) {
if (items == null) {
items = new LinkedList<>();
}
Preconditions.checkNotNull(newItem).setParent(this);
items.add(newItem);
}
private void removeItem(final ChecklistItem item) {
if (items != null) {
items.remove(Preconditions.checkNotNull(item));
}
}
// equals() and hashCode() ...
}实体ChecklistItem:
@Entity
@Table(name = "ChecklistItems")
public class ChecklistItem {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(unique = true)
private long id = Constants.UNASSIGNED;
@NotEmpty @Length(max = 64) @Column(nullable = false, length = 64)
private String name;
@Length(max = 2000) @Column(length = 2000)
private String description;
@Length(max = 2000) @Column(length = 2000)
private String url;
@ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "checkpointId", nullable = false)
private Checkpoint parent;
@Column(nullable = false, length=16) @NotNull @StringEnumeration(enumClass = ChecklistItemState.class)
private String state = ChecklistItemState.UNCHECKED.name();
public ChecklistItem() {
// needed for Jackson
}
@JsonProperty
public long getId() {
return id;
}
public void setId(final long id) {
this.id = id;
}
@JsonProperty
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@JsonProperty @JsonInclude(Include.NON_NULL)
public String getDescription() {
return description;
}
public void setDescription(final String description) {
this.description = description;
}
@JsonProperty @JsonInclude(Include.NON_NULL)
public String getUrl() {
return url;
}
public void setUrl(final String url) {
this.url = url;
}
@JsonIgnore
public Checkpoint getParent() {
return parent;
}
public void setParent(final Checkpoint parent) {
this.parent = parent;
}
@JsonProperty
public ChecklistItemState getState() {
return ChecklistItemState.valueOf(state);
}
// equals() and hashCode() ...
}我相当肯定这与DBMS无关(我尝试过几个不同的关系,也发生了相同的问题),而且with确定它与在事务下运行没有任何关系(我已经用调试println()来证实这一点)。
我怀疑这与我试图在域对象之间执行一个-多个映射的方式有关。
有什么想法吗?
发布于 2015-10-18 18:55:47
问题很简单:当通过Hibernate (实体之间存在双向关系)持久化对象图时,这些关系将由Hibernate在两个方向上导航。如果它们不正确,Hibernate将失败。
我一开始没有注意到这一点,因为我正在使用Builder构建单元测试中的对象,而生成器本身也经过了彻底的测试--我以为我已经完成了这个任务。显然不是!
集成测试失败了,因为错误在于JSON注释(或缺少)来处理这种双向关系--而这只是在我的全面测试中才被揭示出来的,因为只有那时它才在执行破碎的JSON反向引用行为!
@JsonManagedReference和@JsonBackReference正确地注释对象以处理双向引用(子->父和父->子)。@JsonIgnore注释,上述内容将无法工作。https://stackoverflow.com/questions/33198564
复制相似问题