java - JPA 保存实体错误

标签 java spring hibernate jpa

<分区>

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

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

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

2) 第二个问题是 Question 类中的 Answer 列表根本没有保存在 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,因为这是我第一次使用它。如何正确保存我的实体?

最佳答案

看起来您在关联错误的一侧声明了 级联。来自 Hibernate 文档 here :

The @OneToMany association is by definition a parent association, even if it’s a unidirectional or a bidirectional one. Only the parent side of an association makes sense to cascade its entity state transitions to children.

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

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

另一个问题是您在保存时没有同步关联的双方。根据一位 Hibernate 开发者的说法here :

However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.

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

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

TestQuestion 也类似:

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

TestAnswer 也有类似的内容。

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

关于java - JPA 保存实体错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53694836/

相关文章:

java骰子游戏,我是java初学者,正在苦苦挣扎..任何建议都会很好

java - 使用项目作为库,但需要更改 contentView

java - 带 IOC 的 Spring REST 服务?

java - map 的入口值取决于 Spring 的环境变量

java - Hibernate:线程 "main"org.hibernate.MappingException 中出现异常:无效配置

java - 持久化实体时如何从不可更新字段的数据库返回值?

java - int 中的所有数字都可以被某个 int 整除

java - 当没有定义请求对象时,如何使用 Spring Boot 发送 SOAP 请求

java - 从 Hibernate 时间戳检索数据

java - SQL 的未分类 SQLException