【问题标题】:Spring Data JPA with Transactions带有事务的 Spring Data JPA
【发布时间】:2016-02-17 17:06:50
【问题描述】:

我正在使用带有事务的 Spring Data JPA 开发应用程序。虽然我使用的 Spring 版本(4.0.0)可以使用 JavaConfig,但我更喜欢坚持使用 XML。

我有这个配置:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:jpa="http://www.springframework.org/schema/data/jpa"
   xmlns:tx="http://www.springframework.org/schema/tx"
   xmlns:context="http://www.springframework.org/schema/context"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">

<jpa:repositories base-package="repo"/>
<context:component-scan base-package="service" />
<tx:annotation-driven transaction-manager="transactionManager"/>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost/staffing?transformedBitIsBoolean=true"/>
    <property name="username" value="root"/>
    <property name="password" value="vivupdip1`"/>
</bean>

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="showSql" value="true"/>
    <property name="generateDdl" value="true"/>
    <property name="database" value="MYSQL"/>
</bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
    <property name="packagesToScan" value="model"/>
    <property name="jpaPropertyMap">
        <map>
            <entry key="hibernate.hbm2ddl.auto" value="validate"/>
            <entry key="hibernate.format_sql" value="true" />
        </map>
    </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

</beans>

我的单个(到目前为止)存储库是这样的:

package repo;

import org.springframework.data.jpa.repository.JpaRepository;
import model.Volunteer;

public interface VolunteerRepo extends JpaRepository<Volunteer, Integer> {
}

service 包中还有一个 Service 接口:

public interface VolunteerService {
  public List<Volunteer> findAll();
}

和实施:

@Service
@Transactional
public class VolunteerServiceImpl implements VolunteerService {

    @Autowired VolunteerRepo repo;

    public List<Volunteer> findAll() {
      return repo.findAll();
    }

}

从controller 包中的Controller 调用:

@RestController
public class VolunteerController {

  @Autowired VolunteerService vs;

  @RequestMapping(value = "/volunteers")
  List<Volunteer> getVolunteers() {
    return vs.findAll();
  }
}

Volunteer 域对象非常复杂,并且与其他各种域对象相关。

当我在浏览器中发出正确的请求时,我得到以下异常:

org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: model.Volunteer.volunteerSessions, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->model.Volunteer["sessions"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: model.Volunteer.volunteerSessions, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->model.Volunteer["sessions"])

据我所知,它正在抱怨没有会话,(我认为)可以通过正确配置事务来解决。但是,我配置事务的尝试似乎不正确,但我不知道为什么。

【问题讨论】:

  • 您是否将volunteerSessions 实例字段映射为oneToMany,并在Volunteer 类中延迟加载。如果是这样,请启用 EAGER 加载并尝试。

标签: java spring hibernate jpa spring-transactions


【解决方案1】:

您指出没有可用会话的错误消息前面是:

Could not write JSON

这告诉您在尝试将对象序列化为 JSON 时访问了代理。这发生在您的 RestController 中,这确实不是事务性的,因此没有会​​话绑定,并且这就是延迟加载失败的原因。

可能的解决方案是:

  • 从VolunteerServiceImpl 返回时,添加 DAO 层并将 DB 对象转换为简单的 java POJO
  • 您可以尝试将您的控制器注释为@Transactional
  • 如果您知道您将始终需要“自愿”数据,因为您在 JSON 响应中返回,您可能需要考虑关闭延迟加载

【讨论】:

  • 优秀的答案 - 一个很好的清晰解释,非常感谢!
【解决方案2】:

您在 bean 序列化为 JSON 之前关闭了会话。这是在每次事务操作后关闭会话的 CRUD 应用程序的典型行为。因为它发生在服务层中,所以您无法访问为延迟初始化而设计的对象,因此不会被 Hibernate 加载。解决方案是排除这些惰性属性,或者如果您需要它们来初始化,则在序列化期间保持会话打开。您可以通过实现Open Session In View 模式来实现这一点。

典型(web-)应用程序中的一个常见问题是视图的渲染,在动作的主要逻辑完成之后,因此,Hibernate Session 已经关闭并且数据库事务已经结束。如果您访问已在 JSP(或任何其他视图呈现机制)内的 Session 中加载的分离对象,您可能会遇到未加载的集合或未初始化的代理。您得到的异常是: LazyInitializationException: Session has been closed(或非常相似的消息)。当然,这是意料之中的事,毕竟你已经结束了你的工作单元。

第一个解决方案是打开另一个工作单元来呈现视图。这很容易做到,但通常不是正确的方法。为完成的操作渲染视图应该在第一个工作单元内,而不是单独的工作单元内。在两层系统中,通过 Session 执行操作、访问数据以及在同一虚拟机中渲染视图的解决方案是保持 Session 处于打开状态,直到视图被渲染。

【讨论】:

    猜你喜欢
    • 2015-05-04
    • 2011-08-06
    • 2020-09-06
    • 2018-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-13
    • 1970-01-01
    相关资源
    最近更新 更多