首页 文章

JPA保存实体错误[暂停]

提问于
浏览
2

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

我使用ModelMaper将DTO转换为Hibernate实体 . 我的实体保存错误:

1)第一个问题是我在映射完成后在Test实体中设置 User 对象 . JPA在 tests 表中创建了2个实体 . 一个用户ID,一个没有用户ID . Test 类中的 Question 列表在 test_questions 表中正确保存,其中引用了用户标识为空的test id .

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

表:

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 回答

  • 1

    看起来你正在宣布 cascade 在协会的错误方面 . 来自Hibernate docs here

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

    我相信这是你第二个问题的原因,因为你在子实体 TestAnswer 而不是父 TestQuestion 上声明了级联 . 当您创建 TestAnswer 时,父级 TestQuestion 不知道它需要保留其子级 .

    第一个问题也可能是由于 TestTestQuestion 在每一侧都声明了级联( @ManyToOne 指向 TestOneToMany 指向 TestQuestion ),因为当你调用 saveAndFlush() 时可能会导致 tests 被创建一次,再次创建 TestQuestion 时,发现它需要级联持久化其父实体 Test .

    另一个问题是你要保存 . 据其中一个Hibernate开发者here

    但是,我们仍然需要使双方同步,否则,我们打破了域模型关系的一致性,并且除非双方都正确同步,否则不保证实体状态转换有效 .

    换句话说,你需要做一些事情:

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

    TestQuestion 类似:

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

    TestAnswer 类似的东西 .

    链接的Vlad Mihalcea博客通过直接向 TestQuestion 实体添加 addTestAnswer() 方法来显示更好的方法 .

相关问题