Java Spring-使用OpenSessionInViewFilter为新线程提供Hibernate会话

Java Spring-使用OpenSessionInViewFilter为新线程提供Hibernate会话,java,multithreading,spring,hibernate,spring-mvc,Java,Multithreading,Spring,Hibernate,Spring Mvc,我有一个基于Spring4.0和Hibernate4的项目,特别是SpringMVC Hibernate的会话是由OpenSessionInViewFilter为控制器中的每个请求创建的 现在,我正在尝试在控制器的方法中启动一个新线程(以执行一个长过程)。显然,OpenSessionInViewFilter正在请求完成后关闭会话。然后,当我的线程启动时,不再有会话,我得到以下错误: org.hibernate.hibernateeexception:未找到当前线程的会话 下面是类的基本结构,从控

我有一个基于Spring4.0和Hibernate4的项目,特别是SpringMVC

Hibernate的会话是由OpenSessionInViewFilter为控制器中的每个请求创建的

现在,我正在尝试在控制器的方法中启动一个新线程(以执行一个长过程)。显然,OpenSessionInViewFilter正在请求完成后关闭会话。然后,当我的线程启动时,不再有会话,我得到以下错误:

org.hibernate.hibernateeexception:未找到当前线程的会话

下面是类的基本结构,从控制器到我的可调用组件。IReportService扩展了Callable

OBS:我曾尝试使用spring的
@Async
注释,但它仍然不起作用。我将
REQUIRES_NEW
放在服务上,试图获取一个新事务,但它甚至无法更改为嵌套

@Controller
@RequestMapping(value = "/action/report")
@Transactional(propagation = Propagation.REQUIRED)
public class ReportController {

    @Autowired
    private IReportService service;
    private final Map<Long, Future> tasks = new HashMap();

    @RequestMapping(value = "/build")
    public String build(@RequestParam Long id) {

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<StatusProcesso> future = executor.submit(service);

        tasks.put(id, future);

        return "wait-view";
    }

    @RequestMapping(value = "/check", method = RequestMethod.GET)
    public @ResponseBody Map<String, Object> check(@RequestParam Long id) {

        String status = null;

        try {
            Future foo = this.processos.get(id);
            status = foo.isDone() ? "complete" : "building";
        } catch (Exception e) {
            status = "failed";
        }

        return new ResponseBuilder()
                .add("status", status)
                .toSuccessResponse();
    }

    // Another operations...
}


@Service
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class ReportService implements IReportService {

    @Autowired
    private IReportDAO dao;

    @Override
    public Status call() {

        Status status = new Status();

        // do long operation and use DAO...

        return status;
    }
}
@控制器
@请求映射(value=“/action/report”)
@事务性(传播=传播。必需)
公共类报表控制器{
@自动连线
私人IReportService;
私有最终映射任务=新建HashMap();
@请求映射(value=“/build”)
公共字符串生成(@RequestParam Long id){
ExecutorService executor=Executors.newSingleThreadExecutor();
未来=执行人提交(服务);
任务。放置(id,未来);
返回“等待视图”;
}
@RequestMapping(value=“/check”,method=RequestMethod.GET)
public@ResponseBody映射检查(@RequestParam Long id){
字符串状态=空;
试一试{
Future foo=this.processos.get(id);
status=foo.isDone()?“完成”:“构建”;
}捕获(例外e){
status=“失败”;
}
返回新的ResponseBuilder()
.添加(“状态”,状态)
.toSuccessResponse();
}
//另一个行动。。。
}
@服务
@事务性(传播=传播。需要\u新建)
公共类ReportService实现IReportService{
@自动连线
私营机构;
@凌驾
公共状态呼叫(){
状态=新状态();
//做长时间的操作和使用DAO。。。
返回状态;
}
}

您不能在不同的线程中使用相同的hibernate会话-这不是线程安全的。因此,您不应该担心OpenSessionInViewFilter,即使它不会关闭会话,它们仍然无法从其他线程使用(并且隐式延迟加载将使其完全不可预测)

会话绑定到线程-当您尝试从另一个线程访问它时,问题不是不再有会话,而是因为会话从未出现过

您可以将
@Transactional
与服务方法一起使用,然后从其他线程中长时间运行的进程中调用这些方法

另外,要避免使用
(propagation=propagation.REQUIRES_NEW)
当您需要回滚内部事务时,它们是非常罕见的,并且您的数据库(应该是非常复杂的RDBMS)支持这种行为-这是关于复杂的事务脚本体系结构,而不是经典的POJO驱动的Spring应用程序

更新针对那些认为hibernate会话是线程安全的不相信者-引自hibernate会话java文档:

实现程序不是线程安全的。相反,每个 线程/事务应该从 会话工厂

有人发布了一个关于hibernate会话长对话的答案-是的,这个东西是存在的,但它只在单线程环境中工作(如带有事件循环的SWT应用程序)-因此您可以在桌面应用程序中使用单会话,因为它使用单线程来处理用户输入,但它永远不会在服务器环境中工作

其他一些答案警告您从不同线程访问同一会话

另外请注意,著名的警告提醒您不要在不同的线程中使用相同的会话(与会话工厂不同):

在Hibernate p。56:

在大多数Hibernate应用程序中,SessionFactory应该是 在应用程序初始化期间实例化一次。单曲 实例应该被特定进程中的所有代码使用,并且 应使用此单一SessionFactory创建任何会话。这个 SessionFactory是线程安全的,可以共享;会话是一个 单线程对象

在JPA p。74:

  • javax.persistence.EntityManagerFactory—相当于Hibernate SessionFactory。此运行时对象表示 特定的持久性单元。它是线程安全的,通常被处理 作为单例,并提供了创建 EntityManager实例

  • javax.persistence.EntityManager——相当于Hibernate会话此单线程非共享对象表示一个
    数据访问的特定工作单元


问题的原因是您需要了解,默认情况下,会话的范围与事务的范围相同

在Spring中,事务的作用域被绑定到一个线程,因此如果您打开新线程,它们将无法访问tx,从此以后,会话将不可用

请务必记住,会话可以跨事务(也称为对话)扩展,您可以通过几个tx或甚至不同的HTTP请求来保持会话。这是惊人的解释中


作为个人观点,我永远不会使用OpenSessionInViewFilter,交易需要谨慎处理,限制发送请求是像您这样不希望出现的问题的常见来源。

我投票否决您的评论,原因有两个:1)您可以使用