Hibernate first level cache support not working with JpaRepository.findById() when invoked more than once in same @Transactional
@EnableBlazeRepositories integration with spring boot and spring data jpa does not support Hibernate first level cache.
Calling JpaRepository.findById n times in the same @Transactional block executes the SELECT query n times.
This is my repository setup
public interface SupplierReviewMetricRepository extends JpaRepository<SupplierReviewMetric, UUID>,
JpaSpecificationExecutor<SupplierReviewMetric> {}
This is my blaze config
@Configuration
@EnableEntityViews(basePackages = {"com.gitlab.mess"})
@EnableBlazeRepositories(basePackages = {"com.gitlab.mess"})
public class BlazePersistenceConfig {
@PersistenceUnit
private EntityManagerFactory entityManagerFactory;
@Bean
@Lazy(false)
public CriteriaBuilderFactory criteriaBuilderFactory() {
var config = Criteria.getDefault();
return config.createCriteriaBuilderFactory(entityManagerFactory);
}
@Bean
public EntityViewManager entityViewManager(CriteriaBuilderFactory cbf, EntityViewConfiguration evc) {
return evc.createEntityViewManager(cbf);
}
}
Used libraries :
spring-boot:3.5.4blaze-persistence:1.6.16
In my existing code base I used to use the findById always instead of getById which is deprecated in favor of getReferenceById. So I made a simple experiment with findById vs getReferenceById.
I made them on the fly. I used a controller and a service for quick test.
Test 1 (findById)
@Service
public class TestService {
private final CategoryBlazeRepository categoryBlazeRepository;
public TestService(CategoryBlazeRepository categoryBlazeRepository) {
this.categoryBlazeRepository = categoryBlazeRepository;
}
@Transactional
public Category see(UUID id) {
return categoryBlazeRepository.findById(id).orElse(new Category().setName("Not Found 3"));
}
}
@RestController
@RequestMapping("/test")
public class TestController {
private final CategoryBlazeRepository categoryBlazeRepository;
private final TestService testService;
public TestController(CategoryBlazeRepository categoryBlazeRepository, TestService testService) {
this.categoryBlazeRepository = categoryBlazeRepository;
this.testService = testService;
}
@GetMapping
@Transactional
public Object test() {
UUID categoryId = UUID.fromString("36bcec62-f2d0-416b-8618-bf1f24db5fdd");
var c = categoryBlazeRepository.findById(categoryId).orElse(new Category().setName("Not Found 1"));
c = categoryBlazeRepository.findById(categoryId).orElse(new Category().setName("Not Found 2"));
c = testService.see(categoryId);
return c.getName();
}
}
Logs
2025-10-08T09:45:33.589+01:00 DEBUG 7148 --- [spring-demo] [omcat-handler-0] org.hibernate.SQL : select c1_0.id,c1_0.created_at,c1_0.created_by,c1_0.image,c1_0.name,c1_0.sort_order,c1_0.parent_id,c1_0.published_at,c1_0.revision,c1_0.slug,c1_0.updated_at,c1_0.updated_by from spring.category c1_0 where c1_0.id=?
2025-10-08T09:45:33.632+01:00 DEBUG 7148 --- [spring-demo] [omcat-handler-0] org.hibernate.SQL : select c1_0.id,c1_0.created_at,c1_0.created_by,c1_0.image,c1_0.name,c1_0.sort_order,c1_0.parent_id,c1_0.published_at,c1_0.revision,c1_0.slug,c1_0.updated_at,c1_0.updated_by from spring.category c1_0 where c1_0.id=?
2025-10-08T09:45:33.637+01:00 DEBUG 7148 --- [spring-demo] [omcat-handler-0] org.hibernate.SQL : select c1_0.id,c1_0.created_at,c1_0.created_by,c1_0.image,c1_0.name,c1_0.sort_order,c1_0.parent_id,c1_0.published_at,c1_0.revision,c1_0.slug,c1_0.updated_at,c1_0.updated_by from spring.category c1_0 where c1_0.id=?
Test 2 (getReferenceById)
@Service
public class TestService {
private final CategoryBlazeRepository categoryBlazeRepository;
public TestService(CategoryBlazeRepository categoryBlazeRepository) {
this.categoryBlazeRepository = categoryBlazeRepository;
}
@Transactional
public Category see(UUID id) {
return categoryBlazeRepository.getReferenceById(id);
}
}
@RestController
@RequestMapping("/test")
public class TestController {
private final CategoryBlazeRepository categoryBlazeRepository;
private final TestService testService;
public TestController(CategoryBlazeRepository categoryBlazeRepository, TestService testService) {
this.categoryBlazeRepository = categoryBlazeRepository;
this.testService = testService;
}
@GetMapping
@Transactional
public Object test() {
UUID categoryId = UUID.fromString("36bcec62-f2d0-416b-8618-bf1f24db5fdd");
var c = categoryBlazeRepository.getReferenceById(categoryId);
c = categoryBlazeRepository.getReferenceById(categoryId);
c = testService.see(categoryId);
return c.getName();
}
}
Logs
2025-10-08T09:54:03.755+01:00 DEBUG 13028 --- [spring-demo] [omcat-handler-0] org.hibernate.SQL : select c1_0.id,c1_0.created_at,c1_0.created_by,c1_0.image,c1_0.name,c1_0.sort_order,c1_0.parent_id,c1_0.published_at,c1_0.revision,c1_0.slug,c1_0.updated_at,c1_0.updated_by from spring.category c1_0 where c1_0.id=?
Test 3 (Mix findById and getReferenceById)
@Service
public class TestService {
private final CategoryBlazeRepository categoryBlazeRepository;
public TestService(CategoryBlazeRepository categoryBlazeRepository) {
this.categoryBlazeRepository = categoryBlazeRepository;
}
@Transactional
public Category see(UUID id) {
return categoryBlazeRepository.findById(id).orElse(new Category().setName("Not Found 2"));
}
}
@RestController
@RequestMapping("/test")
public class TestController {
private final CategoryBlazeRepository categoryBlazeRepository;
private final TestService testService;
public TestController(CategoryBlazeRepository categoryBlazeRepository, TestService testService) {
this.categoryBlazeRepository = categoryBlazeRepository;
this.testService = testService;
}
@GetMapping
@Transactional
public Object test() {
UUID categoryId = UUID.fromString("36bcec62-f2d0-416b-8618-bf1f24db5fdd");
var c = categoryBlazeRepository.getReferenceById(categoryId);
c = categoryBlazeRepository.findById(categoryId).orElse(null);
c = testService.see(categoryId);
return c.getName();
}
}
Logs
2025-10-08T09:59:06.742+01:00 DEBUG 18760 --- [spring-demo] [omcat-handler-0] org.hibernate.SQL : select c1_0.id,c1_0.created_at,c1_0.created_by,c1_0.image,c1_0.name,c1_0.sort_order,c1_0.parent_id,c1_0.published_at,c1_0.revision,c1_0.slug,c1_0.updated_at,c1_0.updated_by from spring.category c1_0 where c1_0.id=?
2025-10-08T09:59:06.791+01:00 DEBUG 18760 --- [spring-demo] [omcat-handler-0] org.hibernate.SQL : select c1_0.id,c1_0.created_at,c1_0.created_by,c1_0.image,c1_0.name,c1_0.sort_order,c1_0.parent_id,c1_0.published_at,c1_0.revision,c1_0.slug,c1_0.updated_at,c1_0.updated_by from spring.category c1_0 where c1_0.id=?
I'm not sure if this behavior is intentional or a bug. If it's intentional it should be stated because its a breaking change to the default SimpleJpaRepository behavior which mean in this case replacing @EnableJpaRepositories with @EnableBlazeRepositories is not a drop-in replacement
Is Category an entity or an entity view?
Category is an entity
Thanks for the report. I think it should be somewhat easy to do this by special casing in com.blazebit.persistence.spring.data.impl.repository.EntityViewAwareRepositoryImpl.findById like:
Class<V> entityViewClass = metadata == null
|| metadata.getEntityViewClass() == null ? this.entityViewClass : (Class<V>) metadata.getEntityViewClass();
if (entityViewClass == null) {
// todo: determine lockMode and properties based on metadata
return getEntityManager().find(getDomainClass(), id, lockMode, properties);
}