Hibernate 6.2+ with 2nd level cache ignores eager loading

I have project with hibernate 6.2.13.Final and jcache/ehcache for 2nd level cache:

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.2.13.Final</version>
</dependency>

<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-envers</artifactId>
    <version>6.2.13.Final</version>
</dependency>

<!-- API for 2nd Level Cache -->
<dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-jcache</artifactId>
    <version>6.2.13.Final</version>
</dependency>
<!-- Implementation for 2nd Level Cache -->
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <classifier>jakarta</classifier>
    <version>3.10.8</version>
</dependency>

And I have entities with @ManyToOne(fetch = FetchType.EAGER) and caching. I know that Eager Loading is an anti-pattern, but the software is complex and it’s not possible to change that at the moment.

@Entity
@Cacheable
@Audited
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class FooType {

    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid2")
    @Size(max = 36)
    @Column(length = 36)
    @UserInterface(showInForm = false, showInList = false)
    @Comment("Primary key")
    private String id;

    @Version
    @NotNull
    @UserInterface(showInForm = false, showInList = false)
    @Comment("Version column for optimistic locking")
    private int version;

    public String getId() {
        return id;
    }

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

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }
}
@Entity
@Cacheable
@Audited
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Foo {

    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid2")
    @Size(max = 36)
    @Column(length = 36)
    @UserInterface(showInForm = false, showInList = false)
    @Comment("Primary key")
    private String id;

    @Version
    @NotNull
    @UserInterface(showInForm = false, showInList = false)
    @Comment("Version column for optimistic locking")
    private int version;
	
	@ManyToOne(fetch = FetchType.EAGER)
	private FooType;

    public String getId() {
        return id;
    }

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

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }
	
	public FooType getFooType() {
		return fooType;
	}
	
	public void setFooType(FooType fooType) {
		this.fooType = fooType;
	}
}

When I query an entry, everything is fine.

entityManager.find(Foo.class, id);

However, when I query multiple entries, the FooType is lazy loaded in the results of Foo and is a hibernate proxy object.

final TypedQuery<Foo> fooQuery = entityManager.createQuery("FROM Foo f", Foo.class);
final List<Foo> fooList = fooQuery.getResultList();

Hibernate 6.2 and disabled 2nd level cache

When I deactivate the 2nd level cache, the result is eagerly loaded as expected.

<property name="hibernate.cache.use_second_level_cache" value="false" />

Hibernate 6.1 and enabled 2nd level cache

When I change the Hibernate version to 6.1.7.Final and the 2nd level cache is enabled, the result loads eagerly as expected.

Hibernate 6.4.0.CR1

Same issue like 6.2

I’m not sure if this is a bug or an expected change in caching? However, I did not find anything about this in the hibernate 6.2 migration guide. How can I handle this issue, I need the result eager loaded with 2nd level cache enabled.

Just because you see a proxy object being set for the association doesn’t mean it is lazy loaded. Sometimes Hibernate ORM needs to work with a proxy for other reasons, but will ensure the object becomes initialized as part of a load operation.

Please share the SQL queries that are executed and try to ensure objects are initialized with Hibernate.isInitialized().

beikov, thanks for reply.

I checked and the result of Hibernate.isInitialized() is true unlike entities with @ManyToOne(fetch = FetchType.LAZY) and in the logs I don’t see a separate loading either. But it is surprising to me that proxies can also occur for reasons other than lazy loading. The original reason for my investigations was a serialization error at Jackson. I know that Jackson can’t handle Hibernate lazy loading proxies and when eager loading with older Hibernate versions I didn’t have Hibernate proxies.

Now I’m not sure how best to handle this. I know the jackson-datatype-hibernate library, but in the features I only see the handling of lazy loading and nothing for initalized proxies.

Is it necessary to check each loaded entity for properties with initialized proxies and unproxy them (Hibernate.unproxy())? Is it perhaps possible to unproxy an entity transitively?

Update:
I tested with jackson-datatype-hibernate and it serializes the initialized proxy appropriately, even if the following options are set:

hibernate6Module.enable(Hibernate6Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS);
hibernate6Module.disable(Hibernate6Module.Feature.FORCE_LAZY_LOADING);

So the problem for me is solved.

Hibernate proxies can happen in so many cases. Using FetchType.EAGER will not save you from proxies being created in some circumstances.

If you absolutely don’t want proxies, you should consider using a DTO model instead. I can recommend you to look into Blaze-Persistence Entity-Views for that matter, of which I am the author.

1 Like