Страничный запрос Spring Data, дающий разные результаты между H2 и MySQL - PullRequest
0 голосов
/ 19 сентября 2018

Я использую Spring Boot 2.0.5, Spring Data и Hibernate / JPA.Для встроенной базы данных у меня есть H2, а для постановки / производства у меня есть MySQL.Я не могу опубликовать этот код в GitHub / GitLab Repo, потому что он проприетарный, поэтому я представлю его как можно лучше здесь:

У меня есть пейджинговый контроллер

@RestController
@RequestMapping("/api")
public class AircraftListController {

    private static final int DEFAULT_PAGE_NUMBER = 0;
    private static final int DEFAULT_PAGE_SIZE = 10;

    private AircraftService aircraftService;

    @Autowired
    public AircraftListController(AircraftService aircraftService) {
        this.aircraftService = aircraftService;
    }

    @Secured("ROLE_ADMIN")
    @GetMapping(value = {"/maintainers/{mid}/aircrafts"}, produces = "application/json")
    @ResponseStatus(value = HttpStatus.OK)
    Response<Page<AircraftRow>> getPagedMaintainers(
            @PathVariable("mid") Optional<Long> maintainerId,
            @PageableDefault(page = DEFAULT_PAGE_NUMBER, size = DEFAULT_PAGE_SIZE)
            @SortDefault.SortDefaults({
                    @SortDefault(sort = "a.registration", direction = Sort.Direction.ASC)
            }) Pageable pageable) {

        Page<AircraftRow> aircraft = aircraftService.findSortedSummary(maintainerId.get(), pageable);


        return Response.of(aircraft);
    }
}

С помощьюТипы моделей: Самолеты:

@Entity
public class Aircraft {

    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, updatable = false)
    private String registration;

    private String make;

    private String model;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDate manufacture;

    @ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
    private Client owner;

    @ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
    private Client operator;

    private String base;

    @OneToOne(cascade={CascadeType.ALL})
    @JoinColumn(name = "airframe_id")
    private Airframe airframe;

    @OneToMany(cascade={CascadeType.ALL}, fetch = FetchType.EAGER)
    @JoinColumn(name = "aircraft_id")
    private Set<Prop> props;

    @OneToMany(cascade={CascadeType.ALL}, fetch = FetchType.EAGER)
    @JoinColumn(name = "aircraft_id")
    private Set<Engine> engines;

    // builder, accessors, hashCode, equals, and toString omitted
}

Техническое обслуживаниеКонтракт:

@Entity
public class MaintenanceContract {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, updatable = false)
    private String nk;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "maintainer_id")
    @JsonBackReference
    private Maintainer maintainer;

    private boolean current;

    private String image;

    @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
    @JoinColumn(name = "aircraft_id")
    private Aircraft aircraft;

    @OneToOne
    @JoinColumn(name = "primary_contact_id")
    private Client primaryContact;

    @OneToMany(cascade = {CascadeType.ALL})
    @JoinColumn(name = "maintenance_contract_id", referencedColumnName = "id")
    private Set<WorkOrder> workOrders = new HashSet<>();

    @OneToMany
    @JoinTable(name = "maintenance_contract_outstanding_ad",
            joinColumns={ @JoinColumn(name="maintenance_contract_id", referencedColumnName="id") },
            inverseJoinColumns={ @JoinColumn(name="outstanding_ad_id", referencedColumnName="id") }
    )
    private Set<AirworthinessDirective> outstandingAds = new HashSet<>();

    @OneToMany
    @JoinTable(name = "maintenance_contract_completed_ad",
            joinColumns={ @JoinColumn(name="maintenance_contract_id", referencedColumnName="id") },
            inverseJoinColumns={ @JoinColumn(name="completed_ad_id", referencedColumnName="id") }
    )
    private Set<AirworthinessDirective> completedAds = new HashSet<>();
}

Клиент:

@Entity
public class Client {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Access(AccessType.PROPERTY)
    @JsonView({View.ClientView.class, View.AircraftView.class})
    private Long id;

    @Column(unique = true, updatable = false)
    @JsonView({View.ClientView.class, View.AircraftView.class})
    private String nk;

    @Min(10000000000L)
    @JsonView({View.ClientView.class, View.AircraftView.class})
    private Long abn;

    @JsonView({View.ClientView.class, View.AircraftView.class})
    private String name;

    @OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
    @JoinColumn(name = "person_id")
    @JsonView({View.ClientView.class, View.AircraftView.class})
    private Person principal;

    @OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
    @JoinColumn(name = "contact_id")
    @JsonView({View.ClientView.class, View.AircraftView.class})
    private Contact contact;

    @OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
    @JoinColumn(name = "address_id")
    @JsonView({View.ClientView.class, View.AircraftView.class})
    private Address address;
}

... и порядок работы:

@Entity
public class WorkOrder {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, updatable = false)
    private String nk;

    private String workOrderNumber;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDate date;

    private Integer hours;

    private String title;

    @Enumerated(EnumType.STRING)
    @Column(length = 12)
    private WorkOrderStatus status;

    private String notes;

    @OneToMany(cascade = {CascadeType.ALL})
    @JoinColumn(name = "work_order_id", referencedColumnName = "id")
    private Set<WorkSet> workSets = new HashSet<>();

    @ManyToMany(fetch = FetchType.EAGER, cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE})
    @JoinTable(name = "work_order_airworthiness_directive",
            joinColumns = @JoinColumn(name = "work_order_id"),
            inverseJoinColumns = @JoinColumn(name = "airworthiness_directive_id"))
    private Set<AirworthinessDirective> attachedAds = new HashSet<>();

    @Column(name="maintenance_contract_id")
    private Long contractId;
}

Существуют сервисные уровни, но, похоже, все они работают до сих пор, соответствующий репозиторий выглядит следующим образом:

@Repository
public interface AircraftRepository extends JpaRepository<Aircraft, Long> {


    @Query(value = "select new au.com.avmaint.api.aircraft.model.AircraftRow(a.id, mc.id, wo.id, wo.date, a.registration, a.make, a.model, "
            + "c.principal.id, c.principal.firstName, c.principal.lastName, c.contact.phone, mc.current) "
                    + "from MaintenanceContract mc, WorkOrder wo "
                    + "inner join mc.primaryContact c "
                    + "inner join mc.aircraft a "
                    + "where mc.maintainer.id = ?1 "
                    + "and wo.contractId = mc.id "
 + "and wo.date = (select max(wo2.date) from WorkOrder wo2 where wo2.contractId = wo.contractId)",
            countQuery = "select count(*) "
                    + "from MaintenanceContract mc, WorkOrder wo "
                    + "inner join mc.primaryContact c "
                    + "inner join mc.aircraft a "
                    + "where mc.maintainer.id = ?1 "
                    + "and wo.contractId = mc.id "
 + "and wo.date = (select max(wo2.date) from WorkOrder wo2 where wo2.contractId = wo.contractId)")
    Page<AircraftRow> findSortedSummary(Long maintainerId, Pageable pageable);

}

Я фактически извлек результирующий запрос из моих журналов, очистил его и использовалчтобы запросить MySQL:

select
    a.id as aid,
    mc.id as mcid,
    wo.id as woid,
    wo.date as woDate,
    a.registration as reg,
    a.make as make,
    a.model as model,
    client.person_id as pid,
    p.first_name as firstName,
    p.last_name as lastName,
    contact.phone as phone,
    mc.current as current
from
    maintenance_contract mc
inner join client client on mc.primary_contact_id=client.id
cross join person p
cross join contact contact
inner join aircraft a on mc.aircraft_id=a.id
cross join work_order wo
where
    client.person_id=p.id
    and client.contact_id=contact.id
    and mc.maintainer_id=1
    and wo.maintenance_contract_id=mc.id
    and wo.date=(
        select
            max(wo2.date)
        from
            work_order wo2
        where
            wo2.maintenance_contract_id=wo.maintenance_contract_id
    )
order by
    a.registration asc limit 4

Я обнаружил, что в H2 хранилище ничего не выбирает, а точно такая же настройка данных в MySQL возвращает:

enter image description here

, что является ожидаемым результатом.Так что с H2?Другое дело, что условия H2 находятся в функциональных тестах, в то время как MySQL выполняется в основном приложении.Так что, возможно, это как-то связано с контекстом теста?

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("embedded")
@EnableJpaRepositories({ "au.com.avmaint.api" })
@AutoConfigureMockMvc
public class AircraftListControllerFunctionalTest {

Хотя обычно это не имеет значения.У меня есть ощущение, что в отображениях есть что-то, что H2 не поддерживает, и поэтому не может ничего выбрать в запросе, но я не уверен.

...