0

我正在使用 POSTGIS 内置函数查询我的数据库,以检索Machine给定位置的最接近的 s。我必须使用原生 SQL,因为 Hibernate 不支持 POSTGIS 和 CTE:

    @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的。它们是@OneToMany(fetch = FetchType.EAGER)相关的。我将默认的 LAZY 更改为 EAGER,因为我需要MachineAvailability在最终的 JSON 中。

问题是它通过结果机器触发了另外 2 个请求(即著名的 N+1 问题)。

1.我怎样才能在一个请求中解决这个问题?

2.是否有可能以某种方式创建我的 JSON 并直接返回它MachineController

4

1 回答 1

1

在 1 个请求中解决这个问题很困难,因为您必须使用 Hibernate 本机 API 来映射可用性集合的表别名。您需要为主查询中的可用性添加连接并执行以下操作:session.createNativeQuery("...").addEntity("m", Machine.class).addFetch("av", "m", "availabilities")

另一种选择是使用Blaze-Persistence 实体视图,因为 Blaze-Persistence 支持 CTE 和 PostgreSQL 提供的更多好东西,这对您来说可能是一个有趣的解决方案。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您以您喜欢的方式定义您的目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

我不知道您的模型,但是对于您的用例,可能的 DTO 模型使用 Blaze-Persistence Entity-Views 可能如下所示:

@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;
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

MachineDto a = entityViewManager.find(entityManager, MachineDto.class, id);

Spring Data 集成允许您几乎像 Spring Data Projections 一样使用它:https ://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<MachineDto> findAll(Pageable pageable);

然后,您可以使用Sort.asc("distanceInTime")和排序Sort.asc("distanceInMeters")

最好的部分是,它只会获取实际需要的状态!

于 2021-04-30T15:56:29.110 回答