I've got this web service that basically queries the database and returns all persisted entities. For testing purposes, I've created a TestDataManager that persists 2 example entities after Spring context is loaded (BTW, I'm using JAX-WS, Spring, Hibernate and HSQLDB).
My TestDataManager looks like this:
#Component
public class TestDataManager {
#Resource
private SessionFactory sf;
#PostConstruct
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void insertTestData(){
sf.openSession();
sf.openSession().beginTransaction();
sf.openSession().persist(new Site("site one"));
sf.openSession().persist(new Site("site two"));
sf.openSession().flush();
}
}
My JAX-WS endpoint looks like this:
#WebService
public class SmartBrickEndpoint {
#Resource
private WebServiceContext context;
public Set<Site> getSitesForUser(String user){
return getSiteService().findByUser(new User(user));
}
private ISiteService getSiteService(){
ServletContext servletContext = (ServletContext) context.getMessageContext().get("javax.xml.ws.servlet.context");
return (ISiteService) BeanRetriever.getBean(servletContext, ISiteService.class);
}
}
This my Service class:
#Component
#Transactional(readOnly = true)
public class SiteService implements ISiteService {
#Resource
private ISiteDao siteDao;
#Override
public Set<Site> findByUser(User user) {
return siteDao.findByUser(user);
}
}
This is my DAO:
#Component
#Transactional(readOnly = true)
public class SiteDao implements ISiteDao {
#Resource
private SessionFactory sessionFactory;
#Override
public Set<Site> findByUser(User user) {
Set<Site> sites = new LinkedHashSet<Site>(sessionFactory.getCurrentSession().createCriteria(Site.class).list());
return sites;
}
}
This is my applicationContext.xml:
<context:annotation-config />
<context:component-scan base-package="br.unirio.wsimxp.dao"/>
<context:component-scan base-package="br.unirio.wsimxp.service"/>
<context:component-scan base-package="br.unirio.wsimxp.spring"/>
<bean id="applicationDS" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:file:sites"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="applicationDS" />
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.connection.release_mode">on_close</prop>
<!--<prop key="hibernate.current_session_context_class">thread</prop>-->
<prop key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
<prop key="hibernate.hbm2ddl.auto">create-drop</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
This is what's going on now:
when the app is deployed, TestDataManager#insertTestData kicks-in (due to #PostConstruct) and persist does not raise any exception. I should have 2 entities in the DB by now.
Afterwards, I invoke the endpoint by a SOAP client, and the request goes all the way up to the DAO. The Hibernate invocation does not raise any exception, but the returned list is empty.
The odd thing is, in TestDataManager, if I switch from sf.openSession() to sf.getCurrentSession(), I get an error message: "No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here".
What I am doing wrong here? Why is the query "not seeing" the persisted entities? Why do I need to invoke sf.openSession() on TestDataManager although it's annotated with #Transactional?
I have done some tests with hibernate.current_session_context_class=thread in application.xml, but then I just switch problems in each class. I'd like not needing to manually invoke sf.openSession() and leave that for Hibernate to take care.
Thanks a lot for any help!
I think you need to commit the transaction on insertTestData:
#PostConstruct
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void insertTestData(){
Session session = sf.openSession();
session.persist(new Site("site one"));
session.persist(new Site("site two"));
session.flush();
session.close();
}
(I use hibernate in jpa mode)
I think your transactional annotations are not intercepted properly. Have you specified the HibernateVendorAdapter? In jpa+hibernate the integration is not fully setuped without it! Most likely you are missing this declaration.
After you should be able to autowire directly the Session instead of the Factory.
As a side note. If you use opensession in your code at least call it only once and keep the session in a variable. Else you are always opening a new one on each call i believe.
#PostConstruct
public void insertTestData(){
Obejct o = new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
//Your code here
}
});
}
source: http://forum.springsource.org/showthread.php?58337-No-transaction-in-transactional-service-called-from-PostConstruct&p=194863#post194863
Related
I have an old project that I'm trying to upgrade to spring 4 / hibernate 4.
We use this approach to configuring our transactions.
That is to say, in XML we define a transaction manager like:
<bean id="abstractTransactionProxy"
abstract="true"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager" ref="hibernateTransactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="create*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
and
<bean id="MySessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="configLocation" value="WEB-INF/hibernate.cfg.xml" />
</bean>
and then each of our service beans are defined like:
<bean id="customerService" parent="abstractTransactionProxy">
<property name="target" ref="customerTarget"/>
<property name="personDAO" ref="personDAO" />
</bean>
and the DAOs are defined like:
<alias name="personDaoImpl" alias="personDAO"/>
<bean id="personDaoImpl"
class="com.foo.bar.hibernate.PersonDaoImpl">
<property name="sessionFactory" ref="MySessionFactory" />
</bean>
PersonDaoImpl (and all the other DAOs) have many methods that look like this (all of the DAOs extend HibernateDaoSupport):
public List<Person> getPersonByCriteria(final String criteria) {
List<Person> results =
(List<Person>) this.getHibernateTemplate().executeFind(new HibernateCallback<List<Person>>(){
public List<Person> doInHibernate(Session session) throws HibernateException, SQLException {
List<Person> results = (List<Person>) session.createQuery(MYSQLHERE).setString("criteria", criteria).list();
return results;
}
});
return results;
}
Now, the problem I Face at this point is getting rid of our dependency on extending our DAOs from HibernateDaoSupport, and by extension, on using it's HibernateTemplate helper object.
Here's the steps I've taken:
Simply remove them from the DAO methods - don't extend HibernateDaoSupport, remove the HibernateTemplate callback. This looks like:
public List<Person> getPersonByCriteria(final String criteria) {
List<Person> results = (List<Person>) getSessionFactory().getCurrentSession().createQuery(MYSQLHERE).setString("criteria", criteria).list();
return results;
}
Of course this gives me compile-time errors because 'getSessionFactory' was part of the base class 'HibernateDaoSupport' which I just removed.
So I implemented a 'MyHibernateDaoSupport' Class, and had my DAO extend that instead. It looks like:
public class MyHibernateDaoSupport {
private SessionFactory sessionFactory;
protected Session session;
protected static Logger logger = Logger.getLogger(MyHibernateDaoSupport.class);
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
session = this.sessionFactory.getCurrentSession();
}
public SessionFactory getSessionFactory() {
return this.sessionFactory;
}
public Session getHibernateTemplate() {
session = this.sessionFactory.getCurrentSession();
return session;
}
public MyHibernateDaoSupport() {
}
}
Now I can build, deploy and run and I hit this error message:
No session available in this thread
How can I make this work?
In simple terms, what was the 'hibernateCallback()', and it's anoymous inner class actually doing?
How can I make my project's transactions work as before, without using HibernateDaoSupport and HibernateTemplate classes?
I've tried tagging my 'CustomerService' (the business logic object that calls 'PersonDao') with #transactional but (A) I see the same error regardless and (B) I'd really prefer to be able to do this programmatically, to reduce the scale of changes to the way the code works now. I also tried putting the transactions at the DAO level instead, and met the same issue.
Apologies if that was way too much detail for the Stack overflow format.
You need to inject the SessionFactory into your DAO classes:
#Repository
public class PersonDAOImpl implements PersonDAO {
#Autowired
private SessionFactory sessionFactory;
public List<Person> getPersonByCriteria(final String criteria) {
List<Person> results = (List<Person>) getSessionFactory().getCurrentSession().createQuery(MYSQLHERE).setString("criteria", criteria).list();
return results;
}
}
Add #Transactional to your service methods
Add HibernateTransactionManager
Instruct Spring to use declarative transactions:
<tx:annotation-driven transaction-manager="transactionManager"/>
Make sure you use LocalSessionFactoryBean for creating the SessionFactory
Here's a detailed step-by-step tutorial for configuring Spring and Hibernate 4
'm implementing functional tests. To avoid data corruption I need to rollback every test after execution. This is pretty trivial task - just mark rollback=true in test. But if I start another module with another spring context and first module interacts with it somehow (f.e. sends jms message and second one saves it to the same DB) then rollback is not working for second context. How to rollback second module too?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:/my-service-context.xml")
#Transactional(value = "myTransactionManager")
public class ParserServiceTest {
protected Logger l = LoggerFactory.getLogger(getClass());
#Autowired
#Qualifier(value = "earMessageDaoBean")
EARMessageDao dao;
#Autowired
#Qualifier(value = "myParserService")
ParserService service;
#Test
#Rollback(value = true)
public void testExecute() throws Exception {
service.execute("fff", "ttt");
EARMessage byId = dao.findById(1L);
assertNotNull(byId);
assertEquals("fff", byId.getFrom());
assertEquals("ttt", byId.getTo());
l.info("{}", byId);
}
}
if I take a look into db i will see no data and it's good
but if I will add another module
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:/my-service-context.xml")
#Transactional(value = "myTransactionManager")
public class ParserServiceTest {
protected Logger l = LoggerFactory.getLogger(getClass());
ClassPathXmlApplicationContext context2;
#Before
public void setUp() throws Exception {
context2 = new ClassPathXmlApplicationContext(
"my-service-context2.xml"
);
}
#Autowired
#Qualifier(value = "earMessageDaoBean")
EARMessageDao dao;
#Autowired
#Qualifier(value = "myParserService")
ParserService service;
#Test
#Rollback(value = true)
public void testExecute() throws Exception {
service.execute("fff", "ttt");
EARMessage byId = dao.findById(1L);
assertNotNull(byId);
assertEquals("fff", byId.getFrom());
assertEquals("ttt", byId.getTo());
l.info("{}", byId);
// theoretically there could be interaction with service2 via JMS
MyParserService2 service2 = (MyParserService2) context2.getBean("myParserService2");
service2.execute("FFF", "TTT");
}
}
data added by servie2 will not be rollbacked.
I can get EntityManager TransactionManager within test context but I can't roll them back since transactions are commited already.
I can add some marker into second module and mark it in test somehow but have now idea yet what to do
UPDATE
here are services and context configs for better understanding:
#Service(value = "myParserService")
#Transactional(value = "myTransactionManager")
public class ParserService {
protected Logger l = LoggerFactory.getLogger(getClass());
#Autowired
#Qualifier(value = "earMessageDaoBean")
EARMessageDao dao;
public void execute(String from, String to) {
l.info("-------started service 1---------");
EARMessage message = new EARMessage();
message.setFrom(from);
message.setTo(to);
message.setProcessingDate(new DateTime());
dao.persist(message);
l.info("-------ended service 1---------");
}
}
#Service(value = "myParserService2")
#Transactional(value = "myTransactionManager")
public class MyParserService2 {
protected Logger l = LoggerFactory.getLogger(getClass());
#Autowired
#Qualifier(value = "earMessageDaoBean")
EARMessageDao dao;
public void execute(String from, String to) {
l.info("-------started service 2---------");
EARMessage message = new EARMessage();
message.setFrom("666" + from);
message.setTo("666" + to);
message.setProcessingDate(new DateTime());
dao.persist(message);
l.info("-------ended service 2---------");
}
}
my-service-context.xml
<context:annotation-config/>
<context:component-scan base-package="com.dimas.tutorial.hibernate.simple"/>
<import resource="classpath:/my-service-config.xml"/>
<import resource="classpath:/my-data-source.xml"/>
<import resource="classpath:/my-entity-manager.xml"/>
<jdbc:initialize-database data-source="${dataSource.name}">
<jdbc:script location="classpath:/sql/my-schema.sql"/>
</jdbc:initialize-database>
my-service-context2.xml
<context:annotation-config/>
<context:component-scan base-package="com.dimas.tutorial.hibernate.simple"/>
<import resource="classpath:/my-service-config.xml"/>
<import resource="classpath:/my-data-source.xml"/>
<import resource="classpath:/my-entity-manager.xml"/>
<jdbc:initialize-database data-source="${dataSource.name}">
<jdbc:script location="classpath:/sql/my-schema.sql"/>
</jdbc:initialize-database>
UPDATE2: added entity manager config
<bean id="valettaEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="MyPersistence"/>
<property name="packagesToScan" value="com.dimas.tutorial.hibernate.simple.domain"/>
<property name="dataSource" ref="${dataSource.name}"/>
<property name="jpaVendorAdapter" ref="hibernateVendor"/>
<property name="jpaPropertyMap" ref="jpaPropertyMap"/>
</bean>
<util:map id="jpaPropertyMap">
<entry key="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<entry key="hibernate.default_schema" value="public"/>
<entry key="hibernate.show_sql" value="false"/>
<entry key="hibernate.format_sql" value="false"/>
<entry key="hibernate.cache.use_second_level_cache" value="false"/>
<entry key="hibernate.max_fetch_depth" value="3"/>
<entry key="hibernate.jdbc.fetch_size" value="50"/>
<entry key="hibernate.jdbc.batch_size" value="10"/>
</util:map>
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="false"/>
</bean>
<bean id="myTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="valettaEntityManagerFactory"/>
</bean>
<bean id="valettaTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="myTransactionManager"/>
</bean>
<context:annotation-config/>
<tx:annotation-driven transaction-manager="myTransactionManager"/>
Use propagation required for your service.
eg .
First Transaction:
#Transactional(value = "myTransactionManager")
public class ParserServiceTest {
Join by require Transaction:
#Transactional(value = "myTransactionManager", propagation=Propagation.REQUIRED)
public class ParserService {
Join by require Transaction:
#Transactional(value = "myTransactionManager", propagation=Propagation.REQUIRED)
public class MyParserService2 {
PROPAGATION_REQUIRED:
Spring REQUIRED behavior means that the same transaction will be used if there is an already opened transaction in the current bean method execution context. Create a new one if none exists.
In short this means that if an inner(2nd Transaction) method causes a transaction to rollback, the outer(1st Transaction) method will fail to commit and will also rollback the transaction.
I am new to JPA, Hibernate as well as Spring. Currently I am creating a spring web service which work with a database with a large number of tables. To access those tables I have created separate class annotating #Entity. Then I created a generic DAO class as all my entities need similar operations.
#Transactional
public class GenericJpaDao<T, ID extends Serializable> {
private Class<T> persistentClass;
private EntityManager entityManager;
public GenericJpaDao(Class<T> persistentClass) {
this.persistentClass = persistentClass;
}
protected EntityManager getEntityManager() {
return entityManager;
}
#PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public Class<T> getPersistentClass() {
return persistentClass;
}
#Transactional(readOnly = true)
public T findById(ID id) {
T entity = (T) getEntityManager().find(getPersistentClass(), id);
return entity;
}
#SuppressWarnings("unchecked")
#Transactional(readOnly = true)
public List<T> findAll() {
return getEntityManager().createQuery("select x from " + getPersistentClass().getSimpleName() + " x").getResultList();
}
public T save(T entity) {
getEntityManager().persist(entity);
return entity;
}
public T update(T entity) {
T mergedEntity = getEntityManager().merge(entity);
return mergedEntity;
}
public void delete(T entity) {
entity = getEntityManager().merge(entity);
getEntityManager().remove(entity);
}
public void flush() {
getEntityManager().flush();
}
}
Now I tried to instantiate this GenericJpaDao in the code with relevant Entitiy Class as the persistentClass. But then I could not find a way to set the entitiyManager as I am configuring it via datasource-config.xml as
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml"></property>
<property name="persistenceUnitName" value="hibernatePersistenceUnit" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="dataSource" ref="dataSource" />
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
So according to what I understood from the samples available I need to create separate DAO classes for all my entity classes and instantiate them in spring-ws-servlet.xml.
<bean id="testDao" class="com.sample.dao.TestDao" />
<bean id="service"
class="com.sample.service.DefaultService">
<property name="testDao" ref="testDao" />
</bean>
I think this will be a problem in the long run as I need to have two separated classes for each table in the database, instaintiating them in the xml, keep track of all of them at my service class. Is there any method to overcome this or any best practice available?
You wont need to create a specific DAO for each of your classes.
But you will have to remove the constructor and change your method signatures to include the needed persistentClass (or an instance where you call getClass on).
Basically you need to remove the persistentClass property and change the methods to use the class dynamically from the generic parameter of type T or Class.
That way you have ONE spring managed DAO which is able to handle all of your entities.
My entityManager persist() gets id from sequence and puts it to my Image object but the Image object itself is not showing up in the database. EntityManager.flush() gives an error so I can't commit this way. Here is my code.
#Repository
public class ImageDaoImpl extends BaseDao implements ImageDao {
#PersistenceContext
protected EntityManager entityManager;
#Override
#Transactional
public void create(Image image) {
JpaTemplate jpaTemplate = getJpaTemplate(entityManager);
jpaTemplate.persist(image);
}
#Repository
public class BaseDao {
private JpaTemplate jpaTemplate;
public JpaTemplate getJpaTemplate(EntityManager entityManager){
if(jpaTemplate == null)
jpaTemplate = new JpaTemplate(entityManager);
return jpaTemplate;
}
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.PostgreSQLDialect" />
</bean>
</property>
<property name="persistenceUnitName" value="sample"></property>
</bean>
<!-- DataSource Setup -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost:5432/imageCapture" />
<property name="username" value="myusername" />
<property name="password" value="mypassword" />
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
This generally happens when Transaction in not applied.. I doubt #Transactional interceptor is not intercepting properly.
persist() means "add object to list of managed entries". To save object to data base you must call flush() method. But remember you must call in inside the transaction.
//Edit:
Example save method.
public void save(T t){
// begin transaction
em.getTransaction().begin();
if (!em.contains(t)) {
// persist object - add to entity manager
em.persist(t);
// flush em - save to DB
em.flush();
}
// commit transaction at all
em.getTransaction().commit();
}
This is not the best that you can make, but good enough.
Check your server logs. Are you creating new EntityManger? and have not begun the transaction. I think, Where you have begun that is another EntityManager object.
Check EntityManager object is not getting created twice. In my case I was initialize EntityManager object in one method and mistakenly calling that method twice during opening transaction and persisting data.
Incorrect code:
EntityTrasaction transaction = getEntityManager().getTransaction().begin();
getEntityManager().persist(customer); // This line is calling another object of EntityManager
transaction.commit();
Correct code:
EntityManager em = getEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(customer);
transaction.commit();
public EntityManager getEntityManager(){
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("punit");
return entityManagerFactory.createEntityManager();
}
Check your mvc-dispatcher-servlet.xml . Here in <context:component-scan base-package="pass"/> pass should equal to package where your controllers are
I faced this problem in when running test cases with SpringJUnit4ClassRunner
I solved it by
wrapping the test function with
#Autowired
private PlatformTransactionManager transactionManager;
// in your test funciton
// Declare a transaction programmatically to be able to rollback.
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
try {
// here test of Dao goes
} finally {
transactionManager.rollback(transaction);
}
hope this helps you
I have a problem with transactions in that annotating a service that calls a DAO with #Transactional throws an exception stating that the Session is not open. The only way I can get it working is by annotating the DAO with #Transactional. What on earth can be happening?
This is what I'd like to do but doesn't work:
class CustomerService {
private CustomerDao dao;
#Transactional
public void foo() {
int customerId = dao.getCustomer("fred");
}
}
class CustomerDao {
private HibernateTemplate hibernateTemplate;
public int getCustomer(String name) {
String sql = "SELECT {m.*} from Customers {m} where name=:name";
Query qry = getSession().createSQLQuery(sql).addEntity("m", Customer.class);
qry.setParameter("name", name);
qry.setCacheable(false);
List<Customer> list = qry.list();
return list.iterator().next().getId();
}
private Session getSession() {
return hibernateTemplate.getSessionFactory().getCurrentSession();
}
}
This is what I'm doing instead but would rather not have to:
class CustomerService {
private CustomerDao dao;
public Customer(CustomerDao dao) {
this.dao = dao;
}
public void foo() {
int customerId = dao.getCustomer("fred");
}
}
class CustomerDao {
private HibernateTemplate hibernateTemplate;
#Transactional
public int getCustomer(String name) {
String sql = "SELECT {m.*} from Customers {m} where name=:name";
Query qry = getSession().createSQLQuery(sql).addEntity("m", Customer.class);
qry.setParameter("name", name);
qry.setCacheable(false);
List<Customer> list = qry.list();
return list.iterator().next().getId();
}
private Session getSession() {
return hibernateTemplate.getSessionFactory().getCurrentSession();
}
}
The problem seems to be caused by the CustomerService being instantiated inside the constructor of a wrapper class, where the wrapper is declared in the Spring xml context file:
class AllServices {
private final CustomerService customerService;
private final OrderService orderService;
#Autowired
public AllServices(CustomerDao customerDao, OrderDao orderDao) {
this.customerService = new CustomerService(customerDao);
this.orderService = new OrderService(orderDao);
}
public CustomerService getCustomerService() {
return this.customerService;
}
public OrderService getOrderService() {
return this.orderService;
}
}
The spring file looks like this:
<context:annotation-config />
<import resource="classpath:db-spring-conf.xml"/>
<bean id="allServices" class="myPackage.AllServices" />
and the db-spring-conf:
<bean id="editorDatasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${versioning.db}" />
<property name="username" value="${versioning.user}" />
<property name="password" value="${versioning.pass}" />
</bean>
<tx:annotation-driven transaction-manager="editorTransactionManager"/>
<bean id="editorSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="editorDatasource"/>
<property name="exposeTransactionAwareSessionFactory">
<value>true</value>
</property>
<property name="annotatedClasses">
<list>
<value>myPackage.Order</value>
</list>
</property>
<property name="mappingResources">
<list>
<value>mappings/customer.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">validate</prop>
<!-- Enable Query Cache -->
<prop key="hibernate.cache.use_query_cache">false</prop>
<!-- Enable 2nd Level Cache -->
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.connection.autocommit">false</prop>
<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate3.SpringSessionContext</prop>
</props>
</property>
</bean>
<bean id="editorHibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory" ref="editorSessionFactory"/>
</bean>
<bean id="editorTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="editorSessionFactory" />
</bean>
<!-- DAOs -->
<bean id="customerDao" class="myPackage.CustomerHibernateDao" />
<bean id="orderDao" class="myPackage.OrderHibernateDao" />
I've now moved the instantiation of CustomerService up to the Spring config file and everything works a treat. Do all classes using #Transactional have to be in the context file? Also in order to make it work I had to create an interface for CustomerService to prevent an exception whilst loading the context file - Could not generate CGLIB subclass of class
So, you identified the cause of problem - Spring's #Transactional support is an aspect, and aspects in Spring are applied only to the components managed by the Spring contrainer (though it can be changed, but it's an advanced feature for complex cases).
If you don't like declaring services in XML, you may take a look at other options to delcare Spring-managed components:
Classpath scanning
Java-based configuration (since Spring 3.x)
Regarding the problem with CGLIB proxies see 7.6 Proxying mechanisms - probably you don't have CGLIB implementation in the classpath.