JPA保存错误的实体

时间:2018-12-09 17:28:54

标签: java spring hibernate jpa

我正在使用Hibernate和DTO保存我的实体Test (tests)TestQuestion (test_questions)TestAnswer (test_answers)

我使用ModelMaper将DTO转换为休眠实体。我的实体保存错误:

1)第一个问题是完成映射后,我在测试实体中设置了User对象。 JPA在tests表中创建2个实体。一种带有用户ID,一种没有用户ID。将Question类中的Test列表正确地保存在test_questions表中,并引用用户ID为空的测试ID。

2)第二个问题是Answer类中的Question列表根本没有保存在test_answers表中。

表格:

tables

SQL表:

CREATE TABLE tests (
    id UUID DEFAULT uuid_generate_v4 () PRIMARY KEY,
    therapist_id UUID REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE,
    description TEXT DEFAULT NULL,
    level INTEGER NOT NULL,
    active BOOLEAN DEFAULT FALSE,
    date_time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    date_time_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE test_questions (
    id UUID DEFAULT uuid_generate_v4 () PRIMARY KEY,
    test_id UUID NOT NULL REFERENCES tests (id) ON DELETE CASCADE ON UPDATE CASCADE,
    type TEST_TYPE_ENUM NOT NULL,
    question TEXT NOT NULL,
    audio TEXT DEFAULT NULL,
    description TEXT DEFAULT NULL,
    img TEXT NOT NULL,
    cloud_id TEXT NOT NULL,
    date_time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    date_time_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE test_answers (
    id UUID DEFAULT uuid_generate_v4 () PRIMARY KEY,
    question_id UUID NOT NULL REFERENCES test_questions (id) ON DELETE CASCADE ON UPDATE CASCADE,
    answer TEXT NOT NULL,
    audio TEXT DEFAULT NULL,
    img TEXT DEFAULT NULL,
    cloud_id TEXT NOT NULL,
    date_time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    date_time_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

测试类:

@Entity
@Table(name = "tests")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Test implements Serializable {

    private static final long serialVersionUID = -2184376232517605961L;

    @Id
    @GeneratedValue(generator = "uuid2", strategy = GenerationType.SEQUENCE)
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Type(type = "pg-uuid")
    private UUID id;

    private String description;

    private Integer level = 0;

    private Boolean active = false;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "date_time_created")
    @JsonIgnore
    private Date dateTimeCreated = new Date();

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "date_time_updated")
    @JsonIgnore
    private Date dateTimeUpdated = new Date();

    @ManyToOne(fetch = FetchType.LAZY)
    @JsonBackReference
    @JoinColumn(name = "therapist_id")
    private User user;

    @OneToMany(mappedBy = "test", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private Set<TestQuestion> questions = new HashSet<>();

    // getters-setters



    @Override
    public String toString() {
        return "Test{" +
                "id=" + id +
                ", description='" + description + '\'' +
                ", level=" + level +
                ", active=" + active +
                ", dateTimeCreated=" + dateTimeCreated +
                ", dateTimeUpdated=" + dateTimeUpdated +
                ", user=" + user.getId() +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Test test = (Test) o;
        return Objects.equal(id, test.id) &&
                Objects.equal(description, test.description) &&
                Objects.equal(level, test.level) &&
                Objects.equal(active, test.active) &&
                Objects.equal(dateTimeCreated, test.dateTimeCreated) &&
                Objects.equal(dateTimeUpdated, test.dateTimeUpdated) &&
                Objects.equal(user, test.user) &&
                Objects.equal(questions, test.questions);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(id, description, level, active, dateTimeCreated, dateTimeUpdated, user, questions);
    }
}

TestQuestion类:

@Entity
@Table(name = "test_questions")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class TestQuestion implements Serializable {

    private static final long serialVersionUID = 6367504273687746576L;

    @Id
    @GeneratedValue(generator = "uuid2", strategy = GenerationType.SEQUENCE)
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Type(type = "pg-uuid")
    private UUID id;

    private String question;

    private String description;

    @Enumerated(EnumType.ORDINAL)
    @Type(type = "pgsql_enum")
    private TestQuestionTypeEnum type;

    private String img;

    private String audio;

    private String cloudId;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "date_time_created")
    @JsonIgnore
    private Date dateTimeCreated = new Date();

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "date_time_updated")
    @JsonIgnore
    private Date dateTimeUpdated = new Date();

    @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "test_id")
    private Test test;

    @OneToMany(mappedBy = "question", fetch = FetchType.EAGER)
    private Set<TestAnswer> answers = new HashSet<>();

    // getters-setters

@Override
public String toString() {
    return "TestQuestion{" +
            "id=" + id +
            ", question='" + question + '\'' +
            ", description='" + description + '\'' +
            ", type=" + type +
            ", img='" + img + '\'' +
            ", audio='" + audio + '\'' +
            ", cloudId='" + cloudId + '\'' +
            ", dateTimeCreated=" + dateTimeCreated +
            ", dateTimeUpdated=" + dateTimeUpdated +
            '}';
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    TestQuestion that = (TestQuestion) o;
    return Objects.equal(id, that.id) &&
            Objects.equal(question, that.question) &&
            Objects.equal(description, that.description) &&
            type == that.type &&
            Objects.equal(img, that.img) &&
            Objects.equal(audio, that.audio) &&
            Objects.equal(cloudId, that.cloudId) &&
            Objects.equal(dateTimeCreated, that.dateTimeCreated) &&
            Objects.equal(dateTimeUpdated, that.dateTimeUpdated) &&
            Objects.equal(test, that.test) &&
            Objects.equal(answers, that.answers);
}

@Override
public int hashCode() {
    return Objects.hashCode(id, question, description, type, img, audio, cloudId, dateTimeCreated, dateTimeUpdated, test, answers);
}
}

TestAnswer类:

@Entity
@Table(name = "test_answers")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class TestAnswer implements Serializable {

    private static final long serialVersionUID = -2372807870272293491L;

    @Id
    @GeneratedValue(generator = "uuid2", strategy = GenerationType.SEQUENCE)
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Type(type = "pg-uuid")
    private UUID id;

    private String answer;

    private String audio;

    private String img;

    private String cloudId;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "date_time_created")
    @JsonIgnore
    private Date dateTimeCreated = new Date();

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "date_time_updated")
    @JsonIgnore
    private Date dateTimeUpdated = new Date();

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JsonBackReference
    @JoinColumn(name = "question_id")
    private TestQuestion question;

// getters-setters



    @Override
    public String toString() {
        return "TestAnswer{" +
                "id=" + id +
                ", answer='" + answer + '\'' +
                ", audio='" + audio + '\'' +
                ", img='" + img + '\'' +
                ", cloudId='" + cloudId + '\'' +
                ", dateTimeCreated=" + dateTimeCreated +
                ", dateTimeUpdated=" + dateTimeUpdated +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TestAnswer that = (TestAnswer) o;
        return Objects.equal(id, that.id) &&
                Objects.equal(answer, that.answer) &&
                Objects.equal(audio, that.audio) &&
                Objects.equal(img, that.img) &&
                Objects.equal(cloudId, that.cloudId) &&
                Objects.equal(dateTimeCreated, that.dateTimeCreated) &&
                Objects.equal(dateTimeUpdated, that.dateTimeUpdated) &&
                Objects.equal(question, that.question);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(id, answer, audio, img, cloudId, dateTimeCreated, dateTimeUpdated, question);
    }
}

服务类别:

@Component("testService")
@Transactional
public class TestService extends Helper {
    private static final Logger log = LoggerFactory.getLogger(TestService.class);

    private final TestRepository testRepository;
    private final UserService userService;
    private final ModelMapper modelMapper;

    public TestService(TestRepository testRepository, UserService userService, ModelMapper modelMapper) {
        this.testRepository = testRepository;
        this.userService = userService;
        this.modelMapper = modelMapper;
    }

    public Test createTest(TestDTO testDTO) {

        User teacher = userService.findById(getLoggedUserId());

        Test test = toTest(testDTO, modelMapper);
        test.setUser(teacher);

        test = testRepository.saveAndFlush(test);

        return test;
    }

    private Test toTest(TestDTO testDTO, ModelMapper modelMapper) {

        Test test = new Test();
        Set<TestQuestion> testQuestions = new LinkedHashSet<>();

        TestValidity.validate(testDTO);

        testDTO.getQuestions().forEach(q -> {
            TestQuestion question = toQuestion(q, modelMapper);

            Set<TestAnswer> answers = toAnswerSet(q.getAnswers(), modelMapper);
            question.setAnswers(answers);

            testQuestions.add(question);
        });

        test.setQuestions(testQuestions);

        return test;
    }

    private TestQuestion toQuestion(TestQuestionDTO questionDTO, ModelMapper modelMapper) {


        return modelMapper.map(questionDTO, TestQuestion.class);
    }

    private Set<TestAnswer> toAnswerSet(Set<TestAnswerDTO> answerDTOSet, ModelMapper modelMapper) {
        Set<TestAnswer> answers = new HashSet<>();
        answerDTOSet.forEach(a -> {

            TestAnswer answer = modelMapper.map(a, TestAnswer.class);

            answers.add(answer);
        });


        return answers;
    }

我缺少什么吗?我不确定这些问题是否是由于`ModelMapper引起的,因为这是我第一次使用它。如何正确保存我的实体?

1 个答案:

答案 0 :(得分:1)

您似乎在声明cascade在关联的错误方面。来自Hibernate文档here

  

根据定义,@ OneToMany关联是父关联,即使它是单向或双向关联。只有关联的父级才有意义将其实体状态转换级联到子级。

我认为这是第二个问题的原因,因为您在子实体TestAnswer而非父实体TestQuestion上声明了级联。创建TestAnswer时,父TestQuestion并不知道需要保留其子级。

第一个问题也可能是由于TestTestQuestion都在每一侧都声明了级联(在@ManyToOne上都指向Test和{ {1}}指向OneToMany),因为这可能会导致在调用TestQuestion时创建一次tests,并在创建saveAndFlush()并看到它时再次创建TestQuestion需要级联持久其父实体Test

另一个问题是,保存时未同步关联的两端。根据一位Hibernate开发者here

  

但是,我们仍然需要使双方保持同步,否则,我们将破坏域模型关系的一致性,除非双方正确同步,否则不能保证实体状态转换正常工作。

换句话说,您需要执行以下操作:

test.setUser(teacher);
teacher.getTests().add(test);

(对于TestQuestion类似:

testQuestions.add(question);
question.setTest(test);

TestAnswer同样。

链接的Vlad Mihalcea博客通过将addTestAnswer()方法直接添加到TestQuestion实体中,展示了一种更好的方法。