0

Hey i am working on Spring Data jpa and i would like to fetch data lazily only.

I am using lombook in my spring project

I would like to fetch a user and his roles by email.

I have few classes

@Entity
@Table(name = "user")
@Data
public class User extends DateAudit {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    @Column(columnDefinition = "char(50)")
    private String email;

    @Column(columnDefinition = "char(15)")
    private String phone;

    private String password;

    @Column(columnDefinition = "bit")
    private boolean isActive;

    @Column(columnDefinition = "ENUM('MALE', 'FEMALE', 'OTHER')")
    @Enumerated(EnumType.STRING)
    private Gender gender;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    private List<UserRole> userRoles;

    @OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
    private UserProfile userProfile;

}
@Entity
@Table(name = "user_profile")
@Data
public class UserProfile extends DateAudit {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String picture;
    private float avgRating;

    @Column(columnDefinition = "char(13)")
    private String cnic;

    @Temporal(TemporalType.DATE)
    private Date dateOfBirth;

    //relations

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "city_id")
    private City city;
}
@Entity
@Data
@Table
public class UserRole {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "role_id")
    private Role role;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "hospital_id")
    private Hospital hospital;
}

And here is my user repository

@Repository
public interface UserRepository extends CrudRepository<User, Integer> {

    @EntityGraph(attributePaths = {"userRoles"})
    User findByEmail(String email);
}

Below is my action that i am using to fetch user

    @GetMapping("/test")
    public String test(){

        userRepository.findByEmail("admin@domain.com");
        return "test";
    }

I have added below property in application.properties

spring.jpa.properties.hibernate.show_sql=true

so when i hit my /test action from postman I see two queries executed in my application console.

Hibernate: select user0_.id as id1_21_0_, userroles1_.id as id1_23_1_, user0_.created_at as created_2_21_0_, user0_.updated_at as updated_3_21_0_, user0_.email as email4_21_0_, user0_.gender as gender5_21_0_, user0_.is_active as is_activ6_21_0_, user0_.name as name7_21_0_, user0_.password as password8_21_0_, user0_.phone as phone9_21_0_, userroles1_.hospital_id as hospital2_23_1_, userroles1_.role_id as role_id3_23_1_, userroles1_.user_id as user_id4_23_1_, userroles1_.user_id as user_id4_23_0__, userroles1_.id as id1_23_0__ from user user0_ left outer join user_role userroles1_ on user0_.id=userroles1_.user_id where user0_.email=?
Hibernate: select userprofil0_.id as id1_22_0_, userprofil0_.created_at as created_2_22_0_, userprofil0_.updated_at as updated_3_22_0_, userprofil0_.avg_rating as avg_rati4_22_0_, userprofil0_.city_id as city_id8_22_0_, userprofil0_.cnic as cnic5_22_0_, userprofil0_.date_of_birth as date_of_6_22_0_, userprofil0_.picture as picture7_22_0_, userprofil0_.user_id as user_id9_22_0_ from user_profile userprofil0_ where userprofil0_.user_id=?

I am not interested in user profile. what am i doing wrong here.

cool cool
  • 55
  • 6

2 Answers2

2

One to one relations are loaded eagerly. Even if we instruct hibernate to lazily load them, hibernate disregards that hint.

To overcome this problem, we need to enable bytecode enhancement.

Bytecode Enhancement

In our case, we are using the bytecode enhancement plugin that enhances the bytecode of entity classes and allows us to utilize No-proxy lazy fetching strategy. We can define the plugin in pom.xml file in the following way,

<build>
<plugins>
    <plugin>
        <groupId>org.hibernate.orm.tooling</groupId
        <artifactId>hibernate-enhance-maven-plugin</artifactId>
        <version>5.4.2.Final</version>
        <executions>
        <execution>
        <configuration><enableLazyInitialization>true</enableLazyInitialization></configuration>
        <goals>
            <goal>enhance</goal>
        </goals>
        </execution>
        </executions>
    </plugin>
</plugins>
</build>

Enabling no proxy lazy associations

Now we need to add @LazyToOne annotation in entity classes to let hibernate know that we want to enable no proxy lazy fetching for associated entities.

Update:

By default all the lazy properties of an entity class belong to a group named DEFAULT. And fetching any property of the DEFAULT group fetches others as well. To solve this problem, we need to define groups that we wish to fetch individually using the @LazyGroup annotation.

In our case, we want the nested objects to be fetched individually. So, we are annotating both userRoles and userProfiles with @LazyGroup annotation as below.

@OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
@LazyToOne(LazyToOneOption.NO_PROXY)
@LazyGroup("userProfile")
private UserProfile userProfile;
Eklavya
  • 15,459
  • 4
  • 14
  • 41
0

That happens because is a bidirectional association and Hibernate needs to know if it shall initialize the userProfile attribute with null or a proxy class and it can only find that out by querying the user_profile table.

You can avoid it removing foreign key column and using the same primary key value for both associated entities for that you must use MapsId annotation on the owning side, like this:

@Entity
@Table(name = "user_profile")
@Data
public class UserProfile extends DateAudit {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 @OneToOne
 @MapsId
 @JoinColumn(name = "id")
 private User user;
.
.
.
}

Here you can check a complete guide for this problem https://thoughts-on-java.org/hibernate-tip-lazy-loading-one-to-one/


Another option, if you don't want to use the same key, is just map this as OneToMany relation.

lucsbelt
  • 366
  • 1
  • 4