Maifee Ul Asad
Maifee Ul Asad

Reputation: 4607

Spring Boot foreign key entity null

Spring Boot version 2.1.6.

I have User class :

@Data
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    Long userID;
    String eMail;
    public User()
    {

    }
}

And a LoginCredential class :

@Data
@Entity
public class LoginCredential {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    Long userID;
    String eMail;
    String passwordHash;
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "id", referencedColumnName = "id")
    User user;
    public LoginCredential()
    {

    }
}

Shouldn't it create an instance of User when I create an instance of LoginCredential.

Cause I run this command : curl -X POST -H "Content-Type: application/json" -d "{ \"email\": \"a\", \"passwordHash\": \"b\" }" http://localhost:8080/login

And I got an instance of LoginCredential but not of an User.

Response :

{"userID":1,"passwordHash":"b","user":null,"email":"a"}

And then I run this command : curl -X POST -H "Content-Type: application/json" -d "{ \"email\": \"c\", \"passwordHash\": \"d\" ,\"user\":{}}" http://localhost:8080/login

And what I got is nothing, not of same ID.

Response :

{"userID":2,"passwordHash":"d","user":{"userID":3,"email":null},"email":"c"}

Am I missing something ? How this can be resolved ?

LoginCredentialController partially :

@PostMapping("/login")
LoginCredential newLoginCredential(@RequestBody LoginCredential newLoginCredential)
{   
    return repository.save(newLoginCredential);
}

My pom.xml

My application.properties

Upvotes: 2

Views: 7003

Answers (2)

Maifee Ul Asad
Maifee Ul Asad

Reputation: 4607

Here is the solution, I later asked another question and found out an answer

You have three problems

  1. Lacking a cascade option to trigger User entity creation from LoginCredential save.
  2. Lacking @MapsId annotation on User so that they share the same id, otherwise LoginCredential and its created User will have different id values for both of them has @GeneratedValue(strategy = GenerationType.AUTO) on their @Id columns
  3. Not setting both sides of the relationship...

To fix all you need to change your entities to below (I also removed some useless annotations and values);

@Data
@Entity
public class User {

    @Id
    Long userID;

    @JsonBackReference
    @MapsId
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "userID", referencedColumnName = "userID")
    @ToString.Exclude
    private LoginCredential loginCredential;
}

and

@Data
@Entity
public class LoginCredential {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long userID;
    String eMail;
    String passwordHash;

    @JsonManagedReference
    @OneToOne(mappedBy = "loginCredential", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
    private User user;
}

Also need to set both sides of the relationship before finalizing your endpoint;

Optional.ofNullable(loginCredential.getUser())
        .ifPresent(user -> user.setLoginCredential(loginCredential));
loginCredentialRepo.save(loginCredential);

Upvotes: 0

womd
womd

Reputation: 3453

@Data
@Entity
public class LoginCredential implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    Long credentailID;
    String eMail;
    String passwordHash;
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
    @OneToOne(fetch = FetchType.EAGER) //<-- EAGER is default, but try setting it explicitly
    User user;
    public LoginCredential()
    {

    }
}



@PostMapping("/login")
LoginCredential newLoginCredential(@RequestBody LoginCredential newLoginCredential)
{   
    LoginCredential saveResult = repository.save(newLoginCredential);
    //load it again after saving 
    return repository.findbyId(saveResult.getuserId);
}

or better:

   @PostMapping("/login")
    ResponseEntity newLoginCredential(@RequestBody LoginCredential newLoginCredential)
    {   
      try{
        LoginCredential saveResult = repository.save(newLoginCredential);
        //load it again after saving 
        LoginCredential loaded = repository.findbyId(saveResult.getuserId);
//if you set a breakpoint here, you should already see if it is working, if its ok here, then it might have to do with serilalization ( jackson )...
            return new ResponseEntity<>(loaded,HttpStatus.OK);
          }
           catch (Exception e){
                log.error(e.getMessage());
                return       ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
            }
        }

and, do you have a dependency for jackson ? -> its responsible for serializing the object to json ...

<dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>

and this in your properties:

spring.jackson.serialization.indent_output=true
spring.jackson.default-property-inclusion=ALWAYS

hope that helps, cheers

Upvotes: 1

Related Questions