Java JPA错误地保存实体

Java JPA错误地保存实体,java,spring,hibernate,jpa,Java,Spring,Hibernate,Jpa,我正在使用Hibernate和DTO保存实体,这些实体是Test(tests),TestQuestion(Test\u questions)和TestAnswer(Test\u answer) 我使用将DTO转换为Hibernate实体。我的实体保存错误: 1) 第一个问题是在映射完成后,在测试实体中设置User对象。JPA在测试表中创建2个实体。一个有用户id,一个没有用户id。Test类中的Question列表正确保存在Test\u questions表中,并引用用户id为空的测试id 2)

我正在使用Hibernate和DTO保存实体,这些实体是
Test(tests)
TestQuestion(Test\u questions)
TestAnswer(Test\u answer)

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

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

2) 第二个问题是
Question
类中的
Answer
列表根本没有保存在
test\u 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);
    }
}
服务类别:

@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;
    }
@组件(“测试服务”)
@交易的
公共类TestService扩展了Helper{
私有静态最终记录器log=LoggerFactory.getLogger(TestService.class);
私有最终测试库测试库;
专用最终用户服务用户服务;
私人最终模型映射器模型映射器;
公共TestService(TestRepository TestRepository、UserService UserService、ModelMapper ModelMapper){
this.testRepository=testRepository;
this.userService=userService;
this.modelMapper=modelMapper;
}
公共测试createTest(TestDTO TestDTO){
User teacher=userService.findById(getLoggedUserId());
Test=toTest(testDTO,modelMapper);
test.setUser(教师);
test=testRepository.saveAndFlush(测试);
回归试验;
}
私有测试toTest(TestDTO TestDTO,ModelMapper ModelMapper){
测试=新测试();
Set testQuestions=newlinkedhashset();
TestValidity.validate(testDTO);
testDTO.getQuestions().forEach(q->{
TestQuestion=toQuestion(q,modelMapper);
Set answers=toAnswerSet(q.getAnswers(),modelMapper);
问题.答案(答案);;
添加(问题);
});
测试。设置问题(测试问题);
回归试验;
}
私有TestQuestion to Question(TestQuestiondToQuestiondTo,ModelMapper ModelMapper){
返回modelMapper.map(questionDTO,TestQuestion.class);
}
专用集到应答集(Set answerDTOSet,ModelMapper ModelMapper){
Set answers=new HashSet();
forEach回答问题(a->{
TestAnswer answer=modelMapper.map(a,TestAnswer.class);
答案。添加(答案);
});
返回答案;
}

有什么我遗漏了吗?我不确定这些问题是否是因为“ModelMapper”,因为这是我第一次使用它。我如何才能正确保存我的实体?

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

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

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

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

另一个问题是在保存时没有同步关联的双方。根据Hibernate开发人员之一:

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

换句话说,您需要按照以下思路做一些事情:

test.setUser(teacher);
teacher.getTests().add(test);
同样地,对于
测试问题

testQuestions.add(question);
question.setTest(test);
对于
TestAnswer
,也有类似的情况


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

谢谢您的回答。如果我理解正确,我需要将cascade设置为parent-side,这意味着我应该将cascade设置为
@OneToMany
,而不是
@ManyToOne
。这导致了
TransientPropertyValueException
对象引用未保存的瞬态实例-在刷新之前保存瞬态实例:com.project.model.TestQuestion.test->com.project.model.test;嵌套异常为java.lang.IllegalStateException
。或者如果我设置了cascade(
CascadeType.ALL
)若要
@ManyToOne
s并从
@OneToMany
中删除,测试将记录到
测试
表中,但
测试问题
测试答案
表为空。您理解正确,cascade应该位于
@OneToMany
上。当您将其移动到
@ManyToOne
时,您会遇到同样的情况在你的第二期文章中提到。我现在还不清楚你为什么会出现这个错误;父端层叠是应该防止的(hibernate应该先保留父级,然后层叠子级)。我假设移动
cascade=…
是唯一被改变的事情?是的,
test.setUser(teacher);
teacher.getTests().add(test);
testQuestions.add(question);
question.setTest(test);