我正在使用Spring Boot 2.0.5,Spring Data和Hibernate / JPA。对于嵌入式数据库,我使用H2;对于暂存/生产,我使用MySQL。我无法将此代码发布在GitHub / GitLab存储库中,因为它是专有的,因此我将在此处尽力呈现:
我有一个分页控制器
@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
}
MaintenanceContract:
@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不支持映射中的某些内容,因此无法在查询中选择任何内容,但是我不确定。