1

I am using Hibernate 3.6 with a PostgreSQL database. I have a one-to-many relationship mapped with a composite key using @IdClass. When I invoke a SaveOrUpdate on the owning entity, I receive an exception when hibernate attempts to insert the child relationsip.

The exception is:

org.postgresql.util.PSQLException: The column index is out of range: 4, number of columns: 3.
at org.postgresql.core.v3.SimpleParameterList.bind(SimpleParameterList.java:56)
at org.postgresql.core.v3.SimpleParameterList.setNull(SimpleParameterList.java:141)
at org.postgresql.jdbc2.AbstractJdbc2Statement.setNull(AbstractJdbc2Statement.java:1215)
at org.postgresql.jdbc3.AbstractJdbc3Statement.setNull(AbstractJdbc3Statement.java:1490)
at org.postgresql.jdbc4.AbstractJdbc4Statement.setNull(AbstractJdbc4Statement.java:84)
at com.zaxxer.hikari.proxy.PreparedStatementJavassistProxy.setNull(PreparedStatementJavassistProxy.java)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:78)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:283)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:278)
at org.hibernate.type.ComponentType.nullSafeSet(ComponentType.java:317)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2195)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2431)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2875)
at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:79)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:184)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)

...

The entity that is failing looks like this (it includes the class annotated in @IdClass):

@Entity
@IdClass(DsbRowDataDashboard.RowDataDashboardPK.class)
@Table(name = "dsb_row_data_dashboard")
public class DsbRowDataDashboard implements Serializable {
    private static final long serialVersionUID = 2036988934943807608L; 

    public static final String NQ_GetRowsInvolvedForWorkstreamMoveUp = "DsbRowDataDashboard.getRowsInvolvedForWorkstreamMoveUp";
    public static final String NQ_GetRowsInvolvedForWorkstreamMoveDown = "DsbRowDataDashboard.getRowsInvolvedForWorkstreamMoveDown";
    public static final String NQ_GetRowsInvolvedForSubtaskMoveUp = "DsbRowDataDashboard.getRowsInvolvedForSubtaskMoveUp";
    public static final String NQ_GetRowsInvolvedForSubtaskMoveDown = "DsbRowDataDashboard.getRowsInvolvedForSubtaskMoveDown";
    public static final String NQ_FindMaxPositionForWorkstream = "DsbRowDataDashboard.findMaxPositionForWorkstream";
    public static final String NQ_FindMaxPositionForSubtask = "DsbRowDataDashboard.findMaxPositionForSubtask";

    @Id
    @Column(name = "row_data_id", insertable = false, updatable = false)
    private Long rowDataId;

    @Id
    @Column(name = "dashboard_id", insertable = false, updatable = false)
    private Long dashboardId;

    @Column(name = "row_position")
    private Long position;

    @ManyToOne// DO NOT cascade anything here
    @JoinColumn(name = "row_data_id")
    private DsbRowData rowData;

    @ManyToOne // DO NOT cascade anything here
    @JoinColumn(name = "dashboard_id")
    private DsbDashboard dashboard;

    public DsbRowDataDashboard() {
    }

    public DsbRowDataDashboard(DsbRowData rowData, DsbDashboard dsbDashboard, Long position) {
        this.rowData = rowData;
        this.dashboard = dsbDashboard;
        this.position = position;
    }

    public Long getRowDataId() {
        return rowDataId;
    }

    public void setRowDataId(Long rowDataId) {
        this.rowDataId = rowDataId;
    }

    public Long getDashboardId() {
        return dashboardId;
    }

    public void setDashboardId(Long dashboardId) {
        this.dashboardId = dashboardId;
    }

    public Long getPosition() {
        return position;
    }

    public void setPosition(Long position) {
        this.position = position;
    }

    public DsbRowData getRowData() {
        return rowData;
    }

    public void setRowData(DsbRowData rowData) {
        this.rowData = rowData;
    }

    public DsbDashboard getDashboard() {
        return dashboard;
    }

    public void setDashboard(DsbDashboard dashboard) {
        this.dashboard = dashboard;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((rowDataId == null) ? 0 : rowDataId.hashCode());
        result = prime * result + ((dashboardId == null) ? 0 : dashboardId.hashCode());
        result = prime * result + ((position == null) ? 0 : position.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof DsbRowDataDashboard))
            return false;
        DsbRowDataDashboard other = (DsbRowDataDashboard) obj;
        if (rowDataId == null) {
            if (other.rowDataId != null)
                return false;
        } else if (!rowDataId.equals(other.rowDataId))
            return false;
        if (dashboardId == null) {
            if (other.dashboardId != null)
                return false;
        } else if (!dashboardId.equals(other.dashboardId))
            return false;
        if (position == null) {
            if (other.position != null)
                return false;
        } else if (!position.equals(other.position))
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "DsbRowDashboard [rowDataid=" + rowDataId + ", dashboardId = " + dashboardId + "position=" + position + "]";
    }

    public static class RowDataDashboardPK implements Serializable {
        private static final long serialVersionUID = -1665119146485718132L;

        protected Long rowDataId;
        protected Long dashboardId;

        public RowDataDashboardPK() {
        }

        public RowDataDashboardPK(Long rowDataId, Long dashboardId) {
            this.rowDataId = rowDataId;
            this.dashboardId = dashboardId;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((rowDataId == null) ? 0 : rowDataId.hashCode());
            result = prime * result + ((dashboardId == null) ? 0 : dashboardId.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (!(obj instanceof RowDataDashboardPK))
                return false;
            RowDataDashboardPK other = (RowDataDashboardPK) obj;
            if (rowDataId == null) {
                if (other.rowDataId != null)
                    return false;
            } else if (!rowDataId.equals(other.rowDataId))
                return false;
            if (dashboardId == null) {
                if (other.dashboardId != null)
                    return false;
            } else if (!dashboardId.equals(other.dashboardId))
                return false;
            return true;
        }
    }

}

The relationship on the parent looks like this:

@OneToMany(fetch = FetchType.LAZY, mappedBy = "rowData", orphanRemoval = true)
@Cascade({CascadeType.ALL})
private Set<DsbRowDataDashboard> rowPositions = new HashSet<DsbRowDataDashboard>(0);

I've enabled all the relevant debug for hibernate to see the sql and debug. This is what happens that leads to failure:

[DEBUG] [                                                          org.hibernate.SQL:] [  111] - 
    /* insert com.myapp.model.DsbRowDataDashboard
        */ insert 
        into
            dsb_row_data_dashboard
            (dashboard_id, row_position, row_data_id) 
        values
            (?, ?, ?)
[TRACE] [                              org.hibernate.type.descriptor.sql.BasicBinder:] [   82] - binding parameter [1] as [BIGINT] - 1392
[TRACE] [                              org.hibernate.type.descriptor.sql.BasicBinder:] [   82] - binding parameter [2] as [BIGINT] - 0
[TRACE] [                              org.hibernate.type.descriptor.sql.BasicBinder:] [   82] - binding parameter [3] as [BIGINT] - 50
[TRACE] [                              org.hibernate.type.descriptor.sql.BasicBinder:] [   70] - binding parameter [4] as [BIGINT] - <null>
[DEBUG] [                                         org.hibernate.jdbc.AbstractBatcher:] [  418] - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)

The previous sql to this is the parent row being inserted and it is a success. Then hibernate attempts to insert the child and it fails. The entire transaction is rolled back.

I set both sides of the relationship and then call a hibernate saveorupdate:

   DsbRowDataDashboard rowDataDashboard = new DsbRowDataDashboard(rowData, rowData.getDsbDashboard(), maxpos);
    rowData.getRowPositions().add(rowDataDashboard);
    dsbRowDataDao.update(rowData);

-----

    public T update(T obj) {
        getSessionFactory().getCurrentSession().saveOrUpdate(obj);
        return obj;
    }

I've seen two other questions with pretty much the exact same issue, but no real solution.

Here is one that is very close to this same issue:

https://stackoverflow.com/questions/37092382/spring-data-jpa-column-index-out-of-range

Any insight is greatly appreciated.

Thank you.

Community
  • 1
  • 1
Gary
  • 37
  • 1
  • 9
  • Try putting `@Id` annotations on `@ManyToOne` fields and delete the `Long` id fields. Report back if the problem persists! – Janez Kuhar Oct 30 '16 at 00:29
  • Nevermind my first comment, it's just wrong. The problem (I think) is that you're setting `DsbRowData` and `DsbDashboard` fields in `DsbRowDataDashboard` ctor. Option `@Cascade({CascadeType.ALL})` in one of your owning relationships will not save these fields to the DB. It will save the `Long`s, which are still `null`. I hope this helps. – Janez Kuhar Oct 30 '16 at 01:17
  • Thanks @Janez. That fixes this issue. I use read-only (insertable/updatable = false) pointing to the same db columns as persisted relationships often to get directly at the id without invoking the heavy load of large entities. Its the first time I tried this with an IdClass. So I am wondering if this is a bug or known limitation of idclass? – Gary Oct 30 '16 at 01:41
  • @Janez. Your first solution creates a work around (though it mimics embeddedid). The longs where marked read-only (insertable/updatable = false), so it would never try to save those. The issue was the 4th paramter attempt as I showed in the trace from the log (in my post). There are other general answer workarounds as you suggested. Some just commented that hibernate is "buggy" with many read-only fields in the same entity. I do it all the time, so this is something with IdClass specifically. Just not enough trace to know where it breaks in hibernate. – Gary Oct 30 '16 at 01:44
  • Try removing `insertable=false` from `@Id` fields and change nothing else. See if that works. – Janez Kuhar Oct 30 '16 at 01:46
  • @Janez That defeats the purpose of a read-only field. Insertable = false and updatable = false make the field read only. Read only fields are good for many reasons. Your first suggestion is an ok workaround for now. I don't need those fields as read-only, so I made the change and have moved on. But it is still an underlying problem. Something in the hibernate reflection process is calculating 4 parameters from the annotations, instead of 3. So when the AbstractEntityPersister dehydrate method is called, it is being passed 4 fields in the fields[] object array, instead of the 3. – Gary Oct 30 '16 at 02:10
  • Your problem is most confusing to me, as you no doubt must have noticed, since I'm constantly contradicting myself. You should set the `Long`s in `DsbRowDataDashboard` constructor and loose the (insertable/updatable = false) on the `Longs` and add them to `DsbRowData` and `DsbDashboard` fields. This way you'll still have read only fields and it will work as intended. – Janez Kuhar Oct 30 '16 at 02:39
  • And this is no bug nor an error. Hibernate is allowed to throw exceptions if misused and reserves the right not to explain itself. ^^ – Janez Kuhar Oct 30 '16 at 02:50

0 Answers0