Saya melihat InvocationException dikembalikan ketika saya menjalankan JpaRepository.findAll(Contoh contoh) pada database H2.

Itu terjadi ketika saya mencoba mengonfigurasi hubungan kunci awal antara 2 tabel "Akun" dan "Transaksi" (yaitu, satu akun dapat memiliki banyak transaksi, tetapi satu transaksi hanya dapat dimiliki oleh satu akun).

Sebelum saya menambahkan anotasi @OneToMany dan @ManyToOne, tidak ada masalah.

Selamat datang untuk bantuan apa pun, terima kasih.

Permintaan:

enter image description here

Kueri berhasil tetapi memberikan InvocationException, yang pada gilirannya memberikan HTTP500.

enter image description here

Layanan:

Layanan Akun.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;
}
...
......

Model tabel:

Akun.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;
}

Transaksi.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;
}

Info kunci utama dan asing H2 DB:

enter image description here

Contoh data DB pada startup aplikasi 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');

enter image description here

0
Patrick C. 19 November 2020, 14:06

1 menjawab

Jawaban Terbaik

Setelah beberapa penyelidikan, saya telah membuat perubahan berikut dan aplikasi akhirnya berjalan seperti yang diharapkan.

Akun.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;
}

Transaksi.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;
}

Saya telah membuat bidang "akun" di TransactionPK menggantikan bidang "akun_num" (objek akun sudah memiliki info "nomor_akun"), dan membubuhi keterangan dengan @ManyToOne. Ini karena keterkaitannya adalah "Sebuah akun dapat memiliki banyak transaksi (yaitu daftar transaksi), tetapi sebuah transaksi hanya dimiliki oleh satu akun". Relasi berada pada level objek tetapi tidak pada level lapangan.

Untuk "Daftar transaksi" di "Akun" dan "Akun akun" di "TransactionPK", mereka hanya untuk menunjukkan hubungan kunci asing, mereka tidak harus ada di file JSON. Dan jika kita membiarkannya seperti itu, itu akan memberikan kesalahan rekursi tak terbatas saat membuat serial ke JSON (karena masing-masing memiliki elemen yang lain, itu tidak akan pernah selesai menghasilkan JSON).

Untuk mengatasi masalah ini, kami dapat menandai bidang ini dengan @JsonIgnore, yang akan melewatkan serialisasi kedua bidang. Atau jika kita perlu menunjukkan satu tetapi tidak yang lain (misalnya menunjukkan "akun" di Transaksi JSON, tetapi tidak menunjukkan "transaksi" di Akun JSON), maka kita dapat membubuhi keterangan yang ingin kita simpan dengan @JsonManagedReference, dan tandai yang tidak ingin kami tampilkan di JSON dengan @JsonBackReference.

Referensi: https://www.baeldung.com/jackson-bidirectional-relationships- dan-rekursi-tak-hingga

Perubahan lain yang saya buat adalah tidak menggunakan @Data. Hindari menggunakan @Data dari lombok saat Anda bekerja dengan entitas jika mereka akan digunakan dengan ORM seperti JPA. Implementasi @Data dari equals() dan hashCode() dapat mengacaukan perbandingan objek.

Referensi: https://thorben-janssen.com/lombok-hibernate -how-to-avoid-common-pitfalls/

0
Patrick C. 22 Desember 2020, 00:56