为什么 Spring JPA 双向 OneToMany 和 ManyToOne 不更新外键列?
Why Spring JPA Bidirectional OneToMany and ManyToOne is not updating the foreign key column?
你好我正在学习Spring JPA 使用 OneToMany 和 ManyToOne 双向关系,在一些例子中我看到 OneToMany 和 ManyToOne 关系,当我写在两侧时,JPA 添加一个新列作为外键列和从 Parent table 中插入键值。但是当我尝试我的时,该列始终是空白的。这是我的代码的样子:
这是我的 Account.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
}
这是我的 Dealer.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();
}
}
}
然后我 post 将 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 table 中插入查询,但是当我检查我的数据库时 table(即 postgre sql) 我发现有一个字段 "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')
这是我尝试后的更新模型:
我的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
}
这是我的 Dealer.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 数据时遇到了这个错误
> "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
要在双向关系中保存子实体和父实体,在子实体中设置父实体也同步双方。
此处在 dealer
个对象中设置 account
引用
public Account save(Account account) {
for (Dealer dealer: account.getDealer()) {
dealer.setAccount(account);
}
return accountRepository.save(account);
}
更新:
但如果您想使用单向关系,则删除 Dealer
实体中的 Account
关系。删除这部分
@ManyToOne(fetch = FetchType.LAZY)
public Account account;
然后更新 Account
table 中的关系。
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "account_id")
public List<Dealer> dealer;
这里我们删除了 mappedBy
,因为目前我们删除了 Dealer
端的映射,并添加了 @JoinColumn
来定义我们用于帐户引用的列。
如果您在两个实体之间存在双向关系(此处为 Account
和 Dealer
),您必须决定哪一方是所述关系的所有者。
默认情况下,One-side 是导致 Join-Table 的所有者,它在修改列表时更新。
由于您定义了 mappedBy 属性 (@OneToMany(mappedBy = "account")
),因此 Many 端是的关系。这意味着 msDealer
Table 中的 account
列将保存 Account
的外键,然后 Join-Table 将不再使用。 Join-Table 可能是在将 mappedBy 定义添加到注释之前初始化数据库时遗留下来的。
您有以下选择:
- 让
Dealer
保留所有者,不要使用 Join-Table。如果您想观察数据库中的副作用,请查看 msDealer.account
. 列
- 使用
@JoinTable
注释来强制使用这样的 table
您缺少子端的 @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
实体及其子实体,则不应传递子实体的 ID。 (实际上在 persist 上传递任何 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"
}
]
}
保存之前可能还需要双方同步:
account.getDealer().forEach(d -> d.setAccount(account));
编辑:
来自 Author
的编辑必须级联到子项:
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Dealer> dealer;
您还可以在 Action
或 List<Dealer>
上添加 @JsonIgnore
以避免在序列化为 json.
时出现 Whosebug
你说你在学习,我想给你一个详细的答案,让你容易理解。您在这里缺少的是 @JoinColumn
.
@JoinColumn
可以用在关系的两边。这里的重点是物理信息重复(列name) 以及 未优化的 SQL 查询,它将产生一些额外的 UPDATE
语句 .
由于多对一(几乎)总是 JPA 规范中双向关系的所有者方,因此一对多关联由 @OneToMany(mappedBy=...)
注释
通过基本代码示例理解
@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
存在双向一对多关系。您不必(不得)在 mappedBy
端定义任何物理映射。
要映射双向一对多,将 一对多方作为拥有方,您必须删除 mappedBy
元素并设置多对一 @JoinColumn
为 insertable
和 updatable
为假。此解决方案未优化,会产生一些额外的 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() {
...
}
如果您对给出的解释有任何其他问题,请在下方发表评论。 :)
你好我正在学习Spring JPA 使用 OneToMany 和 ManyToOne 双向关系,在一些例子中我看到 OneToMany 和 ManyToOne 关系,当我写在两侧时,JPA 添加一个新列作为外键列和从 Parent table 中插入键值。但是当我尝试我的时,该列始终是空白的。这是我的代码的样子:
这是我的 Account.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
}
这是我的 Dealer.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();
}
}
}
然后我 post 将 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 table 中插入查询,但是当我检查我的数据库时 table(即 postgre sql) 我发现有一个字段 "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')
这是我尝试后的更新模型:
我的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
}
这是我的 Dealer.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 数据时遇到了这个错误
> "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
要在双向关系中保存子实体和父实体,在子实体中设置父实体也同步双方。
此处在 dealer
个对象中设置 account
引用
public Account save(Account account) {
for (Dealer dealer: account.getDealer()) {
dealer.setAccount(account);
}
return accountRepository.save(account);
}
更新:
但如果您想使用单向关系,则删除 Dealer
实体中的 Account
关系。删除这部分
@ManyToOne(fetch = FetchType.LAZY)
public Account account;
然后更新 Account
table 中的关系。
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "account_id")
public List<Dealer> dealer;
这里我们删除了 mappedBy
,因为目前我们删除了 Dealer
端的映射,并添加了 @JoinColumn
来定义我们用于帐户引用的列。
如果您在两个实体之间存在双向关系(此处为 Account
和 Dealer
),您必须决定哪一方是所述关系的所有者。
默认情况下,One-side 是导致 Join-Table 的所有者,它在修改列表时更新。
由于您定义了 mappedBy 属性 (@OneToMany(mappedBy = "account")
),因此 Many 端是的关系。这意味着 msDealer
Table 中的 account
列将保存 Account
的外键,然后 Join-Table 将不再使用。 Join-Table 可能是在将 mappedBy 定义添加到注释之前初始化数据库时遗留下来的。
您有以下选择:
- 让
Dealer
保留所有者,不要使用 Join-Table。如果您想观察数据库中的副作用,请查看msDealer.account
. 列
- 使用
@JoinTable
注释来强制使用这样的 table
您缺少子端的 @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
实体及其子实体,则不应传递子实体的 ID。 (实际上在 persist 上传递任何 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"
}
]
}
保存之前可能还需要双方同步:
account.getDealer().forEach(d -> d.setAccount(account));
编辑:
来自 Author
的编辑必须级联到子项:
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Dealer> dealer;
您还可以在 Action
或 List<Dealer>
上添加 @JsonIgnore
以避免在序列化为 json.
你说你在学习,我想给你一个详细的答案,让你容易理解。您在这里缺少的是 @JoinColumn
.
@JoinColumn
可以用在关系的两边。这里的重点是物理信息重复(列name) 以及 未优化的 SQL 查询,它将产生一些额外的 UPDATE
语句 .
由于多对一(几乎)总是 JPA 规范中双向关系的所有者方,因此一对多关联由 @OneToMany(mappedBy=...)
通过基本代码示例理解
@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
存在双向一对多关系。您不必(不得)在 mappedBy
端定义任何物理映射。
要映射双向一对多,将 一对多方作为拥有方,您必须删除 mappedBy
元素并设置多对一 @JoinColumn
为 insertable
和 updatable
为假。此解决方案未优化,会产生一些额外的 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() {
...
}
如果您对给出的解释有任何其他问题,请在下方发表评论。 :)