Nested @IdClass not working with Hibernate


#1

I use spring data jpa, hibernate I have a complex structure. I search to get samples with some object fetched

@Entity
@IdClass(SamplingsPK.class)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings {

    @Id
    private int year;

    @Id
    @GeneratedValue
    private Integer sequenceId;

    @OneToOne
    private Products product;

    @OneToOne
    private Machines machine;

    @OneToOne
    private Dimensions dimension;

    @OneToOne
    private Colors color;

    @OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Samples> samples = new ArrayList<>();
    ...

}

@Entity
@IdClass(SamplesPK.class)
public class Samples extends BaseEntity { 

    @Id
    private String sampleLetter;

    @Id
    @ManyToOne(optional = false)
    @JoinColumns({
        @JoinColumn(name = "sampling_id", referencedColumnName = "sequenceId"),
        @JoinColumn(name = "sampling_year", referencedColumnName = "year")})
    private Samplings sampling;

    @OneToOne(mappedBy = "sample", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    private TestSamples testSamples;
    ...
}

@Entity
public class TestSamples {

    @Id
    @SequenceGenerator(name = "test_samples_id_seq", sequenceName = "test_samples_id_seq", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "test_samples_id_seq")
    private Integer id;

    @OneToOne(fetch = FetchType.LAZY)
    private Samples sample;

    @OneToOne(mappedBy = "testSample", fetch = FetchType.LAZY)
    private Compressions compressionTest;

    @OneToOne(mappedBy = "testSample", fetch = FetchType.LAZY)
    private Durabilities durabilityTest;

    @OneToOne(mappedBy = "testSample", fetch = FetchType.LAZY)
    private Scalings scalingTest;

    @OneToOne(mappedBy = "testSample", fetch = FetchType.LAZY)
    private Granulometries granulometryTest;

    @OneToOne(fetch = FetchType.LAZY)
    private Absorptions absorptionTest;
    ...
}

When I save a new samplings, i do

Samplings sampling = new Samplings();
..
Samples sample = new Samples();
 ...
TestSamples testSamples = new TestSamples();
testSamples.setSample(sample);
..
sample.setTestSamples(testSamples);

samplings.addSample(sample);

samplings = samplingsRepository.save(sampling);

After saving, If i try to get a samples with testSample, I run this query

    @Query(
            value = "select s from Samples s Join fetch s.sampling sp Left Join fetch sp.machine m Join fetch sp.product p Join  fetch p.productType pt Join Fetch s.testSamples",
            countQuery = "select count(s) from Samples s Join s.sampling sp Left Join  sp.machine m Join  sp.product p Join  p.productType Join s.testSamples")
    public Page<Samples> findAllFullSample(Pageable pageable);

When I check sample, TestSamples is always null

I found a very bad workaround…

Page<Samples> pageSamples = samplesRepository.findAllFullSample(pageable);
List<Samples> samples = pageSamples.getContent();
for (Samples sample : samples) {
	TestSamples testSample= testSamplesRepository.findSamplesWithFullProductAndCompressionTest(sample.getSampling().getSequenceId(),sample.getSampling().getYear(), sample.getSampleLetter());
    sample.setTestSamples(testSample);
}
return pageSamples;

Query

    @Query(value = "select ts from TestSamples ts Join ts.sample s left Join Fetch ts.compressionTest where s.sequenceId=:id and s.year=:year and s.sampleLetter=:sampleLetter")
    public TestSamples findSamplesWithFullProductAndCompressionTest(@Param("id") Integer id, @Param("year") int year, @Param("sampleLetter") String sampleLetter);

So why TestSample is null when I get it from Sample


#2

It’s not clear what you are asking. You need to provide the SQL queries Hibernate executes along with the entire code involved in your problem so we can figure out what you are doing.

Also, the fact that you rely on cascading for the ToOne associations is a code smell.

The sampling and the testSamples are supposed to exist prior to creating the Samples.


#3

Query done for findAllFullSample are available here
https://pastebin.com/yCuCnMCN

I just try from Sample to get TestSample

       Page<Samples> pageSamples = samplesRepository.findAllFullSample(pageable);
        List<Samples> samples = pageSamples.getContent();
        for (Samples sample : samples) {
           
		   //actually  sample.getTestSample() return null... and in findAllFullSample TestSamples is fetched, line below allow to get it... but is not very good for performance
		   
           TestSamples testSample= testSamplesRepository.findSamplesWithFullProductAndCompressionTest(sample.getSampling().getSequenceId(), sample.getSampling().getYear(), sample.getSampleLetter());
            sample.setTestSamples(testSample);
        }
        return pageSamples;

For the ToOne, you talk about this

@OneToOne(mappedBy = "sample", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
    private TestSamples testSamples;

So you suggest

@OneToOne(mappedBy = “sample”, fetch = FetchType.LAZY, orphanRemoval = true)
private TestSamples testSamples;

And would save with

Samplings sampling = new Samplings();
..
Samples sample = new Samples();
 ...
TestSamples testSamples = new TestSamples();
testSamples.setSample(sample);
..
sample.setTestSamples(testSamples);

samplings.addSample(sample);

samplings = samplingsRepository.save(sampling);
testSamplesRepository.save(testSamples);

#4

I noticed you are calling a query from within a for loop. That’s very inefficient. Just execute a single query and pass all the samples you are iterating to be part of an IN clause.

More, it’s not clear why you are setting the sample association. Is it for the UI only? If you really need to set the FKs in the DB, just run a bulk update query.


#5

I done a query in the loop exactly because when i try to fetch this entity it return null… is the main problem… that why I created this post…

you mean this

samplings.addSample(sample);

?


#6

You need to see why the query returned null. Maybe the Session was not flushed. Maybe you are using a read-only transaction.

Anyway, most likely it is because of how you are using Spring than Hibernate.

Try to replicate it with this test case and, if it works just fine, then you will know it’s a Spring usage issue.


#7

get more precision on stack… in AbstractEntityTuplizer.java hibernate class, buildMappedIdentifierValueMarshaller

"it looks like Hibernate compares the types on the @Entity and the matching fields on its @IdClass and tries to proceed either way. This check can be found in the source here.

If the types on the PK match a NormalMappedIdentifierValueMarshaller is used to construct the PK identifier value. If they don’t it seems to make a best effort to proceed with the expressively named IncrediblySillyJpaMapsIdMappedIdentifierValueMarshaller . It seems that from there the code is following a ‘less well trodden’ path with the mixed types - clearly the save works out, but there are surprises to be found later on. "

So if in SamplesPK, I replace

private SamplingsPK sampling;

by

private Samplings sampling;

TestSamples is feeded… so it seem a usecase is missing

Problem happen also if we use a @Embedded on TestSamples instead of @OneToOne in Samples entity


#8

Just send the replicating test case and we will check it out.


#9

i created a test here
https://pastebin.com/eQNCab1e


#10

I noticed that the problem is caused because the SamplesPK was not properly initialized:

0 = {NestedCompositeKeyTest$SamplesPK@4647} 
 sampleLetter = "A"
 sampling = {NestedCompositeKeyTest$SamplingsPK@4649} 
  year = 1
  sequenceId = {Integer@4650} "1"
   value = 1

However, I’m not sure if JPA demands supporting nested IdClasses, so it might not be a bug if it’s not specified to work.

Try to open a Jira issue, and maybe figure out the problem and send a Pull Request as well if you want to make sure this will be included in the next releases.