Контроллер Spring REST и время жизни Hibernate Session

Предположим, у меня есть класс A а также B с индивидуальной ассоциацией из A в B,

@Entity
class A {
    @Id Long id;
    @OneToOne(fetch = LAZY) B b;
    // getters, setters
}

@Entity
class B {
    @Id Long id;
}

Используя Spring JPA, у меня есть хранилище для A следующее

@Repository
public interface ARepository extends JpaRepository<A, Long> {
    A findById(Long id);
    void delete(Long id);
}

Наконец, контроллер REST

@RestController
@RequestMapping("/a")
class AController {
    @Autowired ARepository repo;

    @GetMapping("{id}")
    public A getA(@PathVariable Long id) {
        return repo.findById(id);
    }

    @Transactional
    @DeleteMapping("{id}")
    public A deleteA(@PathVariable Long id) {
        A a = repo.findById(id);
        repo.delete(id);
        return a;
    }
}

проблема

Предположим, я сохранил экземпляр A с id = 1 в базу данных, когда я отправляю запрос GET /a/1возвращает JSON-представление A без проблем.

Но когда я попытался удалить экземпляр, отправив запрос на УДАЛЕНИЕ /a/1, Я получил com.fasterxml.jackson.databind.JsonMappingException со следующей основной причиной

org.hibernate.LazyInitializationException: не удалось лениво инициализировать коллекцию, не удалось инициализировать прокси - нет сеанса

Вопрос

Насколько я понимаю, эта ошибка произошла, потому что Джексон пытался сериализовать экземпляр по вызову a.getB() после завершения метода deleteA() в какой срок жизни сессии Hibernate закончился, это правильно?

Если это так, я не понимаю, почему эта ошибка не возникает в методе getA()Думаю, сессия Hibernate должна завершиться сразу после завершения repo.findById(id);, право?

2 ответа

Мне не нравится open-in-view потому что я чувствую, что это ложь, вы должны знать, как вы получаете доступ к базе данных, и не иметь неожиданного доступа после начальной транзакции.

Я тоже не люблю пользоваться FetchType.EAGER по той же причине вы должны знать, как вы обращаетесь к базе данных. FetchType.EAGER может привести к соединениям, которые вы не ожидаете.

Возможным решением здесь является создание определенных методов в вашем хранилище и аннотирование их с помощью именованных графов. Например

@Entity
@NamedEntityGraph(name = "A.fetchB",attributeNodes=@NamedAttributeNode("b"))
public class A implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id Long id;
    @OneToOne(fetch = FetchType.LAZY) B b;
    // getters, setters
}

а также

@Repository
public interface ARepository extends JpaRepository<A, Long> {
    @EntityGraph(value="A.fetchB", type=EntityGraphType.FETCH)
    A findAFetchBById(Long id);
}

затем

@RestController
@RequestMapping("/a")
public class AController {
    @Autowired 
    private ARepository repo;

    @GetMapping("{id}")
    public A getA(@PathVariable Long id) {
        A a = repo.findAFetchBById(id);
        return a;
    }

    @Transactional
    @DeleteMapping("{id}")
    public A deleteA(@PathVariable Long id) {
        A a = getA(id);
        repo.delete(id);
        return a;
    }
}

Таким образом, вы знаете, что вы делаете с базой данных. Это приведет к одному оптимизированному запросу вместо нескольких запросов, которые выполняются при доступе к различным дочерним элементам Entity,

Скорее всего, проблема связана с попыткой инициализации Джексона A.b для сериализации.

Даже если вы включите open-in-viewошибка будет повторяться, потому что вы пытаетесь инициализировать поле объекта, который больше не существует.

Если A.b действительно должен быть лениво извлечен, подумайте об использовании @JsonIgnore а также (если, с другой стороны, A.b должны быть сериализованы и отправлены в теле ответа вместе с Aнет смысла делать его лениво извлеченным, просто используйте значение по умолчанию FetchType.EAGER).

Другие вопросы по тегам