Контроллер 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
).