Postgresql 如何使用Hibernate避免springboot中本机SQL查询的N+1问题?
我正在使用POSTGIS内置查询我的数据库,以检索给定位置的最近机器。 我必须使用本机SQL,因为Hibernate不支持POSTGIS和CTEs:Postgresql 如何使用Hibernate避免springboot中本机SQL查询的N+1问题?,postgresql,spring-boot,hibernate,postgis,rawsql,Postgresql,Spring Boot,Hibernate,Postgis,Rawsql,我正在使用POSTGIS内置查询我的数据库,以检索给定位置的最近机器。 我必须使用本机SQL,因为Hibernate不支持POSTGIS和CTEs: @Repository public interface MachineRepository extends JpaRepository<Machine, Long>{ @Query(value = "with nearest_machines as\n" +
@Repository
public interface MachineRepository extends JpaRepository<Machine, Long>{
@Query(value =
"with nearest_machines as\n" +
" (\n" +
" select distance_between_days(:id_day, machine_availability.id_day) as distance_in_time,\n" +
" ST_Distance(geom\\:\\:geography, ST_SetSrid(ST_MakePoint(:longitude, :latitude), 4326)\\:\\:geography) as distance_in_meters,\n" +
" min(id_day) over (partition by machine.id) as closest_timeslot_per_machine,\n" +
" machine_availability.id_day,\n" +
" machine.*\n" +
" from machine\n" +
" join machine_availability on machine.id = machine_availability.id_machine\n" +
" where machine_availability.available = true\n" +
" and machine_availability.id_day >= :today\n" +
" and ST_DWithin(geom\\:\\:geography, ST_SetSrid(ST_MakePoint(:longitude, :latitude), 4326)\\:\\:geography, 1000)\n" +
" )\n" +
"select nearest_machines.*\n" +
"from nearest_machines\n" +
"where id_day = closest_timeslot_per_machine\n" +
"order by distance_in_time, distance_in_meters\n" +
"limit 20;",
nativeQuery = true)
List<Machine> findMachinesAccordingToAvailabilities(@Param("longitude") BigDecimal longitude,
@Param("latitude") BigDecimal latitude,
@Param("id_day") String idDay,
@Param("today") String today);
}
当然,Machine和MachineAvailability是@Entity的。它们是@OneToManyfetch=FetchType.EAGER相关的。我将默认的LAZY更改为EAGER,因为我需要最终JSON中的MachineAvailability
问题是,它通过产生机器(即著名的N+1问题)再触发2个请求
1.如何在一个请求中解决该问题
2.是否有可能以某种方式在JSON上创建my并直接在MachineController中返回它?在1个请求中解决此问题很困难,因为您必须使用Hibernate本机API来映射可用性集合的表别名。您需要为主查询中的可用性添加一个联接,并执行如下操作:session.createNativeQuery….addEntitym,Machine.class.addFetchav,m,availabilities 另一种选择是使用Blaze Persistence,因为它支持CTE和PostgreSQL提供的更多功能,这可能是一个有趣的解决方案 我创建了这个库,以便在JPA模型和自定义接口或抽象类定义的模型之间进行简单的映射,类似于类固醇上的Spring数据投影。其思想是以您喜欢的方式定义目标structuredomain模型,并通过JPQL表达式将AttributesGetter映射到实体模型 我不知道您的模型,但您的用例可能的DTO模型在Blaze Persistence实体视图中如下所示:
@EntityView(Machine.class)
@With(NearestMachineCteProvider.class)
@EntityViewRoot(name = "nearest", entity = NearestMachine.class, condition = "machineId = VIEW(id)", joinType = JoinType.INNER)
public interface MachineDto {
@IdMapping
Integer getId();
String getName();
@Mapping("nearest.distanceInTime")
Integer getDistanceInTime();
@Mapping("nearest.distanceInMeters")
Double getDistanceInMeters();
Set<MachineAvailabilityDto> getAvailabilities();
@EntityView(MachineAvailability.class)
interface MachineAvailabilityDto {
@IdMapping
Integer getId();
String getName();
}
class NearestMachineCteProvider implements CTEProvider {
@Override
public void applyCtes(CTEBuilder<?> builder, Map<String, Object> optionalParameters) {
builder.with(NearestMachine.class)
.from(Machine.class, "m")
.bind("distanceInTime").select("CAST_INTEGER(FUNCTION('distance_between_days', :id_day, m.availabilities.idDay))")
.bind("distanceInMeters").select("CAST_DOUBLE(FUNCTION('ST_Distance', m.geom, FUNCTION('ST_SetSrid', FUNCTION('ST_MakePoint', :longitude, :latitude), 4326)))")
.bind("closestTimeslotId").select("min(m.availabilities.idDay) over (partition by m.id)")
.bind("machineId").select("m.id")
.bind("machineAvailabilityDay").select("m.availabilities.idDay")
.where("m.availabilities.available").eqLiteral(true)
.where("m.availabilities.idDay").geExpression(":today")
.where("FUNCTION('ST_DWithin', m.geom, FUNCTION('ST_SetSrid', FUNCTION('ST_MakePoint', :longitude, :latitude), 4326), 1000)").eqLiteral(true)
.end();
}
}
}
@CTE
@Entity
public class NearestMachine {
private Integer distanceInTime;
private Double distanceInMeters;
private Integer closestTimeslotId;
private Integer machineId;
private Integer machineAvailabilityDay;
}
然后可以使用sort.ascdanceintime和sort.ascdanceintimeters进行排序
最好的是,它只会获取实际需要的状态 这里有点不对劲,因为如果您发出的是本机SQL查询,那么Hibernate N+1问题根本不应该是问题,甚至不应该是可能的。是什么让你认为这里发生了N+1?因为我在日志中看到了请求。然后它们来自原始SQL查询,而不是Hibernate/JPA。好的,当您将native设置为true时,您基本上只是在执行一个本地JDBC准备的语句。它们来自@OneToManyfetch=FetchType.EAGER。当我切换到懒惰,问题消失了,但我没有可用性!!!thx用于addFetch!
Page<MachineDto> findAll(Pageable pageable);