Spring Hibernate Lazy Fetch集合事务无法正常工作

时间:2013-12-15 20:39:43

标签: spring hibernate spring-mvc transactions

我完全感到困惑,我一直用hibernate创建我的第一个Spring应用程序,当我从数据库中延迟加载对象时,我似乎无法发现我的错误。

我的模型如下

团队课程

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

人物类

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

然后我的My Data Access Objects都遵循与此相同的模式

@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)

我做错了什么?我对@Transactional注释的理解是不正确的?如果是这样,我应该如何在对象内部进行延迟加载集合,并在某些事务方法中获取它们?

编辑:

这是我弹簧配置的相关部分

<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 个答案:

答案 0 :(得分:13)

您可能从控制器调用服务方法(例如)。会发生什么:

-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有一个代理对象,它只存储实体的id,只在需要时执行select语句(例如访问实体的某些属性)。 您可以查看Open session in view,但这被视为不良做法Why is Hibernate Open Session in View considered a bad practice?。 尝试热切地获取集合。

编辑: 1)Hibernate需要在事务中执行所有操作 e.g。

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发送声明)。当你 尝试访问代理,将一条语句发送给DB 并且结果被检索(这需要在休眠事务中。)

3)Spring简化了事务管理 用AOP拦截给定的方法。首先它开始 一个事务,然后调用该方法并提交或回滚它。

getPeopleForTeam()返回代理。然后在交易之外的某个地方 你访问一些属性,而hibernate试图激活一个select语句。

答案 1 :(得分:9)

您正在访问Hibernate会话之外的延迟加载集合,因此您应该从 lazy 加载更改为急切加载或添加注释@JsonIgnore在模型中的每个@OneToMany注释之前

答案 2 :(得分:0)

我面临同样的问题@JsonIgnore为我工作,我明白你可以获取懒惰的对象集并以编程方式使用它,但对于JSON HTTP响应,它会被忽略。

答案 3 :(得分:0)

我找到了另一个解决这个问题的方法。

您可以显式加载方法中的对象,以便在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;
}