1

I am encountering a problem when trying to implement a mixed inheritance strategy using JPA. The same behaviour is observed (two inserts - one for each subclass level) when using Hibernate 5.2.18 (JPA 2.1), 5.3.5 (JPA 2.2), and 5.4.22 (JPA 2.2).

I believe my implementation is inline with what is suggested in these threads but perhaps there is a subtlety I have missed:

@Entity
@Table(name = "TABLE_A")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
abstract class A {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

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

    public long getId() {
        return id;
    }
}

@Entity
@SecondaryTable(name = "TABLE_B")
@DiscriminatorValue("B")
class B extends A {

    @Column(table = "TABLE_B")
    private String property1;

    public String getProperty1() {
        return property1;
    }

    public void setProperty1(String property1) {
        this.property1 = property1;
    }
}

@Entity
@SecondaryTable(name = "TABLE_B")
@DiscriminatorValue("C")
class C extends B {

    @Column(table = "TABLE_B")
    private String property2;

    public String getProperty2() {
        return property2;
    }

    public void setProperty2(String property2) {
        this.property2 = property2;
    }
}

An attempt to persist (insert) an objet of type C results in hibernate attempting to perform two inserts into TABLE_B:

insert into TABLE_A (id, DTYPE) values (null, 'C')
Natively generated identity: 2
insert into TABLE_B (property1, id) values (?, ?)
binding parameter [1] as [VARCHAR] - [foo]
binding parameter [2] as [BIGINT] - [2]
insert into TABLE_B (property2, id) values (?, ?)
binding parameter [1] as [VARCHAR] - [bar]
binding parameter [2] as [BIGINT] - [2]
could not execute statement [n/a] 
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PRIMARY KEY ON TABLE_B(ID) [2, 'foo', NULL]"; 

Finally, if I change B to be a @MappedSuperclass instead of an @Entity, I am able to persist a C object correctly but this changes semantics and I can no longer persist B objects as they are no longer entities in their own right.

Thanks,

EDIT:

Here is my DDL:

CREATE TABLE TABLE_A  (
    ID BIGINT IDENTITY NOT NULL PRIMARY KEY,
    DTYPE VARCHAR(16) NOT NULL
);

CREATE TABLE TABLE_B  (
    ID BIGINT IDENTITY NOT NULL PRIMARY KEY,
    PROPERTY1 VARCHAR(32),
    PROPERTY2 VARCHAR(32),
    CONSTRAINT FK_TABLE_B_PARENT FOREIGN KEY (ID) REFERENCES TABLE_A (ID)
);

I would attach an ER diagram but, as this is my first post, I don't have the minimal reputation which would allow me to do so.

Andy Edgar
  • 86
  • 1
  • 5
  • I should also add that this problem is similar to [this](https://stackoverflow.com/questions/59406310/hibernate-secondarytable-for-both-parent-and-child-with-the-same-name/64915429#64915429) unanswered question but the context is different – Andy Edgar Nov 20 '20 at 09:07
  • Could you please show ER diagram of related part of your schema. – SternK Nov 20 '20 at 10:04

1 Answers1

0

I had a similar issue, and I was able to solve it by using @MappedSuperclass.

The easiest way to prevent multiple inserts would be to move all the properties down the hierarchy past class B. You can create an abstract class between B and C and use the @MappedSuperclass annotation if you wish to keep the properties defined separately while keeping B as a managed type.

Here would be the full hierarchy:

@Entity
@Table(name = "TABLE_A")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
class A {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

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

    public long getId() {
        return id;
    }
}

/**
 * This will maintain B's interface since the method definitions will now be in an abstract subclass.
 */
interface BProperties {
    String getProperty1();
    void setProperty1(String property1);
}

@Entity
abstract class B extends A implements BProperties {

}

@MappedSuperclass
abstract class AbstractB extends B {

    @Column(table = "TABLE_B")
    private String property1;

    public String getProperty1() {
        return property1;
    }

    public void setProperty1(String property1) {
        this.property1 = property1;
    }
}

@Entity
@SecondaryTable(name = "TABLE_B")
@DiscriminatorValue("C")
class C extends AbstractB {

    @Column(table = "TABLE_B")
    private String property2;

    public String getProperty2() {
        return property2;
    }

    public void setProperty2(String property2) {
        this.property2 = property2;
    }
}

(If you did not need B as a managed type, then you do not need an additional AbstractB class; you can keep the properties in B and mark the class with @MappedSuperclass)

The problem here is that class B can no longer contain properties. If it does, all of its children will fail because of the issue with multiple inserts. If you did need a concrete class with properties defined only in B (or now AbstractB with this pattern), then you can extend AbstractB and declare the class with the @SecondaryTable and @Entity annotations. Example:

@Entity
@SecondaryTable(name = "TABLE_B")
@DiscriminatorValue("B")
class ConcreteB extends AbstractB {

}
heisbrandon
  • 1,180
  • 7
  • 8