Spring 3 : Entity Object is getting persisted even after Exception throw - spring

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.

Related

Spring boot AOP configuration in external Jar file

I am writing a new spring boot app that is using existing jar files that work fine in other non spring boot applications (they are using spring).
In our service layer, we are using spring transactions and spring AOP that have their configurations defined in an XML file. For example:
<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'find' are read-only -->
<tx:method name="find*" read-only="true" propagation="REQUIRED"/>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true" propagation="REQUIRED"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- ensure that the above transactional advice runs for any execution
of an operation on any service -->
<aop:config>
<aop:advisor pointcut="com.company.app.common.aspect.SystemArchitecture.serviceTransaction()"
advice-ref="dbTransaction" order="1010" />
<aop:aspect ref="verifyDatasourceSwitch" order="1001">
<aop:around pointcut="com.company.app.common.aspect.SystemArchitecture.serviceOperation()"
method="verifyDatasource" />
</aop:aspect>
<aop:aspect ref="appDatasourceSwitch" order="12">
<aop:around pointcut="com.company.app.common.aspect.SystemArchitecture.controlDatasource()"
method="switchToControl" />
</aop:aspect>
<aop:aspect ref="controlMongoDatabaseSwitch" order="13">
<aop:around pointcut="com.company.app.common.aspect.SystemArchitecture.controlMongoDatabase()"
method="switchToControl" />
</aop:aspect>
</aop:config>
The aspects and pointcuts are defined like this:
#Aspect
public class SystemArchitecture
{
/**
* A join point in the service layer if the method is defined
* in a type in the com.company.app.service package or
* any sub-package under that
*/
#Pointcut("within(com.company.app.service..*)")
public void inServiceLayer() {}
#Pointcut("inServiceLayer() && this(com.company.app.service.Service)")
public void serviceRegularOperation() {}
/**
* A service operation is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementations are in sub-packages.
*/
#Pointcut("serviceRegularOperation() || serviceBrandOperation() || serviceCustomerOperation() || serviceWithChangeLoggingOperation() || serviceNonStandardService()")
public void serviceOperation() {}
#Pointcut("serviceOperation() && !#annotation(com.company.app.service.advice.NoTransaction)")
public void serviceTransaction() {}
}
This is all working correctly in other apps that are using it.
In the spring boot app, I have a configuration class that includes the xml config containing the above.
#Configuration
#PropertySource("classpath:config/application.properties")
#ImportResource({ "classpath:aspect.xml",
"classpath:rabbit.xml",
"classpath:email.xml",
"classpath:approval.xml",
"classpath:event.xml",
"classpath:facades.xml",
"classpath:general.xml",
"classpath:velocity.xml",
"classpath:mongo.xml",
"classpath:services.xml",
"classpath:velocity-service.xml",
"classpath:velocity-facade.xml",
"classpath:sharedservices.xml",
"classpath:jobs.xml",
"classpath:cachesharedfacades.xml",
"classpath:sharedContext-security.xml",
"classpath:hibernate-common-session-factory.xml",
"classpath:hibernate-datasource.xml",
"classpath:hibernate-local-transaction-manager.xml",
"classpath:hibernate-mysql-session-factory.xml",
"classpath:hibernate-repositories.xml",
"classpath:mysql-repositories.xml",
"classpath:hibernate-template.xml",
"classpath:repository-mbeans.xml",
"classpath:spring-transaction.xml" })
public class ApplicationConfiguration
{
}
The main application is created as:
#SpringBootApplication
public class GraphqlApplication
{
public static void main( String[] args )
{
SpringApplication.run( GraphqlApplication.class, args );
}
}
When the application is started up, I see warnings such as:
Bean 'adminTypeService' of type [com.company.app.service.impl.AdminTypeServiceImpl]
is not eligible for getting processed by all BeanPostProcessors
(for example: not eligible for auto-proxying)
Which I assume is causing the proxying not to happen, which is probably what is stopping the AOP calls from working.
Am I missing something? I can see the beans defined in the services.xml are created, but when calling a service methods, they interface isn't proxied, so there are no transactions or other supporting functions called via AOP that need to happen.
Thanks in advance!

Hibernate EntityInterceptor (EmptyInterceptor): Filter out all #Transaction(readOnly=TRUE) Transactions

I defined and successfully plugged in a Hibernate DB Interceptor which catches all Transactions.
public class HibernateTransactionInterceptor extends EmptyInterceptor {
#Override
public void afterTransactionBegin(Transaction tx) {
System.out.println("Intercepted");
// ...
super.afterTransactionBegin(tx);
}
}
applicationContext.xml:
<bean id="transactionInterceptor" class="myapp.interceptor.HibernateTransactionInterceptor" />
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- Plug into SessionFactory the Interceptor bean define above -->
<property name="entityInterceptor" ref="transactionInterceptor" />
...
</bean>
Now, the Interceptor fires on all #Transaction service methods. But I need to only intercept #Transaction(readOnly=FALSE) methods (i.e., filter out all Read-Only methods). Is there a way to configure that?
I found the solution. There's an easy way to check for Read-Only/non-Read-Only Transactions with a Spring-level transaction listener. On the Spring level, rather than Hibernate, there's a method called TransactionSynchronizationManager.isCurrentTransactionReadOnly(). It can be called from the following implementation of TransactionSynchronization which uses a pointcut to intercept all #Before #Transactional Spring processing.
#Component
#Aspect
public class TransactionSynchronizationAspect implements TransactionSynchronization {
#Before("#annotation(org.springframework.transaction.annotation.Transactional)")
public void registerTransactionSynchronization() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if (!isReadOnly) {
// ... logic goes here...
}
// Continue
TransactionSynchronizationManager.registerSynchronization(this);
}
#Override
public void afterCompletion(int status) {
// No custom code here
}
}
As for the original Hibernate Transaction Listener in the Original Post, that strategy can still be used, but requires a tx:advice to restrict the Listener to specified (mapped) Service method names, like so:
<aop:config>
<aop:advisor id="managerTx" advice-ref="txAdvice"
pointcut="execution(* com.app.*.*.service.*.*(..))"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="customTransactionManager">
<tx:attributes>
<tx:method name="delete*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
<tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
</tx:attributes>
</tx:advice>
This XML file can be imported with #ImportResource. With this strategy, all Write operations would be intercepted with the Listener, while Reads would always go through automatically.

Spring transaction propogation on non spring managed beans

I am working on a migration project which involves upgrading the platform to Spring 4 with MyBatis. In the legacy code, transactions are handled at a central locations wherein call to start/end transactions are spread across various classes like service class, helper class and DAO class.
I managed to convert all service classes to spring managed component and DAO classes to support MyBatis-spring API. Problem is my service class use several other classes to perform a function and those classes are all instantiated manually and used. Now if i start a transaction on service class methods and perform database transactions inside other helper or DAO classes which are not spring managed, my transaction handling doesn't work correctly. I have illustrated this problem in the below code. Could you tell what are the ways to acheive transaction handling without modifying the code?
Example :
package com.service;
#Service
class MyService {
#Transactional( propagation=Propagation.REQUIRED)
public void processRequest () {
HelperClass helper = new HelperClass();
helper.performOperation();
}
}
package com.helper;
// this class is not spring bean
class HelperClass {
// MyBatis mapper class
private EmployeeMapper mapper;
public HelperClass () {
mapper = // retrieve mapper class bean from spring context
}
public performOperation () {
// call to mapper class insert operation
// call to mapper class update operation
}
}
package com.dao;
#Component
interface EmployeeMapper {
// method definition to perform database operation
}
Spring configuration details:
<context:component-scan base-package="com" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
....
....
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" mode="aspectj" />
<mybatis:scan base-package="com.dao" />
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations"
value="classpath*:mybatis/*.xml" />
</bean>
In the above code HelperClass.performOperation() method is doing 2 database operations (insert,update). Say if insert succeeds and update fails, my database transaction doesn't get rollback. Since I already started the transaction at MyService.processRequest() should this not rollback the operations that are carried inside that method call? Correct me if my understanding is wrong.

Spring + hibernate, nested NOT_SUPPORTED transaction attribute

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.

Spring AOP and Exception Intercepting

I'm trying to configure Spring so that it executes advice when a specific exception subclass (MyTestException) is thrown:
public class MyTestExceptionInterceptor implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target, Exception exc) {
// I want this to get executed every time a MyTestException is thrown,
// regardless of the package/class/method that is throwing it.
}
}
And the XML config:
<bean name="interceptor" class="org.me.myproject.MyTestExceptionInterceptor"/>
<aop:config>
<aop:advisor advice-ref="interceptor" pointcut="execution(???)"/>
</aop:config>
I have a feeling that I should be using the target pointcut specifier (instead of execution) since - according to the Spring docs - it seems as though target allows me to specify the type of exception to match against, but I'm not sure if that's wrong, or what my pointcut attribute needs to look like.
I would greatly prefer to keep the AOP config done in XML (as opposed to Java/annotations, but I could probably translate an annotation-based solution into XML if need be.
I'd use an <aop:after-throwing> element and its throwing attribute.
Spring config
<bean name="tc" class="foo.bar.ThrowingClass"/>
<bean name="logex" class="foo.bar.LogException"/>
<aop:config>
<aop:aspect id="afterThrowingExample" ref="logex">
<aop:after-throwing method="logIt" throwing="ex"
pointcut="execution(* foo.bar.*.foo(..))"/>
</aop:aspect>
</aop:config>
The throwing attribute is the parameter name of the aspect's handler method (here it's LogException.logIt) that gets called on the exception:
Aspect
public class LogException {
public void logIt(AnException ex) {
System.out.println("*** " + ex.getMessage());
}
}
The XML and method combo defines the exception type that the aspect applies to. In this example, ThrowingClass throws AnException and AnotherException. Only AnException will have the advice applied because of the advice's method signature.
See example project on github for full source.
Check out a AfterThrowingAdvice. An example is found here (search for "After throwing advice") and you'll find it.

Resources