I am quite confused with Spring and Hibernate transactions. I have the following sample code.
I am wondering if
This is a correct way of retrieval or not.
Should I use getCurrentSession().beginTransaction() as well, should I use it in conjunction with #Transactional at all?
Configuration
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:2000/HiberProject" />
<property name="username" value="jack" />
<property name="password" value="jack" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
depends-on="dataSource">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.hiberproject.model" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.use_sql_comments">true</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean
class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
Service
#Service
public class SampleRecordsServiceImpl implements SampleRecordsService{
#Autowired
SampleRecordsRepository sampleRecordsRepository;
#Override
#Transactional(readOnly=true)
public Record retrieveRecord(long id){
return sampleRecordsRepository.retrieveRecord(id);
}
}
Repository
#Repository
public class SampleRecordsRepository implements SampleRecordsRepository{
#Autowired
SessionFactory sessioFactory;
#Override
public Record retrieveRecord(long id){
return (Record) sessionFactory.getCurrentSession().get(Record.class,id);
}
}
The #Transactional annotation itself defines the scope of a single database transaction. The database transaction happens inside the scope of a persistence context.
The persistence context is just a synchronizer object that tracks the state of a limited set of Java objects and makes sure that changes on those objects are eventually persisted back into the database.
For #Transactional annotation you can set propagation attribute, using Propagation you can handle your tarnsaction in different way like Propagation.REQUIRES_NEW(if you need new transaction on every request) . the default propagation is REQUIRED.
session.beginTransaction() will also either begin a new Transaction if one isn't present, or it will use an existing transaction to begin the unit of work specified.
So you should use either one of approach to manage the transaction.
Yes that's Okay to use only #Transactional annotation like this when you use Spring to manage your transaction.
No. You don't need to do that! If you use #Transactional annotation in your service, then Spring takes care of your persistence layer to manage transaction. All you need is to declare persistence layer specific transaction manager in your Spring configuration. So you do not need to manage transaction with hibernate sessions by using session.beginTransaction() together with #Transactional.
For more information please see the documentation of using #Transactional.
Related
when I try persist some data that I recovered from my csv file on my database using my job processor in spring batch this error appear in the console , for my dao i'm using hibernate
I already tried 2 methode but the same probleme !
first :
Session session = factory.getCurrentSession();
session.saveOrUpdate(p);
second :
Session session = factory.openSession();
session.beginTransaction();
session.save(p);
session.getTransaction().commit();
session.close();
data source in my spring xml config :
all my spring xml config here https://pastebin.com/RZPr1GKL
<bean name="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/yassir" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>tp.entities.Personne</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
the error :
javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.SessionImpl.checkTransactionNeeded(SessionImpl.java:3450)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1418)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1414)
...
When we use spring batch especially for tasklet in spring boot project #EnableBatchProcessing annotations will create its own transactions (Spring Batch Transactions) for its default tables.
Hence if you want to perform any insert, update or delete in your application tables(acutal table performing CRUD). We have to explicitly mention JpaTransactionManager instead of default spring batch transactions.
#Configuration
#EnableBatchProcessing
public class MyJob extends DefaultBatchConfigurer {
#Autowired
private DataSource dataSource;
#Bean
#Primary
public JpaTransactionManager jpaTransactionManager() {
final JpaTransactionManager tm = new JpaTransactionManager();
tm.setDataSource(dataSource);
return tm;
}
}
#Primary is most important one to mention else two transaction managers will be loaded. Mention jpaTransactionManager() in your job.
return stepBuilderFactory.get("stepOne")
.transactionManager(jpaTransactionManager())
.<DetailDTO,DetailDTO>chunk(100)
.reader(reportReader)
.processor(reportProcessor)
.writer(reportWriter)
.build();
Error which i got - Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress.
Click Up Vote if this is useful for you.
You configured Spring Batch to use a DataSourceTransactionManager to drive transactions. This transaction manager knows nothing about your Hibernate context. You need to use the HibernateTransactionManager to make the Hibernate Session in your writer participate in Spring Batch managed transactions.
I would also recommend to use the HibernateItemWriter instead of creating a custom writer (PersonneWriter) and manually creating the session and managing transactions.
A similar question/answer can be found here: Spring Batch JpaItemWriter vs HibernateItemWriter and why HibernateTransactionManager is needed when HibernateItemWriter is used
Hope this helps.
I am trying to implement the following: I need to add two different entities in same same transaction to database.
I have different DAO classes and Service classes for each entity.
public class InvoicesDAO {
#Autowired
protected SessionFactory sessionFactory;
public void save(Invoice object) {
Session session = SessionFactoryUtils.getSession(sessionFactory, false);
session.persist(object);
}
}
public class RequestsDAO {
#Autowired
protected SessionFactory sessionFactory;
public void save(Request object) {
Session session = SessionFactoryUtils.getSession(sessionFactory, false);
session.persist(object);
}
}
public class InvoicesService {
#Autowired
private InvoicesDAO invoicesDAO;
#Autowired
private RequestsDAO requestsDAO;
#Transactional
public void add(Invoice object) throws HibernateException {
invoicesDAO.save(object);
}
#Transactional
public void updateAndGenerate(Invoice object1, Request object2) throws HibernateException {
invoicesDAO.save(object1);
requestsDAO.save(object2);
}
}
The config:
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:/hibernate.properties" />
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${hibernate.connection.driver_class}" />
<property name="url" value="${hibernate.connection.url}" />
<property name="username" value="${hibernate.connection.username}" />
<property name="password" value="${hibernate.connection.password}" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.ejl.butler.object.data" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
<prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<context:annotation-config />
<context:component-scan base-package="com.service" />
<bean id="invoicesDao" class="com.dao.InvoicesDAO" />
<bean id="requestsDao" class="com.dao.RequestsDAO" />
Controller:
//***
/**
* Invoices access service
*/
#Autowired
private InvoicesService invoicesService;
// objects creation
invoicesService.updateAndGenerate(invoice, request);
//***
So when I am trying to call updateAndGenerate method and pass there invalid values for object2 - it fails without rolling back the object1. How can I fix it? Thank you
I dont think it is got to do with Proxies. You dont need a proxy object here. Generally you need a proxy object for instances such for a login service etc where you need a proxy object for the singleton bean definition. But, the only way it can not rollback is if your propogation level on the Transaction isnt correct.
If you use a Trasaction.REQUIRES_NEW then the dao.save wouldnt rollback and it wouldnt tie back to the outer transaction and hence wouldnt rollback.
Finally I figured out where the problem was so I will answer my own question...
According to Declarative transactions (#Transactional) doesn't work with #Repository in Spring and https://stackoverflow.com/a/3250959/705869 the order of the base-package items inside context:component-scan directive is very important. In additional, you should put only really necessary packages.
I had some duplicates inside this directive so the application context was initialized before database context. And that's why transactions were disabled inside services!
So check twice for base-package packages inside context:component-scan and remove unnecessary ones.
I'm following the tutorial here:
http://www.javacodegeeks.com/2013/05/hibernate-4-with-spring.html
to enable the "#Transactional annotation" in my Java web application but failed to make it run properly. Please advise if the JTA manager is really required, and why?
Please note that my webapp is based on Spring 3 + Hibernate 4 + Tomcat 7.
Background and my doubts:
My current web application uses my own custom class (implements HandlerInterceptor) to enable one-hibernatesession-per-request basis. Now I want to improve my application's maintainability by using the "#Transactional annotation" instead since that could save many lines of code.
According to my understanding, the #Transactional basically relies on the AOP concept to ensure the session (Hibernate session) is ready for use in the annotated method. This seems nothing to do with the JTA. But I wonder why can't I make it work on my webapp in Tomcat 7 (without JTA-provider).
After few searches on google, it looks like the JTA is required. This confuses me since this seems to be a very basic functionality that shouldn't have the complicated JTA-provider as a requirement.
Here is the error I got:
org.hibernate.HibernateException: No Session found for current thread
org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)
org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:988)
...
This is the code I use for testing:
....
#Autowired
org.hibernate.SessionFactory sessionFactory;
#Transactional
#RequestMapping(method = RequestMethod.GET)
protected String home() {
Session session = sessionFactory.getCurrentSession(); // I expected the session is good to use now
Province p = (Province) session.get(Province.class, 1L); // This causes no session found error :(
return "home";
}
The spring XML:
....
<tx:annotation-driven/>
<context:component-scan base-package="..."/>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/..."/>
<property name="lookupOnStartup" value="true"/>
<property name="proxyInterface" value="javax.sql.DataSource"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="persistenceExceptionTranslationPostProcessor" class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
....
Thank you !
Just a speculation:
Your controller is defined in some kind of dispatcher-servlet.xml therefore seperates from the applicationContext in which < tx:annotation-driven/> is defined. The compoment you want to enhance with #Transactional need to be within the same context with < tx:annotation-driven> if I'm not mistaken. So the #Transactional does not work.
That was my silly mistake. The Spring uses CGLIB to proxy methods with #Transactional annotated and it seems like CBLIB can't enhance protected method.
protected String home() {
Changing this to
public String home() {
fixed the problem.
My setup is Spring MVC 3.1, Hibernate 4.1 with two databases.
My service methods seem to work fine for reads (for both dbs). But the persist fails to insert the data in the db - and no exception in the logs at all. Looking at the sql output of hibernate it looks like it did retrieve the newly generated id but then never did the insert - if it did it doesn't show in the db nor was an insert logged in the log file. In the db I can see the sequence number is incremented but no data was inserted. I couldn't figure out what is wrong with my config. Hopefully someone has an idea.
Below are the relevant pieces from config file
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
</bean>
<bean id="mgrFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource">
<property name="packagesToScan" value="xxx.yyy" />
<property name="persistenceUnitName" value="puOne"/>
<property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
</property>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="txMgr"/>
<bean id="txMgr"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="mgrFactory">
</bean>
This works fine as it is. What I want to do now is add the ability to use another database. So I added another data source, entity manager factory and transaction manager
<bean id="dataSourceTwo" class="org.apache.commons.dbcp.BasicDataSource">
</bean>
<bean id="mgrFactoryTwo"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSourceTwo">
<property name="packagesToScan" value="xxx.zzz" />
<property name="persistenceUnitName" value="puTwo"/>
<property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"/>
</property>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="txMgrTwo"/>
<bean id="txMgrTwo"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="mgrFactoryTwo">
</bean>
I also made sure that the two respective base DAOs annotate their entity manager's with the unit name like
#PersistenceContext(unitName = "puOne")
protected EntityManager entityManager;
The last thing I did was to add the transaction manager names inside my #Transactional annotations inside my services like
#Override
#Transactional(value="txMgrTwo" propagation = Propagation.REQUIRED)
public boolean create(User user) {
userDao.persist(user);
}
The test below was working in my application with Hibernate3. When I upgraded it to hibernate4, it started failing.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(inheritLocations=false,locations={
"/hibernate/spring-SF-tests.xml",
"/hibernate/spring-transaction.xml",
"/hibernate/testBeans.xml"
,"/hibernate/spring-audit.xml",
"/hibernate/iwrs-mail-beans-test.xml",
"/hibernate/fake-audit-meaning.xml"
})
#TransactionConfiguration(transactionManager="txManager", defaultRollback=true)
public class CodeAuditIntegrationTest extends CodeIntegrationTest {
#Autowired
private SessionFactory auditFactory;
#Before
public void cleanAudit(){
auditFactory.getCurrentSession().createQuery("delete from AuditLogRecord").executeUpdate();
}
#Test
public void clinicalTrialAssociationTest() {
super.clinicalTrialAssociationTest();
}
}
Which is failing with:
org.hibernate.HibernateException: No Session found for current thread
at
org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)
at
org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:881)
I have 2 different session factories in my app: sessionFactory and auditFactory. Both are configured in spring (see [1]).
The problem is that in an hibernate4 configuration the property exposeTransactionAwareSessionFactory was removed. I had that set to true for my auditFactory. I believe removing this property makes the injected auditFactory not being able to pick up the session in the transaction (as txManger is configured for sessionFactory), therefore yielding the error.
Questions:
how can I make the auditFactory have transactions managed by spring on this test?
Is that what was happening with the exposeTransactionAwareSessionFactory property in hibernate 3?
The only alternative I see is to wrap all the code that uses auditFactory in a Helper class annotated with #Transactional(otherTxManager). I did try that, but I got a couple of additional problems there:
I needed to use a separate DataSource (else I'd get [2])
Using 2 separate datasources I'll get a similar error on my Cucumber tests, now related with the transactionManager [3]
[1] The relavant XML configuration:
<bean id="txManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
<bean id="sessionFactory" scope="singleton"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2dll}</prop>
<prop key="hibernate.hbm2ddl.import_files">${hibernate.sql_scripts}</prop>
</props>
</property>
<property name="packagesToScan">...</property>
<property name="annotatedPackages">...</property>
<property name="mappingLocations">...</property>
<property name="dataSource" ref="c3p0DataSource" />
<property name="entityInterceptor" ref="auditInterceptor" />
</bean>
<bean id="auditFactory" scope="singleton"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="exposeTransactionAwareSessionFactory"> -->needs to be removed in hibernate4!
<value>true</value>
</property>
<property name="mappingLocations">...</property>
<property name="packagesToScan">...</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2dll}</prop>
</props>
</property>
<property name="dataSource" ref="c3p0DataSource" />
</bean>
[2] Problem with using the same dataSource for the two factories:
org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! HibernateTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single HibernateTransactionManager for all transactions on a single DataSource, no matter whether Hibernate or JDBC access.
at org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:329)
[3] Problem in cucumber tests due to declaring 2 transaction managers:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single bean but found 2: txAudit,txManager