2

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

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • 1
    Possible duplicate of [Spring @Transaction method call by the method within the same class, does not work?](http://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo) – gstackoverflow Nov 09 '15 at 13:01

1 Answers1

4

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.

Marek Raki
  • 3,056
  • 3
  • 27
  • 50
  • I believe that it is very bad if my business bean depends on applicationContext. – gstackoverflow Nov 08 '15 at 18:55
  • It is just an example how to prove what is the cause of the problem. How do you implement this is up to you. Some people create BeanPostPorcessors, some create utility classes to find a proxy, you may find many of working solutions here on SO. – Marek Raki Nov 08 '15 at 21:15
  • can you show example of **utility classes to find a proxy** ? – gstackoverflow Nov 08 '15 at 21:45
  • Same way like here but without having reference to ApplicationContext inside of bean. – Marek Raki Nov 08 '15 at 22:21
  • You may use also try AopContext.currentProxy() but then you need to expose proxies in Spring which is also bad in some way: "Spring's AOP framework does not expose proxies by default, as there is a performance cost in doing so." http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/aop/framework/AopContext.html – Marek Raki Nov 08 '15 at 22:29
  • what about propagation **MANDATORY** ? – gstackoverflow Nov 08 '15 at 22:49
  • MANDATORY does not change anything because TransactionalAspect responsible for creating a new transaction will not be invoked around your method so @Transactional annotation will not be processed. I have updated the answer. – Marek Raki Nov 09 '15 at 09:39
  • agreed about propagation. In your answer update you just created wrapper above ApplicationContext – gstackoverflow Nov 09 '15 at 10:27
  • bean depends on applicatioContext transiviely – gstackoverflow Nov 09 '15 at 11:41
  • Beans always depend on ApplicatioContext. But if you do not like this approach try to implement BeanPostPorcessor that inject a proxy for you. This is probably technically the best solution. Unfortunately a bit more codding. ... or use AspectJ. – Marek Raki Nov 09 '15 at 13:17