I have the following question regarding one to one relationships (and I guess one to many also):
Let’s suppose I have the following tables:
create table user ( id bigint auto_increment primary key, username varchar(100) not null, constraint UK_username unique (username) );
create table user_details( userId bigint not null primary key, firstName varchar(100) null, lastName varchar(100) null, constraint user_details_user_id_fk foreign key (userId) references user (id) );
As you can see the two tables share the same primary key. Now the entities I created are the following:
import lombok.Data; import javax.persistence.*; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; @Data @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Column(unique = true) @Size(min = 1, max = 100) private String username; @MapsId //without this I get an exception on this table not having a column named: userDetails_userId @JoinColumn(name = "id") @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) private UserDetails userDetails; }
import lombok.Data; import javax.persistence.*; import javax.validation.constraints.Size; @Data @Entity(name = "user_details") public class UserDetails { @Id private Long userId; @Column(unique = true) @Size(min = 1, max = 100) private String firstName; @Column(unique = true) @Size(min = 1, max = 100) private String lastName; }
When I try to persist a new user I have a user object with all the values generated except of the user.id and the userDetail.userId. When I try to persist this the error I get is the following:
"org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save(): com.app.entity.UserDetails; nested exception is org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): com.app.UserDetails
Note that to save a User entity I created this interface:
public interface UserRepository extends JpaRepository<User, Long> { }
and I use the save method provided.
... @Autowired private UserRepository userRepository; ... public ResponseEntity<HttpStatus> addUser(User user) { userRepository.save(user); return ResponseEntity.ok(HttpStatus.OK); }
the object before saving looks like this:
User(id=null, username=test, userDetails=UserDetails(userId=null, firstName=test, lastName=test))
I was wondering if I can simply save a user object and cascade the generated key to the userDetail.
Should I use another approach for saving or there is something wrong with my entities?
Advertisement
Answer
You’ve done things from the wrong direction.
@Data @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Column(unique = true) @Size(min = 1, max = 100) private String username; @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user") private UserDetails userDetails; } @Data @Entity(name = "user_details") public class UserDetails { @Id private Long userId; @Column(unique = true) @Size(min = 1, max = 100) private String firstName; @Column(unique = true) @Size(min = 1, max = 100) private String lastName; @MapsId @JoinColumn(name = "USERID") @OneToOne(fetch = FetchType.LAZY) private User user; }
With this, you can just set the userDetail.user reference and JPA will persist both the user, assign it a ID and use that to populate the UserDetail.id value for you – in your model and in the database.
You should maintain the user.userDetail reference, but it is less consequential to the database row data and more for object consistency reasons.