0

我有一个会话范围类,其中包含一个用于管理用户统计信息的对象。当用户(通过 SSO)登录时,应用程序范围的方法会检查表中的活动会话 - 如果发现任何会话,则使用表中的会话 ID 使会话无效。

在会话范围类中的 userStats 表中添加了一行:

/**
     * get all the info needed for collecting user stats, add userStats to the users session and save to userStats table
     * this happens after session is created
     * @param request
     */
    private void createUserStats(HttpServletRequest request){
        if (!this.sessionExists) {
            this.userStats = new UserStats(this.user, request.getSession(true)
                    .getId(), System.getProperty("atcots_host_name"));
            request.getSession().setAttribute("userstats", this.userStats);
            Events.instance().raiseEvent("userBoundToSession", this.userStats);
            this.sessionExists = true;
            log.info("user " + this.user + " is now logged on");

            // add this to the db
            try {
                this.saveUserStatsToDb();
            } catch (Exception e) {
                log.error("could not save " + this.userStats.getId().getPeoplesoftId() + " information to db");
                e.printStackTrace();
            }           

        }

    }

当用户的会话被销毁时,该行会更新一个注销时间。

由于我无法解释或重复的原因,过去 2 周内有 2 个用户登录并锁定了该行。当这种情况发生时,该用户的任何数据库调用都不再可能,并且该应用程序对于该用户实际上是不可用的。

 [org.hibernate.util.JDBCExceptionReporter] (http-127.0.0.1-8180-3) SQL Error: 0, SQLState: null
2012-07-26 18:45:53,427 ERROR [org.hibernate.util.JDBCExceptionReporter] (http-127.0.0.1-8180-3) Transaction is not active: tx=TransactionImple < ac, BasicAction: -75805e7d:3300:5011c807:6a status: ActionStatus.ABORT_ONLY >; - nested throwable: (javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: -75805e7d:3300:5011c807:6a status: ActionStatus.ABORT_ONLY >)

这些统计数据的收集很重要,但不是生死攸关,如果我无法获得我想放弃并继续前进的信息。但这并没有发生。发生的事情是 entityManager 将事务标记为回滚,之后的任何数据库调用都会返回上述错误。我最初将用户统计信息保存在应用程序范围内 - 因此,当行锁定时,它锁定了整个应用程序的 entityManager(这并没有很好地解决)。当我将该方法移动到会话范围时,它只会锁定有问题的用户。

I tried setting the entityManger to a lesser scope(I tried EVENT and METHOD):
        ((EntityManager) Component.getInstance("entityManager", ScopeType.EVENT)).persist(this.userStats);
        ((EntityManager) Component.getInstance("entityManager", ScopeType.EVENT)).flush();

这根本不会进行 db 调用。我试过手动回滚交易,但没有任何乐趣。

当我锁定表中包含在对话范围级别使用的数据的行时,结果几乎没有灾难性 - 没有数据被保存但它恢复了。

预计到达时间:

我尝试引发一个 AsynchronousEvent - 它在本地工作,但部署到我们的远程测试服务器 - 这很奇怪 - 我得到:

    DEBUG [org.quartz.core.JobRunShell] (qtz_Worker-1) Calling execute on job DEFAULT.2d0badb3:139030aec6e:-7f34
    INFO  [com.mypkg.myapp.criteria.SessionCriteria] (qtz_Worker-1) observing predestroy for seam
    DEBUG [com.mypkg.myapp.criteria.SessionCriteria] (qtz_Worker-1) destroy destroy destroy sessionCriteria
    ERROR [org.jboss.seam.async.AsynchronousExceptionHandler] (qtz_Worker-1) Exeception thrown whilst executing asynchronous call
    java.lang.IllegalArgumentException: attempt to create create event with null entity
            at org.hibernate.event.PersistEvent.<init>(PersistEvent.java:45)
            at org.hibernate.event.PersistEvent.<init>(PersistEvent.java:38)
            at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619)
            at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623)
...

奇怪的是,它似乎正在通过 Quartz 处理程序。

再次预计到达时间:

所以,并不奇怪,我将 Quartz 设置为异步处理程序——我认为它只是用于调度作业。此外,异步方法无法访问会话上下文,因此我必须向我的观察方法添加一个参数才能真正拥有一个要持久的对象:

@Observer("saveUserStatsEvent")
@Transactional
public void saveUserStatsToDb(UserStats userstats) throws Exception {
    if(userstats != null){
        log.debug("persisting userstats to db");
        this.getEntityManager().persist(userstats);
        this.getEntityManager().flush();
    }
}

我该如何从中恢复?

4

1 回答 1

0

首先,在指定范围内Component.getInstance()并没有在指定范围内创建组件的结果。EntityManager实例始终存在于对话上下文中(无论是临时的还是长期运行的)。scope参数的getInstance()唯一目的是提示组件应该位于的上下文,以避免在所有上下文中进行昂贵的搜索(如果您没有指定上下文或指定错误的上下文,就会发生这种情况)。

由于先前的错误,事务被标记为回滚。如果 entityManager 无论如何都要提交,则它不会是事务性的(事务实际上保证如果发生错误,则不会保留任何内容)。如果要将登录事务与统计信息收集隔离开来,最简单的解决方案是saveUserStatsToDb在异步事件中执行该方法(事务绑定到线程,因此使用不同的线程可以保证事件在单独的事务中处理)。

像这样的东西:

@Observer("saveUserStatsEvent")
@Transactional
public void saveUserStatsToDb(UserStats stats) {
    ((EntityManager)Component.getInstance("entityManager")).persist(stats);
}

在你的createUserStats方法中:

Events.instance().raiseAsynchronousEvent("saveUserStatsEvent", this.userStats);

然而,这只是通过将交易一分为二来规避问题。您真正要解决的是问题根源的锁定条件。

于 2012-08-03T17:32:39.457 回答