javaspring-bootjpajparepository

Inserting a column that has refs to other entities in Java Spring JPA


I am creating a database that keeps information about other database and I want ot insert an entry. Classes look like:

@Entity
@Table(name = "table_info")
public class TableInfo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @OneToOne
    @JoinColumn(name = "database_id")
    private DatabaseInfo databaseInfo;

    @Column(name = "table_name", length = 255, nullable = false)
    private String tableName;

    @Column(name = "created_at")
    private Timestamp createdAt;
    // Getter Setters, etc
}
@Entity(name = "UserInfo")
@Table(
        name = "UserInfo",
        uniqueConstraints = {
                @UniqueConstraint(name = "user_unique", columnNames = "username")
        }
)
public class UserInfo {

    @Id
    @SequenceGenerator(
            name = "userinfo_sequence",
            sequenceName = "userinfo_sequence",
            allocationSize = 1
    )
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "userinfo_sequence"
    )
    @Column(
            name = "id",
            updatable = false
    )
    private Long id;

    @Column(
            name = "masterID",
            updatable = true,
            nullable = false
    )
    private Long masterID;

    @Column(
            name = "username",
            updatable = false,
            nullable = false,
            columnDefinition = "TEXT"
    )
    private String username;

    @Column(
            name = "email",
            updatable = false,
            nullable = false,
            columnDefinition = "TEXT"
    )
    private String email;

    @Column(
            name = "password_hash",
            updatable = true,
            nullable = false,
            columnDefinition = "TEXT"
    )
    private String password_hash;
    // Getter Setters, etc

}

@Entity
@Table(name = "ownership_details")
public class OwnershipDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ownership_details_id")
    private Long id;

    @Column(name = "access_level")
    private Integer access_level;

    @OneToOne(
            cascade = CascadeType.MERGE,
            fetch = FetchType.LAZY,
            orphanRemoval = true
    )
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private UserInfo user;

    @OneToOne(
        cascade = CascadeType.MERGE,
        fetch = FetchType.LAZY,
        orphanRemoval = true
    )
    @JoinColumn(name = "table_id", referencedColumnName = "id")
    private TableInfo tableInfo;
}

Here is my OwnershipDetails repository:

@Repository
public interface OwnershipDetailsRepository extends JpaRepository<OwnershipDetails, Long> {
    
}

Whenever I try to insert new OwnershipDetails object I simply use: ownershipDetailsRepository.save(ownershipDetails);

Whenever I do that I get an error: not-null property references a null or transient value : project.BackEnd.User.UserInfo.email Which obviously means that it is trying to insert a new userInfo object instead of creating a reference to the one that already exists. My question is how can I force it to create refs instead of trying to create another entity in database.


Solution

  • Indeed, it is trying to insert a new userInfo object instead of creating a reference to the one that already exists.

    You have to first get a reference to the persistent UserInfo entity by using getReferenceById, findById or any other finder method, then set the UserInfo entity on the OwnershipDetails before saving.

    Whenever you try to insert new OwnershipDetails, Spring Data JPA detects that it's a new entity and calls em.persist. CascadeType.MERGE is not used in this case as it relates to merge operations on the parent entity, not persist. So Hibernate complains that it's trying to persist a detached UserInfo instance (and does not try to persist it because CascadeType.PERSIST is not defined).

    var user = userInfoRepository.getReferenceById(userInfoId);
    // build ownershipDetails
    ownershipDetails.setUser(user);
    ownershipDetailsRepository.save(ownershipDetails);