beginner_
beginner_

Reputation: 7622

Hibernate: Inheritance and relationship mapping + generics

I'm using spring-data JPA with hibernate. I'm having extreme difficulty of getting my inheritance and relationship mappings to work correctly.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name="compound")
@DiscriminatorColumn(name="compound_type")
@DiscriminatorOptions(force=true)
public abstract class Compound<T extends Containable> {

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "pk.compound",
        cascade = CascadeType.ALL, orphanRemoval = true)
    @LazyCollection(LazyCollectionOption.FALSE)     
    private List<CompoundComposition> compositions = new ArrayList<>(); 

    @OneToMany(fetch = FetchType.EAGER, mappedBy="compound",
        targetEntity=Containable.class, cascade = CascadeType.ALL) 
    @LazyCollection(LazyCollectionOption.FALSE)     
    private Set<T> containables = new HashSet<T>();

}

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name="containable")
@DiscriminatorColumn(name="containable_type")
@DiscriminatorOptions(force=true)
public abstract class Containable<T extends Compound> {     

    @ManyToOne(optional=true, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private T compound;
}

The idea is that a certain implementation of AbstractCompound can only be associated with one specific implementation of Containable (and vice-versa). This leads to following implementaions:

@Entity 
@DiscriminatorValue("TestCompound") 
public class TestCompound extends AbstractCompound<TestContainable> {
}

@Entity 
@DiscriminatorValue("RegistrationCompound")
public class RegistrationCompound extends AbstractCompound<Batch> {

    @Column(name = "reg_number", unique = true)
    private String regNumber;
}

@Entity 
@DiscriminatorValue("TestContainable")  
public class TestContainable extends Containable<TestCompound> {
}

@Entity 
@DiscriminatorValue("Batch")    
public class Batch extends Containable<RegistrationCompound>{

    @Column(name = "batch_number")
    private Integer batchNumber;
}

I've played around with all the inheritance strategies and for Compound-hierarchy single-table is the only one that at least partially works. In case of JOINED or table _per_class hibernate creates inconsistent and wrong!!! foreign keys, namely from test_containable to registration_compound (But not from Batch to test_compound, here it correctly maps to registration_compound only).

On Containable side it does not seem to matter what strategy I use.

Now to the actual issue in my Tests. The specific test class. has 3 tests. All doing a specific search for a "TestCompound" instance. The thing is the first executed of these 3 test cases always passed, the other 2 always fail. The order at which the are run seems to be random (JUnit + @RunWith(SpringJUnit4ClassRunner.class)). This means that any of the tests pass, if it is the first one to run.

The tests that fail throw following exception:

org.hibernate.WrongClassException: Object with id: 1000 was not of the specified
    subclass: RegistrationCompound (loaded object was of wrong class class TestCompound)

In cases of the first test, hibernate issues following correct select for fetching the Containables

Hibernate: select containabl0_.compound_id as compound8_1_1_, containabl0_.id as id0_1_, 
containabl0_.id as id0_0_, containabl0_.created as created0_0_, 
containabl0_.created_by as created4_0_0_, containabl0_.last_modified as last5_0_0_, 
containabl0_.last_modified_by as last6_0_0_, containabl0_.compound_id as compound8_0_0_, 
containabl0_.batch_number as batch7_0_0_, containabl0_.containable_type as containa1_0_0_ 
from containable containabl0_ where  containabl0_.containable_type in ('Batch', 'TestContainable') 
and containabl0_.compound_id=?

and the List<CompoundComposition> compositions is selected in another select statement. So they are a total of 3 statements: Get compound, get containables, get compositions.

For the second and 3rd test, the SQL for fetching the containables is merged with the on for fetching compositions and it is build in a way so it is trying to select a RegistrationCompound instead of a TestCompound, as example it contains

registrati1_.reg_number as reg10_1_0_, 

and reg_number is a property of RegistrationCompound only. In both cases the first select statement that selects the actual compound correctly contains the following in the where clause:

testcompou0_.compound_type='TestCompound'

So this is very confusing. Why does it depend on the order the test are run? Why in the heck does it try to select a RegistrationCompound?

Here is the simplest test of the 3 tests:

@Test
@Transactional
public void testFindByCompositionPkStructureId() {
    System.out.println("findByCompositionPkStructureId");

    Long structureId = 1000L;

    TestCompound compound = new TestCompound();
    compound.setId(1000L);
    compound.setCas("9999-99-9");
    compound.setCompoundName("Test Compound");

    List<TestCompound> result = 
        testCompoundRepository.findByCompositionsPkStructureId(structureId);
    assertEquals(compound, result.get(0));
}

If this test is run as the second or third I get the wrong class exception!!! Does anyone have any idea what the heck is going on here? Solution?

Upvotes: 4

Views: 4267

Answers (1)

beginner_
beginner_

Reputation: 7622

The issue was one of the mappings:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name="containable")
@DiscriminatorColumn(name="containable_type")
@DiscriminatorOptions(force=true)
public abstract class Containable<T extends Compound> {     

    @ManyToOne(optional=true, fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private T compound;
}

This mapping is missing the target entity. Correct is

@ManyToOne(optional=true, fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = Compound.class)

For some reason hibernate just assumed the target is RegistrationCompound instead of throwing an exception. Pretty annoying because else it would have been easy to find the issue. But like this it almost drove me crazy.

Upvotes: 8

Related Questions