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;
}