简体   繁体   中英

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);

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);
...

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

您可以使用spring-boot-starter-mail依赖项来完成这项工作

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.

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM