I've read a few articles that shows how to solve n+1 query problem in JPA, but none of it worked for me.
When I try to fetch the data, JPA makes n+1 queries.
select owner0_.id as id1_1_, owner0_.created_at as created_2_1_, owner0_.updated_at as updated_3_1_, owner0_.name as name4_1_, owner0_.version as version5_1_ from owner owner0_
select cars0_.owner_id as owner_id6_0_0_, cars0_.id as id1_0_0_, cars0_.id as id1_0_1_, cars0_.created_at as created_2_0_1_, cars0_.updated_at as updated_3_0_1_, cars0_.license_no as license_4_0_1_, cars0_.owner_id as owner_id6_0_1_, cars0_.version as version5_0_1_ from car cars0_ where cars0_.owner_id=? [1]
select cars0_.owner_id as owner_id6_0_0_, cars0_.id as id1_0_0_, cars0_.id as id1_0_1_, cars0_.created_at as created_2_0_1_, cars0_.updated_at as updated_3_0_1_, cars0_.license_no as license_4_0_1_, cars0_.owner_id as owner_id6_0_1_, cars0_.version as version5_0_1_ from car cars0_ where cars0_.owner_id=? [2]
Below is the code snippet :
@Entity
public class Owner extends BaseEntity implements EntityTransformer<OwnerDto> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Version
private Long version;
@OneToMany(mappedBy = "owner", fetch = FetchType.LAZY)
private Set<Car> cars;
@Override
public OwnerDto convertToDto() {
OwnerDto ownerDto = new OwnerDto();
ownerDto.setId(this.getId());
ownerDto.setName(this.getName());
ownerDto.setVersion(this.getVersion());
if (this.getCars() != null) ownerDto.setCars(this.getCars().stream().map(Car::convertToDto).collect(Collectors.toSet()));
return ownerDto;
}
}
and my Car class is as follow :
@Entity
public class Car {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String licenseNo;
@Version
private Integer version;
@JoinColumn( name = "owner_id" )
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Owner owner;
@Override
public CarDto convertToDto() {
CarDto carDto = new CarDto();
carDto.setId(this.getId());
carDto.setLicenseNo(this.getLicenseNo());
carDto.setVersion(this.getVersion());
return carDto;
}
}
OwnerService :
@Service
public class OwnerServiceImpl implements OwnerService {
@Autowired
OwnerRepository ownerRepository;
@Override
public List<Owner> findAll() {
return ownerRepository.findAll();
}
}
OwnerController :
@RestController
public class OwnerController {
@Autowired
private OwnerService ownerService;
@GetMapping(value = "/owners", produces = "application/vnd.demo.api.v1+json")
public ResponseEntity<List<OwnerDto>> findAll() {
return ResponseEntity.ok(ownerService.findAll().stream().map(Owner::convertToDto).collect(Collectors.toList()));
}
}
cURL :
curl -X POST \
http://localhost:8080/owner \
-H 'Accept: application/vnd.demo.api.v1+json' \
-H 'Content-Type: application/json' \
-H 'Host: localhost:8080' \
-d '{
"name": "pranay5"
}'
curl -X POST \
http://localhost:8080/owner/5/car \
-H 'Accept: application/vnd.demo.api.v1+json' \
-H 'Content-Type: application/json' \
-H 'Host: localhost:8080' \
-d '{
"licenseNo": "MSH-5555"
}'
Is there something wrong with the code?
On a side note: @BatchSize(size = 5) JPA makes only two queries When I set @BatchSize(size = 5) without making any other changes, it makes only two queries to the database.
select o_.id , o_.created_at, o_.updated_at, o_.name from owner o_
select c_.owner_id, c_.id, c_.created_at, c_.updated_at, c_.license_no, c_.owner_id, from car c_ where c_.owner_id in (?, ?, ?, ?, ?) [1,2,3,4,5]
But my doubt is why FetchType.LAZY making N+1 queries?
Code: https://github.com/pranayhere/exception-demo-mvn
What is the N+1 query problem. The N+1 query problem happens when the data access framework executed N additional SQL statements to fetch the same data that could have been retrieved when executing the primary SQL query. The larger the value of N, the more queries will be executed, the larger the performance impact.
Hibernate N+1 issue occurs when you use `FetchType. LAZY` for your entity associations. Hibernate will perform n-additional queries to load lazily fetched objects. To escape this issue use join fetch, batching or sub select.
The FetchType. LAZY tells Hibernate to only fetch the related entities from the database when you use the relationship. This is a good idea in general because there's no reason to select entities you don't need for your uses case. You can see an example of a lazily fetched relationship in the following code snippets.
Actually the problem is that you use the default findAll
of the OwnerRepository
and you put FetchType.LAZY
.
Because you get the cars in the Owner::convertToDto
, Hibernate has to fetch the cars it did not fetched because of the lazy fetching.
To avoid the additional queries, create a new JPA method in the OwnerRepository getAllBy with an EntityGraph to eagerly fetch the cars in the query:
public class OwnerRepository extend JpaRepository<Owner, Long> {
@EntityGraph(attributePaths = {
"cars",
})
List<Owners> getAllBy();
}
Then use it in your service instead of the findAll.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With