Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Spring Data JPA, a derived find method doesn't use first-level cache while called multiple times in one transaction, but the default findById does

I've created a custom (derived) find method in the repository:

Optional<User> findUserById(Long id);

Its signature is the same as the default one (findById(...)) inherited from JpaRepository but their behavior is a bit different:

  • Seems like the derived method does not respect the first-level cache (persistent context) if it's being called multiple times inside the method with @Transactional, and, as a result, we're getting multiple select statements.
  • On the other hand, the default findById method uses the first level cache, and, in the same conditions as above, only one select statement is actually executed (next calls get the cached result).

The same (multiple selects) happens even if my custom method is called findById too (but in this case we obviously cannot use the default one because it's shaded by it).

Does anybody know why this is happening? Thanks in advance.

I'm using Spring Boot 2.3.4.RELEASE / Hibernate 5.4.21.Final

Some meaningful code is below (the full project with tests can be found on Github):

User entity:

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    private Long id;
    private String name;
}

UserRepository:

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

    Optional<User> findUserById(Long id);
}

DatabaseService:

@Service
@RequiredArgsConstructor
@Slf4j
public class DatabaseService {

    private final UserRepository userRepository;

    @Transactional
    public void testDefaultMethod() {
        log.info("Start to call default findById");
        userRepository.findById(1L);
        userRepository.findById(1L);
        userRepository.findById(1L);
        log.info("End to call default findById");
    }

    @Transactional
    public void testCustomMethod() {
        log.info("Start to call custom findUserById");
        userRepository.findUserById(1L);
        userRepository.findUserById(1L);
        userRepository.findUserById(1L);
        log.info("End to call custom findUserById");
    }
}

Output while calling testDefaultMethod():

2020-10-04 03:11:26.379  INFO 19264 --- [           main] c.e.f.domain.DatabaseService             : Start to call default findById
Hibernate: select user0_.id as id1_0_0_, user0_.name as name2_0_0_ from user user0_ where user0_.id=?
2020-10-04 03:11:26.388  INFO 19264 --- [           main] c.e.f.domain.DatabaseService             : End to call default findById

Output while calling testCustomMethod():

2020-10-04 03:11:26.399  INFO 19264 --- [           main] c.e.f.domain.DatabaseService             : Start to call custom findUserById
Hibernate: select user0_.id as id1_0_, user0_.name as name2_0_ from user user0_ where user0_.id=?
Hibernate: select user0_.id as id1_0_, user0_.name as name2_0_ from user user0_ where user0_.id=?
Hibernate: select user0_.id as id1_0_, user0_.name as name2_0_ from user user0_ where user0_.id=?
2020-10-04 03:11:26.500  INFO 19264 --- [           main] c.e.f.domain.DatabaseService             : End to call custom findUserById
like image 316
amseager Avatar asked Oct 26 '25 02:10

amseager


1 Answers

In the absence of a query cache, a custom query is always executed against the DB. JPA doesn't know what your query is asking for, so it cannot 'guess' the result. It needs to delegate to the DB.

The only time a query is not executed is when the entity has already been loaded into the context and you call EntityManager.find() directly (which is what the default repository method does under the hood).

like image 93
crizzis Avatar answered Oct 27 '25 16:10

crizzis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!