我正在使用带有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
复制相似问题