Spring 带事务的数据 JPA

Spring Data JPA with Transactions

我正在开发一个使用 Spring Data JPA with Transactions 的应用程序。尽管我使用的 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包中也有一个服务接口:

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"])

据我所知,这是在抱怨没有会话,(我认为)可以通过正确配置事务来解决这个问题。但是,我尝试配置交易似乎不正确,但我无法弄清楚为什么。

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

Could not write JSON

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

可能的解决方案是:

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

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

A common issue in a typical (web-)application is the rendering of the view, after the main logic of the action has been completed, and therefore, the Hibernate Session has already been closed and the database transaction has ended. If you access detached objects that have been loaded in the Session inside your JSP (or any other view rendering mechanism), you might hit an unloaded collection or a proxy that isn't initialized. The exception you get is: LazyInitializationException: Session has been closed (or a very similar message). Of course, this is to be expected, after all you already ended your unit of work.

A first solution would be to open another unit of work for rendering the view. This can easily be done but is usually not the right approach. Rendering the view for a completed action is supposed to be inside the first unit of work, not a separate one. The solution, in two-tiered systems, with the action execution, data access through the Session, and the rendering of the view all in the same virtual machine, is to keep the Session open until the view has been rendered.