0

Today I came across this annoying bug and took me a few hours to solve. I have the following enity classes in a spring boot application

An Entry model class

@Data
@Entity
@Table(name="entry")
@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
@AllArgsConstructor(access = AccessLevel.PUBLIC)
public class Entry {

@Id
@NotBlank
private String id;

@ManyToOne(targetEntity = User.class)
@JoinColumn(name = "username")
@NotNull
private User username;

@Enumerated(EnumType.STRING)
@NotNull
@Column(name = "type")
private Type Type;

@ManyToOne(targetEntity = Category.class)
@JoinColumn(name = "category")
@NotNull
private Category category;

@NotBlank
@Size(max = 45)
private String description;

@NotNull
private Double amount;

@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd")
@NotNull
private Date date;

@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
@Column(name = "lastUpdate")
@NotNull
private Date lastUpdate;

@NotNull
@Column(name = "isDeleted")
private Boolean isDeleted;

public enum Type {
    Income("I"), Expense("E");

    private String code;

    Type(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

and a User model class

@Data
@Entity 
@Table(name="user")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
public class User {

@Id
@Size(max = 45)
private String username;

@Size(max = 100)
private String email;

@Size(max = 60)
private String password;


private Date registeredAt;

private Date lastLoginAt;

public User(String username){
  this.username = username;
  this.email = "";
  this.password = "";
  this.registeredAt = new Date();
  this.lastLoginAt = new Date();
}

}

As you can see the Entry class has a ManyToOne relationship with the User class (A User has many Entries). If I delete the single argument constructor from the User class and attempt to save an Entry object to the database I get the following error :

JSON parse error: Cannot construct instance of com.apboutos.moneytrackbackend.model.User (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('woody'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.apboutos.moneytrackbackend.model.User (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('woody') at [Source: (PushbackInputStream); line: 3, column: 16] (through reference chain: com.apboutos.moneytrackbackend.model.Entry["username"])]

The above error is solved when I create the single argument constructor for the User class and the Entry is properly stored. My guess is that Spring attempts to instantiate the User class passing as it's argument the username string when it tries to create an instance of the Entry class and because there is no constructor that matches that signature I get the error above.

My question therefore is: Is there any way to achieve the above result without having to create this hackish single argument constructor? For example can I switch the type of the username property in the Entity class from User to String and the map that property with a ManyToOne relationship to the username property of the User class and not the whole class?

apboutos
  • 109
  • 2
  • 16

1 Answers1

0

I guess the issue could be due to:

@NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)

try making the default constructor to public; sometimes the lombok annotation tricks the things.

Check the below questions for more details:

  1. Why does Hibernate require no argument constructor?

  2. Hibernate failed on using protected default constructor

sham
  • 422
  • 4
  • 18