4

I have a few classes that we are attempting to extend to allow reuse of code, but hibernate is having none of it. Here are the new classes and their extensions:

Super statement class

@MappedSuperclass
public abstract class CoreStatement<S extends Approval>
    implements java.io.Serializable
{

    public abstract Long getId();

    public abstract void setId(Long id);

    public abstract Set<S> getApprovals();

    public abstract void setApprovals(Set<S> approvals);

}

Base statement class - This does get extended later on, but via a single table inheritance

@Entity
@Table(name="EXPNS_STTMNT")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
        name="CLASS_ID",
        discriminatorType = DiscriminatorType.INTEGER
)
public abstract class ExpenseStatement extends CoreStatement<ExpenseApproval>
{
    private Set<ExpenseApproval> approvals;

    @Override
    @Id
    @Column(name="ID", unique=true, nullable=false, precision=10, scale=0)
    public Long getId() {
        return this.id;
    }

    @Override
    public void setId(Long id) {
        this.id = id;
    }

    @Override
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="statement",
        targetEntity = ExpenseApproval.class)
    public Set<ExpenseApproval> getApprovals() {
        return approvals;
    }

    public void setApprovals(Set<ExpenseApproval> approvals) {
        this.approvals = approvals;
    }
}

Approval super class

@MappedSuperclass
public abstract class Approval<T extends CoreStatement> implements java.io.Serializable  {
    public abstract Long getId();

    public abstract void setId(Long id);
    public abstract T getStatement();

    public abstract void setStatement(T statement);
}

Approval base class

@Entity
@Table(name="APPRVL")
public class ExpenseApproval extends Approval<ExpenseStatement>{
    private Long id;
    private ExpenseStatement statement;
    @Id
    @Column(name="ID", unique=true, nullable=false, precision=10, scale=0)
    public Long getId() {
        return this.id;
    }

    @Override
    public void setId(Long id) {
        this.id = id;
    }
    @Override
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="EXPENSE_STATEMENT_ID", nullable=true)
    public ExpenseStatement getStatement() {
        return statement;
    }

    @Override
    public void setStatement(ExpenseStatement statement) {
        this.statement= statement;
    }
}

When running through the UnitTests, we get the error:

java.lang.ExceptionInInitializerError Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Set, at table: EXPNS_STTMNT, for columns: [org.hibernate.mapping.Column(approvals)] at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:314) at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:292) ....

It appears to be a mapping error of some kind, but I can't narrow it down. Many people who have posted the issue before had the problem where their Annotations were located above the private property and also getters i.e. They mixed and matched their annotation placement, but this doesn't appear to be the case here. Does anyone else have any suggestions on what could be causing the issue?

Draken
  • 3,134
  • 13
  • 34
  • 54
  • Could it be due to you have your mappings on getters/setters methods instead of on top of each field? – Manu AG May 18 '16 at 14:45
  • Shouldn't matter, just as long as don't I mix and match. [See here for more details](http://stackoverflow.com/a/307238/833070) – Draken May 18 '16 at 14:50
  • On Base statement class, at getApprovals method. Check that annotation says MappedBy="statements", but on ExpenseApproval class there isnt property called statements. – Manu AG May 18 '16 at 15:07
  • `MappedBy` is done by the getProperty name, not the variable in the class. There is a property called `getStatement()` and it is mapped to `statement` – Draken May 18 '16 at 15:10
  • I've updated my code, and changed `expenseStatement` to `statement` to prevent confusion, since someone else commented that and then deleted the comment. And as expected, I still get the same error. Mapping is done by the getMethod naming convention, not the underlying variable – Draken May 18 '16 at 15:12
  • After further investigation, I think it's the use of Java generics in the collection on the `MappedSuperClass` causing the issue. This is a shame as I now have no way to enforce the classes to implement specific methods for that use sub classes of specified super classes. I'm investigating further – Draken May 19 '16 at 07:57

1 Answers1

0

Hibernate and it's error messages are generally terrible, but I managed to solve this. It was nothing to do with the set, but more the location of the annotations. The annotations should generally be on the superclass and not the base class.

So the example would be:

@MappedSuperclass
public abstract class Approval<T extends CoreStatement> implements java.io.Serializable  {

    private Long id;
    private T statement


    @Id
    @Column(name="ID", unique=true, nullable=false)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="STATEMENT_ID", nullable=true)
    public T getStatement() {
        return statement;
    }

    public void setStatement(T statement) {
        this.statement = statement;
    }
}

And then to set the appropriate column or override the attributes of the annotation, you can use @AssociationOverrides on top of the class naming convention.

@Entity
@Table(name="APPRVL")
@AssociationOverrides({
    @AssociationOverride(name="statement", joinColumns = @JoinColumn(name = "EXPENSE_STATEMENT_ID"))
})    
public class ExpenseApproval extends Approval<ExpenseStatement>{
    private ExpenseStatement statement;

}
Draken
  • 3,134
  • 13
  • 34
  • 54