eixcs
eixcs

Reputation: 1989

Spring Data JPA saving two entities at the same time, null value in column "user_id"

I have users in my application and users have work details, I use one method to set the user/work details data and then I either add a new user or modify an existing one.

This is the method for setting user/work data:

public WorkDetail setWorkerData(PatchWorkerRequest request, User user, WorkDetail workDetail) {

        if (request.getName() != null) {
            user.setName(request.getName());
        }

        if (request.getIdCode() != null) {
            user.setIdCode(request.getIdCode());
        }

        if (request.getEmail() != null) {
            user.setEmail(request.getEmail());
        }

        if (request.getPhone() != null) {
            user.setPhone(request.getPhone());
        }

        if (request.getAddress() != null) {
            user.setAddress(request.getAddress());
        }

        if (request.getSignatureLevel() != null) {
            user.setSignatureLevel(request.getSignatureLevel());
        }

        if (request.getAltContactRelation() != null) {
            user.setAltContactRelation(request.getAltContactRelation());
        }

        if (request.getAltContactPhone() != null) {
            user.setAltContactPhone(request.getAltContactPhone());
        }

        if (request.getRoles() != null) {
            user.setRoles(request.getRoles());
        }

        if (request.getStatus() != null) {
            user.setStatus(request.getStatus());
        }

        // Work details

        if (request.getJobRelation() != null) {
            workDetail.setJobRelation(request.getJobRelation());
        }

        if (request.getSalary() != null) {
            workDetail.setSalary(request.getSalary());
        }

        if (request.getDistricts() != null) {
            workDetail.setDistricts(request.getDistricts());
        }

        if (request.getCompetences() != null) {
            workDetail.setCompetences(request.getCompetences());
        }

        workDetail.setUser(user);
        user.setWorkDetail(workDetail);

        return workDetailRepository.save(workDetail);
    }

Now, modifying an existing worker works fine with this code:

public WorkDetail modifyWorker(Long workerId, PatchWorkerRequest request) {
        WorkDetail workDetail = this.getWorkDetailById(workerId);
        User user = userService.getUserById(workDetail.getUser().getId());
        return setWorkerData(request, user, workDetail);
    }

But when I try to create a new user/worker I get an error that "null value in column "user_id" violates not-null constraint". I assume it's because the workDetail and user don't get connected properly.

This is the method for creating a new worker:

public WorkDetail createWorker(PatchWorkerRequest request) {
        WorkDetail workDetail = new WorkDetail();
        User user = new User();
        String generatedPassword = userService.generatePassword(8);
        user.setPassword(passwordEncoder.encode(generatedPassword));
        emailService.sendMail("SYDA", new String[]{request.getEmail()},
                "Project SYDA",
                "New password: " + generatedPassword + ".);
        return setWorkerData(request, user, workDetail);
    }

Also, is there any way I could send the email after saving the user so in case of errors it wouldn't send the email?

Entities:

User:

@Entity
@Data
@NoArgsConstructor
@TypeDefs({
        @TypeDef(name = "pgsql_enum", typeClass = PostgreSQLEnumType.class)
})
@Table(name = "user_acc")
public class User implements Serializable {

    @Id
    @Column(unique = true, nullable = false, columnDefinition = "serial")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    @Column(name = "id_code")
    private BigInteger idCode;

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

    @Column(name = "address")
    private String address;

    @Column(name = "phone")
    private BigInteger phone;

    @Column(name = "alt_contact_relation")
    private String altContactRelation;

    @Column(name = "alt_contact_phone")
    private BigInteger altContactPhone;

    @Column(name = "password", nullable = false)
    @JsonIgnore
    private String password;

    @JsonIgnore
    @Column(name = "create_time", nullable = false, updatable = false)
    private Date createTime = new Date();

    @JsonIgnore
    @Column(name = "update_time", nullable = false)
    private Date updateTime = new Date();

    @Column(name = "status", nullable = false, columnDefinition = "active_status")
    @Enumerated(EnumType.STRING)
    @Type(type = "pgsql_enum")
    private UserStatus status;

    @OneToOne
    @PrimaryKeyJoinColumn
    @JsonIgnore
    private WorkDetail workDetail;

    @OneToMany(mappedBy = "user")
    private List<UserFile> userFiles = new ArrayList<>();

    @ManyToOne
    @JoinColumn(name = "signature_level_id")
    private SignatureLevel signatureLevel;

    @ManyToMany(fetch = FetchType.EAGER, targetEntity = UserRole.class)
    @JoinTable(name = "user_has_user_role",
            joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id")},
            inverseJoinColumns = { @JoinColumn(name = "user_role_id", referencedColumnName = "id")}
    )
    private Set<UserRole> roles = new HashSet<>();

    @ManyToMany(fetch = FetchType.EAGER, targetEntity = Mechanism.class)
    @JoinTable(name = "user_has_mechanism",
            joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id")},
            inverseJoinColumns = { @JoinColumn(name = "mechanism_id", referencedColumnName = "id")}
    )
    private Set<Mechanism> mechanisms = new HashSet<>();

    @ManyToMany(fetch = FetchType.EAGER, targetEntity = Service.class)
    @JoinTable(name = "user_has_service",
            joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id")},
            inverseJoinColumns = { @JoinColumn(name = "service_id", referencedColumnName = "id")}
    )
    private Set<Service> services = new HashSet<>();

    @PreUpdate
    @PrePersist
    public void onCreateOnUpdate() {
        updateTime = new Date();
    }

    public enum UserStatus {
        active, temporarily_inactive, inactive
    }
}

WorkDetail:

@Entity
@Data
@Table(name = "work_detail")
public class WorkDetail {

    @Id
    @Column(unique = true, nullable = false, columnDefinition = "serial")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "salary")
    private float salary;

    @OneToOne(mappedBy = "workDetail",fetch = FetchType.LAZY, optional = true)
    @PrimaryKeyJoinColumn
    private User user;

    @Column(name = "create_time", nullable = false, updatable = false)
    @JsonIgnore
    private Date createTime = new Date();

    @Column(name = "update_time", nullable = false)
    @JsonIgnore
    private Date updateTime = new Date();

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "workDetail")
    private List<UserLeave> userLeaves = new ArrayList<>();

    @ManyToOne
    @JoinColumn(name = "job_relation_id")
    private JobRelation jobRelation;

    @ManyToMany(fetch = FetchType.EAGER, targetEntity = District.class)
    @JoinTable(name = "work_detail_has_district",
            joinColumns = { @JoinColumn(name = "work_detail_id", referencedColumnName = "id")},
            inverseJoinColumns = { @JoinColumn(name = "district_id", referencedColumnName = "id")}
    )
    private Set<District> districts = new HashSet<>();

    @ManyToMany(fetch = FetchType.EAGER, targetEntity = Competence.class)
    @JoinTable(name = "work_detail_has_competence",
            joinColumns = { @JoinColumn(name = "work_detail_id", referencedColumnName = "id")},
            inverseJoinColumns = { @JoinColumn(name = "competence_id", referencedColumnName = "id")}
    )
    private Set<Competence> competences = new HashSet<>();

    @PreUpdate
    @PrePersist
    public void onCreateOnUpdate() {
        updateTime = new Date();
    }
}

Db tables:

-----------------------
-- User table
-----------------------

CREATE TABLE IF NOT EXISTS user_acc (
  id SERIAL NOT NULL PRIMARY KEY,
  name text NOT NULL,
  id_code numeric NOT NULL,
  email text NOT NULL UNIQUE,
  address text NULL,
  alt_contact_relation text NULL,
  alt_contact_phone numeric NULL,
  signature_level_id integer NULL,
  username text NOT NULL,
  password text NOT NULL,
  create_time TIMESTAMP without TIME ZONE DEFAULT now() NOT NULL,
  update_time TIMESTAMP without TIME ZONE DEFAULT now() NOT NULL,
  status active_status NOT NULL DEFAULT 'active',
  CONSTRAINT FK_user_signature_level FOREIGN KEY (signature_level_id) REFERENCES signature_level (id)
  ON DELETE NO ACTION ON UPDATE NO ACTION
);

-----------------------
-- User: work detail table
-----------------------

CREATE TABLE IF NOT EXISTS work_detail (
  id SERIAL NOT NULL PRIMARY KEY,
  salary decimal NULL,
  job_relation_id integer NOT NULL,
  user_id integer NOT NULL,
  create_time TIMESTAMP without TIME ZONE DEFAULT now() NOT NULL,
  update_time TIMESTAMP without TIME ZONE DEFAULT now() NOT NULL,
  CONSTRAINT FK_work_detail_user FOREIGN KEY (user_id) REFERENCES user_acc (id)
  ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT fk_work_details_job_relations1 FOREIGN KEY (job_relation_id)
    REFERENCES job_relation (id)
    ON DELETE NO ACTION
    ON UPDATE NO ACTION);

Upvotes: 0

Views: 1641

Answers (5)

MattOverF.
MattOverF.

Reputation: 69

Your mapping is wrong.
Here's what you should do :
in WorkDetail :
@OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "user_id") private User user;

in User :
@OneToOne(mappedBy = "user") @JsonIgnore private WorkDetail workDetail;

Upvotes: 0

Vikrant Kashyap
Vikrant Kashyap

Reputation: 6856

In your setWorker() method you try to set user for workDetail Object but user Object doesn't have their user_id because this user object is detached mode and still no user_id is associated with user Object.

 //some code in your setWorker() Method.
 workDetail.setUser(user);   //you try to set a detached User Object which doesn't have it's id.
 user.setWorkDetail(workDetail);

 return workDetailRepository.save(workDetail);

So, either save that user just before setting and make sure user Object is persisted into database .

add this line before above code..

user = userRepository.save(user); // add this line....
workDetail.setUser(user);   //now your User Object will have it's id.
user.setWorkDetail(workDetail);

return workDetailRepository.save(workDetail);

there is also a another way to perform this without calling save() method just by using the concept of cascading for more info go through with this Link Here

Upvotes: 1

MattOverF.
MattOverF.

Reputation: 69

It means the user object is not saved when you try to save the workdetail object. So there is not user_id to put, hence the null value. Could should post you entities please ? (specially the mapping between them). I think, you should have something like @OneToOne(cascade = CascadeType.PERSIST) if you want you save both entities at the same time.

Upvotes: 0

Dimitar Spasovski
Dimitar Spasovski

Reputation: 2132

You need to save the user before you add it to the workDetail. The new User object does not have an id, and that is why you are getting that exception. Something like this:

public WorkDetail createWorker(PatchWorkerRequest request) {
        WorkDetail workDetail = new WorkDetail();
        User user = new User();
        String generatedPassword = userService.generatePassword(8);
        user.setPassword(passwordEncoder.encode(generatedPassword));
        user = userRepository.save(user);
        emailService.sendMail("SYDA", new String[]{request.getEmail()},
                "Project SYDA",
                "New password: " + generatedPassword + ".);
        return setWorkerData(request, user, workDetail);
    }

or you can add a saveUser call inside the setWorkerData method.

...
 if (request.getStatus() != null) {
            user.setStatus(request.getStatus());
        }
user = userRepository.save(user);
...

Upvotes: 1

Nicolaj Lerch
Nicolaj Lerch

Reputation: 1

You can use the spring-boot-starter-mail dependency to get this work

Upvotes: 0

Related Questions