Without seeing your code, I’m 99% sure that is the problem you are seeing.
Play around with the following test and HEAD of 5.4. Specifically the setting of hibernate.bytecode.allow_enhancement_as_proxy
. False (the default on 5.4) leads to what you see. True (the default on 5.5/master) leads to what you think you want.
@RunWith( BytecodeEnhancerRunner.class )
@EnhancementOptions( lazyLoading = true )
public class TheTests extends BaseNonConfigCoreFunctionalTestCase {
private SQLStatementInterceptor statementInterceptor;
@Test
public void testIt() {
statementInterceptor.clear();
inTransaction(
(session) -> {
final Post loaded = session.byId( Post.class ).load( 10 );
assertTrue( Hibernate.isInitialized( loaded ) );
// The post's author should be an enhanced-proxy...
assertTrue( Hibernate.isPropertyInitialized( loaded, "author" ) );
assertFalse( Hibernate.isInitialized( loaded.getAuthor() ) );
// there should have been just a single SQL statement
// and it should have included the `tbl_post.user_id` column
assertEquals( 1, statementInterceptor.getSqlQueries().size() );
final String sql = statementInterceptor.getSqlQueries().get( 0 );
assertTrue( sql.toLowerCase( Locale.ROOT ).contains( ".user_id" ) );
// now let's access the lazy `Post#content`
statementInterceptor.clear();
final String content = loaded.getContent();
assertEquals( 1, statementInterceptor.getSqlQueries().size() );
// and now, let's access the author
statementInterceptor.clear();
final User author = loaded.getAuthor();
assertEquals( 0, statementInterceptor.getSqlQueries().size() );
final Integer id = author.getId();
assertEquals( 0, statementInterceptor.getSqlQueries().size() );
final String username = author.getUsername();
assertEquals( 1, statementInterceptor.getSqlQueries().size() );
}
);
}
@Before
public void prepareTestData() {
inTransaction(
(session) -> {
final User user = new User( 1, "johnny", "appleseed" );
new Post( 10, "My first post", "Testing 1, 2, 3", user );
new Post( 11, "Follow-up post", "Hello?", user );
session.persist( user );
}
);
}
@After
public void cleanUpTestData() {
inTransaction(
(session) -> {
session.createQuery( "delete Post" ).executeUpdate();
session.createQuery( "delete User" ).executeUpdate();
}
);
}
@Override
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
super.configureStandardServiceRegistryBuilder( ssrb );
ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" );
statementInterceptor = new SQLStatementInterceptor( ssrb.getSettings() );
}
@Override
protected void applyMetadataSources(MetadataSources sources) {
super.applyMetadataSources( sources );
sources.addAnnotatedClass( User.class )
.addAnnotatedClass( Post.class );
}
@Entity( name = "User" )
@Table(name = "tbl_user")
public static class User {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Post> posts = new ArrayList<>();
private User() {
}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public Integer getId() {
return id;
}
private void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
}
@Entity( name = "Post" )
@Table(name = "tbl_post")
public static class Post {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "title")
private String title;
@Basic(fetch = FetchType.LAZY)
@Column(name = "content", nullable = false)
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User author;
private Post() {
}
public Post(Integer id, String title, String content, User author) {
this.id = id;
this.title = title;
this.content = content;
this.author = author;
author.posts.add( this );
}
public Integer getId() {
return id;
}
private void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
}
}
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.bytecode.enhancement.lazy.proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.Hibernate;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.EnhancementOptions;
import org.hibernate.testing.jdbc.SQLStatementInterceptor;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
@RunWith( BytecodeEnhancerRunner.class )
@EnhancementOptions( lazyLoading = true )
public class TheTests extends BaseNonConfigCoreFunctionalTestCase {
private SQLStatementInterceptor statementInterceptor;
@Test
public void testIt() {
statementInterceptor.clear();
inTransaction(
(session) -> {
final Post loaded = session.byId( Post.class ).load( 10 );
assertTrue( Hibernate.isInitialized( loaded ) );
// The post's author should be an enhanced-proxy...
assertTrue( Hibernate.isPropertyInitialized( loaded, "author" ) );
assertFalse( Hibernate.isInitialized( loaded.getAuthor() ) );
// there should have been just a single SQL statement
// and it should have included the `tbl_post.user_id` column
assertEquals( 1, statementInterceptor.getSqlQueries().size() );
final String sql = statementInterceptor.getSqlQueries().get( 0 );
assertTrue( sql.toLowerCase( Locale.ROOT ).contains( ".user_id" ) );
// now let's access the lazy `Post#content`
statementInterceptor.clear();
final String content = loaded.getContent();
assertEquals( 1, statementInterceptor.getSqlQueries().size() );
// and now, let's access the author
statementInterceptor.clear();
final User author = loaded.getAuthor();
assertEquals( 0, statementInterceptor.getSqlQueries().size() );
final Integer id = author.getId();
assertEquals( 0, statementInterceptor.getSqlQueries().size() );
final String username = author.getUsername();
assertEquals( 1, statementInterceptor.getSqlQueries().size() );
}
);
}
@Before
public void prepareTestData() {
inTransaction(
(session) -> {
final User user = new User( 1, "johnny", "appleseed" );
new Post( 10, "My first post", "Testing 1, 2, 3", user );
new Post( 11, "Follow-up post", "Hello?", user );
session.persist( user );
}
);
}
@After
public void cleanUpTestData() {
inTransaction(
(session) -> {
session.createQuery( "delete Post" ).executeUpdate();
session.createQuery( "delete User" ).executeUpdate();
}
);
}
@Override
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
super.configureStandardServiceRegistryBuilder( ssrb );
ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" );
statementInterceptor = new SQLStatementInterceptor( ssrb.getSettings() );
}
@Override
protected void applyMetadataSources(MetadataSources sources) {
super.applyMetadataSources( sources );
sources.addAnnotatedClass( User.class )
.addAnnotatedClass( Post.class );
}
@Entity( name = "User" )
@Table(name = "tbl_user")
public static class User {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Post> posts = new ArrayList<>();
private User() {
}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public Integer getId() {
return id;
}
private void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
}
@Entity( name = "Post" )
@Table(name = "tbl_post")
public static class Post {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "title")
private String title;
@Basic(fetch = FetchType.LAZY)
@Column(name = "content", nullable = false)
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User author;
private Post() {
}
public Post(Integer id, String title, String content, User author) {
this.id = id;
this.title = title;
this.content = content;
this.author = author;
author.posts.add( this );
}
public Integer getId() {
return id;
}
private void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
}
}