Spring Hibernate Lazy Fetch collections транзакции не работают
я совершенно запутался, я создавал свое первое весеннее приложение с hibernate, и я не могу обнаружить свою ошибку при ленивой загрузке объектов из моей базы данных.
мои модели следующие
класс команды
@Entity
public class Team {
@Id
@Column
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column
private String name;
@Column
private String description;
@OneToMany(fetch=FetchType.LAZY , cascade = CascadeType.ALL, mappedBy="team")
@JsonIgnore
public List<Person> members;
//Constructors and setters getters ommited
}
класс Person
@Entity
@Table(name="`person`")
public class Person {
@Id
@Column
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
@Column
private String name;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="team")
@JsonIgnore
private Team team;
//omitted constructor and methods
}
затем у меня есть объекты доступа к данным, которые все следуют тому же шаблону, что и этот
@Repository
public class TeamDaoImpl implements TeamDao {
@Autowired
private SessionFactory session;
@Override
public void add(Team team) {
session.getCurrentSession().save(team);
}
@Override
public void edit(Team team) {
session.getCurrentSession().update(team);
}
@Override
public void delete(int teamId) {
session.getCurrentSession().delete(getTeam(teamId));
}
@Override
public Team getTeam(int teamId) {
return (Team) session.getCurrentSession().get(Team.class, teamId);
}
@Override
public List getAllTeam() {
return session.getCurrentSession().createQuery("from Team").list();
}
}
во всех этих методах я делаю обязательно используйте текущий сеанс для выполнения этих действий. Насколько я понимаю, это гарантирует, что он помещает эти вызовы в существующую транзакцию, которая создается в классе обслуживания следующим образом:
@Service
public class PersonServiceImpl implements PersonService {
Logger log = Logger.getLogger(PersonServiceImpl.class);
@Autowired
PersonDao personDao;
@Autowired
TeamDao teamDao;
//Ommitted other methods
@Transactional
public List getPeopleForTeam(int teamId) {
log.info("Getting people for team with id " + teamId);
Team team = teamDao.getTeam(teamId);
return team.getMembers();
}
}
мое понимание @Transactional
аннотация должна поместить этот метод в одну транзакцию.
это все компилируется отлично, но когда я запускаю его, я получаю следующую ошибку
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: model.Team.members, no session or session was closed; nested exception is
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: model.Team.members, no session or session was closed
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.writeInternal(MappingJackson2HttpMessageConverter.java:207)
org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:179)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:148)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:90)
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:189)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:69)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827)
javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812)
javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
что я делаю не так? Это мое понимание @ Транзакционная аннотация неверна? Если да, то как я должен заниматься ленивой загрузкой коллекций внутри объекта, но получать их в определенных транзакционных методах?
EDIT:
это часть моей весенней конфигурации
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.databaseuri}" p:username="${jdbc.username}" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<property name="configurationClass">
<value>org.hibernate.cfg.AnnotationConfiguration</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${jdbc.dialect}</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="dataSource" ref="dataSource" />
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
это еще одна часть конфигурации
<mvc:annotation-driven>
<mvc:message-converters>
<!-- Use the HibernateAware mapper instead of the default -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.bt.opsscreens.mappers.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
это часть контроллера
@RequestMapping(value="/{teamId}/people", method=RequestMethod.GET)
public @ResponseBody List<Person> getPeopleForTeam(@PathVariable int teamId) {
return personService.getPeopleForTeam(teamId);
}
4 ответов
вы, вероятно, вызываете метод службы из контроллера (например). Что происходит:
-controller method
-call service method
-start transaction
-hibernate do lazy loading
-end transaction
-end service method
you try to access somethind from the lazy initialied list, but no hibernate transaction here
-end controller method
ленивая загрузка означает, что hibernate имеет прокси-объект, который хранит только идентификатор сущности и выполняет инструкцию select только тогда, когда это необходимо(например, доступ к некоторому свойству сущности).
Вы можете взглянуть на Open session in view
но это считается плохой практикой почему открытая сессия Hibernate считается плохой практикой?.
Попробуйте получить коллекции охотно.
изменить: 1) Hibernate требует, чтобы все выполнялось в транзакции например,
Transaction tx = null;
try{
tx = session.beginTransaction();
...
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
2)ленивая загрузка: Hibernate создает прокси-объект (он не запускает оператор в DB). Когда вы попробуйте получить доступ к прокси-серверу, оператор отправляется в БД и в результате получили(это нужно, чтобы быть в сделке гибернации.)
3)весна упрощает ведение сделки перехват данного метода с помощью AOP. Сначала начинается транзакция, затем вызывает метод и фиксация или откаты.
getPeopleForTeam () возвращает прокси-сервер. Затем где-то вне транзакции вы получаете доступ к некоторому свойству, и hibernate пытается запустить инструкцию select.
вы получаете доступ к коллекции с ленивой загрузкой вне сеанса гибернации, поэтому вы должны либо изменить лень загрузка готовностью загрузка или добавление аннотации @JsonIgnore
перед каждым @OneToMany
Аннотация В вашей модели
я столкнулся с той же проблемой @JsonIgnore
работал для меня, я понимаю, что вы можете получить набор объектов ленивый и использовать его программно, но для ответа HTTP JSON он игнорируется.
Я нашел еще одно решение этой проблемы.
вы можете явным образом загрузить объекты в методе, чтобы он уже был создан, когда это требуется синтаксическим анализатором JSON. Я попробовал и это работает для меня.
пример в моем случае:
public Company getCompany(Long companyId){
Company company = companyDao.findById(companyId);
// For avoiding Lazy initialization error. If not done, JSON serializer is unhappy
// as Address object is not created till that moment when JSON serialization is done.
for(Address address: company.getAddressSet() ){
addressDao.findById(address.getId());
}
return company;
}