Я использую 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 возвращает:
, что является ожидаемым результатом.Так что с H2?Другое дело, что условия H2 находятся в функциональных тестах, в то время как MySQL выполняется в основном приложении.Так что, возможно, это как-то связано с контекстом теста?
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("embedded")
@EnableJpaRepositories({ "au.com.avmaint.api" })
@AutoConfigureMockMvc
public class AircraftListControllerFunctionalTest {
Хотя обычно это не имеет значения.У меня есть ощущение, что в отображениях есть что-то, что H2 не поддерживает, и поэтому не может ничего выбрать в запросе, но я не уверен.