18

having this classes:

User.java:

@Entity
@Setter
@Getter
@NoArgsConstructor
public class User {
    @Id
    private int id;
    private String username;
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Address address;

    public User(String username) {
        this.username = username;
    }
}

Address.java:

@Entity
@Data
public class Address {
    @Id
    private int id;
    private String country;
    @OneToOne
    private User user;
}

UserRepository.java:

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

DemoApplcation.java:

@Bean
    public CommandLineRunner loadData(UserRepository userRepo){
        return new CommandLineRunner() {
            @Override
            public void run(String... args) throws Exception {
                User u = new User("motherfucker");
                Address a = new Address();
                a.setCountry("CZ");
                u.setAddress(a);
                a.setUser(u);
                userRepo.save(u);

                //User newUser = userRepo.getById(0);
                User newUser = userRepo.findById(0).orElse(null);
                System.out.println(newUser.getUsername());
            }
        };
    }

Now the findById(int: id) works without problem (defined in CrudRepository from which extends JpaRepository). However the getById(int :id) (defined in JpaRepository) gives LazyInitializationException even with fetch = Fetch.EAGER attribute specified in mapping. In documentation it says

Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is implemented this is very likely to always return an instance and throw an EntityNotFoundException on first access. Some of them will reject invalid identifiers immediately.

  1. I didn't get EntityNotFoundException but LazyInitializationException
  2. Why is there a method which declares in its documentation that it throws exception without any reason? -> always return an instance and throw an EntityNotFoundException

from this description it seems for me this method be useless if it always throws exception. Why does this method exists?

What is the right way (method) for fetching data in hibernate?

milanHrabos
  • 2,010
  • 3
  • 11
  • 45
  • you might need to save address separately before saving that user. You also can use `User newUser = userRepo.save(u);` so you dont need to refetch it from database. – Joker Sep 08 '21 at 22:21
  • 1
    @Joker that's the reason I specified `cascade = CascadeType.ALL` so that I don't have to save the address. – milanHrabos Sep 08 '21 at 22:24

4 Answers4

14

Usually we can use either findById or getById. There's no issue in using either of it.

But if we want to understand more specific differences the those differences are as follows:

- getById: This is used only if we are sure about getting an entity we requested from the database. If we don't get any entity, it gives an exception. This is similar to a child who get's whatever it wants but starts screaming if it doesn't get what he requires.

- findById: This is used if we are not sure that whether the requested entity in the database is present or not. So even if the entity is not present in the database it returns null and doesn't throw any exception.

RanjeetBorate
  • 149
  • 1
  • 2
8
  1. You got LazyInitializationException exactly because you set fetch = Fetch.EAGER. getById() returns a lazily fetched entity and thus the exception.
  2. Just guessing here, but maybe because of the laziness behaviour of getById(). But to be honest I don't understand that either.

It is also important to highlight another detail: findById() method uses EntityManager find(Class<T> entityClass, Object primaryKey, LockModeType lockMode, Map<String,Object> properties)) method internally and getById() uses EntityManager getReference(Class<T> entityClass, Object primaryKey) method. As a consequence, findById() returns the actual object and getById returns a reference of the entity.

Reference documentation:

João Dias
  • 16,277
  • 6
  • 33
  • 45
  • So what is the right way (method) for fetching data in hibernate? – milanHrabos Sep 08 '21 at 21:40
  • I always used `findById()` because it seems more predictable than `getById()` which may throw exceptions in very specific cases. With `findById()` you get `null` if the record does not exist in the database and that is it, no surprises. – João Dias Sep 08 '21 at 21:45
  • both ways are possible. I usually use findByX when I expect that it might not exist whereas I use getById() when I expect them to exist. – Joker Sep 08 '21 at 22:18
  • I disagree with that. I think that the most important thing in these cases (when there is not a huge advantage of one over another) is to be consistent. Therefore I would suggest picking one and using it, but not both intermittently. But then again, this is just my opinion. – João Dias Sep 08 '21 at 22:23
  • So in your case you decided that you dont want to accept null values deciding you use findById() and then throw an entitynotfoundexception if it returns null and writing this code block over and over again. And you would favor this over using getById (where this would be handled by the lib) in that case? Sure its opinionated, but this would be unnecessary repetition in my opinion. – Joker Sep 08 '21 at 22:30
  • But what if I don't want to throw an `EntityNotFoundException` but a custom one instead? Or throwing none at all and simply handle that any other way. What would then be the benefit of using `getById()` in these cases? Bottom line, the usage of one over the other is personal preference and my preference is `findById()`. Still, I am not against anyone using `getById()`. The only thing that I wouldn't do is mixing both in the same codebase. But then again, that is just my personal preference. – João Dias Sep 08 '21 at 23:06
  • well, today I hit the same wall as well. If you check the implementation on Spring source, getById returns a ref (em.getRefference..) If you read the getRef spec documentation, it says some persistence providers (in this case hibernate) **may** return EntityNotFoundExceiton. But, instead, hibernate returns a proxy object. If you try to get a property of the proxy in the Transaction, then you are ok. But if you try to get out of the transaction you will get that LazyInit exception. – Olgun Kaya Dec 09 '21 at 16:25
7

getById -> returns a reference proxy to for the actual Entity. Where only the id set (since you already passed it). This object's getters and setters may be called in the same @Transaction. But, once you get out of the transaction, then the proxy will return LazyInitializationException.

getById implementation

if you look at the getReference java doc. I believe this makes all clear. So, hibernate does not hit to the database if you call getById.

Get an instance, whose state may be lazily fetched. If the requested instance does not exist in the database, the EntityNotFoundException is thrown when the instance state is first accessed. (The persistence provider runtime is permitted to throw the EntityNotFoundException when getReference is called.) The application should not expect that the instance state will be available upon detachment, unless it was accessed by the application while the entity manager was open.

on the other hand, findById directly hit to DB and performs the select query.

Olgun Kaya
  • 2,519
  • 4
  • 32
  • 46
-1

I will provide examples for this simple mapping

@Entity
public class User {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String userName;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn // mappedBy can be used here for a bidirectional relation
    private Address address;

}

@Entity
public class Address {

    @Id
    private Long id;

    public Address(Long id) {
        this.id = id;
    }
    
}

Using transient Address

Lets say we have Address table with tabular data — many users can have the same address. We want to create a new user and let's say that we know a user's Address id = 100L from somewhere.

User user = new User();
Address address = new Address(100L);
user.setAddress(address);
userRepo.save(user);

It will work, but Hibernate generates an additionally SQL query to load the address with id=100L from a database.

Using Address proxy with getById()

Let's change our example

User user = new User();
Address address = addressRepo.getById(100L);
user.setAddress(address);
userRepo.save(user);

We get an address using addressRepo.getById(100L). This call doesn't hit the database at all. Hibernate just returns a proxy for the address id = 100L and doesn't load fields of the address from a database. And when we do userRepo.save(user) Hibernate just does one SQL insert. It doesn't load the address from a database like in the first example does.

Using attached Address with findById()

@Transactional
public void save() {
    User user = new User();
    Address address = addressRepo.findById(100L);
    user.setAddress(address);
    userRepo.save(user);
}

Using deattached Address with findById()

public void save() {
    User user = new User();
    Address address = addressRepo.findById(100L);
    user.setAddress(address);
    userRepo.save(user);
}
v.ladynev
  • 19,275
  • 8
  • 46
  • 67