I am using Spring-batch Spring-batch-admin and Spring-boot
I defined repository and created basic entities
However when I do save(..) I check my database table and nothing is persisted. also not errors being thrown except of some debug logs:
2015-01-12 12:21:12.652 DEBUG 13692 --- [nio-8080-exec-1] stomAnnotationTransactionAttributeSource : Adding transactional method 'save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2015-01-12 12:21:12.653 DEBUG 13692 --- [nio-8080-exec-1] o.s.b.f.s.DefaultListableBeanFactory : Returning cached instance of singleton bean 'transactionManager'
2015-01-12 12:21:12.678 DEBUG 13692 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2015-01-12 12:21:12.683 DEBUG 13692 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Acquired Connection [jdbc:mysql://localhost:3306/spring_batch_test, UserName=root#localhost, MySQL Connector Java] for JDBC transaction
2015-01-12 12:21:12.693 DEBUG 13692 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [jdbc:mysql://localhost:3306/spring_batch_test, UserName=root#localhost, MySQL Connector Java] to manual commit
2015-01-12 12:21:12.816 DEBUG 13692 --- [nio-8080-exec-1] ataPostProcessor$ThreadBoundTargetSource : Initializing lazy target object
Gradle build:
buildscript {
repositories {
maven { url 'http://artifactory/artifactory/libs-release-local' }
mavenCentral()
maven { url 'http://repo.spring.io/milestone/' }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.0.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'war'
war {
baseName = 'notification-processor-service'
version = '1.0.0-SNAPSHOT'
}
jar {
baseName = 'notification-processor-service'
version = '1.0.0-SNAPSHOT'
}
ext {
springIntegrationKafkaVersion = '1.0.0.M2'
}
repositories {
maven { url 'http://artifactory/artifactory/libs-release-local' }
maven { url 'http://artifactory/artifactory/resin-hessian' }
mavenCentral()
maven {
url 'https://repository.apache.org/content/groups/public'
}
maven { url 'http://repo.spring.io/milestone/' }
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("com.caucho:resin-hessian:4.0.23")
compile("mysql:mysql-connector-java")
compile('org.springframework.boot:spring-boot-starter-web:1.2.0.RELEASE')
compile("org.springframework.boot:spring-boot-starter-batch:1.2.0.RELEASE")
compile("org.springframework.boot:spring-boot-starter-actuator")
compile("javax.inject:javax.inject:1")
compile('org.springframework.batch:spring-batch-admin-manager:1.3.0.RELEASE') {
exclude module: 'slf4j-log4j12'
}
testCompile('org.springframework.boot:spring-boot-starter-test:1.2.0.RELEASE')
}
task wrapper(type: Wrapper) {
gradleVersion = '1.11'
}
Application.class:
package com.mycompany.notification.processor.service.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.orm.jpa.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Created by Ext_IdanF on 25/12/2014.
*/
#ComponentScan({"com.mycompany.notification.processor.service"})
#EnableAutoConfiguration
#Configuration
#EntityScan({"com.mycompany.notification.processor.service.entities"})
#ImportResource({
"classpath:integration-context.xml", "classpath:launch-context.xml", "classpath:applicationContext-NotificationProcessorService.xml"
})
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.mycompany.notification.processor.service.dao"})
//#Import({ ServletConfiguration.class, WebappConfiguration.class })
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
System.out.printf("hello man");
}
}
Spring batch configurations:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--<context:property-placeholder location="classpath:batch-mysql.properties,
file:///d:/etc/mycompany/services/pushExecuterService/pushExecuterServices.properties"
ignore-unresolvable="true"
/>
-->
<bean id="jobBuilderFactory" class="org.springframework.batch.core.configuration.annotation.JobBuilderFactory">
<constructor-arg ref="jobRepository"/>
</bean>
<bean id="stepBuilderFactory" class="org.springframework.batch.core.configuration.annotation.StepBuilderFactory">
<constructor-arg index="0" ref="jobRepository"/>
<constructor-arg index="1" ref="transactionManager"/>
</bean>
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean"
p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${batch.jdbc.driver}"/>
<property name="url" value="${batch.jdbc.url}"/>
<property name="username" value="${batch.jdbc.user}"/>
<property name="password" value="${batch.jdbc.password}"/>
</bean>
<bean id="jobOperator"
class="org.springframework.batch.core.launch.support.SimpleJobOperator"
p:jobLauncher-ref="jobLauncher" p:jobExplorer-ref="jobExplorer"
p:jobRepository-ref="jobRepository" p:jobRegistry-ref="jobRegistry"/>
<bean id="jobExplorer"
class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean"
p:dataSource-ref="dataSource"/>
<bean id="jobRegistry"
class="org.springframework.batch.core.configuration.support.MapJobRegistry"/>
<bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry"/>
</bean>
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
</bean>
</beans>
this is batch-mysql.properties:
# GENERAL:
batch.jdbc.testWhileIdle=true
batch.business.schema.script=classpath:/business-schema.sql
# Set to False to stop the database from being wiped and re-created.
batch.data.source.init=false
batch.schema=spring_batch_test
# MySQL:
batch.jdbc.driver=com.mysql.jdbc.Driver
batch.jdbc.url=jdbc:mysql://localhost:3306/spring_batch_test
batch.jdbc.user=root
batch.jdbc.password=root
batch.jdbc.validationQuery=SELECT 1
batch.schema.script=classpath:/org/springframework/batch/core/schema-mysql.sql
batch.drop.script=classpath*:/org/springframework/batch/core/schema-drop-mysql.sql
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementr
and application.propteties
#LOGS PROPS
logging.level.com.mycompany.notification.processor.service: DEBUG
logging.level.org.springframework : DEBUG
logging.level.org.springframework.integration : INFO
#SPRING BATCH
spring.batch.job.enabled=false
#SPRING-BOOT
spring.freemarker.check-template-location=false
spring.velocity.checkTemplateLocation=false
server.servletPath=/*
spring.datasource.url=jdbc:mysql://localhost:3306/spring_batch_test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#DB
ENVIRONMENT=mysql
#TOMCAT
server.port=8080
I afraid I got some collision between spring-batch && spring-batch-admin && spring boot configurations
I defined inside batch-mysql.proprties the jdbc props and also in application.properties
btw: Spring-batch-admin managed to write into the database into it's own tables
I added Spring jpa support and I get different error this time:
No transactional EntityManager available
new classes:
public class AbstractJpaDAO<T, PK extends Serializable> {
#PersistenceContext
protected EntityManager entityManager;
...
public interface NotificationDao {
NotificationJobEntity save(NotificationJobEntity entity);
}
execution code:
#Override
public NotificationJobEntity save(final NotificationJobEntity entity) {
super.persist(entity);
logger.debug("Persist a Notification entity in persistence store with Job-ID {}", entity.getJobId());
return entity;
}
thank you
I am not sure if thats the right way
what I did was to use Spring JPA
added entity manager
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="default" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="generateDdl" value="false" />
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
</bean>
</property>
</bean>
and Using AbstractJpaDAO
this way:
public class AbstractJpaDAO<T, PK extends Serializable> {
#PersistenceContext
protected EntityManager entityManager;
private Class<T> persistentClass;
protected AbstractJpaDAO(final Class<T> persistentClass) {
this.persistentClass = persistentClass;
}
public void persist(T entity) {
entityManager.persist(entity);
}
public void update(T entity) {
entityManager.merge(entity);
}
public T find(PK pk, Class<T> t) {
return entityManager.find(t, pk);
}
public void delete(T entity) {
entityManager.remove(entity);
}
#SuppressWarnings("unchecked")
public Collection<T> findAll() {
return this.entityManager.createQuery("select obj from " + this.persistentClass.getName() + " obj").getResultList();
}
#SuppressWarnings("unchecked")
public Collection<T> findAll(int page, int pageSize) {
TypedQuery<T> query = (TypedQuery<T>) entityManager.createQuery("select obj from " + this.persistentClass.getName() + " obj");
query.setFirstResult(page * pageSize);
query.setMaxResults(pageSize);
return query.getResultList();
}
}
too bad I couldnt find a way somehow to use Spring-data and earn all this code
Related
I have been facing a problem when i put #Transactional annotation on a method.
Sometime before starting the method, it got delayed start for 15 minutes (928787 millisecond in my log) and then complete the execution in a few millisecond.
It's not always reproduced. I found this problem 1 in 100000 of the method executions.
It seems something went wrong in the proxy when TaskDispatcher trying to invoke save() method.
TaskDispatcher.java
public class TaskDispatcher extends Thread {
private DBProcessor dbProcessor;
private ThreadPoolTaskExecutor myTaskExecutor;
...
#Override
public void run() {
this.myTaskExecutor.execute(new Runnable() {
#Override
public void run() {
dbProcessor.processData(data);
}
});
}
}
DBProcessor.java
public class DBProcessor extends ADataProcessor {
private DBService dbService;
...
#Override
public boolean processData(Data data) throws DataProcessorException {
long startTime = System.currentTimeMillis();
success = dbService.save(tradeReportID, translatedMessages, result);
logger.info("processing time : {} ms.", tradeReportID, action, (System.currentTimeMillis() - startTime));
return success;
}
}
DBServiceImpl.java
public class DBServiceImpl extends CustomRepository implements DBService {
...
#Transactional
public boolean save(String tradeOrderId, List<TranslatedMessage> translatedMessages, DBDealServiceResult result) throws DealProcessorException, DealProcessorExceptionNoRollback {
logger.info("start DB processing");
... // do some db operation
logger.info("finished DB processing");
return success;
}
}
spring.xml
...
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="connectionPoolDataSource" />
<property name="packagesToScan" value="com.mycompany.myapp.jpa.entity" />
<property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.generate_statistics" value="${hibernate.generate_statistics}"/>
<entry key="hibernate.format_sql" value="${hibernate.format_sql}"/>
</map>
</property>
</bean>
<bean id="hibernateJpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.Oracle12cDialect" />
</bean>
Logs show the timing of executions
2018/05/17 01:58:36.903|INFO |[myTaskExecutor-4] c.m.m.a.d.DBServiceImpl start DB processing
2018/05/17 01:58:36.915|INFO |[myTaskExecutor-4] c.m.m.a.p.DBServiceImpl finished DB processing
2018/05/17 01:58:36.920|INFO |[myTaskExecutor-4] c.m.m.a.d.DBProcessor processing time : 17 ms.
2018/05/17 01:58:36.931|INFO |[myTaskExecutor-16] c.m.m.a.d.DBServiceImpl start DB processing
2018/05/17 01:58:37.745|INFO |[myTaskExecutor-16] c.m.m.a.p.DBServiceImpl finished DB processing
2018/05/17 01:58:37.745|INFO |[myTaskExecutor-16] c.m.m.a.d.DBProcessor processing time : 928787 ms.
Version Details :
spring-framework : 5.0.5.RELEASE
spring-tx : 5.0.5.RELEASE
spring-jdbc : 5.0.5.RELEASE
spring-orm : 5.0.5.RELEASE
hibernate-jpa-2.1-api : 1.0.0.Final
hibernate-core : 5.2.7.Final
tomcat-jdbc : 7.0.70
oracle driver : ojdbc7 12.1.0.2
I have a single-context Spring-WS application (that is, there is only one file in my project that contains definitions), something-servlet.xml. When I call persist through my DAOs, no entries are getting created in my database. I'm also not getting any exceptions thrown.
If there is any more information I've left out that'd be helpful in determining the issue, please let me know. Thanks.
something-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:context="http://www.springframework.org/schema/context"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.abc.direct.mailserver" />
<context:property-placeholder location="classpath:manager.properties" />
<sws:annotation-driven />
<sws:dynamic-wsdl id="manager" portTypeName="AbcDirect"
locationUri="/mailerManagerService/" targetNamespace="http://ecsdfsds.com/direct/definitions">
<sws:xsd location="/WEB-INF/mailManagerRequest.xsd" />
</sws:dynamic-wsdl>
<bean id="mailServerPersistenceManager"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="directMailDataSource" />
<property name="packagesToScan" value="com.abc.direct.mailserver.dao"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
</bean>
<bean id="directMailDataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${database.driverClassName}" />
<property name="url" value="${database.url}" />
<property name="username" value="${database.username}" />
<property name="password" value="${database.password}" />
</bean>
</beans>
Dao Classes:
#Entity
#Table(name="virtualusertable")
public class VirtualUser {
#Id
#Column(name="user")
private String user;
// private String domain;
// private String targetAddress;
public VirtualUser(String username) {
this.user = username;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
}
#Component
public class VirtualUsers {
#PersistenceContext
EntityManager entityManager;
public void save(VirtualUser user) {
this.entityManager.persist(user);
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
}
Endpoint:
#Endpoint
public class MailManagerEndpoint {
private static final String NAMESPACE_URI = "http://asfsdfs.com/direct/schemas";
static final Namespace NAMESPACE = Namespace.getNamespace(NAMESPACE_URI);
private MailManagerService service;
#Autowired
private VirtualUsers virtualUsers;
#Autowired
public MailManagerEndpoint(MailManagerService mailManagerService) {
this.service = mailManagerService;
}
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "MailManagerRequest")
public #ResponsePayload Element handleRequest(#RequestPayload Element element) throws JDOMException, IOException {
VirtualUser user = new VirtualUser("newuser");
virtualUsers.save(user);
return new Element("success");
}
}
Startup logs: http://pastebin.com/9Z6MWRSG
Transaction logs: http://pastebin.com/ZFEC0EMJ
In the startup logs, I"m really curious as to why this block repeats itself like 7 times:
2014-04-25 10:34:03,634 DEBUG EntityLoader:146 - Static select for entity com.abc.direct.mailserver.dao.VirtualUser [READ]: select virtualuse0_.user as user1_0_0_ from virtualusertable virtualuse0_ where virtualuse0_.user=?
2014-04-25 10:34:03,635 DEBUG QuerySpacesImpl:177 - Adding QuerySpace : uid = <gen:0> -> org.hibernate.loader.plan.build.internal.spaces.EntityQuerySpaceImpl#e58a68d]
2014-04-25 10:34:03,635 DEBUG FetchStyleLoadPlanBuildingAssociationVisitationStrategy:94 - Building LoadPlan...
2014-04-25 10:34:03,635 DEBUG LoadQueryJoinAndFetchProcessor:111 - processing queryspace <gen:0>
2014-04-25 10:34:03,636 DEBUG LoadPlanTreePrinter:72 - LoadPlan(entity=com.abc.direct.mailserver.dao.VirtualUser)
- Returns
- EntityReturnImpl(entity=com.abc.direct.mailserver.dao.VirtualUser, querySpaceUid=<gen:0>, path=com.abc.direct.mailserver.dao.VirtualUser)
- QuerySpaces
- EntityQuerySpaceImpl(uid=<gen:0>, entity=com.abc.direct.mailserver.dao.VirtualUser)
- SQL table alias mapping - virtualuse0_
- alias suffix - 0_
- suffixed key columns - {user1_0_0_}
UPDATE:
persist() output after adding #Transactional and :
2014-04-25 11:24:47,517 DEBUG SharedEntityManagerCreator$SharedEntityManagerInvocationHandler:253 - Creating new EntityManager for shared EntityManager invocation
2014-04-25 11:24:47,950 DEBUG AbstractSaveEventListener:130 - Generated identifier: newuser, using strategy: org.hibernate.id.Assigned
2014-04-25 11:24:48,011 DEBUG EntityManagerFactoryUtils:435 - Closing JPA EntityManager
If you want to insert data to DB, you need a transaction. In order for Spring to provide a transaction, you need to put <tx:annotation-driven /> to your Spring context file, and put #Transactional annotation on the method that you insert data to DB. This way, Spring initiates a transaction for you as the method execution starts, and commits it when the method execution comes to an end (or rolls it back if an exception is thrown).
I am quite new to Spring and Spring-Batch in particular.
Still I somehow managed to install the Spring Batch-Admin. I added custom jobs and Hibernate/JPA for persistence.
Everything is working as expected, up to the point where the first chunk should be persisted. Then I receive the following error-message:
org.springframework.transaction.CannotCreateTransactionException:
Could not open JPA EntityManager for transaction;
nested exception is java.lang.IllegalStateException: Already value
[org.springframework.jdbc.datasource.ConnectionHolder#60d31437]
for key [org.springframework.jdbc.datasource.DriverManagerDataSource#12da4b19]
bound to thread [jobLauncherTaskExecutor-1]
This is the full stacktrace:
org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#43f9e588] for key [org.springframework.jdbc.datasource.DriverManagerDataSource#84f171a] bound to thread [jobLauncherTaskExecutor-1]
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:427)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:371)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:335)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:105)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy41.saveIfUnique(Unknown Source)
at com.qompa.batch.ArticleItemWriter.write(ArticleItemWriter.java:28)
at org.springframework.batch.core.step.item.SimpleChunkProcessor.writeItems(SimpleChunkProcessor.java:171)
at org.springframework.batch.core.step.item.SimpleChunkProcessor.doWrite(SimpleChunkProcessor.java:150)
at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor$3.doWithRetry(FaultTolerantChunkProcessor.java:313)
at org.springframework.batch.retry.support.RetryTemplate.doExecute(RetryTemplate.java:240)
at org.springframework.batch.retry.support.RetryTemplate.execute(RetryTemplate.java:187)
at org.springframework.batch.core.step.item.BatchRetryTemplate.execute(BatchRetryTemplate.java:213)
at org.springframework.batch.core.step.item.FaultTolerantChunkProcessor.write(FaultTolerantChunkProcessor.java:402)
at org.springframework.batch.core.step.item.SimpleChunkProcessor.process(SimpleChunkProcessor.java:194)
at org.springframework.batch.core.step.item.ChunkOrientedTasklet.execute(ChunkOrientedTasklet.java:74)
at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:386)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:130)
at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:264)
at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:76)
at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:367)
at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:214)
at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:143)
at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:250)
at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:195)
at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:135)
at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:61)
at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:60)
at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:144)
at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:124)
at org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:135)
at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:281)
at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:120)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
Caused by: java.lang.IllegalStateException: Already value [org.springframework.jdbc.datasource.ConnectionHolder#43f9e588] for key [org.springframework.jdbc.datasource.DriverManagerDataSource#84f171a] bound to thread [jobLauncherTaskExecutor-1]
at org.springframework.transaction.support.TransactionSynchronizationManager.bindResource(TransactionSynchronizationManager.java:189)
at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:402)
... 36 more
The same Job executes fine in a standalone application. The problem occurs only in the Spring-Batch-Admin environment. Below you can see the project structure and dependencies:
This is the app-context.xml that overrides/extends the Batch-Admin configuration:
<?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:batch="http://www.springframework.org/schema/batch"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-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/task http://www.springframework.org/schema/task/spring-task-3.2.xsd">
<context:component-scan base-package="com.company.batch" />
<context:property-placeholder location="classpath:batch.properties" />
<import resource="classpath:/META-INF/spring/batch/jobs/article-job.xml" />
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${batch.jdbc.driver}" />
<property name="url" value="${batch.jdbc.url}" />
<property name="username" value="${batch.jdbc.user}" />
<property name="password" value="${batch.jdbc.password}" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.qompa.batch" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="POSTGRESQL"></property>
<property name="showSql" value="true" />
<property name="generateDdl" value="false" />
<property name="databasePlatform" value="com.company.utils.persistence.CustomPGDialect" />
</bean>
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto"></prop>
</props>
</property>
</bean>
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
<!-- schedule tasks -->
<task:scheduled-tasks>
<task:scheduled ref="articleRetrieval" method="run"
cron="0 0 */4 * * *" />
<task:scheduled ref="articleConversion" method="run"
cron="0 15 */4 * * *" />
</task:scheduled-tasks>
</beans>
What I understand so far is that it has to do with the ThreadPoolTaskExecutor to which the jobLauncherTaskExecutor bean refers. It seems to handle connection pooling for concurrently running jobs ... but to be honest I have no clue how to change my configurations to make these things work.
[Edit]: I am not even sure wether it is the afromentioned ThreadPoolTaskExecutor. But it seem s to be an implementation of the TaskExecutor interface.
If anyone ran into a similar issue, or has a suggestion how to configure my application in a way that transactions can be created for my persistence methods: Please give me a hint!
The error comes from JpaTransactionManager line 403:
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
The error means that the transaction manager is trying to bind the datasource (not the entity manager) to the thread, but the datasource is already there and this is unexpected.
Note that the transaction manager had not started yet to bind the Entity Manager to the thread, that would happen next at JpaTransactionManager line 416:
There are two possible explanations:
Somebody (another transaction manager?) is adding the datasource to the thread before the transaction manager and this is unexpected.
Or no one is adding the datasource to the transaction manager, is just that at the end of the task execution no one cleans the thread before returning it to the pool, maybe due an error or an unhandled exception.
One question, does this also happen for only one execution thread, or only when there are several?
To find out what the problem is, these are some steps:
run with a minimal number of threads that cause the problem
put a breakpoint in TransactionSynchronizationManager.bindResource() to see who adds the connection to the thread. The breakpoint can be a conditional breakpoint with a condition on the thread name: "jobLauncherTaskExecutor-1".equals(Thread.currentThread().getName())
put also a breakpoint in TransactionSynchronizationManager.unbindResource(), to see if the datasource is unbound from the thread. when the breakpoints hit, scroll down the stacktrace and see which classes are causing this.
This normally happens when you have multiple transaction managers in place.
Some hints..
When using annotaion #EnableBatchProcessing, Spring Batch automatically registers a transaction manager , and your JpaTransactionManager may never get used.
If you want to change the transaction manager that spring batch uses, you have to implement the interface BatchConfigurer.(https://blog.codecentric.de/en/2013/06/spring-batch-2-2-javaconfig-part-3-profiles-and-environments/).
You can specify transaction manager for tasklets as follows:
<tasklet transaction-manager="transactionManager">
If you have 2 dataSource, I suggest you to read:
https://github.com/spring-projects/spring-boot/issues/3012
So... configure the main datasource (is important the transaction manager's name)
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManager",
transactionManagerRef = "transactionManager",
basePackages = "a.b.c")
#PropertySource({"classpath:db_persistence.properties"})
#EnableTransactionManagement
and the other datasource:
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "another_EntityManager",
transactionManagerRef = "another_transactionManager",
basePackages = "x.y.z")
#PropertySource({"classpath:db_persistence.properties"})
#EnableTransactionManagement
I hope that this help you.
return stepBuilderFactory.get("orderStep1").<sourceBean, destBean>chunk(5)
.reader(reader)
.processor(batchFileRowProcessor)
.writer(batchFileRowDataWritter)
.taskExecutor(taskExecutor)
.transactionManager(platformTransactionManager)
.throttleLimit(1).build();
platformTransactionManager is the qualified bean from the data source configuration
I was able to solve a similar issue by implementing a spring batch configuration for JPA
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.configuration.BatchConfigurationException;
import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
import org.springframework.batch.core.explore.support.MapJobExplorerFactoryBean;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
#Configuration
public class JpaBatchConfigurer implements BatchConfigurer {
private static final Logger logger = LoggerFactory
.getLogger(JpaBatchConfigurer.class);
#Inject
private DataSource dataSource;
#Inject
private PlatformTransactionManager transactionManager;
private JobRepository jobRepository;
private JobLauncher jobLauncher;
private JobExplorer jobExplorer;
protected JpaBatchConfigurer() {
}
#Override
#Bean
public JobRepository getJobRepository() {
return jobRepository;
}
#Override
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
#Override
#Bean
public JobLauncher getJobLauncher() {
return jobLauncher;
}
#Override
#Bean
public JobExplorer getJobExplorer() {
return jobExplorer;
}
#PostConstruct
public void initialize() {
try {
if (dataSource == null) {
logger.warn("No datasource was provided...using a Map based JobRepository");
if (this.transactionManager == null) {
this.transactionManager = new ResourcelessTransactionManager();
}
MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(
this.transactionManager);
jobRepositoryFactory.afterPropertiesSet();
this.jobRepository = jobRepositoryFactory.getObject();
MapJobExplorerFactoryBean jobExplorerFactory = new MapJobExplorerFactoryBean(
jobRepositoryFactory);
jobExplorerFactory.afterPropertiesSet();
this.jobExplorer = jobExplorerFactory.getObject();
} else {
this.jobRepository = createJobRepository();
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
jobExplorerFactoryBean.setDataSource(this.dataSource);
jobExplorerFactoryBean.afterPropertiesSet();
this.jobExplorer = jobExplorerFactoryBean.getObject();
}
this.jobLauncher = createJobLauncher();
} catch (Exception e) {
throw new BatchConfigurationException(e);
}
}
private JobLauncher createJobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository);
jobLauncher.setTaskExecutor( new SimpleAsyncTaskExecutor());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setIsolationLevelForCreate("ISOLATION_SERIALIZABLE");
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setValidateTransactionState(false);
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public JobBuilderFactory jobBuilderFactory(JobRepository jobRepository){
return new JobBuilderFactory(jobRepository);
}
#Bean
public StepBuilderFactory stepBuilderFactory(JobRepository jobRepository, PlatformTransactionManager transactionManager){
return new StepBuilderFactory(jobRepository, transactionManager);
}
}
This was copied from:https://github.com/hantsy/spring4-sandbox/blob/master/batch-jpa/src/main/java/com/hantsylabs/example/spring/config/JpaBatchConfigurer.java
These kind of problems occur with older version of java like jdk 6 or further lower versions.Upgrade your jdk version to 7 or above. Even i do had the same kinda issue before which gone when i updated my jdk version to 7.
I have a java stuts2 web application using spring and hibernate.
Im getting org.hibernate.HibernateException: No Session found for current thread.
SpringBean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="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.1.xsd">
<context:component-scan base-package="org.rohith" />
<context:annotation-config />
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/company" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</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">update</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>hibernate.cfg.xml</value>
</list>
</property>
</bean>
<!-- <tx:annotation-driven/> -->
<bean id = "transactionManager" class = "org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name = "sessionFactory" ref = "sessionFactory" />
</bean>
<bean id="customerDaoImpl" class="org.rohith.dao.impl.CustomerDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="customerServiceImpl" class="org.rohith.service.impl.CustomerServiceImpl">
<property name="customerDaoImpl" ref="customerDaoImpl"/>
</bean>
</beans>
hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Names the annotated entity class -->
<mapping class="org.rohith.model.Customer"/>
</session-factory>
</hibernate-configuration>
CustomerServiceImpl.java
package org.rohith.service.impl;
import org.rohith.dao.impl.CustomerDaoImpl;
import org.rohith.model.Customer;
import org.rohith.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class CustomerServiceImpl implements CustomerService {
#Autowired
private CustomerDaoImpl customerDaoImpl;
#Override
public void saveCustomer(Customer customer) {
customerDaoImpl.saveCustomer(customer);
}
public CustomerDaoImpl getCustomerDaoImpl() {
return customerDaoImpl;
}
public void setCustomerDaoImpl(CustomerDaoImpl customerDaoImpl) {
this.customerDaoImpl = customerDaoImpl;
}
}
CustomerDaoImpl.java
package org.rohith.dao.impl;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.rohith.dao.CustomerDao;
import org.rohith.model.Customer;
public class CustomerDaoImpl implements CustomerDao {
private SessionFactory sessionFactory;
#Override
public void saveCustomer(Customer customer) {
Session session = getSession();
session.clear();
try {
session.saveOrUpdate(customer);
session.flush();
} catch (Throwable e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public SessionFactory getSessionFactory() {
return this.sessionFactory;
}
public Session getSession() throws HibernateException {
Session sess = getSessionFactory().getCurrentSession();
if (sess == null) {
sess = getSessionFactory().openSession();
}
// Session sess = getSessionFactory().openSession();
return sess;
}
}
CustomerAction.java
public class CustomerAction extends ActionSupport{
private String name;
private String addr1;
private String addr2;
private String city;
private String state;
private CustomerServiceImpl customerServiceImpl;
//Getters and setters
public String execute(){
Customer cust= new Customer();
cust.setName(name);
cust.setAddress1(addr1);
cust.setAddress2(addr2);
cust.setCity(city);
cust.setState(state);
System.out.println(name);
WebApplicationContext webApplicationContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(getRequest().getSession()
.getServletContext());
customerServiceImpl = (CustomerServiceImpl) webApplicationContext.getBean("customerServiceImpl");
customerServiceImpl.saveCustomer(cust);
//saveCust(cust);
return "success";
}
protected HttpServletRequest getRequest() {
return ServletActionContext.getRequest();
}
protected HttpServletResponse getResponse() {
return ServletActionContext.getResponse();
}
}
The Exception I am getting
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:978)
org.rohith.dao.impl.CustomerDaoImpl.getSession(CustomerDaoImpl.java:33)
org.rohith.dao.impl.CustomerDaoImpl.saveCustomer(CustomerDaoImpl.java:16)
org.rohith.service.impl.CustomerServiceImpl.saveCustomer(CustomerServiceImpl.java:18)
org.rohith.CustomerAction.execute(CustomerAction.java:36)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:606)
You have a transaction manager specified in your Spring config, but no configuration on when or where to apply transactions.
In your SpringBean.xml you should uncomment <tx:annotation-driven/>:
<tx:annotation-driven transaction-manager="transactionManager"/>
And then you should annotate the CustomerServiceImpl.saveCustomer method as #Transactional:
#Service
public class CustomerServiceImpl implements CustomerService {
...
#Override
#Transactional
public void saveCustomer(Customer customer) {
customerDaoImpl.saveCustomer(customer);
}
...
}
Add the following property in your hibernate.cfg.xml file for Hibernate 4.
<property name="hibernate.current_session_context_class">thread</property>
Add #EnableTransactionManagement annotation in your java configuration file if your configuration based on annotation without xml.
This may help someone
You can add in hibernate.proerties below value. It's worked for me.
hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
or
hibernate.current_session_context_class=thread
So, I try to use Spring for transaction management. Finally my application had worked, but deleting do not commits. Before using Spring I managed transactions in DAO, and I thought that after I can remove this code. But now deleting do not commit to DB.
My DAO:
protected Session getSession() {
Session session = sessionFactory.openSession();
ThreadLocalSessionContext.bind(session);
return session;
}
public void delete(T t) {
Session session = getSession();
// **this approach I used before**
// session.beginTransaction();
// try {
// session.delete(t);
// session.getTransaction().commit();
// } catch (Exception e) {
// session.getTransaction().rollback();
// }
session.delete(t);
}
My applicationContext:
<?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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation=" http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-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/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://ngs-java-srv.synapse.com:3306/mybase" />
<property name="username" value="user" />
<property name="password" value="password" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- <property name="configLocation" value="hibernate.cfg.xml" /> -->
<property name="packagesToScan" value="todolist.entity" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- dao beans -->
<bean id="userDao"
class="todolist.dao.hibernate.UserDaoImpl">
<constructor-arg>
<value>todolist.entity.User</value>
</constructor-arg>
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="itemDao"
class="todolist.dao.hibernate.ItemDaoImpl">
<constructor-arg>
<value>todolist.entity.Item</value>
</constructor-arg>
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- service bean -->
<bean id="userService" class="todolist.service.UserServiceImpl">
<property name="userDao" ref="userDao" />
<property name="itemDao" ref="itemDao" />
</bean>
<!-- transaction manager -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj"/>
<!-- <tx:annotation-driven/> -->
<!-- <aop:config>
<aop:pointcut id="serviceMethods"
expression="execution(* todolist.service.UserService.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRES_NEW" />
<tx:method name="deleteItem" no-rollback-for="Exception" />
</tx:attributes>
</tx:advice> -->
<!-- backbeans -->
<bean id="userLogin" class="todolist.jsf.UserLogin"
scope="request">
<property name="userService" ref="userService" />
</bean>
<bean id="userLogged" class="todolist.jsf.UserLogged"
scope="session">
<aop:scoped-proxy />
</bean>
<bean id="userRegister" class="todolist.jsf.UserRegister"
scope="request">
<property name="userService" ref="userService" />
</bean>
<bean id="createItem" class="todolist.jsf.CreateItem"
scope="request">
<property name="userService" ref="userService" />
</bean>
<bean id="todoList" class="todolist.jsf.TodoList"
scope="request">
<property name="userService" ref="userService" />
</bean>
</beans>
UserServiceImpl class
package todolist.service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import todolist.exception.AuthorizationError;
import todolist.exception.DuplicateLoginsException;
import todolist.service.StringToHashTool;
import todolist.dao.ItemDao;
import todolist.dao.UserDao;
import todolist.entity.Item;
import todolist.entity.User;
public class UserServiceImpl implements UserService {
//private static final Logger log = Logger.getLogger(UserServiceImpl.class);
private UserDao userDao;
private ItemDao itemDao;
public void setItemDao(ItemDao itemDao) {
this.itemDao = itemDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean isUserExists(String login) {
return (userDao.getUserByLogin(login) != null);
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean isPasswordCorrect(String login, String password) {
if (isUserExists(login)) {
return userDao.getUserByLogin(login).getPassword()
.equals(StringToHashTool.getHash(password));
} else {
return false;
}
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public User login(String login, String password) {
if (isPasswordCorrect(login, password)) {
return userDao.getUserByLogin(login);
} else {
throw new AuthorizationError("Incorrect password");
}
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean register(String login, String password) {
if (isUserExists(login)) {
throw new DuplicateLoginsException("Login " + login + " is already used.");
} else {
User user = new User();
user.setLogin(login);
user.setPassword(StringToHashTool.getHash(password));
userDao.save(user);
return true;
}
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void logout() {
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Item createItem(Long creator_id, Long performer_id,
String description, Date dueDate) {
Item item = new Item();
User user = userDao.getById(creator_id);
item.setCreator(user);
user = userDao.getById(performer_id);
item.setPerformer(user);
item.setDescription(description);
item.setStartDate(new Date());
item.setDueDate(dueDate);
itemDao.save(item);
return item;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void deleteItem(Long item_id) {
Item item = itemDao.getById(item_id);
itemDao.delete(item);
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public List<Item> getUserItems(String login) {
User user = userDao.getUserByLogin(login);
return itemDao.getItemsByPerformer(user.getId());
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public List<User> getUsers() {
return userDao.getUsers();
}
}
<aop:pointcut
id="serviceMethods"
expression="execution(*todolist.service.UserService.*(..))"
/>
First Step:
Change this to:
<aop:pointcut
id="serviceMethods"
expression="todolist.service.UserServiceImpl.delete()"
/>
See if delete's start committing. I just want to make sure you flush out any typos and what have you. If this explicit config fails then there must be something wrong with the config.
I have had success using the Spring HibernateTransactionManager object and annotation driven configuration. In my application-context, I simply declare the following two beans:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="session_factory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
Then in my service layer, your UserServiceImpl for example:
public class UserServiceImpl implements UserService {
private final UserDao userDao;
private final ItemDao itemDao;
public UserServiceImpl(UserDao userDao, ItemDao itemDao) {
this.userDao = userDao;
this.itemDao = itemDao;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean isUserExists(String login) {
return (userDao.getUserByLogin(login) != null);
}
...
It's important to move you transaction annotations as close to the view (in MVC) as possible. The reason is 1) performance and 2) atomic transactions. If the call calling the UserService calls the isUserExists, isPasswordCorrect and login methods in a single function call, you would want those to execute in a single transaction. To do so, remove the #Transactional annotation from the UserServiceImpl and move it to the calling class. This ensures that a single transaction is used for all three method calls. For atomic execution, this also works. Think of the classic bank money transfer example. If you had two separate transactions on the UserService, the first two method call can succeed and the last one fail. The firt two transactions would be committed because they are in separate transaction blocks which would leave you database in an inconsistent state. Of course, atomic transactions are mostly relevant for transactions that write data.
Also, don't add the transactional annotations to the interfaces, keep them on the implementations.
Then in my DAO, I simply do a sessionFactory.getCurrentSession() and go from there. getCurrentSession() is thread safe but keep it private to your DAOs.
Use getCurrentSession instead of openSession in your DAO, so that the getSession method looks like:
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
You're creating a new session that uses a different database connection so the transaction set up for the service doesn't apply.
Also as you have them configured the DAOs will have a default scope of singleton; passing an entity as a constructor-argument doesn't make sense. A singleton shouldn't have instance state specific to some individual transaction.