Java hibernate entity: разрешить установку связанного объекта как с помощью id, так и с самого объекта

У меня есть следующий Java-класс, который также является объектом Hibernate:

@Entity
@Table(name = "category")
public class Category {

    @ManyToOne
    @JoinColumn(name="parent_id")
    private Category parent;

    public Category getParent() {
        return parent;
    }

    public void setParent(Category parent) {
        this.parent = parent;
    }

Категория представляет собой node в дереве категорий. Я реализую веб-сервис, который позволяет категориям CRUD. Например, интерфейс имеет возможность создавать дерево категорий node и передает идентификатор категории в качестве параметра.

Я хочу просто создать новый объект Category и сохранить его в базе данных без извлечения родительского объекта. Мой класс поставщика данных выглядит следующим образом:

public void createCategory(int parent_id, String name, CategoryType type) {
    Category category = new Category();
    category.setName(name);
    // category.setParent(?); <- I don't have this object here
    // category.setParentId(id); <- but I do have the id
    category.setType(type);
    this.categoryDao.save(category);
}

Мой вопрос: что я могу сделать для создания нового объекта Category с помощью родительского набора, если предположить, что я не буду вызывать hibernate для извлечения родителя для меня (это было бы глупо)? Могу ли я предоставить метод setParentId/getParentId для класса категории? Какие аннотации для спящего режима будут иметь?

Ответ 1

Hibernate предоставляет метод, называемый (довольно смутно) Session.load() для этого сценария.

Session.load() возвращает ленивый прокси с данным идентификатором без запроса базы данных (если объект с данным идентификатором уже загружен в текущий Session, он возвращает сам объект).

Вы можете использовать этот прокси для инициализации отношений в сохраняемых вами сущностях:

category.setParent(session.load(Category.class, parent_id));

Обратите внимание, что этот код не проверяет существование Category с данным идентификатором. Однако, если у вас есть ограничение внешнего ключа в вашей схеме БД, вы получите ошибку нарушения ограничений при передаче недопустимого id.

JPA эквивалент этого метода называется EntityManager.getReference().

Ответ 2

Есть еще одно решение проблемы - с использованием конструктора по умолчанию для объекта, для которого вы хотите ссылаться, и для установки идентификатора и версии (если это версия).

        Constructor<S> c = entityClass.getDeclaredConstructor();
        c.setAccessible(true);

        S instance = c.newInstance();
        Fields.set(instance, "id", entityId.getId());
        Fields.set(instance, "version", entityId.getVersion());
        return instance;

Мы можем использовать такой подход, потому что мы не используем ленивую загрузку, а вместо этого имеем "представления" объектов, которые используются в графическом интерфейсе. Это позволяет нам уйти от всех объединений, которые использует Hibernate для заполнения всех нетерпеливых отношений. В представлениях всегда есть id и версия объекта. Следовательно, мы можем заполнить ссылку, создав объект, который будет выглядеть как Hibernate как не преходящий.

Я пробовал и этот подход, и тот, у кого есть session.load(). Оба работали нормально. Я вижу некоторое преимущество в моем подходе, поскольку Hibernate не будет течь со своими прокси-серверами в другом месте кода. Если он не используется должным образом, я просто получаю NPE вместо исключения "no session bound to thread".

Ответ 3

Вы можете определить ленивый init

@ManyToOne(fetch=FetchType.LAZY)

Угадайте, что столбец имеет значение NULL, поэтому не проблема сохранения категории без родителя (root)