<SpringBoot / Hibernate> 调用时发生 InvocationException JpaRepository.findAll(示例)
<SpringBoot / Hibernate> InvocationException on calling JpaRepository.findAll(Example example)
我注意到在 H2 数据库上执行 JpaRepository.findAll(示例)时返回了一个 InvocationException。
当我尝试配置“Account”和“Transaction”这两个表之间的外键关系时出现(即一个账户可以有很多笔交易,但一笔交易只能属于一个账户)。
在我添加@OneToMany 和@ManyToOne 注释之前,没有任何问题。
欢迎任何帮助,谢谢。
要求:
查询成功,但它给出了一个 InvocationException,它又给出了一个 HTTP500。
服务:
AccountService.java
...
......
public List<Transaction> getAllTransactions(Account account) {
TransactionPK inputTransactionPK = new TransactionPK();
inputTransactionPK.setAccountNum(account.getAccountNum());
Transaction inputTransaction = new Transaction();
inputTransaction.setTransactionPK(inputTransactionPK);
ExampleMatcher matcher = ExampleMatcher.matchingAll().withIgnorePaths("debitAmt", "creditAmt");
Example<Transaction> example = Example.of(inputTransaction, matcher);
List<Transaction> transactionList = transactionRepository.findAll(example);
log.info("##################################################\n"
+ "Retrieved transaction list for account with account number " + account.getAccountNum()
+ "\n##################################################");
return transactionList;
}
...
......
Table 型号:
Account.java
package com.somecompany.account.model;
import java.sql.Timestamp;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import lombok.Data;
@Entity
@Data
public class Account {
@OneToMany(mappedBy = "account")
private Set<Transaction> transaction;
@Column(name = "cust_id")
@NotEmpty(message = "Customer ID cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Customer ID must be a number")
@Min(value = 1L, message = "Customer ID must not be less than 1")
@Max(value = 9999999999L, message = "Customer ID must not be larger than 9999999999")
private long custId;
@Column(name = "account_num")
@Id
@NotEmpty(message = "Account number cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Account number must be a number")
@Min(value = 1L, message = "Account number must not be less than 1")
@Max(value = 9999999999L, message = "Account number must not be larger than 9999999999")
private long accountNum;
@Column(name = "account_name")
@NotEmpty(message = "Account name cannot be null nor empty")
@Size(min = 1, max = 30, message = "Account name must have length between 1 and 30")
private String accountName;
@Column(name = "account_type")
@NotEmpty(message = "Account type cannot be null nor empty")
@Size(min = 1, max = 7, message = "Account type must have length between 1 and 7")
private String accountType;
@Column(name = "balance_date")
@NotEmpty(message = "Balance date cannot be null nor empty")
private Timestamp balanceDate;
@Column(name = "currency")
@NotEmpty(message = "Currency cannot be null nor empty")
@Size(min = 3, max = 3, message = "Currency must have length exactly equal to 3")
private String currency;
@Column(name = "opening_available_balance", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Opening available balance cannot be null nor empty")
@Pattern(regexp = "^[0-9.]+$", message = "Opening available balance must be a decimal number")
@DecimalMin(value = "0.0", message = "Opening available balance cannot be negative")
private float openingAvailableBalance;
}
Transaction.java
package com.somecompany.account.model;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import lombok.Data;
@Entity
@Data
public class Transaction {
@ManyToOne
@JoinColumn(name = "account_num", referencedColumnName = "account_num", insertable = false, updatable = false, nullable = false)
private Account account;
@EmbeddedId
private TransactionPK transactionPK;
@Column(name = "account_name")
@NotEmpty(message = "Account name cannot be null nor empty")
@Size(min = 1, max = 30, message = "Account name must have length between 1 and 30")
private String accountName;
@Column(name = "currency")
@NotEmpty(message = "Currency cannot be null nor empty")
@Size(min = 3, max = 3, message = "Currency must have length exactly equal to 3")
private String currency;
@Column(name = "debit_amt", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Debit amount cannot be null nor empty")
@DecimalMin(value = "0.0", message = "Debit amount cannot be negative")
private float debitAmt;
@Column(name = "credit_amt", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Credit amount cannot be null nor empty")
@DecimalMin(value = "0.0", message = "Credit amount cannot be negative")
private float creditAmt;
@Column(name = "debit_credit")
@NotEmpty(message = "Debit/Credit cannot be null nor empty")
@Size(min = 1, max = 6, message = "Debit/Credit must have length between 1 and 6")
private String debitCredit;
@Column(name = "transaction_narrative")
@Size(min = 0, max = 50, message = "Transaction narrative must have length between 0 and 50")
private String transactionNarrative;
}
TransactionPK.java
package com.somecompany.account.model;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import lombok.Data;
@Embeddable
@Data
public class TransactionPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
@Column(name = "account_num")
@NotEmpty(message = "Account number cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Account number must be a number")
@Min(value = 1L, message = "Account number must not be less than 1")
@Max(value = 9999999999L, message = "Account number must not be larger than 9999999999")
private long accountNum;
@Column(name = "value_date")
@NotEmpty(message = "Value date cannot be null nor empty")
private Timestamp valueDate;
}
H2 DB 主键和外键信息:
SpringBoot 应用程序启动时的示例数据库数据 (data.sql):
INSERT INTO ACCOUNT (cust_id, account_num, account_name, account_type, balance_date, currency, opening_available_balance) VALUES
(1111111111, 1111111111, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'Savings', TIMESTAMP '2020-11-01 11:01:01', 'SGD', 99999.99),
(2, 2, 'B', 'Savings', TIMESTAMP '2020-11-02 11:02:02', 'AUD', 0.0),
(1111111111, 3333333333, 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC', 'Current', TIMESTAMP '2020-11-03 11:03:03', 'USD', 99999.99);
INSERT INTO TRANSACTION (account_num, account_name, value_date, currency, debit_amt, credit_amt, debit_credit, transaction_narrative) VALUES
(1111111111, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', TIMESTAMP '2012-11-01 11:01:01', 'SGD', 0.0, 99999.99, 'Credit', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'),
(2, 'Savings Account', TIMESTAMP '2012-11-02 11:02:02', 'USD', 0.1, 0.0, 'Debit', null),
(1111111111, 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC', TIMESTAMP '2012-11-03 11:03:03', 'USD', 99999.99, 0.0, 'Debit', 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC');
经过一些调查,我最终按预期进行了以下更改和应用程序 运行。
Account.java
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonBackReference;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
/**
* The model class for "Account" table.
*
* @author patrick
*
*/
public class Account {
@OneToMany(mappedBy = "transactionPK.account")
@JsonBackReference
private List<Transaction> transactions = new ArrayList<>();
@Column(name = "cust_id")
@NotEmpty(message = "Customer ID cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Customer ID must be a number")
@Min(value = 1L, message = "Customer ID must not be less than 1")
@Max(value = 9999999999L, message = "Customer ID must not be larger than 9999999999")
private long custId;
@Column(name = "account_num")
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@NotEmpty(message = "Account number cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Account number must be a number")
@Min(value = 1L, message = "Account number must not be less than 1")
@Max(value = 9999999999L, message = "Account number must not be larger than 9999999999")
private long accountNum;
@Column(name = "account_name")
@NotEmpty(message = "Account name cannot be null nor empty")
@Size(min = 1, max = 30, message = "Account name must have length between 1 and 30")
private String accountName;
@Column(name = "account_type")
@NotEmpty(message = "Account type cannot be null nor empty")
@Size(min = 1, max = 7, message = "Account type must have length between 1 and 7")
private String accountType;
@Column(name = "balance_date")
@NotEmpty(message = "Balance date cannot be null nor empty")
private Timestamp balanceDate;
@Column(name = "currency")
@NotEmpty(message = "Currency cannot be null nor empty")
@Size(min = 3, max = 3, message = "Currency must have length exactly equal to 3")
private String currency;
@Column(name = "opening_available_balance", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Opening available balance cannot be null nor empty")
@Pattern(regexp = "^[0-9.]+$", message = "Opening available balance must be a decimal number")
@DecimalMin(value = "0.0", message = "Opening available balance cannot be negative")
private float openingAvailableBalance;
}
Transaction.java
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
/**
* The model class for "Transaction" table.
*
* @author patrick
*
*/
public class Transaction {
@EmbeddedId
private TransactionPK transactionPK;
@Column(name = "account_name")
@NotEmpty(message = "Account name cannot be null nor empty")
@Size(min = 1, max = 30, message = "Account name must have length between 1 and 30")
private String accountName;
@Column(name = "currency")
@NotEmpty(message = "Currency cannot be null nor empty")
@Size(min = 3, max = 3, message = "Currency must have length exactly equal to 3")
private String currency;
@Column(name = "debit_amt", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Debit amount cannot be null nor empty")
@DecimalMin(value = "0.0", message = "Debit amount cannot be negative")
private float debitAmt;
@Column(name = "credit_amt", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Credit amount cannot be null nor empty")
@DecimalMin(value = "0.0", message = "Credit amount cannot be negative")
private float creditAmt;
@Column(name = "debit_credit")
@NotEmpty(message = "Debit/Credit cannot be null nor empty")
@Size(min = 1, max = 6, message = "Debit/Credit must have length between 1 and 6")
private String debitCredit;
@Column(name = "transaction_narrative")
@Size(min = 0, max = 50, message = "Transaction narrative must have length between 0 and 50")
private String transactionNarrative;
}
TransactionPK.java
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.Data;
@Embeddable
@Data
/**
* The model class for the EmbeddedId (i.e. primary key) of the "Transaction" table.
*
* @author patrick
*
*/
public class TransactionPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
@ManyToOne
@JoinColumn(name = "account_num", referencedColumnName = "account_num", insertable = false, updatable = false, nullable = false)
@JsonManagedReference
private Account account;
@Column(name = "value_date")
@NotEmpty(message = "Value date cannot be null nor empty")
private Timestamp valueDate;
}
我在 TransactionPK 中创建了一个“account”字段来替换“account_num”字段(无论如何,account 对象已经有“account_num”信息),并用@ManyToOne 对其进行注释。
这是因为关系是“一个账户可以有很多交易(即交易列表),但一笔交易只属于一个账户”。关系在对象级别而不是字段级别。
对于“Account”中的“List transactions”和“TransactionPK”中的“Account account”,它们仅用于表示外键关系,不必存在于JSON文件。
如果我们就这样保留它,在序列化为 JSON 时会出现无限递归错误(因为每个元素都有另一个元素,它永远无法完成生成 JSON)。
为了解决这个问题,我们可以用@JsonIgnore 标记这些字段,这将跳过序列化这两个字段。
或者,如果我们需要显示一个而不显示另一个(例如,在 Transaction JSON 中显示“account”,但在 Account JSON 中不显示“transactions”),那么我们可以注释我们想要的那个与@JsonManagedReference 保持一致,并用@JsonBackReference 标记我们不想在JSON 中显示的那个。
参考:
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
我做的另一个改变是不使用@Data。如果实体将与 ORM 一起使用,如 JPA.The @Data 的 equals() 和 hashCode() 实现可能会混淆对象比较,请避免在处理实体时使用来自 lombok 的 @Data。
参考:
https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/
我注意到在 H2 数据库上执行 JpaRepository.findAll(示例)时返回了一个 InvocationException。
当我尝试配置“Account”和“Transaction”这两个表之间的外键关系时出现(即一个账户可以有很多笔交易,但一笔交易只能属于一个账户)。
在我添加@OneToMany 和@ManyToOne 注释之前,没有任何问题。
欢迎任何帮助,谢谢。
要求:
查询成功,但它给出了一个 InvocationException,它又给出了一个 HTTP500。
服务:
AccountService.java
...
......
public List<Transaction> getAllTransactions(Account account) {
TransactionPK inputTransactionPK = new TransactionPK();
inputTransactionPK.setAccountNum(account.getAccountNum());
Transaction inputTransaction = new Transaction();
inputTransaction.setTransactionPK(inputTransactionPK);
ExampleMatcher matcher = ExampleMatcher.matchingAll().withIgnorePaths("debitAmt", "creditAmt");
Example<Transaction> example = Example.of(inputTransaction, matcher);
List<Transaction> transactionList = transactionRepository.findAll(example);
log.info("##################################################\n"
+ "Retrieved transaction list for account with account number " + account.getAccountNum()
+ "\n##################################################");
return transactionList;
}
...
......
Table 型号:
Account.java
package com.somecompany.account.model;
import java.sql.Timestamp;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import lombok.Data;
@Entity
@Data
public class Account {
@OneToMany(mappedBy = "account")
private Set<Transaction> transaction;
@Column(name = "cust_id")
@NotEmpty(message = "Customer ID cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Customer ID must be a number")
@Min(value = 1L, message = "Customer ID must not be less than 1")
@Max(value = 9999999999L, message = "Customer ID must not be larger than 9999999999")
private long custId;
@Column(name = "account_num")
@Id
@NotEmpty(message = "Account number cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Account number must be a number")
@Min(value = 1L, message = "Account number must not be less than 1")
@Max(value = 9999999999L, message = "Account number must not be larger than 9999999999")
private long accountNum;
@Column(name = "account_name")
@NotEmpty(message = "Account name cannot be null nor empty")
@Size(min = 1, max = 30, message = "Account name must have length between 1 and 30")
private String accountName;
@Column(name = "account_type")
@NotEmpty(message = "Account type cannot be null nor empty")
@Size(min = 1, max = 7, message = "Account type must have length between 1 and 7")
private String accountType;
@Column(name = "balance_date")
@NotEmpty(message = "Balance date cannot be null nor empty")
private Timestamp balanceDate;
@Column(name = "currency")
@NotEmpty(message = "Currency cannot be null nor empty")
@Size(min = 3, max = 3, message = "Currency must have length exactly equal to 3")
private String currency;
@Column(name = "opening_available_balance", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Opening available balance cannot be null nor empty")
@Pattern(regexp = "^[0-9.]+$", message = "Opening available balance must be a decimal number")
@DecimalMin(value = "0.0", message = "Opening available balance cannot be negative")
private float openingAvailableBalance;
}
Transaction.java
package com.somecompany.account.model;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import lombok.Data;
@Entity
@Data
public class Transaction {
@ManyToOne
@JoinColumn(name = "account_num", referencedColumnName = "account_num", insertable = false, updatable = false, nullable = false)
private Account account;
@EmbeddedId
private TransactionPK transactionPK;
@Column(name = "account_name")
@NotEmpty(message = "Account name cannot be null nor empty")
@Size(min = 1, max = 30, message = "Account name must have length between 1 and 30")
private String accountName;
@Column(name = "currency")
@NotEmpty(message = "Currency cannot be null nor empty")
@Size(min = 3, max = 3, message = "Currency must have length exactly equal to 3")
private String currency;
@Column(name = "debit_amt", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Debit amount cannot be null nor empty")
@DecimalMin(value = "0.0", message = "Debit amount cannot be negative")
private float debitAmt;
@Column(name = "credit_amt", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Credit amount cannot be null nor empty")
@DecimalMin(value = "0.0", message = "Credit amount cannot be negative")
private float creditAmt;
@Column(name = "debit_credit")
@NotEmpty(message = "Debit/Credit cannot be null nor empty")
@Size(min = 1, max = 6, message = "Debit/Credit must have length between 1 and 6")
private String debitCredit;
@Column(name = "transaction_narrative")
@Size(min = 0, max = 50, message = "Transaction narrative must have length between 0 and 50")
private String transactionNarrative;
}
TransactionPK.java
package com.somecompany.account.model;
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import lombok.Data;
@Embeddable
@Data
public class TransactionPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
@Column(name = "account_num")
@NotEmpty(message = "Account number cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Account number must be a number")
@Min(value = 1L, message = "Account number must not be less than 1")
@Max(value = 9999999999L, message = "Account number must not be larger than 9999999999")
private long accountNum;
@Column(name = "value_date")
@NotEmpty(message = "Value date cannot be null nor empty")
private Timestamp valueDate;
}
H2 DB 主键和外键信息:
SpringBoot 应用程序启动时的示例数据库数据 (data.sql):
INSERT INTO ACCOUNT (cust_id, account_num, account_name, account_type, balance_date, currency, opening_available_balance) VALUES
(1111111111, 1111111111, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 'Savings', TIMESTAMP '2020-11-01 11:01:01', 'SGD', 99999.99),
(2, 2, 'B', 'Savings', TIMESTAMP '2020-11-02 11:02:02', 'AUD', 0.0),
(1111111111, 3333333333, 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC', 'Current', TIMESTAMP '2020-11-03 11:03:03', 'USD', 99999.99);
INSERT INTO TRANSACTION (account_num, account_name, value_date, currency, debit_amt, credit_amt, debit_credit, transaction_narrative) VALUES
(1111111111, 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', TIMESTAMP '2012-11-01 11:01:01', 'SGD', 0.0, 99999.99, 'Credit', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'),
(2, 'Savings Account', TIMESTAMP '2012-11-02 11:02:02', 'USD', 0.1, 0.0, 'Debit', null),
(1111111111, 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC', TIMESTAMP '2012-11-03 11:03:03', 'USD', 99999.99, 0.0, 'Debit', 'CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC');
经过一些调查,我最终按预期进行了以下更改和应用程序 运行。
Account.java
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonBackReference;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
/**
* The model class for "Account" table.
*
* @author patrick
*
*/
public class Account {
@OneToMany(mappedBy = "transactionPK.account")
@JsonBackReference
private List<Transaction> transactions = new ArrayList<>();
@Column(name = "cust_id")
@NotEmpty(message = "Customer ID cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Customer ID must be a number")
@Min(value = 1L, message = "Customer ID must not be less than 1")
@Max(value = 9999999999L, message = "Customer ID must not be larger than 9999999999")
private long custId;
@Column(name = "account_num")
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@NotEmpty(message = "Account number cannot be null nor empty")
@Pattern(regexp = "^[0-9]+$", message = "Account number must be a number")
@Min(value = 1L, message = "Account number must not be less than 1")
@Max(value = 9999999999L, message = "Account number must not be larger than 9999999999")
private long accountNum;
@Column(name = "account_name")
@NotEmpty(message = "Account name cannot be null nor empty")
@Size(min = 1, max = 30, message = "Account name must have length between 1 and 30")
private String accountName;
@Column(name = "account_type")
@NotEmpty(message = "Account type cannot be null nor empty")
@Size(min = 1, max = 7, message = "Account type must have length between 1 and 7")
private String accountType;
@Column(name = "balance_date")
@NotEmpty(message = "Balance date cannot be null nor empty")
private Timestamp balanceDate;
@Column(name = "currency")
@NotEmpty(message = "Currency cannot be null nor empty")
@Size(min = 3, max = 3, message = "Currency must have length exactly equal to 3")
private String currency;
@Column(name = "opening_available_balance", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Opening available balance cannot be null nor empty")
@Pattern(regexp = "^[0-9.]+$", message = "Opening available balance must be a decimal number")
@DecimalMin(value = "0.0", message = "Opening available balance cannot be negative")
private float openingAvailableBalance;
}
Transaction.java
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
/**
* The model class for "Transaction" table.
*
* @author patrick
*
*/
public class Transaction {
@EmbeddedId
private TransactionPK transactionPK;
@Column(name = "account_name")
@NotEmpty(message = "Account name cannot be null nor empty")
@Size(min = 1, max = 30, message = "Account name must have length between 1 and 30")
private String accountName;
@Column(name = "currency")
@NotEmpty(message = "Currency cannot be null nor empty")
@Size(min = 3, max = 3, message = "Currency must have length exactly equal to 3")
private String currency;
@Column(name = "debit_amt", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Debit amount cannot be null nor empty")
@DecimalMin(value = "0.0", message = "Debit amount cannot be negative")
private float debitAmt;
@Column(name = "credit_amt", columnDefinition = "Decimal(20,2) default '0.0'")
@NotEmpty(message = "Credit amount cannot be null nor empty")
@DecimalMin(value = "0.0", message = "Credit amount cannot be negative")
private float creditAmt;
@Column(name = "debit_credit")
@NotEmpty(message = "Debit/Credit cannot be null nor empty")
@Size(min = 1, max = 6, message = "Debit/Credit must have length between 1 and 6")
private String debitCredit;
@Column(name = "transaction_narrative")
@Size(min = 0, max = 50, message = "Transaction narrative must have length between 0 and 50")
private String transactionNarrative;
}
TransactionPK.java
import java.io.Serializable;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotEmpty;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import lombok.Data;
@Embeddable
@Data
/**
* The model class for the EmbeddedId (i.e. primary key) of the "Transaction" table.
*
* @author patrick
*
*/
public class TransactionPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
@ManyToOne
@JoinColumn(name = "account_num", referencedColumnName = "account_num", insertable = false, updatable = false, nullable = false)
@JsonManagedReference
private Account account;
@Column(name = "value_date")
@NotEmpty(message = "Value date cannot be null nor empty")
private Timestamp valueDate;
}
我在 TransactionPK 中创建了一个“account”字段来替换“account_num”字段(无论如何,account 对象已经有“account_num”信息),并用@ManyToOne 对其进行注释。 这是因为关系是“一个账户可以有很多交易(即交易列表),但一笔交易只属于一个账户”。关系在对象级别而不是字段级别。
对于“Account”中的“List transactions”和“TransactionPK”中的“Account account”,它们仅用于表示外键关系,不必存在于JSON文件。 如果我们就这样保留它,在序列化为 JSON 时会出现无限递归错误(因为每个元素都有另一个元素,它永远无法完成生成 JSON)。
为了解决这个问题,我们可以用@JsonIgnore 标记这些字段,这将跳过序列化这两个字段。 或者,如果我们需要显示一个而不显示另一个(例如,在 Transaction JSON 中显示“account”,但在 Account JSON 中不显示“transactions”),那么我们可以注释我们想要的那个与@JsonManagedReference 保持一致,并用@JsonBackReference 标记我们不想在JSON 中显示的那个。
参考: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
我做的另一个改变是不使用@Data。如果实体将与 ORM 一起使用,如 JPA.The @Data 的 equals() 和 hashCode() 实现可能会混淆对象比较,请避免在处理实体时使用来自 lombok 的 @Data。
参考: https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/