public class BusinessService { //spring bean
public dumpAllData(List){
/* Complicated DB operation here
* We dont want to be in transaction now (because of performance issues)
*/
for(...){ //iterating through whole list
**updateItem(item);**
}
}
public updateItem(Entity e){
//saves entity into DB
//we want to be in transaction now
}
}
Spring configuration :
<tx:advice id="txAdvice" transaction-manager="wsTransactionManager">
<tx:attributes>
<tx:method name="dumpAllData" propagation="NOT_SUPPORTED" />
<tx:method name="updateItem" propagation="REQUIRES_NEW" />
</tx:attributes>
</tx:advice>
Is possible to have nested REQUIRED_NEW propagation which will be called from method with propagation NOT_SUPPORTED ?
Thing is we run an extensive DB operation (~ 100Mb) in dumpAllData() so we dont want to be in transaction (oterwise it would be performance issue). But we want to be in transaction (rollback/commit) in updateItem method (where we do just simple update of entities).
I fail to see how being inside a transaction or not has an incidence on performance. Have you measured a performance difference, or are you just guessing?
Anyway, if you really need to do this, then the updateItem method should be in another Spring bean, injected into the BusinessService bean.
Indeed, Spring is only able to start/commit a transaction when a bean method is called through a proxy. If you call a bean method from another method of the same bean, Spring can't intercept the call and do its transaction management.
Your transaction annotation in update method will not be intercept by Spring transaction infrastructure if called from some method of same class. For more understanding on how Spring transaction works please refer to Spring Transaction.
Related
I want to test a whole Spring Batch but Ihave a problem. I have a service to delete rows in a DB and in a LDAP.
For the BD I have implemented a H2 in-memory database so no problems.
For the LDAP it's more difficult to have a in-memory LDAP so I want to fake the DAO LDapRepository calling method "delete" (LDapRepository is the interface and LDapRepositoryImpl annotated by #Repository the implementation)
So I tried to inject a mock in my configuration but it doesn't work.
The test fails with a null pointer exception when I try to make the fake call ldapRepository.removePerson (so ldapRepository is not correctly injected).
How is it possible to substitute the bean LDapRepository in my configuration ?
Here's the test code :
#ContextConfiguration(locations = {"classpath:purge/job-test.xml"})
public class PurgeJobTest {
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
#InjectMocks
#Qualifier(value = "ldapRepositoryImpl")
private LdapRepository ldapRepository;
#Test
public void testExampleJob() throws Exception {
Mockito.doNothing().when(ldapRepository.removePerson(any(String.class)));
JobParameters jobParameters = new JobParametersBuilder()
.addString("fichierJob", "/purge/job-test.xml")
.addString("nomJob", "purgeJob").toJobParameters();
JobExecution exec =jobLauncherTestUtils.launchJob(jobParameters);
assertEquals(BatchStatus.COMPLETED, exec.getStatus());
}
}
OK so I admit my question was a little tricky but I want to share the answer to other people who faced a similar problem.
The null pointer exception was quite logic. Indeed the bean was not recognized in the context because nowhere it was correctly injected in it.
It's a common pitfall related by example in this post Injecting Mockito Mock objects using Spring JavaConfig and #Autowired.
or this other post : Injecting Mockito mocks into a Spring bean
So in my job-test.xml I commented the component-scan in which my "real" LdapRepository" and LdapRepositoryImpl were declared and replace them with :
<bean id="ldapRepository" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="purge.batch.repository.LdapRepository" />
</bean>
<bean id="ldapRepositoryImpl" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="purge.batch.repository.LdapRepositoryImpl" />
</bean>
(I could have place these bean declaration before the component-scan to give it priority)
And that works like a charm !
For example, I have 3 beans in my spring configuration: A, B, C. And I want to create bean B and C as usual. And than (when all others beans were created) I want to ask spring to create bean A.
Any suggestion ?
Thanks.
Spring framework triggers a ContextRefreshedEvent once the contexts has been fully refreshed and all the configured beans have been created.
You could try to create a listener to catch that event and initialise bean A.
#Component
public class ContextRefreshedEventListener implements
ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
// Init your bean here
}
}
You should try #DependsOn adnotation
For example
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
I know it's not really a bean ordering answer, but maybe you can achieve your goal with a #PostConstruct method that will be called just after a bean is constructed, dependencies are injected and all properties are set.
best nas
Easier way to do this would be using #Lazy annotation to your bean. This makes your bean do not get initialized eagerly during context initialization. In simple words,your bean will get created when you ask for it, not before.
#Bean
#Lazy
public A beanA() {
//some code here
}
transaction has been initiated using following codes in Application Context file:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*InOwnTransaction" propagation="REQUIRES_NEW"
rollback-for="com.dummy.common.exception.DummyException" />
<tx:method name="*" propagation="REQUIRED"
rollback-for="com.dummy.common.exception.DummyException" />
</tx:attributes>
</tx:advice>
In Service impl, function for updating entity
public Offering selectiveUpdateInOwnTransaction(Map<String, String> offeringData) throws DummyException
{
// search data from DB
AbstractJpaEntity priceObj = selectEntityByCriteria(Price.class, paramMap);
// now "priceObj" contain requied data;
..
priceObj.setPublishedStatus(true);
// some businesslogic which is throwing exception <-- Point 1 : This is throwing exception
priceObj.setPrice(23);
..
updateEntity(priceObj);
}
Two function calling same above mentioned function "selectiveUpdateInOwnTransaction"
public boolean updateOffering(Offering offering) // In same Service Impl
public boolean updateOfferingInOwnTransaction(....) throws DummyException // In different Service Impl
In Both these function, call to "selectiveUpdateInOwnTransaction" is as follow:
try
{
selectiveUpdateInOwnTransaction(dataMap);
}
catch (DummyException e)
{
....
}
Issue:
Point 1 is throwing exception and "priceObj" should not be updated in DB, but when "selectiveUpdateInOwnTransaction" is called from "updateOffering", it get persisted in DB.
Also only contents that are persisted in DB are contents which are updated in object before exception throw.
Call from "updateOfferingInOwnTransaction" is showing no such error.
I am not able to understand why "updateOffering" is not working as per expectation.
A workaround
As quick fix, i did following changes in "selectiveUpdateInOwnTransaction", and after that, it worked fine (entity not persisted in DB on exception throw, as expected):
Price priceObj1 = new Price();
BeanUtils.copyProperties(priceObj, priceObj1);
updateEntity(priceObj1);
But in this also, I do not understand, why this is working ?
Other configuration details
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<aop:config>
<aop:pointcut id="serviceOperation"
expression="execution(* com.dummy.offering.db.service..*Service.*(..)) || execution(* com.dummy.common.db.service..*Service.*(..))" />
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice" />
</aop:config>
If you rely on name="*InOwnTransaction" propagation="REQUIRES_NEW" to be working for the method call from updateOffering then this won't happen. You make an internal call (self-invocation) for a proxied class: the internal call to selectiveUpdateInOwnTransaction from updateOffering will be a call to a regular (non-proxied) method.
I strongly suggest to read carefully this section of the documentation.
To directly apply what you have in your code with the sample in the documentation: SimplePojo is your ServiceImpl, foo() is your updateOffering and bar() is your selectiveUpdateInOwnTransaction. Think about a proxy as an entirely new class that intercepts calls to your own methods and classes.
So, basically, when you call updateOffering from your controller you are calling updateOffering on a different class (which is not ServiceImpl) instance.
This new class applies the transactional behavior (starting a new transaction, associating transactional resources with the current thread etc) and then calls the real updateOffering from your own ServiceImpl.
updateOffering then calls selectiveUpdateInOwnTransaction, but since this call is like this.selectiveUpdateInOwnTransaction then the call would be on your ServiceImpl, not the newly created class that acts as a proxy. Because of this, Spring treats your selectiveUpdateInOwnTransaction as a regular, nothing special method.
On the other hand, if selectiveUpdateInOwnTransaction is called from anther class, that another class will call that method on the proxy, and this is why it works if you call it from a different ServiceImpl.
In that section of the documentation, there is an ugly solution to this restriction
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
but the real acceptable approach would be to redesign your classes a bit: move updateOffering to another class.
I am using annotation based transactions in a Spring MVC 3.1 project, and my transactions are not being rolled back when an exception is thrown.
Here is my Service code
#Service
public class ImportService {
#Autowired
ImportMapper importMapper;
#Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, rollbackFor=Throwable.class)
public void processImport() throws ServiceException, DatabaseException {
iImport import = new Import();
createImport(import);
throw new ServiceException("");
}
#Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, rollbackFor=Throwable.class)
private void createImport(Import import) throws DatabaseException {
try {
importMapper.createImport(eventImport);
} catch (Exception e) {
throw new DatabaseException(e);
}
}
So, hopefully, the createImport method should be rolled back after the exception is thrown. But unfortunately it's not.
I am defining my datasource in the server context.xml
<Resource name="datasource.import" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="user" password="password" driverClassName="oracle.jdbc.driver.OracleDriver"
url="jdbc:oracle:thin:#INFO" />
And I'm looking it up with JNDI:
<jee:jndi-lookup id="dataSource" jndi-name="datasource.import"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
I'm using an Oracle database, and the JDBC spec says that auto commit is true by default. I thought that if I set it to false explicitly that would help, but I can't figure out how to do that.
Is there any way to get rollbacks working, while looking up your Oracle datasource by JNDI.
Please be aware that Spring's transaction management rolls-back transactions only for unchecked exceptions (RuntimeException) by default. If you wish to perform rollback also on checked exceptions, you need to define that.
When using annotations as attribute source, you need to provide rollbackFor attribute with the list of exception classes, which should cause a transaction to be rolled-back (quote from the JavaDoc):
/**
* Defines zero (0) or more exception {#link Class classes}, which must be a
* subclass of {#link Throwable}, indicating which exception types must cause
* a transaction rollback.
* <p>This is the preferred way to construct a rollback rule, matching the
* exception class and subclasses.
* <p>Similar to {#link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}
*/
Class<? extends Throwable>[] rollbackFor() default {};
It is said that, if a #transational method is invoked by another #transational method, the first one will not work. You may try to remove the first #transational and have a try.
I have following code inside my class
public void startListeners() throws Exception {
List<QueueConfiguration> queueConfigs = queueConfigResolver.getQueueConfigurations();
for(QueueConfiguration queueConfig : queueConfigs){
//TODO : work on this make it more testable
ICustomListener readerListener = new MyCustomListener(queueConfig);
readerListeners.add(readerListener);
readerListener.start();
}
}
I am using Spring for dependency injection(not in this case but overall). Now there two problems with this code.
I cannot put mock for each of the listeners created, while testing.
I dont want to use ApplicationContext.getBean() because it will have same affect. AFAIK spring cannot do this dynamically , but any other pointers?
As far as I can understand, you want to create a new bean instead of
ICustomListener readerListener = new MyCustomListener(queueConfig);
If that is the case, creating a factory for mycustomlistener and using
public abstract TestClient createTestClient();
to create your beans, and defining
<bean id="testClient" class="com.myproject.testbeans.TestClient" scope="prototype">
</bean>
<bean id="testClientFactory" class="com.myproject.testbeans.TestClientFactory">
<lookup-method name="createTestClient" bean="testClient" />
</bean>
in your context will solve your problem. This way, every time the createTestClient method of the factory is called, a new bean is created and given to your code. However, you have to give the config object via a setter instead of the constructor.