我正在使用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
表中。
表格:
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引起的,因为这是我第一次使用它。如何正确保存我的实体?
答案 0 :(得分:1)
您似乎在声明cascade
在关联的错误方面。来自Hibernate文档here:
根据定义,@ OneToMany关联是父关联,即使它是单向或双向关联。只有关联的父级才有意义将其实体状态转换级联到子级。
我认为这是第二个问题的原因,因为您在子实体TestAnswer
而非父实体TestQuestion
上声明了级联。创建TestAnswer
时,父TestQuestion
并不知道需要保留其子级。
第一个问题也可能是由于Test
和TestQuestion
都在每一侧都声明了级联(在@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
实体中,展示了一种更好的方法。