Я приготовил рабочий раствор: Cepr0 / greyhound-demo .Я немного переработал ваш проект - сделал это с базами данных Spring-Boot, Lombok и H2, просто для демонстрационных целей и для упрощения.
Итак, если я не ошибаюсь, задача состоит в том, чтобы преобразовать ' назначения ' (с сайта борзой):
{
"results": [
{
"oper_nbr": 1,
"carrier_cd": "GLX ",
"last_name": "JOHN",
"first_name": "SMITH",
"middle_init": null,
"home_loc_6": 12345,
"home_loc_3": "NLX",
"oper_class": "T"
},
{
"oper_nbr": 2,
"carrier_cd": "GLX ",
"last_name": "JOHN",
"first_name": "DOE",
"middle_init": null,
"home_loc_6": 67890,
"home_loc_3": "NLX",
"oper_class": "T"
}
]
}
в три объекта: Driver
, Location
и Carrier
с отношениями:
Location -1---*- Driver -*---1- Carrier
, т.е. Driver
имеет «много-отношение «один к одному» с Location
и Carrier
.
Основная проблема этой задачи заключается в том, что при сохранении сущности Driver
нам необходимо использовать уже сохраненный Location
и Carrier
сущностей, или используйте новые .Таким образом, чтобы решить эту проблему, мы должны:
- Подготовить 3 репозитория для этих объектов.
- Для каждого «задания» найти , связанных
Location
и Carrier
, - Если
Location
и Carrier
не найдены, создайте новые. - Создайте новый
Driver
и установите найденные Location
и Carrier
или новыесоздал. - Сохраните
Driver
(и каскадно сохраните Location
и Carrier
, если они не найдены).
Окончательный кодметод GreyhoundService.process()
:
@Transactional
public void process() {
client.getAssignments()
.stream()
.forEach(a -> {
log.debug("[d] Assignment: {}", a);
Driver driver = new Driver();
driver.setId(a.getDriverId());
driver.setFirstName(a.getFirstName());
driver.setLastName(a.getLastName());
driver.setMiddleName(a.getMiddleName());
driver.setLocation(
locationRepo.findById(new Location.PK(a.getLocationId(), a.getLocationName()))
.orElse(new Location(a.getLocationId(), a.getLocationName()))
);
driver.setCarrier(
carrierRepo.findById(a.getCarrierId().trim())
.orElse(new Carrier(a.getCarrierId().trim()))
);
driverRepo.saveAndFlush(driver);
log.debug("[d] Driver: {}", driver);
});
}
Чтобы свести к минимуму размер данных в базе данных и количество SQL-запросов, я преобразовал исходные объекты следующим образом:
Драйвер
@Getter
@Setter
@ToString
@EqualsAndHashCode(of = "id")
@Entity
@Table(name = "drivers")
public class Driver implements Persistable<Long> {
@Id private Long id;
private String firstName;
private String lastName;
private String middleName;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "carrierId", foreignKey = @ForeignKey(name = "drivers_carriers"))
private Carrier carrier;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumns(
value = {@JoinColumn(name = "locationId"), @JoinColumn(name = "locationName")},
foreignKey = @ForeignKey(name = "drivers_locations")
)
private Location location;
@Override
public boolean isNew() {
return true;
}
}
Местоположение
@Data
@NoArgsConstructor
@Entity
@Table(name = "locations")
@IdClass(Location.PK.class )
public class Location {
@Id private Long locationId;
@Id private String locationName;
public PK getId() {
return new PK(locationId, locationName);
}
public void setId(PK id) {
this.locationId = id.getLocationId();
this.locationName = id.getLocationName();
}
public Location(final Long locationId, final String locationName) {
this.locationId = locationId;
this.locationName = locationName;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class PK implements Serializable {
private Long locationId;
private String locationName;
}
}
Перевозчик
@Data
@NoArgsConstructor
@Entity
@Table(name = "carriers")
public class Carrier {
@Id private String carrierId;
public Carrier(final String carrierId) {
this.carrierId = carrierId;
}
}
Как вы можете видеть, я использовал натуральные идентификаторыдля Location
и Carrier
(и составной в Carrier
).Это позволило не только уменьшить размер данных, но и уменьшить количество дополнительных запросов SQL, которые Hibernate выполняет при хранении сложных объектов.Когда таблицы Location
и Carrier
заполнены, Hibernate не выполняет ненужных запросов для их поиска, а берет их данные из собственного кэша (вы можете увидеть это в журнале приложения).
PS Обратите внимание, что этоРешение не является оптимальным.ИМО, чтобы сделать его лучше, вы можете разделить основной процесс на две части: первая сохраняется с разными Location
с и Carrier
с, а вторая просто сохраняется Driver
с, не находя Location
с и Carrier
с,Обе части исполняются с пакетной вставкой .
ОБНОВЛЕНИЕ
Ответвление с оптимальным решением: Cepr0 / greyhound-demo: async_and_batch_insert
Из-за асинхронного сохранения местоположений и носителей и пакетной вставки обработка занимает всего около 5 секунд.