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
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.