Search code examples
javaspringhibernatespring-transactionstransactional

HibernateException: Could not obtain transaction-synchronized Session for current thread although I invoke transactional method


I have followig service:

@Service
public class CompanyServiceImpl implements CompanyService {
    @PostConstruct
    public void init() {
        this.refreshStopJobs();
    }
    @Transactional(readOnly = true)
    @Override
    public void refreshStopJobs() {
        companyDao.getCompanysByStatus(CampaignStatus.START).forEach(this::refreshStopJob);
    }
}

and following dao:

@SuppressWarnings("unchecked")
@Override
public List<Campaign> getCompanysByStatus(CampaignStatus campaignStatus) {
    Criteria criteria = createCriteriaForGettingList(null, campaignStatus);
    return criteria.list();
}

If I run my application I see following log:

2015-11-08 17:54:04.601:WARN:oejw.WebAppContext:main: Failed startup of context o.e.j.m.p.JettyWebAppContext@48e4fba9{/,file:/D:/freelance/marcproject/src/main/webapp/,STARTING}{file:/D:/freelance/marcproject/src/main/webapp/}
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'companyServiceImpl': Invocation of init method failed; nested exception is org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
.....
Caused by: 
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
    at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:134)
    at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
    at com.terminal.dao.impl.CompanyDaoImpl.createCriteriaForGettingList(CompanyDaoImpl.java:77)
    at com.terminal.dao.impl.CompanyDaoImpl.getCompanysByStatus(CompanyDaoImpl.java:40)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:201)
    at com.sun.proxy.$Proxy80.getCompanysByStatus(Unknown Source)
    at com.terminal.service.impl.CompanyServiceImpl.refreshStopJobs(CompanyServiceImpl.java:319)
    at com.terminal.service.impl.CompanyServiceImpl.init(CompanyServiceImpl.java:313)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)

If mark dao method getCompanysByStatus as @Transactional - application starts fine. But I am not understand why. Because I had already started a transaction in service method refreshStopJobs


Solution

  • This is because you are not invoking refreshStopJobs() through Spring proxy but directly through this. You can see this clearly by observing stack trace. In the first case you wont see transactional aspect around your method invocation.

    If you move @Transactional to dao this will work but it is considered as a bad practice to have @Transactional in DAO layer.

    Another solution is to move refreshStopJobs() method to another service or inject self reference to your service.

    You may see invocation like yours works for some people. This is because they use AspectJ proxy instead of spring proxy based AOP. To get know how Spring AOP works read about "proxy pattern".

    AspectJ uses bytecode manipulation during compile time so it just adds some code around real methods and during runtime it works as good as normal object invocation.

    Example how to inject a proxy (works only when CompanyService is defined as singleton not prototype):

    @Service
    public class CompanyServiceImpl implements CompanyService, BeanNameAware {
    
    private String name;
    
      private CompanyService proxy;
    
      @Autowired
      private ApplicationContext applicationContext;
    
      @Override
      public void setBeanName(String name) {
        this.name = name;
      }
    
    @PostConstruct
      public void postConstruct() {
        proxy = (CompanyService)applicationContext.getBean(name);
        proxy.refreshStopJobs();
      }
    
    @Transactional(readOnly = true)
        @Override
        public void refreshStopJobs() {
            companyDao.getCompanysByStatus(CampaignStatus.START).forEach(this::refreshStopJob);
        }
    
    }
    

    Getting proxy statically:

    @Service
    public class SpringContext implements ApplicationContextAware {
      private static ApplicationContext context;
    
      public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.context = context;
      }
    
    public static <T> T getProxy (Class<T> proxyClass){
        return (T) context.getBean(proxyClass);
      }
    }
    

    Please keep in mind this service has to be initialized before CompanyService.