<SpringBoot / Hibernate> InvocationException on calling JpaRepository.findAll(Example example) - java

I am noticing an InvocationException being returned when I execute a JpaRepository.findAll(Example example) on H2 database.
It occurs when I try to configure the foregin key relationship between the 2 tables "Account" and "Transaction" (i.e. 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.
Welcome for any help, thank you.
Request:
Query is successful but it gives an InvocationException, which in turns give a HTTP500.
Service:
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 models:
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 primary and foreign key info:
Sample DB data on SpringBoot app startup (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
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;
}
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.
This is because the releationship is "An account can have many transactions (i.e. 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.
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).
To solve the issue, we may mark these fields with #JsonIgnore, which will skip serializing both fields.
Or if we need to show one but not the other (e.g. 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.
Referece:
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
Another change I made is not using #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.
Referece:
https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/

Related

How do I reuse multiple DTOs (spring boot)

I am a student who wants to implement api server using spring.
Currently, I use request and response to per api. (If api exists for signin, I have created AccountSignInDto.Request, AccountSignInResponse.) but, I felt that there were more and more duplicate codes.
I wonder how I can reuse multiple dto to reduce duplicate code.
The details are as follows.
AccountCreateDto.java
AccountFindIdByEmailDto.java
AccountFindPasswordDto.java
AccountReadDto.java
AccountSignInDto.java
AccountUpdateDto.java
QuestionReadDto.java
package com.se.apiserver.v1.account.application.dto;
import com.se.apiserver.v1.account.domain.entity.AccountType;
import javax.validation.constraints.Email;
import javax.validation.constraints.Size;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
public class AccountCreateDto {
#Data
#NoArgsConstructor
#AllArgsConstructor
#ApiModel("signIn request")
#Builder
static public class Request {
#ApiModelProperty(example = "account", notes = "id")
#Size(min = 4, max = 20)
private String id;
#ApiModelProperty(example = "password", notes = "password")
#Size(min = 8, max = 20)
private String password;
#ApiModelProperty(example = "name", notes = "name")
#Size(min = 2, max = 20)
private String name;
#ApiModelProperty(example = "nickname", notes = "nick-name")
#Size(min = 2, max = 20)
private String nickname;
#ApiModelProperty(example = "11110000", notes = "stu-num")
#Size(min = 8, max = 20)
private String studentId;
#ApiModelProperty(example = "STUDENT", notes = "account type")
private AccountType type;
#ApiModelProperty(example = "01012345678", notes = "phone-number, 00011112222")
#Size(min = 10, max = 20)
private String phoneNumber;
#ApiModelProperty(example = "abc#def.com", notes = "email")
#Email
private String email;
#ApiModelProperty(example = "1", notes = "question number")
private Long questionId;
#ApiModelProperty(example = "region", notes = "answer")
#Size(min = 2, max = 100)
private String answer;
}
#Data
#AllArgsConstructor
#ApiModel("signIn response")
static public class Response {
#ApiModelProperty(example = "1", notes = "account pk")
private Long id;
}
}
I'm sorry for my poor English.
Thanks in advance!
Try declare Request as a abstract class, and all other dtos extend Request. Like code shows below:
public abstract class Request {
#ApiModelProperty(example = "account", notes = "id")
#Size(min = 4, max = 20)
private String id;
#ApiModelProperty(example = "password", notes = "password")
#Size(min = 8, max = 20)
private String password;
...
}
public class AccountCreateDto extends Request {
}
public class AccountUpdateDto extends Request {
}
This example ignore the influence of lombok.

How to map the results of a stored procedure to an entity in spring-boot/hibernate

I have an Entity class as seen below
NB: The checkNumber is unique.
package tz.go.ega.biometic.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import lombok.ToString;
import org.hibernate.annotations.NaturalId;
import org.hibernate.validator.constraints.SafeHtml;
import org.springframework.data.annotation.Transient;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Entity
#Table(name = "employee", uniqueConstraints = {
#UniqueConstraint(columnNames = {"check_number"})
})
#Data
#AllArgsConstructor
#ToString
#NoArgsConstructor
public class Employee implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Long id;
#Basic(optional = true)
//#NotEmpty(message = "Please enter first name")
#Column(name = "first_name")
private String firstName;
#Basic(optional = true)
//#NotEmpty(message = "Please enter middle name")
#Column(name = "middle_name")
private String middleName;
#Basic(optional = true)
//#NotEmpty(message = "Please enter last name")
#Column(name = "last_name")
private String lastName;
private String status;
#Basic(optional = true)
// #Pattern(regexp ="^[a-zA-Z0-9_]*$",message = "{field.validation.voteCode}")
// #SafeHtml(message = "{field.validation.voteCode}")
#Column(name = "vote_code", length = 50)
private String voteCode;
#NaturalId
#Basic(optional = true)
//#NotNull(message = "Please enter check number")
#Column(name = "check_number")
private long checkNumber;
private Boolean isActive = true;
#Basic(optional = false)
#Column(name = "created_at", updatable = false)
#DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime createdAt = LocalDateTime.now();
#Column(name = "updated_at")
#DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime updatedAt = LocalDateTime.now();
#Column(name = "email")
private String email;
}
I then have a stored procedure that calculates each employee's working hours then returns the results as seen below.
+--------------+-------+
| checkNumber | Time |
+--------------+-------+
| 1122334455 | 29893 |
| 1234567890 | 15427 |
| 2233445566 | 19745 |
| 6655443322 | 12578 |
+--------------+-------+
What I am trying to achieve is, to map the results ( as seen above ) of the stored procedure to an entity (let's call it EmployeeWorkHours ) and then create a relationship between this Entity and the Employee entity using the checkNumber.
I want the EmployeeWorkHours object to be able to reference it's employee directly like in normal hibernate relationships.
How can I go about this, any help will be much appreciated. Thank you.
On your EmployeeWorkHours entity you need a OneToOne relationship with Employee entity
#OneToOne(optional = false, fetch = FetchType.EAGER)
#JoinColumn(name = "checkNumber", unique = true, nullable = false)
private Employee employee;
In your repository you can write a sql query like this :
#Query(value = "{CALL yourStoredProcedure (:var1, :var2, ..., :varn)}", nativeQuery = true)
int getWorkHours(#Param("var1") String var1, #Param("var2") String var2,...,
#Param("varn") String varn);
And then in your service layer you will just call this method do what else you want and persist it.
Hope it helps

Unable to map List<Source> to List<Target> with mapstruct

I have a basic Spring boot app and I am trying to map a list of entities to list of DTOs using Mapstruct (version 1.3.0.Final).
Source:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;
import java.sql.Timestamp;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "source")
#Entity(name = "Source")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Source implements Serializable {
private static final long serialVersionUID = 964150155782995534L;
#Id
#JsonIgnore
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SourceSeq")
#SequenceGenerator(sequenceName = "source_id_seq", allocationSize = 1, name = "SourceSeq")
private long id;
#NotNull
#Size(min = 36, max = 36)
#Column(name = "uuid", nullable = false, length = 36)
private String uuid;
#Column(name = "user_id")
private Long userId;
#Column(name = "username")
private String username;
#Column(name = "user_org_id")
private Long userOrgId;
#Column(name = "user_org_name")
private String userOrgName;
#Column(name = "account_number")
private Integer accountNumber;
#Column(name = "account_name")
private String accountName;
#Column(name = "billing_delay")
private Integer billingDelay;
#Column(name = "default_billing_delay")
private Integer defaultBillingDelay;
#Column(name = "billing_enabled")
private Boolean billingEnabled = true;
#JsonIgnore
#CreationTimestamp
#Column(name = "created_date")
private Timestamp createdDate;
#JsonIgnore
#UpdateTimestamp
#Column(name = "updated_date")
private Timestamp updatedDate;
}
Target:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Target implements Serializable {
private static final long serialVersionUID = 8939532280496355293L;
#ApiModelProperty(hidden = true)
private String uuid;
#ApiModelProperty(value = "user ID", example = "123456", dataType = "int64", position = 1)
private Long userId;
#ApiModelProperty(value = "username", example = "myUser", position = 2)
private String username;
#ApiModelProperty(hidden = true)
private String firstName;
#ApiModelProperty(hidden = true)
private String lastName;
#ApiModelProperty(value = "user organization ID", example = "71836", dataType = "int64", position = 3)
private Long userOrgId;
#ApiModelProperty(value = "user organization name", example = "Org Inc", position = 4)
private String userOrgName;
#ApiModelProperty(value = "account number", example = "987654", position = 5)
private Integer accountNumber;
#ApiModelProperty(value = "account name", example = "My Mapping Acc", position = 6)
private String accountName;
#ApiModelProperty(value = "billing delay (in days)", example = "60", position = 7)
private Integer billingDelay;
#ApiModelProperty(value = "default billing delay (in days)", example = "30", position = 8)
private Integer defaultBillingDelay;
#ApiModelProperty(value = "is billing enabled?", example = "true", position = 9)
private Boolean billingEnabled = true;
#ApiModelProperty(hidden = true)
private Date createdDate;
}
Mapper:
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
#Mapper
public interface MyMapper {
MyMapper MAPPER = Mappers.getMapper(MyMapper.class);
// Target toTarget(Source source);
// I have tried using this as well but my target mapped list only contains billingEnabled = true for every object in the response list. MapperImpl class also included below. Without toTarget method get a compilation error (also included below)
// Response:
/*[
{
"billingEnabled": true
},
{
"billingEnabled": true
},
{
"billingEnabled": true
}
]*/
List<Target> toTargets(List<Source> sources);
}
MapperImpl:
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-09-16T00:06:14-0700",
comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)"
)
public class MyMapperImpl implements MyMapper {
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
return target;
}
#Override
public List<Target> toTargets(List<Source> sources) {
if ( sources == null ) {
return null;
}
List<Target> list = new ArrayList<Target>( sources.size() );
for ( Source source : sources ) {
list.add( toTarget( source ) );
}
return list;
}
}
Error:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.5.1:compile (default-compile) on project my-project: Compilation failure
[ERROR] /Users/user8812/workspace/my-project-/src/main/java/MyMapper.java:[17,23] Can't map Collection element "Source source" to "Target target". Consider to declare/implement a mapping method: "Target map(Source value)".
I'm looking to have Target list with the same field names mapped without another individual toTarget method as that has worked for me in another project with an older Mapstruct version.
The exception thrown by MapStruct during compilation is telling you how to fix it:
Can't map Collection element "Source source" to "Target target". Consider to declare/implement a mapping method: "Target map(Source value)".
You could even place this method signature inside the same interface you've shown us.
Edit
It seems like the default global configuration for MapStruct has been changed in the application. Try applying this annotation to you "source to target" method inside the interface:
#BeanMapping(ignoreByDefault = false)
Or you can explicitly map their fields with the #Mapping annotation.
Since the field names and types in Target type and in Source type are the same, wouldn't it be more beneficial and easier to use BeanUtils.copyProperties(source, target)? It is very straightforward and allows stating of ignored fields.
I think declaration should have annotation in MyMapper interface.
#Mapping(source = "sources", target = "targets")
List<Target> toTargets(List<Source> sources);
Reference: https://mapstruct.org/

How to determine violated entity during batch saving in spring data

I have next problem
public void batchSave(List<Entity> entities) {
repositoryJpa.save(entities);
}
If entities list contains already persisted entity i got DataIntegrityViolationException.
Is there way how to know which entity violated persisting?
Researching spring data source code and DataIntegrityViolationException i could find any place where wrong entity can be stored.
UPD
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "GENERATOR")
#Column(name = "ID", unique = true, nullable = false)
public Long getId() {
return id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "NTRY_TABS_TABH_ID", nullable = false)
public OtherEntity getOtherEntity() {
return otherEntity;
}
#Column(name = "SORT_KEY", nullable = false)
public String getSortKey() {
return sortKey;
}
#Enumerated(EnumType.STRING)
#Column(name = "TYPE", nullable = false)
public OperationType getOperationType() {
return operationType;
}
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "activeEntryEntity")
public SortKeyEntity getSortKeyEntity() {
return sortKeyEntity;
}
#Version
#Column(name = "VERSION", nullable = false, insertable = false)
public Long getVersion() {
return version;
}
}
Use javax validation , and you will be able to do something like this:
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
public class User {
#NotNull(message = "Name cannot be null")
private String name;
#AssertTrue
private boolean working;
#Size(min = 10, max = 200, message
= "About Me must be between 10 and 200 characters")
private String aboutMe;
#Min(value = 18, message = "Age should not be less than 18")
#Max(value = 150, message = "Age should not be greater than 150")
private int age;
#Email(message = "Email should be valid")
private String email;
// standard setters and getters
}
And then you can validate this like :
Set<ConstraintViolation<User>> violations = validator.validate(user);
Take a look at this:
https://www.baeldung.com/javax-validation

Hibernate Data Integrity Violation Exception

I have a many to many relation between Permission and Role classes.
And there is a role_permission table to keep relation between role and permission
CREATE TABLE public.role_permissions
(
role_id bigint NOT NULL,
permission_id bigint NOT NULL,
CONSTRAINT role_permissions_pkey PRIMARY KEY (role_id, permission_id),
CONSTRAINT fkh0v7u4w7mttcu81o8wegayr8e FOREIGN KEY (permission_id)
REFERENCES public.permission (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fklodb7xh4a2xjv39gc3lsop95n FOREIGN KEY (role_id)
REFERENCES public.role (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.role_permissions
OWNER TO postgres;
When I want to delete Permission it throws following error
org.postgresql.util.PSQLException: ERROR: update or delete on table "permission" violates foreign key constraint "fkh0v7u4w7mttcu81o8wegayr8e" on table "role_permissions"
Detail: Key (id)=(6) is still referenced from table "role_permissions".
Here is my class implementations
package com.nova.stats.client.backend.auth.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.PreRemove;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode(exclude = {"users", "roles"})
#ToString(exclude = {"users", "roles"})
#Entity
public class Permission implements GrantedAuthority {
#Transient
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 2, max=100, message = "Permission name must be between {min} and {max} characters long")
#Column(unique = true)
private String name;
#NotNull
#Size(min = 10, max=250, message = "Permission description must be between {min} and {max} characters long")
#Column
private String description;
#Getter(onMethod = #__(#Override))
#NotNull
#Size(min = 6, max=100, message = "Permission authority must be between {min} and {max} characters long")
#Column(unique = true)
private String authority;
#Getter(onMethod = #__(#JsonIgnore))
#ManyToMany(mappedBy = "permissions")
private Set<Role> roles;
#Getter(onMethod = #__(#JsonIgnore))
#ManyToMany(mappedBy = "permissions")
private Set<User> users;
public Permission(String name, String description, String authority) {
this.name = name;
this.description = description;
this.authority = authority;
}
}
package com.nova.stats.client.backend.auth.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(exclude = {"users", "permissions"})
#ToString(exclude = {"users", "permissions"})
#Getter
#Setter
#Entity
public class Role implements GrantedAuthority {
#Transient
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 2, max=100, message = "Role name must be between {min} and {max} characters long")
#Column(unique = true)
private String name;
#NotNull
#Size(min = 10, max=250, message = "Role description must be between {min} and {max} characters long")
#Column
private String description;
#Getter(onMethod = #__(#Override))
#NotNull
#Size(min = 6, max=100, message = "Role authority must be between {min} and {max} characters long")
#Column(unique = true)
private String authority;
#Getter(onMethod = #__(#JsonIgnore))
#ManyToMany(mappedBy = "roles")
private Set<User> users = new HashSet<>(0);
#Getter(onMethod = #__(#JsonIgnore))
#Setter(onMethod = #__(#JsonProperty("permissions")))
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "role_permissions", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "permission_id", referencedColumnName = "id"))
private Set<Permission> permissions;
public Role(String name, String description, String authority, Set<Permission> permissions) {
this(name, description, authority);
this.permissions = permissions;
}
public Role(String name, String description, String authority) {
this.name = name;
this.description = description;
this.authority = authority;
}
}
Here is what I need; when I want to delete any permission then foreign key must be deleted in role_permission table but related role can't be delete. I mean when delete permission, just delete permission on table permission and relation on table role_permission
What should I do for that ?
The problem is that when try to delete a permission, it is still referenced on user. You need to delete the user first or remove it's permission before removing from permission table.
To delete in cascade you can try this:
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "permissions")
private Set<User> users;
I added "#OnDelete(action = OnDeleteAction.CASCADE)" annotation on User and Permission in Role class
And also added Role and User in Permission class
And now, It works as I want
Here is an Example
package com.asd.asd;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Set;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode(exclude = {"users", "roles"})
#ToString(exclude = {"users", "roles"})
#Entity
public class Permission implements GrantedAuthority {
#Transient
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min = 2, max=100, message = "Permission name must be between {min} and {max} characters long")
#Column(unique = true)
private String name;
#NotNull
#Size(min = 10, max=250, message = "Permission description must be between {min} and {max} characters long")
#Column
private String description;
#Getter(onMethod = #__(#Override))
#NotNull
#Size(min = 6, max=100, message = "Permission authority must be between {min} and {max} characters long")
#Column(unique = true)
private String authority;
#Getter(onMethod = #__(#JsonIgnore))
#ManyToMany(mappedBy = "permissions")
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<Role> roles;
#Getter(onMethod = #__(#JsonIgnore))
#ManyToMany(mappedBy = "permissions")
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<User> users;
public Permission(String name, String description, String authority) {
this.name = name;
this.description = description;
this.authority = authority;
}
}

Categories