OneToMany: Не удается добавить или обновить дочернюю строку: не удается выполнить ограничение внешнего ключа

#spring #hibernate #jpa #one-to-many

Вопрос:

Имея эти классы:

Vehicle.java:

 @Data
@Entity
@NoArgsConstructor(force = true)
@RequiredArgsConstructor
public class Vehicle {
    @Id
    private final int vehicleId;
    
    private final String vehicleName;
}
 

User.java:

 @Entity
@Data
@Table(name = "UserDetails")
public class User {
    @Id
    private int userId;
    
    private String userName;
    
    @OneToMany
    @JoinColumn(name = "vehicleId")
    private Collection<Vehicle> vehicles = new ArrayList<>();
}
 

DemoApp.java:

 @Bean
CommandLineRunner dataLoader3(VehicleRepository vehicleRepo, UserRepository userRepo){
  return new CommandLineRunner() {
    @Override
    public void run(String... args) throws Exception {
      Vehicle v = new Vehicle(0, "Car");
      vehicleRepo.save(v);
      Vehicle v2 = new Vehicle(1, "Bus");
      vehicleRepo.save(v2);

      User u = new User();
      u.setUserName("First User");
      u.setVehicles(Arrays.asList(v, v2));

      userRepo.save(u);
    }
  }
}
 

Пружина выдает 2 ошибки относительно внешнего ключа:

Ошибка выполнения DDL «изменить таблицу транспортного средства удалить внешний ключ FKk65fmqecth7bpxy163dwj685f» с помощью инструкции JDBC

и затем

Невозможно добавить или обновить дочернюю строку: сбой ограничения внешнего ключа ( test_db . vehicle , ОГРАНИЧЕНИЕ FKk65fmqecth7bpxy163dwj685f ВНЕШНЕГО КЛЮЧА ( vehicle_id ) ССЫЛКИ user_details ( user_id ))

Я только что попытался изменить @JoinColumn имя в @OneToMany отношении. Как это решить?

PS: Я удалил все созданные таблицы раньше, поэтому ограничений не осталось.

PPS: использую MySQL.

Комментарии:

1. «изменить таблицу, удалить внешний ключ FKk65fmqecth7bpxy163dwj685f» — почему это происходит, если вы удалили все таблицы? Что-то вызывает DDL, чтобы попытаться удалить ограничение, и ошибка может дать ключ к проблеме, с которой вы столкнулись, тем более, что в нем говорится, что оно не может удалить его, и это же ограничение мешает какому-либо другому оператору обновления. Поскольку в нем упоминается «VEHICLE_ID», а вы используете «VehicleId», я подозреваю, что вы используете генерацию DDL и позволяете ей пытаться «исправить» существующую схему, но она не может. Полностью отбросьте это, убедитесь, что они исчезли, исправьте свои сущности и начните все сначала.

Ответ №1:

Проблема связана с определением @JoinColumn(имя = «Идентификатор транспортного средства») для сущности пользователя:

 @OneToMany
@JoinColumn(name = "vehicleId")
private Collection<Vehicle> vehicles = new ArrayList<>();
 

Это указывает JPA на создание и использование столбца «Идентификатор транспортного средства» в таблице транспортных средств для ссылки на сущность пользователя. Ваша сущность/таблица транспортного средства, однако, уже определяет «Идентификатор транспортного средства», что вызывает ошибки и конфликты, особенно если один пользователь ссылается на два транспортных средства, которые, вероятно, попытаются использовать одно и то же значение для идентификатора транспортного средства.

Попробуй:

 @OneToMany
@JoinColumn(name = "USER_ID")
private Collection<Vehicle> vehicles = new ArrayList<>();
 

вместо этого в транспортном средстве будет создан столбец «Идентификатор пользователя» для идентификации одного (и только одного) пользователя, который может быть связан с транспортным средством в этом типе сопоставления. Обычно это делается с помощью

 public class User {
    @Id
    private int userId;
    
    private String userName;
    
    @OneToMany(mappedBy="user")
    private Collection<Vehicle> vehicles = new ArrayList<>();
}

public class Vehicle {
    @Id
    private final int vehicleId;
    private final String vehicleName;
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="USER_ID")
    private User user;
}
 

В качестве альтернативы, как было предложено, вы можете просто отказаться от совместной колонки. Это создаст таблицу соединений для этой связи, которая будет очень похожа на множество отношений, но с ограничениями на таблицу отношений.

Комментарии:

1. Это работает (есть @JoinColumn(name="userId") , но если я это запущу, jpa создаст только одно транспортное средство ( v2 «Автобус»), которое вставлено. Этого v1 («Автомобиля») нет. Это как-то связано с этим?

2. Этого не должно быть — что происходит при втором вызове сохранения транспортного средства? Включите ведение журнала SQL, чтобы узнать, что делает JPA/Spring во время этого вызова.

Ответ №2:

В вашей настройке вы не должны использовать @JoinColumn(name = "vehicleId") в User классе. Пожалуйста, попробуйте следующее (я предполагаю, что Vehicle ссылки, на которые нет ни в одном User , не нужны):

 @Entity
@Data
@Table(name = "UserDetails")
public class User {
    @Id
    private int userId;
    
    private String userName;
    
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private Collection<Vehicle> vehicles = new ArrayList<>();
}
 

Предполагается ли однонаправленная связь?

Комментарии:

1. Почему нельзя использовать объединенную колонку? Как исходная форма с столбцом соединения, так и эта без него делают это однонаправленным отображением OneToMany — единственная разница в том, что для вашей требуется дополнительная таблица соединений, в то время как операции использовали внешний ключ в целевой таблице (транспортное средство), аналогично двунаправленному 1:m.

2. Мне следовало бы быть более подробным в предложении you should not use @JoinColumn , вы правы. Я имел you should not use @JoinColumn(name = "vehicleId") в виду причины, которые вы подробно описали. Что касается однонаправленных отношений, я знал, что мое предложение также создает их, мне просто было интересно, действительно ли это было задумано.