Почему Spring JPA Bidirectional OneToMany и ManyToOne не обновляют столбец внешнего ключа? - PullRequest
0 голосов
/ 07 апреля 2020

Привет, я изучаю Spring JPA, используя двунаправленные отношения OneToMany и ManyToOne, в некоторых примерах я вижу взаимосвязь OneToMany и ManyToOne, когда я пишу с двух сторон, JPA добавляет новый столбец в качестве столбца внешнего ключа и вставляет значение ключа из Родительский стол. Но когда я пробую свою, колонка всегда пуста. Вот как выглядел мой код:

Вот мой аккаунт. java модель:

@Entity
@Table(name = "msAccount")
public class Account {

    @Id
    @NotBlank(message = "Not Blank")
    @Size(min = 0, max = 20)
    public String accountId;

    @NotBlank(message = "Not Blank")
    public String accountName;

    @NotBlank(message = "Not Blank")
    @Email(message = "Should be the right email")
    public String accountEmail;

    @NotBlank(message = "Not Blank")
    @Size(min = 5, message = "Minimal 5 char")
    public String accountAddress;

    @NotBlank(message = "Not Blank")
    public String town;

    @NotBlank(message = "Not Blank")
    public String npwp;

    @NotBlank(message = "Not Blank")
    public String phoneNumber;

    public String fax;

    public String remarks;

    @NotNull
    public Date entryTime;

    @NotNull
    public Boolean active;

    @OneToMany(mappedBy="account", cascade = CascadeType.ALL, orphanRemoval = true)
    public List<Dealer> dealer;

//getter setter skipped

}

, а вот мой дилер. java модель:

@Entity
@Table(name = "msDealer")
public class Dealer {

    @Id
    @NotBlank(message = "Tidak Boleh Kosong")
    @Size(min = 0, max = 20)
    public String dealerId;

    @NotBlank(message = "Tidak Boleh Kosong")
    public String dealerName;

    @NotBlank(message = "Tidak Boleh Kosong")
    @Email(message = "Masukkan Email yang bener")
    public String dealerEmail;

    @NotBlank(message = "Tidak Boleh Kosong")
    @Size(min = 5, message = "Minimal 5 karakter")
    public String dealerAddress;

    @ManyToOne(fetch = FetchType.LAZY)
    public Account account;

//getter setter skipped

}

и вот мой репозиторий:

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {

}

, а вот мой сервис:

@Service
public class AccountService {

    @Autowired
    private AccountRepository accountRepository;

    public Account save(Account account) {
        return accountRepository.save(account);
    }

}

, а вот мой контроллер:

@RestController
@RequestMapping("/api/account")
public class AccountController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final int ROW_PER_PAGE = 10;

    @Autowired
    private AccountService accountService;

    @PostMapping("/new")
    public ResponseEntity<Account> addAccount(@Valid @RequestBody Account account) {
        try {
            Account newAccount = accountService.save(account);
            return ResponseEntity.created(new URI("/api/account/" + newAccount.getAccountId()))
                    .body(account);
        } catch(Exception ex) {
            logger.error(ex.getMessage());
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
        }
    }

}

тогда я отправьте JSON в мою конечную точку сохранения:

{
  "accountId": "USA001",
  "accountName": "string",
  "accountEmail": "string",
  "accountAddress": "string",
  "town": "string",
  "npwp": "string",
  "phoneNumber": "string",
  "fax": "string",
  "remarks": "string",
  "entryTime": "2020-04-07T15:01:29.404Z",
  "active": true,
  "dealer": [
    {
      "dealerId": "MMO001",
      "dealerName": "string",
      "dealerEmail": "string",
      "dealerAddress": "string"
    }
  ]
}

, и когда я сохраняю ее, спящий режим, который обнаружился в моем терминале, выглядел как вставка запроса в эту таблицу 2, но когда я проверял свою таблицу базы данных (которая является postgresql) я обнаружил, что есть поле "account_account_id", которое является нулевым, что я здесь пропустил?

я хочу запустить Hibernate sql следующим образом:

insert into account (account_id, account_name, ...etc)
values ('USA001', 1)

insert into dealer (account_account_id, dealer_name, dealer_id, ...etc)
values ('USA001', 'New dealer 1', 'MMO001')

Здесь моя обновленная модель после некоторой попытки:

моя учетная запись. java я удаляю cascade = CascadeType.ALL, orphanRemoval = true

@Entity
@Table(name = "msAccount")
public class Account {

    @Id
    @NotBlank(message = "Tidak Boleh Kosong")
    @Size(min = 0, max = 20)
    public String accountId;

    @NotBlank(message = "Tidak Boleh Kosong")
    public String accountName;

    @NotBlank(message = "Tidak Boleh Kosong")
    @Email(message = "Masukkan Email yang bener")
    public String accountEmail;

    @NotBlank(message = "Tidak Boleh Kosong")
    @Size(min = 5, message = "Minimal 5 karakter")
    public String accountAddress;

    @NotBlank(message = "Tidak Boleh Kosong")
    public String town;

    @NotBlank(message = "Tidak Boleh Kosong")
    public String npwp;

    @NotBlank(message = "Tidak Boleh Kosong")
    public String phoneNumber;

    public String fax;

    public String remarks;

    @NotNull
    public Date entryTime;

    @NotNull
    public Boolean active;

    @OneToMany(mappedBy="account")
    // @JoinColumn(name = "accountId")
    public List<Dealer> dealer;

//getter setter skipped

}

и вот мой дилер. java. Добавлен @JoinColumn:

@Entity
@Table(name = "msDealer")
public class Dealer {

    @Id
    @NotBlank(message = "Tidak Boleh Kosong")
    @Size(min = 0, max = 20)
    public String dealerId;

    @NotBlank(message = "Tidak Boleh Kosong")
    public String dealerName;

    @NotBlank(message = "Tidak Boleh Kosong")
    @Email(message = "Masukkan Email yang bener")
    public String dealerEmail;

    @NotBlank(message = "Tidak Boleh Kosong")
    @Size(min = 5, message = "Minimal 5 karakter")
    public String dealerAddress;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "account_id")
    public Account account;

//getter setter skipped

}

, теперь ошибка становится странной, эта ошибка появляется, когда я сохраняю JSON data

> "Unable to find com.api.b2b.Model.Dealer with id MMO001; nested
> exception is javax.persistence.EntityNotFoundException: Unable to find
> com.api.b2b.Model.Dealer with id MMO001"

в каком-то учебнике, но он работает нет, что я сделал не так?

вот мой репозиторий github: https://github.com/Fly-Away/LearningSpring

Ответы [ 4 ]

6 голосов
/ 10 апреля 2020

Вам не хватает @JoinColumn на дочерней стороне:

@Entity
@Table(name = "ms_dealer")
public class Dealer {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "account_account_id")
    public Account account;

    // other fields

}

Вы использовали mappedBy на родительской стороне, но на дочерней стороне нет сопоставления. Вы должны указать, что Dealer является владельцем отношения - у него есть внешний ключ.

Редактировать: если вы сохраняете (не объединяете) сущность Account вместе с ее дочерними элементами, вы не должен передавать идентификаторы дочерних объектов. (Фактически передача любых идентификаторов при сохранении - это запах кода и, скорее всего, снижение производительности.) Используемый json должен выглядеть следующим образом:

{
  "accountName": "string",
  "accountEmail": "string",
  "accountAddress": "string",
  "town": "string",
  "npwp": "string",
  "phoneNumber": "string",
  "fax": "string",
  "remarks": "string",
  "entryTime": "2020-04-07T15:01:29.404Z",
  "active": true,
  "dealer": [
    {
      "dealerName": "string",
      "dealerEmail": "string",
      "dealerAddress": "string"
    }
  ]
}

Перед сохранением может потребоваться синхронизация обеих сторон:

account.getDealer().forEach(d -> d.setAccount(account));

Редактировать:

С Author изменения должны каскадно передаваться дочернему элементу:

@OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Dealer> dealer;

Вы также можете добавить @JsonIgnore к Action или List<Dealer> к избегать переполнения стека при сериализации до json.

1 голос
/ 07 апреля 2020

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

Здесь задается account ссылка в dealer объектах

public Account save(Account account) {
    for (Dealer dealer: account.getDealer()) {
        dealer.setAccount(account);
    }
    return accountRepository.save(account);
}

Обновление:

Но если вы хотите использовать однонаправленное отношение, удалите Account отношение в Dealer Entity. Удалите эту часть

@ManyToOne(fetch = FetchType.LAZY)
public Account account;

Затем обновите отношение в таблице Account.

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "account_id")
public List<Dealer> dealer;

Здесь мы удаляем mappedBy, потому что в настоящее время мы удалили отображение на стороне Dealer и добавили @JoinColumn чтобы определить, какой столбец мы используем для ссылки на аккаунт.

0 голосов
/ 13 апреля 2020

Поскольку вы сказали, что учитесь, я хотел бы дать вам подробный ответ, чтобы вам было легко его понять. То, что вам здесь не хватает, это @JoinColumn.

@JoinColumn можно использовать с обеих сторон отношения. Смысл здесь в дублировании физической информации ( имя столбца) вместе с неоптимизированным SQL запросом, который выдаст несколько дополнительных UPDATE операторов .

Согласно документации :

С многие к одному (почти) всегда сторона владельца двунаправленных отношений в JPA spe c, связь один ко многим аннотируется @OneToMany(mappedBy=...)

Понимание по основному c примеру кода

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
} 

Troop имеет двунаправленное отношение один ко многим с Soldier через свойство tr oop. Вам не нужно (не нужно) определять какое-либо физическое отображение на стороне mappedBy.

Чтобы отобразить двунаправленную связь один-ко-многим, со стороной один-ко-многим в качестве стороны-владельца , вы должны удалить элемент mappedBy и установить для множества значение 1 @JoinColumn, для insertable и updatable значение false. Это решение не оптимизировано и даст несколько дополнительных UPDATE утверждений.

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}

Прокомментируйте ниже, если у вас возникнут дополнительные вопросы по поводу данного объяснения. :)

0 голосов
/ 09 апреля 2020

Если у вас есть двунаправленные отношения между двумя сущностями (здесь Account и Dealer), вы должны решить, какая сторона является владельцем указанных отношений. По умолчанию сторона One является владельцем, который ведет к объединяемой таблице, которая обновляется при изменении списка.

Поскольку вы определили свойство mappedBy (@OneToMany(mappedBy = "account")) Многие сторона является владельцем отношений. Это означает, что столбец account в таблице msDealer будет содержать внешний ключ Account, и тогда Join-Table больше не будет использоваться. Соединительная таблица, вероятно, не используется для инициализации базы данных до того, как вы добавили определение mappedBy в аннотацию.

Имеющиеся параметры:

  1. Позвольте Dealer оставайтесь владельцем и не используйте объединенный стол. Если вы хотите наблюдать побочные эффекты в базе данных, посмотрите на столбец msDealer.account.
  2. . Используйте аннотацию @JoinTable, чтобы обеспечить использование такой таблицы
.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...