[英]<SpringBoot / Hibernate> InvocationException on calling JpaRepository.findAll(Example example)
I am noticing an InvocationException being returned when I execute a JpaRepository.findAll(Example example) on H2 database.我注意到在 H2 数据库上执行 JpaRepository.findAll(Example example) 时返回了 InvocationException。
It occurs when I try to configure the foregin key relationship between the 2 tables "Account" and "Transaction" (ie An account can have many transactions, but a transaction can only belong to one account).当我尝试配置“帐户”和“交易”这两个表之间的外键关系时会发生这种情况(即一个帐户可以有多个交易,但一个交易只能属于一个帐户)。
Before I add the @OneToMany and @ManyToOne annotations, there were no issues.在我添加 @OneToMany 和 @ManyToOne 注释之前,没有任何问题。
Welcome for any help, thank you.欢迎任何帮助,谢谢。
Request:要求:
Query is successful but it gives an InvocationException, which in turns give a HTTP500.查询成功,但它给出了一个 InvocationException,它反过来给出了 HTTP500。
Service:服务:
AccountService.java账户服务.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 models:表型号:
Account.java账号.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事务.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事务PK.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 primary and foreign key info: H2 DB 主外键信息:
Sample DB data on SpringBoot app startup (data.sql): 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');
After some investigation, I have made the following changes and the application run as expected finally.经过一番调查,我进行了以下更改,应用程序最终按预期运行。
Account.java账号.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事务.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事务PK.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;
}
I have created an "account" field in TransactionPK replacing the "account_num" field (the account object already has the "account_num" info anyway), and annotated it with @ManyToOne.我在 TransactionPK 中创建了一个“account”字段来替换“account_num”字段(无论如何,帐户对象已经具有“account_num”信息),并用@ManyToOne 对其进行了注释。 This is because the releationship is "An account can have many transactions (ie list of transactions), but a transaction only belongs to one account".这是因为关系是“一个账户可以有多个交易(即交易列表),但一个交易只属于一个账户”。 The releationship is at the object level but not field level.关系是在对象级别而不是字段级别。
For the "List transactions" in "Account" and "Account account" in "TransactionPK", they are for indicating the foreign key relationship only, they don't have to be existing in the JSON files.对于“Account”中的“List transactions”和“TransactionPK”中的“Account account”,它们仅用于指示外键关系,它们不必存在于JSON文件中。 And if we just leave it like that, it will give an infinite recursion error when serializing to JSON (since each has an element of the other, it can never finish generating the JSON).如果我们就这样保留它,在序列化为 JSON 时会出现无限递归错误(因为每个都有另一个元素,它永远无法完成生成 JSON)。
To solve the issue, we may mark these fields with @JsonIgnore, which will skip serializing both fields.为了解决这个问题,我们可以用@JsonIgnore 标记这些字段,这将跳过序列化这两个字段。 Or if we need to show one but not the other (eg show "account" in Transaction JSON, but not showing "transactions" in Account JSON), then we can annotate the one which we want to keep with the @JsonManagedReference, and mark the one that we don't want to show in JSON with @JsonBackReference.或者,如果我们需要显示一个而不是另一个(例如,在交易 JSON 中显示“帐户”,但在帐户 JSON 中不显示“交易”),那么我们可以用@JsonManagedReference 注释我们想要保留的那个,并标记我们不想用@JsonBackReference 在JSON 中显示的那个。
Referece: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion参考: https ://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
Another change I made is not using @Data.我所做的另一个更改是不使用@Data。 Avoid using @Data from lombok when you are working with entities if they will be used with ORM like JPA.The @Data's implementation of equals() and hashCode() may mess with the object comparison.当您处理实体时,避免使用 lombok 中的 @Data,如果它们将与 JPA 等 ORM 一起使用。@Data 的 equals() 和 hashCode() 实现可能会干扰对象比较。
Referece: https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/参考: https ://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.