Spring transaction doesn't rollback when switching to JDBCTemplate programmatically - java

I have this use case in which I need to get the data from one Oracle schema and insert them to another schema, table by table. For reading and writing I use different datasources through JDBCTemplate. The switching between them is done within the code. Additionally I have a Hibernate connection, that I use to read data from configuration tables. This is also my default connection, the one that is set through autowiring when the application starts. I am using Spring 4, Hibernate 4.3 and Oracle 11.
For the JDBCTemplate I have an abstract class that holds the JDBCTemplate, like this:
public abstract class GenericDao implements SystemChangedListener {
private NamedParameterJdbcTemplate jdbcTemplate;
/**
* Initializing the bean with the definition data source through #Autowired
* #param definitionDataSource as instance of #DataSource
*/
#Autowired
private void setDataSource(DataSource definitionDataSource) {
this.jdbcTemplate = new NamedParameterJdbcTemplate(definitionDataSource);
}
public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(){
return this.jdbcTemplate;
}
#Override
public void updateDataSource(DataSource dataSource) {
this.setDataSource(dataSource);
}
}
The interface SystemChangedListener defines the updateDataSource method which is called, when the DataSource is switched through a Service method, like this:
public class SystemServiceImpl implements SystemService, SystemChangable {
private List<GenericDao> daoList;
#Autowired
public void setDaoList(final List<GenericDao> daoList){
this.daoList = daoList;
}
#Override
public void notifyDaos(SystemDTO activeSystem) {
logger.debug("Notifying DAO of change in datasource...");
for(GenericDao dao : this.daoList){
dao.updateDataSource(activeSystem.getDataSource());
}
logger.debug("...done.");
}
#Override
public Boolean switchSystem(final SystemDTO toSystem) {
logger.info("Switching active system...");
notifyDaos(toSystem);
logger.info("Active system and datasource switched to: " + toSystem.getName());
return true;
}
}
The switching works perfectly for reading so far. I can switch between schemas with no problem, but if for some reason during the copying I get an exception the transaction doesn't get rolled back.
This is my copyint method:
#Transactional(rollbackFor = RuntimeException.class, propagation=Propagation.REQUIRED)
public void replicateSystem(String fromSystem, String toSystem) throws ApplicationException {
// FIXME: pass the user as information
// TODO: actually the method should take some model from the view and transform it in DTOs and stuff
StringBuffer protocolMessageBuf = new StringBuffer();
ReplicationProtocolEntryDTO replicationDTO = new ReplicationProtocolEntryDTO();
String userName = "xxx";
Date startTimeStamp = new Date();
try {
replicationStatusService.markRunningReplication();
List<ManagedTableReplicationDTO> replications = retrieveActiveManageTableReplications(fromSystem, toSystem);
protocolMessageBuf.append("Table count: ");
protocolMessageBuf.append(replications.size());
protocolMessageBuf.append(". ");
for (ManagedTableReplicationDTO repDTO : replications) {
protocolMessageBuf.append(repDTO.getTableToReplicate());
protocolMessageBuf.append(": ");
logger.info("Switching to source system: " + repDTO.getSourceSystem());
SystemDTO system = systemService.retrieveSystem(repDTO.getSourceSystem());
systemService.switchSystem(system);
ManagedTableDTO managedTable = managedTableService.retrieveAllManagedTableData(repDTO.getTableToReplicate());
protocolMessageBuf.append(managedTable.getRows() != null ? managedTable.getRows().size() : null);
protocolMessageBuf.append("; ");
ManagedTableUtils managedTableUtils = new ManagedTableUtils();
List<String> inserts = managedTableUtils.createTableInserts(managedTable);
logger.info("Switching to target system: " + repDTO.getSourceSystem());
SystemDTO targetSystem = systemService.retrieveSystem(repDTO.getTargetSystem());
systemService.switchSystem(targetSystem);
// TODO: what about constraints? foreign keys
logger.info("Cleaning up data in target table: " + repDTO.getTargetSystem());
managedTableService.cleanData(repDTO.getTableToReplicate());
/*
managedTableDao.deleteContents(repDTO.getTableToReplicate());
*/
// importing the data
managedTableService.importData(inserts);
/*
for (String insrt : inserts) {
managedTableDao.executeSqlInsert(insrt);
}
*/
protocolMessageBuf.append("Replication successful.");
}
} catch (ApplicationException ae) {
protocolMessageBuf.append("ERROR: ");
protocolMessageBuf.append(ae.getMessage());
throw new RuntimeException("Error replicating a table. Rollback.");
} finally {
replicationDTO = this.prepareProtocolRecord(userName, startTimeStamp, protocolMessageBuf.toString(), fromSystem, toSystem);
replicationProtocolService.writeProtocolEntry(replicationDTO);
replicationStatusService.markFinishedReplication();
}
}
What I do is, I retrieve a list with tables whose content should be copied and in a loop, generate insert statements for them, delete the contents of the target table and execute the inserts with
public void executeSqlInsert(String insert) throws DataAccessException {
getNamedParameterJdbcTemplate().getJdbcOperations().execute(insert);
}
In this the correct DataSource is used - the DataSource of the target system. When, for instance there's an SQLException somwhere during insertion of the data, the deleting of the data is still committed and the data of the target table get lost. I have no problem with getting exceptions. In fact this is part of the requirement - all the exceptions should get protocolled and the whole copying process must be rolled back if there are exceptions.
Here's my db.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- Scans within the base package of the application for #Components to configure as beans -->
<bean id="placeholderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:/db.properties" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:packagesToScan="de.telekom.cldb.admin"
p:dataSource-ref="dataSource"
p:jpaPropertyMap-ref="jpaPropertyMap"
p:jpaVendorAdapter-ref="hibernateVendor" />
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="true" />
<property name="databasePlatform" value="${db.dialect}" />
</bean>
<!-- system 'definition' data source -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${db.driver}"
p:url="${db.url}"
p:username="${db.username}"
p:password="${db.password}" />
<!--
p:maxActive="${dbcp.maxActive}"
p:maxIdle="${dbcp.maxIdle}"
p:maxWait="${dbcp.maxWait}"/>
-->
<util:map id="jpaPropertyMap">
<entry key="generateDdl" value="false"/>
<entry key="hibernate.hbm2ddl.auto" value="validate"/>
<entry key="hibernate.dialect" value="${db.dialect}"/>
<entry key="hibernate.default_schema" value="${db.schema}"/>
<entry key="hibernate.format_sql" value="false"/>
<entry key="hibernate.show_sql" value="true"/>
</util:map>
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- supports both JDBCTemplate connections and JPA -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
So my problem is that the transaction isn't rolled back. And also, I don't see any clues in the log file, that a trnsaction is started at all. What am I doing wrong?
Thank you for the help!
al

As I said in my comment, by default,spring framework mark a transaction for rollback in the case of runtime i.e. unchecked exceptions (any exception that is an subclass of RuntimeException also included in this). On other hand, Checked exceptions that are generated from a transactional method will not trigger auto transaction rollback.
Why? It's simple, As we learned checked exceptions are necessary(must) for handling or throwing out. so as you did, throwing the checked exception out of the transactional method will tell spring framework that (this thrown exception is occurred and) you know what you're doing, resulting framework skip rollback part. In case of unchecked exception it's considered as a bug or a bad exception handling, so transaction is rolled back to avoid data corruption.
According to your code of replicateSystem method where you have have checked for ApplicationException, ApplicationException do not trigger automatic rollback. because when the exception is occur the client (application) has an opportunity to recover.
According to Docs application exceptions are that do not extend RuntimeException.
As per my knowledge in EJB we can use #ApplicationException(rollback=true) if there is need to transaction to be rolled back automatically.

I'm not sure? but I think the problem in this point
// TODO: what about constraints? foreign keys
logger.info("Cleaning up data in target table: " + repDTO.getTargetSystem());
managedTableService.cleanData(repDTO.getTableToReplicate());
If the clearing of tables goes throw trunc some_table then at this point Oracle commit transaction.

Related

Delete by username spring boot [duplicate]

I get this error when trying to invoke "persist" method to save entity model to database in my Spring MVC web application. Can't really find any post or page in internet that can relate to this particular error. It seems like something's wrong with EntityManagerFactory bean but i'm fairly new to Spring programming so for me it seems like everything is initialized fine and according to various tutorial articles in web.
dispatcher-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.2.xsd">
<context:component-scan base-package="wymysl.Controllers" />
<jpa:repositories base-package="wymysl.repositories"/>
<context:component-scan base-package="wymysl.beans" />
<context:component-scan base-package="wymysl.Validators" />
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean class="org.springframework.orm.hibernate4.HibernateExceptionTranslator"/>
<bean id="passwordValidator" class="wymysl.Validators.PasswordValidator"></bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:#localhost:1521:xe" />
<property name="username" value="system" />
<property name="password" value="polskabieda1" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:./META-INF/persistence.xml" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
<property name="showSql" value="true" />
<property name="generateDdl" value="false" />
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.max_fetch_depth">3</prop>
<prop key="hibernate.jdbc.fetch_size">50</prop>
<prop key="hibernate.jdbc.batch_size">10</prop>
</props>
</property>
</bean>
<mvc:annotation-driven />
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages" />
</bean>
<bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<mvc:resources mapping="/resources/**" location="/resources/" />
<mvc:resources mapping="/resources/*" location="/resources/css/"
cache-period="31556926"/>
</beans>
RegisterController.java
#Controller
public class RegisterController {
#PersistenceContext
EntityManager entityManager;
#Autowired
PasswordValidator passwordValidator;
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(passwordValidator);
}
#RequestMapping(value = "/addUser", method = RequestMethod.GET)
public String register(Person person) {
return "register";
}
#RequestMapping(value = "/addUser", method = RequestMethod.POST)
public String register(#ModelAttribute("person") #Valid #Validated Person person, BindingResult result) {
if(result.hasErrors()) {
return "register";
} else {
entityManager.persist(person);
return "index";
}
}
I had the same problem and I annotated the method as #Transactional and it worked.
UPDATE: checking the spring documentation it looks like by default the PersistenceContext is of type Transaction, so that's why the method has to be transactional (http://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html):
The #PersistenceContext annotation has an optional attribute type,
which defaults to PersistenceContextType.TRANSACTION. This default is
what you need to receive a shared EntityManager proxy. The
alternative, PersistenceContextType.EXTENDED, is a completely
different affair: This results in a so-called extended EntityManager,
which is not thread-safe and hence must not be used in a concurrently
accessed component such as a Spring-managed singleton bean. Extended
EntityManagers are only supposed to be used in stateful components
that, for example, reside in a session, with the lifecycle of the
EntityManager not tied to a current transaction but rather being
completely up to the application.
I got this exception while attempting to use a deleteBy custom method in the spring data repository. The operation was attempted from a JUnit test class.
The exception does not occur upon using the #Transactional annotation at the JUnit class level.
This error had me foxed for three days, the situation I faced produced the same error. Following all the advice I could find, I played with the configuration but to no avail.
Eventually I found it, the difference, the Service I was executing was contained in a common jar, the issue turned out to be AspectJ not treating the Service instantiation the same. In effect the proxy was simply calling the underlying method without all the normal Spring magic being executed before the method call.
In the end the #Scope annotation placed on the service as per the example solved the issue:
#Service
#Scope(proxyMode = ScopedProxyMode.INTERFACES)
#Transactional
public class CoreServiceImpl implements CoreService {
#PersistenceContext
protected EntityManager entityManager;
#Override
public final <T extends AbstractEntity> int deleteAll(Class<T> clazz) {
CriteriaDelete<T> criteriaDelete = entityManager.getCriteriaBuilder().createCriteriaDelete(clazz);
criteriaDelete.from(clazz);
return entityManager.createQuery(criteriaDelete).executeUpdate();
}
}
The method I have posted is a delete method but the annotations affect all persistence methods in the same way.
I hope this post helps someone else who has struggled with the same issue when loading a service from a jar
boardRepo.deleteByBoardId(id);
Faced the same issue. GOT javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread
I resolved it by adding #Transactional annotation above the controller/service.
You need to add #Transactional to your methode
I had the same error because I switched from XML- to java-configuration.
The point was, I didn't migrate <tx:annotation-driven/> tag, as Stone Feng suggested.
So I just added #EnableTransactionManagement as suggested here
Setting Up Annotation Driven Transactions in Spring in #Configuration Class, and it works now
Adding the org.springframework.transaction.annotation.Transactional annotation at the class level for the test class fixed the issue for me.
I had the same problem and I added tx:annotation-driven in applicationContext.xml and it worked.
I had the same error when accessing an already transactional-annotated method from a non-transactional method within the same component:
Before:
#Component
public class MarketObserver {
#PersistenceContext(unitName = "maindb")
private EntityManager em;
#Transactional(value = "txMain", propagation = Propagation.REQUIRES_NEW)
public void executeQuery() {
em.persist(....);
}
#Async
public void startObserving() {
executeQuery(); //<-- Wrong
}
}
//In another bean:
marketObserver.startObserving();
I fixed the error by calling the executeQuery() on the self-referenced component:
Fixed version:
#Component
public class MarketObserver {
#PersistenceContext(unitName = "maindb")
private EntityManager em;
#Autowired
private GenericApplicationContext context;
#Transactional(value = "txMain", propagation = Propagation.REQUIRES_NEW)
public void executeQuery() {
em.persist(....);
}
#Async
public void startObserving() {
context.getBean(MarketObserver.class).executeQuery(); //<-- Works
}
}
Just a note for other users searching for answers for thie error. Another common issue is:
You generally cannot call an #transactional method from within the same class.
(There are ways and means using AspectJ but refactoring will be way easier)
So you'll need a calling class and class that holds the #transactional methods.
If you have
#Transactional // Spring Transactional
class MyDao extends Dao {
}
and super-class
class Dao {
public void save(Entity entity) { getEntityManager().merge(entity); }
}
and you call
#Autowired MyDao myDao;
myDao.save(entity);
you won't get a Spring TransactionInterceptor (that gives you a transaction).
This is what you need to do:
#Transactional
class MyDao extends Dao {
public void save(Entity entity) { super.save(entity); }
}
Unbelievable but true.
Without #Transactional annotation you can achieve the same goal with finding the entity from the DB and then removing that entity you got from the DB.
CrudRepositor -> void delete(T var1);
For us, the problem came down to same context settings in multiple configuration files. Check you've not duplicated the following in multiple config files.
<context:property-placeholder location="classpath*:/module.properties"/>
<context:component-scan base-package="...." />
I had the same error code when I used #Transaction on a wrong method/actionlevel.
methodWithANumberOfDatabaseActions() {
methodA( ...)
methodA( ...)
}
#Transactional
void methodA( ...) {
... ERROR message
}
I had to place the #Transactional just above the method methodWithANumberOfDatabaseActions(), of course.
That solved the error message in my case.
I removed the mode from
<tx:annotation-driven mode="aspectj"
transaction-manager="transactionManager" />
to make this work
I already had the #Transactional but still wasn't working. Turns out I had to get rid of parallelism to make it work.
If you are doing things in parallel, DON'T.
I had this issue for days and nothing I found anywhere online helped me, I'm posting my answer here in case it helps anyone else.
In my case, I was working on a microservice being called through remoting, and my #Transactional annotation at the service level was not being picked up by the remote proxy.
Adding a delegate class between the service and dao layers and marking the delegate method as transactional fixed this for me.
This helped us, maybe it can help others in the future. #Transaction was not working for us, but this did:
#ConditionalOnMissingClass("org.springframework.orm.jpa.JpaTransactionManager")
I got the same error when I executed the Spring JPA deleteAll() method from Junit test cases. I simply used the deleteInBatch() & deleteAllInBatch() and its perfectly works. We do not need to mark #Transactional at the test cases level.
For anyone with the same issue as I had, I was calling a public method method1 from within another class.
method1 then called another public method method2 within the same class.
method2 was annotated with #Transactional, but method1 was not.
All that method1 did was transform some arguments and directly call method2, so no DB operations here.
The issue got solved for me once I moved the #Transactional annotation to method1.
Not sure the reason for this, but this did it for me.
Calling the repository method was being called within a class with #Component, taking that method out of that class and placing it inside another with #Service worked.
It's like you are using the shared EntityManager when you are getting it Autowired so for persisting spring tells that this EntityManager bean is a shared bean and for persisting it needs a hold of this bean till the data persist doesn't get completed so for that we have to use #Transactional so that it gonna start and commit the persistence in a transaction so the data or operation gets completely saved or get rollback completely.
To fix this in a test, you can use #DataJpaTest or #AutoConfigureTestDatabase.

Using a spring bean with #Transactional is giving TransactionRequiredException: No EntityManager with actual transaction available for current thread [duplicate]

I get this error when trying to invoke "persist" method to save entity model to database in my Spring MVC web application. Can't really find any post or page in internet that can relate to this particular error. It seems like something's wrong with EntityManagerFactory bean but i'm fairly new to Spring programming so for me it seems like everything is initialized fine and according to various tutorial articles in web.
dispatcher-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.2.xsd">
<context:component-scan base-package="wymysl.Controllers" />
<jpa:repositories base-package="wymysl.repositories"/>
<context:component-scan base-package="wymysl.beans" />
<context:component-scan base-package="wymysl.Validators" />
<bean
class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean class="org.springframework.orm.hibernate4.HibernateExceptionTranslator"/>
<bean id="passwordValidator" class="wymysl.Validators.PasswordValidator"></bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:#localhost:1521:xe" />
<property name="username" value="system" />
<property name="password" value="polskabieda1" />
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:./META-INF/persistence.xml" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.H2Dialect" />
<property name="showSql" value="true" />
<property name="generateDdl" value="false" />
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.max_fetch_depth">3</prop>
<prop key="hibernate.jdbc.fetch_size">50</prop>
<prop key="hibernate.jdbc.batch_size">10</prop>
</props>
</property>
</bean>
<mvc:annotation-driven />
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages" />
</bean>
<bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<mvc:resources mapping="/resources/**" location="/resources/" />
<mvc:resources mapping="/resources/*" location="/resources/css/"
cache-period="31556926"/>
</beans>
RegisterController.java
#Controller
public class RegisterController {
#PersistenceContext
EntityManager entityManager;
#Autowired
PasswordValidator passwordValidator;
#InitBinder
private void initBinder(WebDataBinder binder) {
binder.setValidator(passwordValidator);
}
#RequestMapping(value = "/addUser", method = RequestMethod.GET)
public String register(Person person) {
return "register";
}
#RequestMapping(value = "/addUser", method = RequestMethod.POST)
public String register(#ModelAttribute("person") #Valid #Validated Person person, BindingResult result) {
if(result.hasErrors()) {
return "register";
} else {
entityManager.persist(person);
return "index";
}
}
I had the same problem and I annotated the method as #Transactional and it worked.
UPDATE: checking the spring documentation it looks like by default the PersistenceContext is of type Transaction, so that's why the method has to be transactional (http://docs.spring.io/spring/docs/current/spring-framework-reference/html/orm.html):
The #PersistenceContext annotation has an optional attribute type,
which defaults to PersistenceContextType.TRANSACTION. This default is
what you need to receive a shared EntityManager proxy. The
alternative, PersistenceContextType.EXTENDED, is a completely
different affair: This results in a so-called extended EntityManager,
which is not thread-safe and hence must not be used in a concurrently
accessed component such as a Spring-managed singleton bean. Extended
EntityManagers are only supposed to be used in stateful components
that, for example, reside in a session, with the lifecycle of the
EntityManager not tied to a current transaction but rather being
completely up to the application.
I got this exception while attempting to use a deleteBy custom method in the spring data repository. The operation was attempted from a JUnit test class.
The exception does not occur upon using the #Transactional annotation at the JUnit class level.
This error had me foxed for three days, the situation I faced produced the same error. Following all the advice I could find, I played with the configuration but to no avail.
Eventually I found it, the difference, the Service I was executing was contained in a common jar, the issue turned out to be AspectJ not treating the Service instantiation the same. In effect the proxy was simply calling the underlying method without all the normal Spring magic being executed before the method call.
In the end the #Scope annotation placed on the service as per the example solved the issue:
#Service
#Scope(proxyMode = ScopedProxyMode.INTERFACES)
#Transactional
public class CoreServiceImpl implements CoreService {
#PersistenceContext
protected EntityManager entityManager;
#Override
public final <T extends AbstractEntity> int deleteAll(Class<T> clazz) {
CriteriaDelete<T> criteriaDelete = entityManager.getCriteriaBuilder().createCriteriaDelete(clazz);
criteriaDelete.from(clazz);
return entityManager.createQuery(criteriaDelete).executeUpdate();
}
}
The method I have posted is a delete method but the annotations affect all persistence methods in the same way.
I hope this post helps someone else who has struggled with the same issue when loading a service from a jar
boardRepo.deleteByBoardId(id);
Faced the same issue. GOT javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread
I resolved it by adding #Transactional annotation above the controller/service.
You need to add #Transactional to your methode
I had the same error because I switched from XML- to java-configuration.
The point was, I didn't migrate <tx:annotation-driven/> tag, as Stone Feng suggested.
So I just added #EnableTransactionManagement as suggested here
Setting Up Annotation Driven Transactions in Spring in #Configuration Class, and it works now
Adding the org.springframework.transaction.annotation.Transactional annotation at the class level for the test class fixed the issue for me.
I had the same problem and I added tx:annotation-driven in applicationContext.xml and it worked.
I had the same error when accessing an already transactional-annotated method from a non-transactional method within the same component:
Before:
#Component
public class MarketObserver {
#PersistenceContext(unitName = "maindb")
private EntityManager em;
#Transactional(value = "txMain", propagation = Propagation.REQUIRES_NEW)
public void executeQuery() {
em.persist(....);
}
#Async
public void startObserving() {
executeQuery(); //<-- Wrong
}
}
//In another bean:
marketObserver.startObserving();
I fixed the error by calling the executeQuery() on the self-referenced component:
Fixed version:
#Component
public class MarketObserver {
#PersistenceContext(unitName = "maindb")
private EntityManager em;
#Autowired
private GenericApplicationContext context;
#Transactional(value = "txMain", propagation = Propagation.REQUIRES_NEW)
public void executeQuery() {
em.persist(....);
}
#Async
public void startObserving() {
context.getBean(MarketObserver.class).executeQuery(); //<-- Works
}
}
Just a note for other users searching for answers for thie error. Another common issue is:
You generally cannot call an #transactional method from within the same class.
(There are ways and means using AspectJ but refactoring will be way easier)
So you'll need a calling class and class that holds the #transactional methods.
If you have
#Transactional // Spring Transactional
class MyDao extends Dao {
}
and super-class
class Dao {
public void save(Entity entity) { getEntityManager().merge(entity); }
}
and you call
#Autowired MyDao myDao;
myDao.save(entity);
you won't get a Spring TransactionInterceptor (that gives you a transaction).
This is what you need to do:
#Transactional
class MyDao extends Dao {
public void save(Entity entity) { super.save(entity); }
}
Unbelievable but true.
Without #Transactional annotation you can achieve the same goal with finding the entity from the DB and then removing that entity you got from the DB.
CrudRepositor -> void delete(T var1);
For us, the problem came down to same context settings in multiple configuration files. Check you've not duplicated the following in multiple config files.
<context:property-placeholder location="classpath*:/module.properties"/>
<context:component-scan base-package="...." />
I had the same error code when I used #Transaction on a wrong method/actionlevel.
methodWithANumberOfDatabaseActions() {
methodA( ...)
methodA( ...)
}
#Transactional
void methodA( ...) {
... ERROR message
}
I had to place the #Transactional just above the method methodWithANumberOfDatabaseActions(), of course.
That solved the error message in my case.
I removed the mode from
<tx:annotation-driven mode="aspectj"
transaction-manager="transactionManager" />
to make this work
I already had the #Transactional but still wasn't working. Turns out I had to get rid of parallelism to make it work.
If you are doing things in parallel, DON'T.
I had this issue for days and nothing I found anywhere online helped me, I'm posting my answer here in case it helps anyone else.
In my case, I was working on a microservice being called through remoting, and my #Transactional annotation at the service level was not being picked up by the remote proxy.
Adding a delegate class between the service and dao layers and marking the delegate method as transactional fixed this for me.
This helped us, maybe it can help others in the future. #Transaction was not working for us, but this did:
#ConditionalOnMissingClass("org.springframework.orm.jpa.JpaTransactionManager")
I got the same error when I executed the Spring JPA deleteAll() method from Junit test cases. I simply used the deleteInBatch() & deleteAllInBatch() and its perfectly works. We do not need to mark #Transactional at the test cases level.
For anyone with the same issue as I had, I was calling a public method method1 from within another class.
method1 then called another public method method2 within the same class.
method2 was annotated with #Transactional, but method1 was not.
All that method1 did was transform some arguments and directly call method2, so no DB operations here.
The issue got solved for me once I moved the #Transactional annotation to method1.
Not sure the reason for this, but this did it for me.
Calling the repository method was being called within a class with #Component, taking that method out of that class and placing it inside another with #Service worked.
It's like you are using the shared EntityManager when you are getting it Autowired so for persisting spring tells that this EntityManager bean is a shared bean and for persisting it needs a hold of this bean till the data persist doesn't get completed so for that we have to use #Transactional so that it gonna start and commit the persistence in a transaction so the data or operation gets completely saved or get rollback completely.
To fix this in a test, you can use #DataJpaTest or #AutoConfigureTestDatabase.

EntityManager.merge() is not being committed (Wildfly, JPA, JTA)

I can persist new data, but I cannot do updates. There are no errors, just no transactions committing the changes. I'm assuming this has something to do with the way that I've set up transactions. I'm trying a bunch of relatively new (to me) set of technologies. Below are the details.
I'm using the following tools/technologies:
Wildfly 8 and Java 7 (which is what my hosting service uses)
Annotations, with minimal XML being the goal
Struts 2.3 (using the convention plugin)
Spring 3.2
Hibernate 4.3
JTA (with container managed transactions (CMT))
JPA 2 (with a Container Managed Persistence Context)
EJBs (I have a remote client app that runs htmlunit tests)
Three WAR files and one EJB JAR file deployed
SpringBeanAutowiringInterceptor to autowire the EJBs (could there be an error in here where transactions don't commit?)
beanRefContext.xml (required by SpringBeanAutowiringInterceptor)
<beans>
<bean
class="org.springframework.context.support.ClassPathXmlApplicationContext">
<constructor-arg value="classpath:campaignerContext.xml" />
</bean>
</beans>
campaignerContext.xml
<beans>
<context:component-scan base-package="..." />
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/CampaignerDS"/>
<tx:annotation-driven/>
<tx:jta-transaction-manager/>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="campaigner" />
</bean>
<bean id="ehCacheManager" class="net.sf.ehcache.CacheManager" factory-method="create">
<constructor-arg type="java.net.URL" value="classpath:/campaigner_ehcache.xml"/>
</bean>
</beans>
persistence.xml
<persistence>
<persistence-unit name="campaigner" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:/jdbc/CampaignerDS</jta-data-source>
<class>....UserRegistration</class>
...
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform" />
</properties>
</persistence-unit>
</persistence>
SecurityServiceBean.java
#EnableTransactionManagement
#TransactionManagement(value = TransactionManagementType.CONTAINER)
#TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW)
#Stateless
#Interceptors(SpringBeanAutowiringInterceptor.class)
#DeclareRoles("Security Admin")
public class SecurityServiceBean extends AbstractCampaignerServiceImpl implements
SecurityServiceLocal, SecurityServiceRemote
{
#Override
#PermitAll
#Transactional(propagation = Propagation.REQUIRES_NEW)
public UserRegistration confirmRegistration(
String confirmationCode) throws ApplicationException
{
UserRegistration userRegistration = this.userRegistrationDAO
.find(new UserRegistrationQuery(null, confirmationCode)).uniqueResult(); // Should be attached now
...
userRegistration.setConfirmationDate(new Date());
userRegistration.setState(State.CONFIRMED);
userRegistration = this.userRegistrationDAO.saveOrUpdate(userRegistration);
...
}
}
UserRegistrationDAO.java
#Override
public UserRegistration saveOrUpdate(
UserRegistration obj) throws DAOException
{
log.debug("[saveOrUpdate] isJoinedToTransaction? "
+ (this.em.isJoinedToTransaction() ? "Y " : "N"));
try
{
if (obj.getId() == null)
{
this.em.persist(obj);
log.debug("[saveOrUpdate] called persist()");
return obj;
}
else
{
UserRegistration attached = this.em.merge(obj);
log.debug("[saveOrUpdate] called merge()");
return attached;
}
}
catch (PersistenceException e)
{
throw new DAOException("[saveOrUpdate] obj=" + obj.toString() + ",msg=" + e.getMessage(), e);
}
}
Are there any settings in Wildfly's standalone.xml that you need to see or that I should be setting?
BTW, this is incredibly annoying and frustrating. This should be an easy one-time setup that I can do and then forget about as I move on to creating my website, which should be where most of my time is spent. The lack of comprehensive documentation anywhere is AMAZING. Right now, development has been halted until this is solved
/rant
UPDATES
I tried switching to an XA data source, because some sites claimed that was necessary, but that didn't work (didn't think so but had to try). Also tried configuring emf with dataSource instead of persistenceUnitName as some other sites have. No joy.
I tried replacing the transactionManager with JpaTransactionManager, but that just led to this exception: A JTA EntityManager cannot use getTransaction()
The answer, thanks to M. Deinum, is that I was using the wrong #Transactional. I should have been using javax.transaction.Transactional but was using the Spring one instead. Note that the correct one will look like "#Transactional(TxType.REQUIRES_NEW)" instead of "#Transactional(propagation = Propagation.REQUIRES_NEW)"

Can instantiate hibernate session Factory directly but cannot do it through spring

I have started learning hibernate and spring through an assignment in which i am trying to use session factory instance through spring. I understood the hibernate part but just cant go on with spring. I have tried numerous tutorials and examples but just cant get my spring working. Although it works when i instantiate it directly.
Here are the problem related details of my project...
applicationContext.xml (inside WEB-INF)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:lang="http://www.springframework.org/schema/lang"
xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<context:annotation-config />
<context:component-scan
base-package="com.nagarro.training.assignment6.dao.entity.Student" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.HSQLDialect
</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<tx:annotation-driven />
<bean id="studentDAO"
class="com.nagarro.training.assignment6.dao.impl.StudentDAOImplementation">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
Edit: including changes as suggested
StudentDAOImplementaion.java (the file where it is being used )
public class StudentDAOImplementation implements StudentDAO {
/**
* single instance of hibernate session
*/
#Autowired
private SessionFactory sessionFactory;
// HibernateUtil.getSessionFactory().openSession()
/**
* #param sessionFactory
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
/**
* #return the sessionFactory
*/
public SessionFactory getSessionFactory() {
return sessionFactory;
}
private Session getSession() {
return this.sessionFactory.getCurrentSession();
}
/**
* #return list of all the students
*/
public List<Student> getStudentList() {
System.out.println(this.getSession().getStatistics());
return (List<Student>) this.getSEssion.createQuery("from Student")
.list();
}
}
And here is the snip of my jar files in lib folder:
I think that i dont need to include hibernate files and beans as it is working fine without spring. Its the spring i cant get to work.I have tried many different implementation from the web but i just cannot get it working. It just says null pointer exception on ** System.out.println(sessionFactory.getStatistics());** line in StudentDAOImplementation.
Test class that calls StudentDAO
public class Test {
public static void main(String[] args) {
StudentDAOImplementation sd = new StudentDAOImplementation();
List<Student> list = sd.getStudentList();
for(Student s : list) {
System.out.println(s.getName());
}
}
}
Stack trace
Exception in thread "main" java.lang.NullPointerException
at StudentDAOImplementation.getStudentList(StudentDAOImplementation.java:116)
at Test.main(Test.java:13)
Your sessionFactory variable is misleadingly named, since the type is actually Session. Session != SessionFactory. You're getting a NPE on sessionFactory.getStatistics() because there's no way that Spring can autowire a Session into a DAO like that. If you're not seeing an error before the NPE, then you're not actually instantiating the DAO with Spring, or else you'd get an error about not being able to find a dependency of type Session. The appropriate way to use a Hibernate-based DAO is to inject it with a SessionFactory and call getCurrentSession() in your methods where you need a Session. See "Implementing DAOs based on plain Hibernate 3 API" and following for details about this approach and about setting up appropriate transaction management.
Update: On a second glance, I see that the package for your component-scan is set to com.nagarro.training.assignment6.dao.entity.Student, which looks exactly like a class, not a package. It's also not even close to anything you'd actually want to component-scan. Maybe you don't understand what component-scan is for. It's covered under "Annotation-based container configuration" in the reference guide.
Update 2: About your "test" code: you're not using Spring at all, so you might as well remove the XML and save yourself the trouble. On the other hand, if you'd like to actually use Spring, you'd need to create a context in your main method based on said XML file, such as:
ApplicationContext context = new FileSystemXmlApplicationContext(locationOfXmlFile);
Then if you want a Spring-managed DAO, you can't just create one with new. Spring isn't magic. It doesn't grab control away from you just because you have it loaded somewhere in the same JVM.* You have to ask Spring for the DAO that it created, like:
StudentDAO dao = context.getBean(StudentDAO.class);
Note that I used the interface type, not the concrete type. That's always an advisable practice for numerous reasons.
This (not starting Spring) is your first problem. As soon as you do this, you're going to run into other problems with your configuration. You should post a new question if you need help solving one of them.
*Unless you're using AspectJ weaving to inject arbitrary objects.
You are injecting a Session instead of SessionFactory with #Autowired private Session sessionFactory; in the DAO class. It needs to be a SessionFactory , like this
#Autowired SessionFactory sessionFactory;
And then use it like this to do a DAO operations like save
Session session = sessionFactory.getCurrentSession()
session.persist(entity);
EDIT
Your testcase should be something like this
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:<path_to_your_appcontext.xml>" })
public class StudentDAOTest {
#Autowired
private StudentDAO studentDAO
#Test
public void test() {
List<Student> list = studentDAO.getStudentList();
assertNotNull(list)
}
}

Hibernate creates too many connections using #Transactional, how to prevent this?

I'm fairly new to Hibernate and PostgreSQL, but so far it's going well, although I'm running into a problem now that I can't solve. I'm getting an error while filling the database on the very first operation (which is one transaction inserting or updating 1000 rows in the database). The error is:
SQL Error: 0, SQLState: 53300
FATAL: sorry, too many clients already
Exception in thread "main" org.hibernate.exception.GenericJDBCException: Cannot open connection
This is the important code:
#Repository
public class PDBFinderDAO extends GenericDAO<PDBEntry> implements IPDBFinderDAO {
#Override
#Transactional
public void updatePDBEntry(Set<PDBEntry> pdbEntrySet) {
for (PDBEntry pdbEntry : pdbEntrySet) {
getCurrentSession().saveOrUpdate(pdbEntry);
}
}
}
getCurrentSession() is extended from GenericDAO and calls sessionFactory.getCurrentSession().
This is my Hibernate configuration:
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="hibernate.connection.url">jdbc:postgresql://localhost/PDBeter</property>
<property name="hibernate.connection.username">xxxx</property>
<property name="hibernate.connection.password">xxxx</property>
<!-- Create or update the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<!-- Use the C3P0 connection pool provider -->
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.timeout">300</property>
<property name="hibernate.c3p0.max_statements">50</property>
<property name="hibernate.c3p0.idle_test_period">300</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Batch size -->
<property name="hibernate.jdbc.batch_size">50</property>
<!-- this makes sure the more efficient new id generators are being used,
though these are not backwards compatible with some older databases -->
<property name="hibernate.id.new_generator_mappings">true</property>
<!-- Echo all executed SQL to stdout -->
<!--
<property name="hibernate.show_sql">true</property>
-->
<property name="format_sql">true</property>
<property name="use_sql_comments">true</property>
</session-factory>
</hibernate-configuration>
This is my Spring configuration:
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">
<context:component-scan base-package="nl.ru.cmbi.pdbeter" />
<!-- Transaction Manager -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven />
<!-- Session Factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="configLocation" value="hibernate.cfg.xml" />
<property name="packagesToScan" value="nl.ru.cmbi.pdbeter.core.model.domain" />
</bean>
<!-- Task Executor -->
<task:annotation-driven />
</beans>
I'm not really sure what is going wrong, this should only open one connection every time, and close it afterwards, isn't that what #Transactional is supposed to do? Also, is there an easy way to check how many connections are open at a certain time so that I can check before and after the error how many connections were open?
EDIT: when I check the database nothing has been added, so it can't even make one connection, what is going wrong here?
EDIT: I'm sorry but I already solved it myself. It was a very stupid mistake, there was a very small query using criteria that was executed 1000 times as well, but that one was executed before the transaction, causing it to be executed in 1000 separate transactions/sessions/connections (I think, correct me if I'm wrong!)
EDIT: ok, turns out that didn't solve it at all, cause I needed that small query to see if something was already in the database, and if so, get that object from the database so I could update it's fields/columns/whatever you want to call it.
This is the method in the GenericDAO:
#Override
public PDBEntry findByAccessionCode(String accessionCode) {
return (PDBEntry) createCriteria(Restrictions.eq("accessionCode", accessionCode)).uniqueResult();
}
There is a function that builds the mapped objects that isn't in the DAO, since it converts a raw datafile into the database object, so I wanted to keep that out of database operations and only put the saveOrUpdate() within the database module. The problem I have now is that the findByAccessionCode() is being called a 1000 times during the conversion of the raw datafile to the database objects, because I need to check whether a certain piece of data is already present in the database, and if so, get the object from the database instead of making a new one.
Now how would I execute that query a 1000 times inside one connection in this context? I tried making that conversion method that converts the 1000 files #transactional, but that didn't work.
Here's the conversion method:
private void updatePDBSet(Set<RawPDBEntry> RawPDBEntrySet) {
Set<PDBEntry> pdbEntrySet = new LinkedHashSet<PDBEntry>();
for (RawPDBEntry pdb : RawPDBEntrySet) {
PDBEntry pdbEntry = pdbEntryDAO.findByAccessionCode(pdb.id);
if (pdbEntry == null) {
pdbEntry = new PDBEntry(pdb.id, pdb.header.date);
}
pdbEntry.setHeader(pdb.header.header);
ExpMethod expMethod = new ExpMethod.Builder(pdbEntry, pdb.expMethod.expMethod.toString()).build();
if (pdb.expMethod.resolution != null) {
expMethod.setResolution(pdb.expMethod.resolution);
}
if (pdb.expMethod.rFactor != null) {
expMethod.setRFactor(pdb.expMethod.rFactor.rFactor);
if (pdb.expMethod.rFactor.freeR != null) {
expMethod.setFreeR(pdb.expMethod.rFactor.freeR);
}
}
if (pdb.hetGroups != null) {
for (PFHetId hetId : pdb.hetGroups.hetIdList) {
HetGroup hetGroup = new HetGroup(pdbEntry, hetId.hetId);
if (hetId.nAtom != null) {
hetGroup.setNAtom(hetId.nAtom);
}
if (hetId.name != null) {
hetGroup.setName(hetId.name);
}
}
}
for (PFChain chain : pdb.chainList) {
new Chain(pdbEntry, chain.chain);
}
pdbEntrySet.add(pdbEntry);
}
pdbFinderDAO.updatePDBEntry(pdbEntrySet);
}
(The pdbFinderDAO.updatePDBEntry(pdbEntrySet) was where I originally thought the problem originated)
EDIT: First of all sorry that I created this new post, I really thought I found the answer, but I'll just continue in this post for further edits.
Ok, now I put all the 1000 findAccessionCode criteria inside the DAO by sending a Set of the raw data files to the DAO so it can retrieve the id's there, then finding them in the database, getting the ones it can find and adding it to a HashMap where the database object is mapped with the reference to the raw data file as key (so I know what raw data belongs to what database entry). This function I made #Transactional like so:
#Override
#Transactional
public Map<RawPDBEntry, PDBEntry> getRawPDBEntryToPDBEntryMap(Set<RawPDBEntry> rawPDBEntrySet) {
Map<RawPDBEntry, PDBEntry> RawPDBEntryToPDBEntryMap = new HashMap<RawPDBEntry, PDBEntry>();
for (RawPDBEntry pdb : rawPDBEntrySet) {
RawPDBEntryToPDBEntryMap.put(pdb, (PDBEntry) createCriteria(Restrictions.eq("accessionCode", pdb.id)).uniqueResult());
}
return RawPDBEntryToPDBEntryMap;
}
Still, no success... I get the exact same error, but it does tell me it's the criteria that causes it. Why can't I execute all these 1000 queries within the same connection?
EDIT: Yet another update: I tried adding all the queries 1 by 1, and this worked, slowly, but it worked. I did this on an empty database. Next I tried the same thing but now the database already contained the stuff from the first try, and I got the following error:
Exception in thread "main" org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
I'm guessing this has something to do with the fact that I'm getting the objects (that are already in the database and so have to be updated) within the DAO, then sending references back to the conversion method, then changing their fields, then sending them back to the DAO to make them persistent. Although after googling a bit I found people that had problems with collections in their POJO's with the annotation:
#OneToMany(mappedBy = "pdbEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
where the cascading caused the problem. Do I have to remove all of these cascades and hardcode the saveOrUpdate() operations for all the different mapped objects? Or has this nothing to do with that error?
And finally: I'm still no closer to figuring out how to do this for 1000 objects at a time.
Solved the problem, it had to do with a bad setup of Spring, which caused the #Transactional to not be recognized. I fixed that, and then the error went away.

Categories