-
- Transaction-Enabled Transfer Example
-
1.1 Implementation
-
Data Access Layer
-
Database Connection Utility
-
Transaction Management Utility
-
bean.xml Configuration
-
1.2 Service Layer Transaction Control Issues
-
- Transfer Control Using Proxy Pattern
-
2.1 Proxy Pattern Review
-
Interface-Based Dynamic Proxy
-
Class-Based Dynamic Proxy
-
2.2 Refactoring Transfer Case with Dynamic Proxy
-
Proxy Factory Class
-
Transfer Service Class (Target)
-
Remaining Issues (AOP Motivation)
-
- AOP in Spring Framework
-
3.1 AOP Core Concepts
-
3.2 XML-Based AOP Configuration
-
3.3 AOP Advice Types
-
3.4 Annotation-Based AOP Configuration
-
Related Annotations
-
3.5 Transfer Case with AOP Annotations
1-1 Implementation
Data Access Layer
package com.example.demo.dao.impl;
import com.example.demo.dao.IAccountDao;
import com.example.demo.domain.Account;
import com.example.demo.utils.ConnectionHolder;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.SQLException;
import java.util.List;
public class AccountDao implements IAccountDao {
private QueryRunner queryRunner;
private ConnectionHolder connectionHolder;
public void setConnectionHolder(ConnectionHolder connectionHolder) {
this.connectionHolder = connectionHolder;
}
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
public List<Account> findAllAccounts() {
try {
return queryRunner.query(
connectionHolder.getConnection(),
"SELECT * FROM account",
new BeanListHandler<>(Account.class)
);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public Account findAccountById(Integer id) {
try {
return queryRunner.query(
connectionHolder.getConnection(),
"SELECT * FROM account WHERE id = ?",
new BeanHandler<>(Account.class),
id
);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public void insertAccount(Account account) {
try {
queryRunner.update(
connectionHolder.getConnection(),
"INSERT INTO account(name, balance) VALUES(?, ?)",
account.getName(),
account.getBalance()
);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void modifyAccount(Account account) {
try {
queryRunner.update(
connectionHolder.getConnection(),
"UPDATE account SET name = ?, balance = ? WHERE id = ?",
account.getName(),
account.getBalance(),
account.getId()
);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void removeAccount(Integer id) {
try {
queryRunner.update(
connectionHolder.getConnection(),
"DELETE FROM account WHERE id = ?",
id
);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public Account findAccountByName(String name) {
try {
List<Account> accounts = queryRunner.query(
connectionHolder.getConnection(),
"SELECT * FROM account WHERE name = ?",
new BeanListHandler<>(Account.class),
name
);
if (accounts == null || accounts.isEmpty()) {
return null;
}
if (accounts.size() > 1) {
throw new RuntimeException("Multiple records found, data integrity issue");
}
return accounts.get(0);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
}
Database Connection Utility
- Connection Object: Thread isolation using ThreadLocal
package com.example.demo.utils;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* Manages database connections with thread-local storage for isolation.
*/
public class ConnectionHolder {
private ThreadLocal<Connection> threadConnection = new ThreadLocal<>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Connection getConnection() {
Connection conn = threadConnection.get();
try {
if (conn == null) {
conn = dataSource.getConnection();
threadConnection.set(conn);
}
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void cleanup() {
threadConnection.remove();
}
}
Transaction Management Utility
package com.example.demo.utils;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Transaction management utility providing:
* 1) Begin transaction
* 2) Commit transaction
* 3) Rollback transaction
* 4) Release connection
*/
public class TransactionManager {
private ConnectionHolder connectionHolder;
public void setConnectionHolder(ConnectionHolder holder) {
this.connectionHolder = holder;
}
public void startTransaction() {
try {
connectionHolder.getConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void commitTransaction() {
try {
connectionHolder.getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void rollbackTransaction() {
try {
connectionHolder.getConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void closeConnection() {
try {
connectionHolder.getConnection().close();
connectionHolder.cleanup();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
bean.xml 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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.demo"/>
<bean id="accountService" class="com.example.demo.service.impl.AccountService">
<property name="accountDao" ref="accountDao"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="accountDao" class="com.example.demo.dao.impl.AccountDao">
<property name="queryRunner" ref="queryRunner"/>
<property name="connectionHolder" ref="connectionHolder"/>
</bean>
<!-- QueryRunner as prototype scope for thread safety -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"/>
<bean id="connectionHolder" class="com.example.demo.utils.ConnectionHolder">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/demo"/>
<property name="user" value="root"/>
<property name="password" value="password"/>
</bean>
<bean id="transactionManager" class="com.example.demo.utils.TransactionManager">
<property name="connectionHolder" ref="connectionHolder"/>
</bean>
</beans>
1-2 Service Layer Transaction Control and Problems
package com.example.demo.service.impl;
import com.example.demo.dao.IAccountDao;
import com.example.demo.domain.Account;
import com.example.demo.service.IAccountService;
import com.example.demo.utils.TransactionManager;
import java.util.List;
public class AccountService implements IAccountService {
private IAccountDao accountDao;
private TransactionManager transactionManager;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public List<Account> findAllAccounts() {
try {
transactionManager.startTransaction();
List<Account> accounts = accountDao.findAllAccounts();
transactionManager.commitTransaction();
return accounts;
} catch (Exception e) {
transactionManager.rollbackTransaction();
} finally {
transactionManager.closeConnection();
}
return null;
}
public Account findAccountById(Integer id) {
try {
transactionManager.startTransaction();
Account account = accountDao.findAccountById(id);
transactionManager.commitTransaction();
return account;
} catch (Exception e) {
transactionManager.rollbackTransaction();
} finally {
transactionManager.closeConnection();
}
return null;
}
public void insertAccount(Account account) {
try {
transactionManager.startTransaction();
accountDao.insertAccount(account);
transactionManager.commitTransaction();
} catch (Exception e) {
transactionManager.rollbackTransaction();
} finally {
transactionManager.closeConnection();
}
}
public void modifyAccount(Account account) {
try {
transactionManager.startTransaction();
accountDao.modifyAccount(account);
transactionManager.commitTransaction();
} catch (Exception e) {
transactionManager.rollbackTransaction();
} finally {
transactionManager.closeConnection();
}
}
public void removeAccount(Integer id) {
try {
transactionManager.startTransaction();
accountDao.removeAccount(id);
transactionManager.commitTransaction();
} catch (Exception e) {
transactionManager.rollbackTransaction();
} finally {
transactionManager.closeConnection();
}
}
@Override
public void transfer(String sourceName, String targetName, float amount) {
try {
transactionManager.startTransaction();
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetName);
source.setBalance(source.getBalance() - amount);
target.setBalance(target.getBalance() + amount);
accountDao.modifyAccount(source);
accountDao.modifyAccount(target);
transactionManager.commitTransaction();
} catch (Exception e) {
e.printStackTrace();
transactionManager.rollbackTransaction();
} finally {
transactionManager.closeConnection();
}
}
}
Identified Problems
Problem 1: Service methods are tightly coupled to the TransactionManager API.
Changing TransactionManager method names requires updating all service methods.
Problem 2: Significant code duplication in service layer for transaction handling.
Dynamic Proxy Benefits
1) No proliferation of proxy classes when adding target classes
2) Interface modifications don't affect proxy classes
- Problem 1 can be solved using Dynamic Proxy benefit #2
- Problem 2 is addressed by the Proxy Pattern, which externalizes transaction control
- Transfer Control Using Proxy Pattern =======================================
2-1 Proxy Pattern Review
Dynamic Proxy Characteristics: Proxy class bytecode is created on-demand, loaded dynamically without modifying source code.
Types:
1) Interface-based dynamic proxy
2) Class-based (CGLIB) dynamic proxy
2-1-1 Interface-Based Dynamic Proxy
Required Class: JDK's Proxy class
Creation Method: Proxy.newProxyInstance()
Requirement: Target class must implement at least one interface
Method Signature:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Parameters:
ClassLoader: Loads proxy class bytecode, use target's class loader
Class[]: Bytecode array for methods, proxy and target share same interface
InvocationHandler: Enhancement logic, defines how proxy methods behave
InvocationHandler Method:
Object invoke(Object proxy, Method method, Object[] args)
invoke Parameters:
proxy: Proxy instance reference
method: Currently executing method
args: Method arguments
Return value: Same as target method's return value
Example:
package com.example.demo.proxy;
public interface IProducer {
void sellProduct(float price);
void afterSalesService(float price);
}
package com.example.demo.proxy;
public class Producer implements IProducer {
@Override
public void sellProduct(float price) {
System.out.println("Product sold, revenue: " + price);
}
@Override
public void afterSalesService(float price) {
System.out.println("After-sales service, revenue: " + price);
}
}
package com.example.demo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
IProducer proxyInstance = (IProducer) Proxy.newProxyInstance(
producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
Float price = (Float) args[0];
if ("sellProduct".equals(method.getName())) {
result = method.invoke(producer, price * 0.8f);
}
return result;
}
}
);
proxyInstance.sellProduct(12000f);
}
}
Output:
Product sold, revenue: 9600.0
2-1-2 Class-Based Dynamic Proxy
Required Class: CGLIB's Enhancer
Creation Method: Enhancer.create()
Requirement: Target class must be non-final (extendable)
Method Parameters:
class: Target class bytecode
Callback: Enhancement logic, implements MethodInterceptor
intercept Parameters:
intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
obj: Proxy instance reference
method: Currently executing method
args: Method arguments
proxy: Method proxy for invocation
Example:
package com.example.demo.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer proxyInstance = (Producer) Enhancer.create(
producer.getClass(),
new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
Object result = null;
Float price = (Float) args[0];
if ("sellProduct".equals(method.getName())) {
result = method.invoke(producer, price * 0.8f);
}
return result;
}
}
);
proxyInstance.sellProduct(12000f);
}
}
2-2 Refactoring Transfer Case with Dynamic Proxy
Proxy Factory Class
package com.example.demo.factory;
import com.example.demo.domain.Account;
import com.example.demo.service.IAccountService;
import com.example.demo.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
/**
* Factory producing proxied AccountService instances with transaction handling.
*/
public class ServiceProxyFactory {
private IAccountService targetService;
private TransactionManager transactionManager;
public void setTargetService(IAccountService service) {
this.targetService = service;
}
public void setTransactionManager(TransactionManager manager) {
this.transactionManager = manager;
}
public IAccountService createProxy() {
return (IAccountService) Proxy.newProxyInstance(
targetService.getClass().getClassLoader(),
targetService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
transactionManager.startTransaction();
result = method.invoke(targetService, args);
transactionManager.commitTransaction();
return result;
} catch (Exception e) {
transactionManager.rollbackTransaction();
} finally {
transactionManager.closeConnection();
}
return null;
}
}
);
}
}
Transfer Service Class (Target)
package com.example.demo.service.impl;
import com.example.demo.dao.IAccountDao;
import com.example.demo.domain.Account;
import com.example.demo.service.IAccountService;
import java.util.List;
public class AccountService implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public List<Account> findAllAccounts() {
return accountDao.findAllAccounts();
}
public Account findAccountById(Integer id) {
return accountDao.findAccountById(id);
}
public void insertAccount(Account account) {
accountDao.insertAccount(account);
}
public void modifyAccount(Account account) {
accountDao.modifyAccount(account);
}
public void removeAccount(Integer id) {
accountDao.removeAccount(id);
}
@Override
public void transfer(String sourceName, String targetName, float amount) {
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targetName);
source.setBalance(source.getBalance() - amount);
target.setBalance(target.getBalance() + amount);
accountDao.modifyAccount(source);
accountDao.modifyAccount(target);
}
}
bean.xml Updated 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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.demo"/>
<bean id="accountService" class="com.example.demo.service.impl.AccountService">
<property name="accountDao" ref="accountDao"/>
</bean>
<bean id="accountDao" class="com.example.demo.dao.impl.AccountDao">
<property name="queryRunner" ref="queryRunner"/>
<property name="connectionHolder" ref="connectionHolder"/>
</bean>
<bean id="serviceProxyFactory" class="com.example.demo.factory.ServiceProxyFactory">
<property name="transactionManager" ref="transactionManager"/>
<property name="targetService" ref="accountService"/>
</bean>
<bean id="proxiedAccountService" factory-bean="serviceProxyFactory" factory-method="createProxy"/>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"/>
<bean id="connectionHolder" class="com.example.demo.utils.ConnectionHolder">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/demo"/>
<property name="user" value="root"/>
<property name="password" value="password"/>
</bean>
<bean id="transactionManager" class="com.example.demo.utils.TransactionManager">
<property name="connectionHolder" ref="connectionHolder"/>
</bean>
</beans>
Test Class
package com.example.demo.test;
import com.example.demo.domain.Account;
import com.example.demo.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
@Qualifier("proxiedAccountService")
private IAccountService accountService;
@Test
public void testFindAll() {
List<Account> accounts = accountService.findAllAccounts();
for (Account account : accounts) {
System.out.println(account);
}
}
@Test
public void testTransfer() {
accountService.transfer("alice", "bob", 100);
}
}
Remaining Issue (AOP Motivation)
While dynamic proxy solves the problems, complex XML configuration is still required
for proxy classes. Spring AOP simplifies this configuration significantly.
- AOP in Spring Framework ==========================
3-1 AOP Core Concepts
Definition: Aspect-Oriented Programming (AOP) separates cross-cutting concerns by extracting repeated code and applying it via dynamic proxy without modifying source code.
Mechenism: Pre-compilation or runtime dynamic proxy to enhance methods without changing implementation
Benefits:
- Eliminates code duplication
- Improves development efficiency
- Easier maintenance (focus only on enhancement logic)
AOP Terminology:
Joinpoint: Methods in a class that can be enhanced
Pointcut: Definition of wich Joinpoints to intercept
Advice: Actions to perform when intercepting Joinpoints. Types include:
- Before advice
- After returning advice
- After throwing advice
- After (finally) advice
- Around advice
Aspect: Combination of Pointcuts and Advice
Introduction: Special advice to dynamically add methods or fields to classes
Target: The object being proxied
Weaving: Process of applying advice to target
Proxy: Resulting class after weaving
3-2 XML-Based AOP Configuration
Requirement: Add logging before service method execution
Steps:
1) Register advice bean with Spring
2) Use aop:config to begin AOP configuration
3) Use aop:aspect to define aspect
4) Configure advice types with pointcut expressions
Pointcut Expression Syntax:
execution([modifiers] returnType [classPath].methodName(parameters))
Examples:
execution(public void com.example.demo.service.impl.AccountService.saveAccount())
execution(* com.example.demo.service.impl.*.*(..)) // Wildcards for any modifiers, return, methods
Logger Advice Class:
package com.example.demo.logger;
public class Logger {
public void logBefore() {
System.out.println("Before advice: Logging started");
}
}
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountService" class="com.example.demo.service.impl.AccountService"/>
<bean id="logger" class="com.example.demo.logger.Logger"/>
<aop:config>
<aop:aspect id="logAspect" ref="logger">
<aop:before method="logBefore" pointcut="execution(* com.example.demo.service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
package com.example.demo.aop;
import com.example.demo.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AopDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
IAccountService service = (IAccountService) context.getBean("accountService");
service.saveAccount();
}
}
Output:
Before advice: Logging started
Account saved
3-3 AOP Advice Types
try {
try {
// @Before
method.invoke();
} finally {
// @After
}
// @AfterReturning
} catch() {
// @AfterThrowing
}
| Advice Type | Characteristics |
|---|---|
| Before | Executes before target method |
| After Returning | Executes after successful completion |
| After Throwing | Executes when exception occurs |
| After (Finally) | Always executes |
| Around | Full control over execution timing |
Around Advice: Requires ProceedingJoinPoint.proceed() to execute target method.
package com.example.demo.logger;
import org.aspectj.lang.ProceedingJoinPoint;
public class Logger {
public Object aroundAdvice(ProceedingJoinPoint pjp) {
Object result = null;
try {
System.out.println("Before");
result = pjp.proceed();
System.out.println("After Returning");
return result;
} catch (Throwable t) {
System.out.println("After Throwing");
throw new RuntimeException(t);
} finally {
System.out.println("After (Finally)");
}
}
}
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.example.demo.service.impl.*.*(..))"/>
<aop:aspect id="logAspect" ref="logger">
<aop:around method="aroundAdvice" pointcut-ref="pt1"/>
</aop:aspect>
</aop:config>
3-4 Annotation-Based AOP Configuration
Related Annotations
| Annotation | Purpose |
|---|---|
| @Aspect | Declares class as aspect |
| @Pointcut | Defines pointcut expression |
| @Before | Before advice |
| @AfterReturning | After successful return |
| @AfterThrowing | After exception |
| @After | Finally advice |
| @Around | Around advice |
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.example.demo"/>
<aop:aspectj-autoproxy/>
</beans>
package com.example.demo.logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Logger {
@Pointcut("execution(* com.example.demo.service.impl.*.*(..))")
private void pointcut() {}
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint pjp) {
Object result = null;
try {
System.out.println("Before");
result = pjp.proceed();
System.out.println("After Returning");
return result;
} catch (Throwable t) {
System.out.println("After Throwing");
throw new RuntimeException(t);
} finally {
System.out.println("After (Finally)");
}
}
}
Note: With annotation-based advice, @After and @AfterReturning execution order may vary. Around advice provides precise control over execution sequence.
3-5 Transfer Case with AOP Annotations
package com.example.demo.factory;
import com.example.demo.utils.TransactionManager;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TransactionAspect {
@Autowired
private TransactionManager transactionManager;
@Pointcut("execution(* com.example.demo.service.impl.*.*(..))")
private void serviceMethods() {}
@Around("serviceMethods()")
public Object manageTransaction(ProceedingJoinPoint pjp) {
Object result;
try {
System.out.println("Starting transaction");
transactionManager.startTransaction();
result = pjp.proceed();
transactionManager.commitTransaction();
System.out.println("Committing transaction");
return result;
} catch (Throwable t) {
transactionManager.rollbackTransaction();
throw new RuntimeException(t);
} finally {
transactionManager.closeConnection();
System.out.println("Connection released");
}
}
}
Summary: AOP annotations enable declarative transaction management by enhancing service layer operations without modifying business logic code.