I'm trying to detect the N+1 problem in my code. Every example I've found of the issue always involves two entities, which doesn't really explains if that could also happen to single entities.
The general example is: A car has many wheels. For each car I want to get every wheel.
Select * from cars
Select * from wheels where carId=?
or
List<Car> cars = getCarsFromDB();
for(Car car : cars){
List<Wheel> wheels = car.getWheels(); //Wheels are entities.
}
But what about non-entities?:
List<Car> cars = getCarsFromDB();
for(Car car : cars){
//select car.plate from Car car where car.id = ?
String plate = car.getPlate(); //Plate is just a varchar.
}
Does JPA also lazy fetch non-entities? Is this also a N+1 problem?
The short answer, it depends.
If you aren't using Hibernate's bytecode enhancement, then any column that is a basic type will be fetched automatically when you load the entity, therefore they're not lazy nor do they introduce an additional query to make them available.
So in your case, the plate is populated when the Car
instance was created.
Now, if you're using Hibernate's bytecode enhancement and you specifically annotate the field to be @Lazy
, then that field is lazy and would incur a database query cost when you access the field for the first time, just like a lazy association.
In this scenario, what you can do to avoid the N+1 problem (here where N is each lazy annotated field), you can specify @LazyGroup
and group a number of lazy initialized fields together so that when one in the group is accessed, the remainder of them will be loaded simultaneously.
But again, the latter case I described above only applies if you're using bytecode enhancement and when you specifically specify that a field is to be lazily loaded.
The JPA specification states the following:
3.2.9:
An entity is considered to be loaded if all attributes with FetchType.EAGER—whether explictly specified or by default—(including relationship and other collection-valued attributes) have been loaded from the database or assigned by the application. Attributes with FetchType.LAZY may or may not have been loaded. The available state of the entity instance and associated instances is as described in section 3.2.7.
An attribute that is an embeddable is considered to be loaded if the embeddable attribute was loaded from the database or assigned by the application, and, if the attribute references an embeddable instance (i.e., is not null), the embeddable instance state is known to be loaded (i.e., all attributes of the embeddable with FetchType.EAGER have been loaded from the database or assigned by the applica- tion).
...
A basic attribute is considered to be loaded if its state has been loaded from the database or assigned by the application.
So given you have a Car
instance loaded from the database (e.g. it isn't a Car instance you obtained by #getReference()
for example), basic attributes are considered loaded & assigned.