Hibernate lazy loading doesn't work (@JoinColumnOrFormulas)


#1

I’ve this class

@javax.persistence.Entity
@Table(name = "OD_STOCK")
@Inheritance(strategy = InheritanceType.JOINED)
@PrimaryKeyJoinColumn(name = "OD_STOCKID")
@XmlRootElement(name = "Stock")
public class Stock extends Entity  {

 @ManyToOne(fetch = FetchType.LAZY)
    @NotFound(action = NotFoundAction.IGNORE)
    @JoinColumnsOrFormulas({@JoinColumnOrFormula(formula = @JoinFormula(value = "STOCKGROUPCODE", referencedColumnName = "GD_CODEID")),
            @JoinColumnOrFormula(formula = @JoinFormula(referencedColumnName = "GD_CODETYPE", value = "'STOCKGROUP'"))})
    private StockGroup stockGroup;

}

But when i load the Stock the stockGroup is always loaded… lazy loaded is not working.

I see in the SQL log this SELECT to load the stockGroup

   select
        stockgroup0_.GD_CODEID as GD_CODEI2_52_0_,
        stockgroup0_.GD_CODETYPE as GD_CODET1_52_0_,
        stockgroup0_.DBUSERINS as DBUSERIN3_52_0_,
        stockgroup0_.DBUSERUPD as DBUSERUP4_52_0_,
        stockgroup0_.AU_USERIDINS as AU_USERI5_52_0_,
        stockgroup0_.AU_USERIDUPD as AU_USERI6_52_0_,
        stockgroup0_.INSERTDATE as INSERTDA7_52_0_,
        stockgroup0_.MODIFYDATE as MODIFYDA8_52_0_,
        stockgroup0_.VERSION as VERSION9_52_0_,
        stockgroup0_.GD_CODE as GD_CODE10_52_0_,
        stockgroup0_.ISACTIVE as ISACTIV11_52_0_,
        stockgroup0_.PRINTORDER as PRINTOR12_52_0_,
        stockgroup0_.XMLGD_CODE as XMLGD_C13_52_0_ 
    from
        GD_CODE stockgroup0_ 
    where
        stockgroup0_.GD_CODEID=? 
        and stockgroup0_.GD_CODETYPE=? 
        and stockgroup0_.GD_CODETYPE='STOCKGROUP'

And worse when GD_CODEID is NULL in OD_STOCK the SELECT for load the object is done, when the SELECT is uneccessary as the GD_CODEID is NULL

The GD_CODEID is bind to NULL when GD_CODEID is NULL…

Why ? What can i do ?


#2

You don’t need @JoinColumnsOrFormulas here. Use @JoinColumns instead as explained in this article:

@ManyToOne(fetch = FetchType.LAZY)
//Relying on @NotFound is relational database code smell
//@NotFound(action = NotFoundAction.IGNORE) 
@JoinColumns({
	@JoinColumn(
		name = "STOCKGROUPCODE",
		referencedColumnName = "GD_CODEID"),
	@JoinColumn(
		name = "STOCKGROUP",
		referencedColumnName = "GD_CODETYPE")
})
private StockGroup stockGroup;

If it does not work, send us a Pull Request with a replicating test case as explained in this post.


#3

No, i can’t in table OD_STOCK there is no column GD_CODETYPE (or STOCKGROUP), just GD_CODEID. But the PK in the other table are both columns.

STOCKGROUP is the value that should have GD_CODETYPE in the referenced table GD_CODE

I can’t change the DB structure.


#4

I just did exactly what you mapped previously. If your code worked, it should work with my change as well.


#5

You mean name in @JoinColumn can be a value instead of a columnName of the Table?? I will try on Monday and give feedback! Thanks


#6

No, it must be a column name, not a value. In that case use JoinColumnOrFormula.

Create a replicating test case and send it as a Pull Request so we can better understand why lazy loading would not work.


#7

I’m trying to send a pull request with the test, but i’m getting 403 Forbidden

13:54:43.403: [hibernate-orm] git -c core.quotepath=false -c log.showSignature=false push --progress --porcelain origin refs/heads/HHH-12770:HHH-12770 --set-upstream
fatal: HttpRequestException encountered.
   An error occurred while sending the request.
remote: Permission to hibernate/hibernate-orm.git denied to ferguardiola.
fatal: unable to access 'https://github.com/hibernate/hibernate-orm/': The requested URL returned error: 403

Here is the tets i’m trying to upload

package org.hibernate;

import org.hibernate.annotations.JoinColumnOrFormula;
import org.hibernate.annotations.JoinColumnsOrFormulas;
import org.hibernate.annotations.JoinFormula;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue;
import org.junit.Before;
import org.junit.Test;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import java.util.List;
import java.util.Map;

import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;

@TestForIssue(jiraKey = "HHH-12770")
public class HHH12770 extends BaseEntityManagerFunctionalTestCase {

    @Override
    protected Class<?>[] getAnnotatedClasses() {
        return new Class<?>[] {
                Stock.class,
                Code.class,
        };
    }

    @Override
    protected void addConfigOptions(Map options) {
        options.put(
                AvailableSettings.SHOW_SQL,
                Boolean.TRUE
        );
    }

    @Before
    public void setUp() {
        doInJPA( this::entityManagerFactory, entityManager -> {
            Code codeA1 = new Code();
            codeA1.setId((long)1);
            codeA1.setCopeType(CodeType.TYPE_A);
            entityManager.persist(codeA1);

            Code codeA2 = new Code();
            codeA2.setId((long)2);
            codeA2.setCopeType(CodeType.TYPE_A);
            entityManager.persist(codeA2);

            Code codeB1 = new Code();
            codeB1.setId((long)1);
            codeB1.setCopeType(CodeType.TYPE_B);
            entityManager.persist(codeB1);

            Code codeB2 = new Code();
            codeB2.setId((long)2);
            codeB2.setCopeType(CodeType.TYPE_B);
            entityManager.persist(codeB2);

            Stock stock1 = new Stock();
            stock1.setId((long)1);
            stock1.setCode(codeA1);
            entityManager.persist(stock1);

            Stock stock2 = new Stock();
            stock2.setId((long)2);
            entityManager.persist(stock2);

            Stock stock3 = new Stock();
            stock3.setId((long)2);
            stock3.setCode(codeB1);
            entityManager.persist(stock3);
        });
    }



    @Test
    public void testSelectAll() {
        doInJPA( this::entityManagerFactory, entityManager -> {
            List<Stock> stocks = entityManager.createQuery(
                    "  SELECT s FROM Stock s  "            )
                    .getResultList();

            assertEquals(3, stocks.size());
        });

    }





    @Entity(name = "Stock")
    public static class Stock {

        @Id
        @GeneratedValue
        private Long id;

        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumnsOrFormulas({@JoinColumnOrFormula(column = @JoinColumn(name = "ID", referencedColumnName = "ID")),
                @JoinColumnOrFormula(formula = @JoinFormula(referencedColumnName = "TYPE", value = "'TYPE_A'"))})
        private Code code;

        public Long getId() {
            return id;
        }

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

        public Code getCode() {
            return code;
        }

        public void setCode(Code code) {
            this.code = code;
        }
    }


    @Entity(name = "Code")
    public static class Code {

        @Id
        @Column(name = "ID")
        private Long id;

        @Id
        @Enumerated
        @Column(name = "TYPE")
        private CodeType copeType;

        public Long getId() {
            return id;
        }

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

        public CodeType getCopeType() {
            return copeType;
        }

        public void setCopeType(CodeType copeType) {
            this.copeType = copeType;
        }
    }

    public enum CodeType {
        TYPE_A, TYPE_B;
    }






}

Here is the JIRA related: https://hibernate.atlassian.net/browse/HHH-12770

The problem is that code from Stock is not load in a lazy way


#8

You need to fork the repository first. Then add your changes and send the PR.


#10

Hi Again,

Well after debugging the hibernate-core i found that if it’s marked the relation as @NotFound(action = NotFoundAction.IGNORE) then is loaded EAGER always. I don’t kwno the reason. But if i remove the @NotFoundAction.IGNORE then relation is loaded LAZY.

Thanks!


#11

You should open a Jira issue. Maybe there’s a way to allows NotFoundAction.IGNORE associations to be loaded lazily as well.


#12

Done!