简体   繁体   English

为什么 Spring JPA 双向 OneToMany 和 ManyToOne 不更新外键列?

[英]Why Spring JPA Bidirectional OneToMany and ManyToOne is not updating the foreign key column?

Hi i am learning Spring JPA using OneToMany and ManyToOne bidirectional relationship, in some example i see OneToMany and ManyToOne relationship when i written in two side, the JPA add a new column as the foreign key column and insert the key value from the Parent table. Hi i am learning Spring JPA using OneToMany and ManyToOne bidirectional relationship, in some example i see OneToMany and ManyToOne relationship when i written in two side, the JPA add a new column as the foreign key column and insert the key value from the Parent table. But when i try mine, the column is always blank.但是当我尝试我的时,该列始终为空白。 Here is how my code looked like:这是我的代码的样子:

Here is my Account.java model:这是我的 Account.java model:

@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

}

and here is my Dealer.java model:这是我的经销商。java model:

@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

}

and here is my Repository:这是我的存储库:

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

}

and here is my Service:这是我的服务:

@Service
public class AccountService {

    @Autowired
    private AccountRepository accountRepository;

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

}

and here is my controller:这是我的 controller:

@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();
        }
    }

}

then i post the JSON into my save endpoint:然后我将 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"
    }
  ]
}

and when i save it the hibernate that showed up in my terminal looked inserting query into that 2 table, but when i check my database table (which is postgresql) i found there is a field "account_account_id" that is null, what did i miss here?当我保存它时,出现在我的终端中的 hibernate 看起来将查询插入到那 2 个表中,但是当我检查我的数据库表(即 postgresql)时,我发现有一个字段“account_account_id”是 null,我错过了什么这里?

i want the Hibernate run sql like this:我希望 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')

Here is my UPDATED Model after some try:经过一番尝试,这是我更新的 Model:

my Account.java I delete cascade = CascadeType.ALL, orphanRemoval = true我的 Account.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

}

and here is my Dealer.java.这是我的经销商。java。 Added @JoinColumn:添加了@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

}

now the error is getting weird, i got this error when i save the JSON data现在错误越来越奇怪,当我保存 JSON 数据时出现此错误

> "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"

in some tutorial it worked, but mine is not, what did i do wrong?在某些教程中它有效,但我的不是,我做错了什么?

here is my github repo: https://github.com/Fly-Away/LearningSpring这是我的 github 回购: https://github.com/Fly-Away/LearningSpring

You're missing the @JoinColumn on the child side:您缺少子方面的@JoinColumn

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

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

    // other fields

}

You have used mappedBy on the parent side, but there is no mapping on the child side.您在父端使用了mappedBy ,但在子端没有映射。 You need to indicate, that the Dealer is the relationship owner - it has the foreign key.您需要指出, Dealer是关系所有者 - 它具有外键。

Edit: if you're persisting (not merging) the Account entity, together with its children, you should not pass ids of child entities.编辑:如果您保留(不合并) Account实体及其子实体,则不应传递子实体的 ID。 (Actually passing any ids upon persist is a code smell and most probably a performance killer.) The json used should look like: (实际上在持久化上传递任何 id 都是代码异味,很可能是性能杀手。)使用的 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"
    }
  ]
}

Before saving both-side synchronization might also be needed:在保存双方同步之前,可能还需要:

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

Edit:编辑:

From Author edits must cascade to child:Author编辑必须级联到子级:

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

You might also add @JsonIgnore over Action or List<Dealer> to avoid stackoverflow on serialization to json.您还可以在ActionList<Dealer>上添加@JsonIgnore以避免序列化到 json 时出现堆栈溢出。

To save child with parent in the bidirectional relationship set parent in child entity also to sync both side.要在双向关系中保存与父级的子级,在子级实体中设置父级也要同步双方。

Here set account reference in dealer objects此处在dealer对象中设置account参考

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

Update:更新:

But if you want to use Unidirectional relation then remove Account relation in Dealer Entity.但是,如果您想使用单向关系,请删除Dealer实体中的Account关系。 Remove this portion删除这部分

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

Then update the relation in Account table.然后更新Account表中的关系。

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

Here we remove mappedBy because currently we removed mapping in Dealer side and add @JoinColumn to define which column we are using for account refference.这里我们移除了mappedBy ,因为目前我们移除了Dealer端的映射,并添加@JoinColumn来定义我们使用哪一列作为账户参考。

If you have a bidirectional relationship between two entities (here Account and Dealer ) you have to decide which side is the owner of said relationship.如果您在两个实体(此处为AccountDealer )之间存在双向关系,则您必须决定哪一方是所述关系的所有者。 By default the One -side is the owner which leads to a Join-Table which is updated when modifying the List.默认情况下,一侧是所有者,它导致在修改列表时更新的联接表。

Since you defined the mappedBy property ( @OneToMany(mappedBy = "account") ) the Many -side is the owner of the relationship.由于您定义了mappedBy属性( @OneToMany(mappedBy = "account") ),因此Many -side 是关系的所有者。 This means the account column in the msDealer Table will hold the foreign key of the Account and then Join-Table will not be used anymore.这意味着msDealer表中的account列将保存Account的外键,然后 Join-Table 将不再使用。 The Join-Table is probably a left over from initializing the database before you added the mappedBy definition to the annotation. Join-Table 可能是在将mappedBy定义添加到注释之前初始化数据库的遗留物。

Options you have:您拥有的选项:

  1. Let the Dealer stay the owner and don't use a Join-Table.Dealer保持所有者身份,不要使用 Join-Table。 If you want to observe side effects in the database look at column msDealer.account .如果您想观察数据库中的副作用,请查看列msDealer.account
  2. Use the @JoinTable annotation to enforce the usage of such a table使用@JoinTable注释来强制使用这样的表

As you said you are learning, I would like to give you a detailed answer so it will be easy for you to understand.正如您所说的您正在学习,我想给您一个详细的答案,以便您理解。 What you are missing here is @JoinColumn .您在这里缺少的是@JoinColumn

@JoinColumn could be used on both sides of the relationship. @JoinColumn可用于关系的双方。 The point here is in physical information duplication (column name) along with not optimized SQL query that will produce some additional UPDATE statements .这里的重点是物理信息重复(列名)以及未优化的 SQL 查询,这将产生一些额外的UPDATE语句

According to documentation :根据文件

Since many to one are (almost) always the owner side of a bidirectional relationship in the JPA spec, the one to many association is annotated by @OneToMany(mappedBy=...)由于多对一(几乎)始终是 JPA 规范中双向关系的所有者,一对多关联由@OneToMany(mappedBy=...)

Understand by basic code example通过基本代码示例理解

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

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

Troop has a bidirectional one to many relationship with Soldier through the troop property. Troop通过部队属性与Soldier具有双向的一对多关系。 You don't have to (must not) define any physical mapping in the mappedBy side.您不必(不得)在mappedBy端定义任何物理映射。

To map a bidirectional one to many, with the one-to-many side as the owning side , you have to remove the mappedBy element and set the many to one @JoinColumn as insertable and updatable to false.对于 map 双向一对多,以一对多作为拥有方,您必须删除mappedBy元素并将多对一@JoinColumn设置为可insertableupdatable为 false。 This solution is not optimized and will produce some additional UPDATE statements.此解决方案未优化,会产生一些额外的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() {
    ...
}

Comment below if you have any further questions on the explanation given.如果您对给出的解释有任何进一步的问题,请在下方评论。 :) :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM