将类标记为@Transactional

时间:2019-02-26 21:01:59

标签: spring hibernate spring-transactions linkageerror

将一个类标记为@Transactional(见下文)后,当我仍然在用户登录时重新加载我的应用程序时,我收到关于“尝试重复类定义”的LinkageErrors。

错误:

org.springframework.security.authentication.InternalAuthenticationServiceException: java.lang.LinkageError-->loader 'app' (instance of jdk.internal.loader.ClassLoaders$AppClassLoader) attempted duplicate class definition for [redacted].AdminUserDao$$FastClassBySpringCGLIB$$2ffa020e.
        at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:126)

其中adminUserDao是我们在自定义UserDetailsS​​ervice中用于在身份验证期间加载用户的岛。

原始AdminUserDao.java

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class AdminUserDao {
    private final SessionFactory sessionFactory;

    @Autowired
    public AdminUserDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public AdminUser getUserByEmailAddress(String emailAddress) {
        String hql = "from " + AdminUser.class.getName()
                + " admin_user where admin_user.emailAddress = :emailAddress";
        Session session = null;
        try {
          session = sessionFactory.openSession();
          return session
                .createQuery(hql, AdminUser.class)
                .setParameter("emailAddress", emailAddress)
                .uniqueResult();
       } finally {
          if (session != null && session.isOpen()) {
            session.close();
          }
    }
}

Transactional AdminUserDao.java

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Repository
public class AdminUserDao {
    private final SessionFactory sessionFactory;

    @Autowired
    public AdminUserDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public AdminUser getUserByEmailAddress(String emailAddress) {
        String hql = "from " + AdminUser.class.getName()
                + " admin_user where admin_user.emailAddress = :emailAddress";
        return sessionFactory.getCurrentSession()
                .createQuery(hql, AdminUser.class)
                .setParameter("emailAddress", emailAddress)
                .uniqueResult();
    }
}

Transaction Manager Bean

@Bean
public HibernateTransactionManager transactionManager(@Qualifier("sessionFactory") SessionFactory sessionFactory) {
    return new HibernateTransactionManager(sessionFactory);
}

复制步骤:

  1. 登录
  2. 重新启动应用程序(不注销)
  3. 重新加载页面(用户仍应登录)
  4. 退出
  5. 尝试再次使用同一用户登录。当Spring尝试对用户进行身份验证时会触发该错误。

我已经看到Stack Overflow上的其他人在错误地定义自定义ClassLoader(here)时遇到了此问题;但是,我没有使用自定义的ClassLoader。

可能相关的依赖版本:

  • Java:11
  • Spring Boot:2.1.1.RELEASE
  • 春季:5.1.3。发布
  • 春季安全性:5.1.4。发布

1 个答案:

答案 0 :(得分:0)

上面给出的链接:https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch08s06.htmlhttps://stackoverflow.com/users/1567452/jwilner 给出正确的提示(thx;))

@Transactional批注意味着Spring正在为您生成一个代理类,该类将您的方法包装为:

GeneratedProxy {

void proxiedMethod(){
   try{ 
     tx.begin();

     yourClass.yourMethod(); 

     tx.commit();
   }
   .. plus usual rollback and error stuff .. 
}
}

现在spring有两个选项(请参见https://botproxy.net/docs/how-to/mismatched-proxy-types-jdk-vs-cglib-when-using-enablecaching-with-custom-aop-advice/):

如果您的代码实现了一个接口,他将使用JDK代理并公开该接口的所有方法:

(有关JDK代理示例,请参见例如https://www.byteslounge.com/tutorials/jdk-dynamic-proxies

如果您的代码未实现任何接口,他将创建一个CGLIB代理 (实际上是猜测要公开的方法)

最后,如果他生成一个CGLIB代理,他将创建一个运行时类,例如 AuthcUserService $$ EnhancerBySpringCGLIB $$ 5c9dcb68

他将创建一个com.sun.proxy。$ Proxy1631之类的JDK代理

这两个将在setter(生成的setter)方法中转换为正确的类:

在第一种情况下,您可以在父类中使用该类本身 在第二种情况下,您应该使用接口,否则会得到 org.springframework.beans.factory.BeanNotOfRequiredTypeException

就您而言,您也许可以摆脱@Transactional, 就我而言,我无法摆脱@Transactional,因为我正在Wildfly中跨多个数据库进行真正的事务。

总结一下,这是在Java 13中发生的,可能是由于jee和Reflection的变化所致,实际上在jdk1.8中,下面的代码完全没有意识到这些机制。