Spring CrudRepository cannot share generated id in composite primary key

9.5k views Asked by At

I'm trying use spring CRUDRepository for inserting new object into database using JPA database model (User.java, UserInfo.java). Database model are related with composite primary key (UserPK.java), where one of them is automatic generated (field named id) and second (field named type) is set in beginning.

And I get errors when I create new object with using CRUDRepository (UserRepository.java) - cannot insert new object into second model (UserInfo.java), because id is null (first model was added correctly). I think problem is in sharing/mapping composite primary key in database model. I tried the same model with EntityManager and it wasn't error - all was added. Next I used @PrimaryKeyJoinColumns annotation, but with that same result as above (but there I'm not sure that I used it correctly) - CRUDRepository failed and EntityManager success.

Can anyone help me find solution? I add also source code on GitHub if someone wants to run code.

Logs below:

Log CRUDRepository

Hibernate: select user0_.id as id1_0_1_, user0_.type as type2_0_1_, user0_.email as email3_0_1_, user0_.login as login4_0_1_, userinfo1_.id as id3_1_0_, userinfo1_.type as type4_1_0_, userinfo1_.name as name1_1_0_, userinfo1_.surname as surname2_1_0_ from user user0_ left outer join user_info userinfo1_ on user0_.id=userinfo1_.id and user0_.type=userinfo1_.type where user0_.id=? and user0_.type=?
Hibernate: call next value for seq_id
Hibernate: select userinfo0_.id as id3_1_0_, userinfo0_.type as type4_1_0_, userinfo0_.name as name1_1_0_, userinfo0_.surname as surname2_1_0_ from user_info userinfo0_ where userinfo0_.id=? and userinfo0_.type=?
Hibernate: insert into user (email, login, id, type) values (?, ?, ?, ?)
Hibernate: insert into user_info (name, surname, id, type) values (?, ?, ?, ?)
 WARN 17653 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23502, SQLState: 23502
ERROR 17653 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper   : NULL not allowed for column "ID"; SQL statement:
insert into user_info (name, surname, id, type) values (?, ?, ?, ?) [23502-196]
...

Log EntityManager

Hibernate: call next value for seq_id
Hibernate: insert into user (email, login, id, type) values (?, ?, ?, ?)
Hibernate: insert into user_info (name, surname, id, type) values (?, ?, ?, ?)

Code below:

Main model: User.java

import com.fasterxml.jackson.annotation.JsonManagedReference;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

@Entity
@Table(name = "USER")
@IdClass(UserPK.class)
public class User implements Serializable {

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
    @JsonManagedReference
    private UserInfo info;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_ID")
    @SequenceGenerator(name = "SEQ_ID", sequenceName = "SEQ_ID", allocationSize = 1)
    @NotNull
    @Column(name = "ID")
    private Long id;
    @Id
    @NotNull
    @Column(name = "TYPE")
    private String type;
    @NotNull
    @Column(name = "LOGIN")
    private String login;
    @NotNull
    @Column(name = "EMAIL")
    private String email;
    /* ... */
}

Second model: UserInfo.java

import com.fasterxml.jackson.annotation.JsonBackReference;
import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "USER_INFO")
public class UserInfo implements Serializable {

    @Id
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumns({
     @JoinColumn(name = "id", referencedColumnName = "id"),
     @JoinColumn(name = "type", referencedColumnName = "type")
    })
    @JsonBackReference
    @MapsId
    private User user;

    @Column(name = "NAME")
    private String name;
    @Column(name = "SURNAME")
    private String surname;
    /* ... */
}

Composed primary key: UserPK.java

import java.io.Serializable;

public class UserPK implements Serializable {
    private Long id;
    private String type;
    /* ... */
}

spring CRUDRepository: UserRepository.java

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends CrudRepository<User, UserPK> {
    User findByIdAndAndType(Long id, String type);
}

Repository using EntityManager: UserRepositoryEM.java

import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

@Repository
@Transactional
public class UserRepositoryEM {

    @PersistenceContext
    private EntityManager entityManager;

    public User findByKey(UserPK key) {
        return entityManager.find(User.class, key);
    }

    public User save(User user) {
        entityManager.persist(user);
        entityManager.flush();
        return user;
    }
}
1

There are 1 answers

3
Indra Basak On

The User and UserPK classes need slight modification. Your field to column mappings for the primary key should be inside the UserPK class. Here are the changes,

@Entity
@Table(name = "USER")
public class User implements Serializable {

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "user")
    @JsonManagedReference
    private UserInfo info;

    @EmbeddedId
    private UserPK id;

    @Column(name = "LOGIN", nullable = false)
    private String login;

    @Column(name = "EMAIL", nullable = false)
    private String email;
    /* ... */

    @Embeddable
    @SequenceGenerator(name = "SEQ_ID",  initialValue=1, allocationSize=100)
    public static class UserPK implements Serializable {

        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_ID")
        @Column(name = "ID", nullable = false)
        private Long id;

        @Column(name = "TYPE", nullable = false)
        private String type;
    }
}

Corresponding changes to the repository,

@Repository
public interface UserRepository extends CrudRepository<User, User.UserPK> {
    User findByIdAndAndType(Long id, String type);
}