#java #quarkus #quarkus-panache
#java #quarkus #quarkus-panache
Вопрос:
В настоящее время я работаю с простым HTTP-ресурсом. Моя модель состоит из «деревьев» с несколькими «плодами». Оба наследуются от PanacheEntity .
Дерево:
@Entity
public class Tree extends PanacheEntity {
public String name;
@OneToMany(mappedBy = "tree", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
public List<Fruit> fruits = new ArrayList<>();
}
Фрукты
@Entity
public class Fruit extends PanacheEntity {
public String name;
public String color;
public int cores;
@ManyToOne
@JsonbTransient
public Tree tree;
}
Ресурс:
import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;
public interface TreeResource extends PanacheEntityResource<Tree, Long> { }
Это мой POST-запрос, который я отправляю через Swagger
{
"name": "Apple Tree",
"fruits": [
{
"color": "green",
"cores": 3,
"name": "Apple2"
},
{
"color": "red",
"cores": 4,
"name": "Apple"
}
]
}
В ответе говорится, что идентификаторы были созданы для всех объектов:
{
"id": 4,
"fruits": [
{
"id": 5,
"color": "green",
"cores": 3,
"name": "Apple2"
},
{
"id": 6,
"color": "red",
"cores": 4,
"name": "Apple"
}
],
"name": "Apple Tree"
}
НО когда я вызываю Get all под / деревьями, я получаю следующий ответ:
[
{
"id": 1,
"fruits": [],
"name": "Oak"
},
{
"id": 4,
"fruits": [],
"name": "Apple Tree"
}
]
Фрукты всегда пусты. Проверка базы данных postgres показывает, что все столбцы «tree_id» в Fruit имеют значение null. Я почти уверен, что это проблема для начинающих, но после проверки нескольких образцов я просто не могу найти, что не так с моим кодом.
Комментарии:
1. Не могли бы вы подготовить автономный тестовый пример (простой проект Maven, воспроизводящий проблему) и опубликовать его в Quarkus tracker? Я думаю, нам нужно взглянуть на этот вариант использования (а ресурс Panache REST довольно новый, поэтому обратная связь очень приветствуется).
2. Конечно, я создам небольшой проект за выходные. Тем временем я решил проблему, просто объявив «OneToMany», но не наоборот «ManyToOne», так что теперь оно однонаправленное.
3. Итак, я как раз собирался закончить свой пример проекта и решил попробовать еще раз, и теперь он работает нормально, по крайней мере, после первоначального сообщения. Затем я попытался обновить свои данные с помощью PUT, и теперь я снова вижу ту же проблему. Ссылка на мой пример проекта (на основе официального приложения quarkus panache-quickstart): github.com/WeinbM/quarkus-hibernate-orm-sample
Ответ №1:
У меня была такая же проблема, и я решил ее, установив родительские объекты дочерними. У меня были элементы Job и JobArgs для сохранения.
// ... somewhere in JobArg.java
@JsonbTransient
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "job_id", foreignKey = @ForeignKey(name = "job_id_fk"))
public Job job;
@OneToMany(mappedBy = "job", cascade = CascadeType.ALL, orphanRemoval = true)
public List<JobArg> arguments = new ArrayList<>();
// ... somewhere in Job.java
// I used reactive-pgclient so my method return Uni<T>
public void addArgument(final JobArg jobArg) {
arguments.add(jobArg);
jobArg.job = this;
}
public static Uni<Job> insert(final UUID userId, final JobDto newJob) {
final Job job = new Job();
//... map fields from dto ...
newJob.getArguments().stream().map(arg -> {
final JobArg jobArg = new JobArg();
//... map fields from dto ...
return jobArg;
}).forEach(job::addArgument);
final Uni<Void> jobInsert = job.persist();
final Uni<UserAction> userActionInsert = UserAction.insertAction(type, job.id, userId, null);
return Uni.combine().all().unis(jobInsert, userActionInsert).combinedWith(result -> job);
}
Это пример кода из блога Влада Михалча:
Для двунаправленных сопоставлений:
@Entity(name = "Post")
@Table(name = "post")
public class Post {
@Id
@GeneratedValue
private Long id;
private String title;
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
//Constructors, getters and setters removed for brevity
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
}
@Entity(name = "PostComment")
@Table(name = "post_comment")
public class PostComment {
@Id
@GeneratedValue
private Long id;
private String review;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
//Constructors, getters and setters removed for brevity
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PostComment )) return false;
return id != null amp;amp; id.equals(((PostComment) o).getId());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
В вышеупомянутом сопоставлении следует отметить несколько моментов:
Ассоциация @ManyToOne использует FetchType .ЛЕНИВЫЙ, потому что в противном случае мы бы вернулись к БЫСТРОЙ выборке, что плохо сказывается на производительности.
Родительский объект Post имеет два служебных метода (например, AddComment и removeComment), которые используются для синхронизации обеих сторон двунаправленной ассоциации. Вы всегда должны предоставлять эти методы всякий раз, когда работаете с двунаправленной ассоциацией, поскольку в противном случае вы рискуете очень тонкими проблемами с распространением состояния.
Дочерний объект PostComment реализует методы equals и hashCode . Поскольку мы не можем полагаться на естественный идентификатор для проверки равенства, нам нужно использовать идентификатор объекта вместо метода equals. Однако вам нужно сделать это правильно, чтобы равенство было согласованным во всех переходах состояния объекта, что также является причиной, по которой хэш-код должен быть постоянным значением. Поскольку мы полагаемся на равенство для removeComment, рекомендуется переопределять equals и hashCode для дочернего объекта в двунаправленной ассоциации
.