1. 项目概述
"实验六 AOP,JdbcTemplate及声明式事务"这个标题涵盖了Spring框架中三个核心技术的实践应用。作为Java开发者,掌握这些技术对于构建企业级应用至关重要。AOP(面向切面编程)解决横切关注点问题,JdbcTemplate简化数据库操作,而声明式事务则让复杂的事务管理变得优雅简洁。
我在实际项目中发现,很多开发者在单独使用这些技术时表现尚可,但当需要将它们组合应用时就容易出现问题。比如AOP代理与事务管理的冲突、JdbcTemplate在事务环境下的特殊表现等。本文将基于真实项目经验,带你深入理解这三个技术的原理和最佳实践方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心概念解析
2.1 AOP原理与实现机制
AOP的核心思想是将那些与业务无关但却为业务模块所共同调用的逻辑(如日志、事务、权限控制等)封装起来,减少重复代码,降低模块间耦合度。Spring AOP主要通过动态代理实现,有两种方式:
- JDK动态代理:基于接口实现,要求目标类必须实现至少一个接口
- CGLIB代理:通过继承目标类生成子类来实现代理,不需要接口
java复制// JDK动态代理示例
public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy1, method, args1) -> {
System.out.println("Before method: " + method.getName());
Object result = method.invoke(target, args1);
System.out.println("After method: " + method.getName());
return result;
}
);
proxy.saveUser(new User());
}
}
注意:Spring默认优先使用JDK动态代理,当目标类没有实现接口时才会使用CGLIB。可以通过@EnableAspectJAutoProxy(proxyTargetClass=true)强制使用CGLIB。
2.2 JdbcTemplate设计哲学
JdbcTemplate是Spring对JDBC的封装,主要解决了以下问题:
- 资源管理(Connection/Statement/ResultSet的自动关闭)
- 异常处理(将检查异常转换为运行时异常)
- 简化模板代码(如查询结果到对象的映射)
java复制// JdbcTemplate基本使用
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public User findById(Long id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
(rs, rowNum) -> {
User user = new User();
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
return user;
},
id
);
}
}
2.3 声明式事务本质
声明式事务基于AOP实现,通过@Transactional注解将事务管理代码从业务逻辑中分离。其核心属性包括:
- propagation:事务传播行为(REQUIRED, REQUIRES_NEW等)
- isolation:隔离级别(READ_COMMITTED等)
- timeout:超时时间
- readOnly:是否只读
- rollbackFor/rollbackForClassName/noRollbackFor:异常回滚规则
java复制@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Transactional(propagation=Propagation.REQUIRED,
isolation=Isolation.READ_COMMITTED,
timeout=30)
@Override
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 业务逻辑
}
}
3. 整合应用实践
3.1 环境准备与配置
首先确保项目中包含必要依赖(以Maven为例):
xml复制<dependencies>
<!-- Spring核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
<!-- AOP支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.22</version>
</dependency>
<!-- JDBC支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.22</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
配置类示例:
java复制@Configuration
@EnableTransactionManagement
@EnableAspectJAutoProxy
@ComponentScan("com.example")
public class AppConfig {
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("password");
return ds;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
3.2 AOP切面开发实战
开发一个记录方法执行时间的切面:
java复制@Aspect
@Component
public class PerformanceAspect {
private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class);
@Around("execution(* com.example.service.*.*(..))")
public Object logPerformance(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = pjp.getSignature().getName();
try {
Object result = pjp.proceed();
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("方法 [{}] 执行耗时: {} ms", methodName, elapsedTime);
return result;
} catch (Exception e) {
logger.error("方法 [{}] 执行异常", methodName, e);
throw e;
}
}
}
3.3 JdbcTemplate高级用法
3.3.1 批量操作
java复制public void batchInsert(List<User> users) {
jdbcTemplate.batchUpdate(
"INSERT INTO users(name, age) VALUES(?, ?)",
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
User user = users.get(i);
ps.setString(1, user.getName());
ps.setInt(2, user.getAge());
}
@Override
public int getBatchSize() {
return users.size();
}
}
);
}
3.3.2 命名参数
java复制public void updateUser(User user) {
NamedParameterJdbcTemplate namedTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("id", user.getId());
params.addValue("name", user.getName());
params.addValue("age", user.getAge());
namedTemplate.update(
"UPDATE users SET name = :name, age = :age WHERE id = :id",
params
);
}
3.4 声明式事务深度配置
3.4.1 事务传播行为实践
java复制@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Transactional
public void placeOrder(Order order) {
// 主业务逻辑
productService.reduceStock(order.getProductId(), order.getQuantity());
// 调用另一个事务方法
userService.updatePoints(order.getUserId(), order.getAmount());
}
}
@Service
public class UserServiceImpl implements UserService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updatePoints(Long userId, BigDecimal amount) {
// 独立事务逻辑
}
}
3.4.2 事务隔离级别测试
java复制@Service
public class AccountService {
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
// 高隔离级别的转账操作
}
}
4. 常见问题与解决方案
4.1 AOP代理失效场景
问题现象:在同一个类中方法A调用方法B,方法B上的AOP切面不生效。
原因分析:Spring AOP基于代理实现,内部方法调用不走代理。
解决方案:
- 将方法B移到另一个类中
- 通过AopContext获取当前代理对象(需开启exposeProxy)
java复制@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
// 配置类
}
@Service
public class SomeService {
public void methodA() {
((SomeService)AopContext.currentProxy()).methodB();
}
@Transactional
public void methodB() {
// 事务逻辑
}
}
4.2 JdbcTemplate异常处理
问题现象:EmptyResultDataAccessException当查询结果为空时抛出。
最佳实践:
java复制public Optional<User> findById(Long id) {
try {
return Optional.of(jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
userRowMapper,
id
));
} catch (EmptyResultDataAccessException e) {
return Optional.empty();
}
}
4.3 事务不生效的排查清单
- 检查是否启用了事务管理(@EnableTransactionManagement)
- 检查方法是否为public(非public方法事务不生效)
- 检查异常类型是否匹配rollbackFor(默认只回滚RuntimeException)
- 检查是否在同一个类中自调用
- 检查数据库引擎是否支持事务(如MyISAM不支持)
4.4 性能优化建议
-
JdbcTemplate:
- 重用JdbcTemplate实例(线程安全)
- 使用BatchPreparedStatementSetter进行批量操作
- 考虑使用SimpleJdbcInsert简化插入操作
-
AOP:
- 精确指定切点表达式,避免拦截不必要的方法
- 在环绕通知中尽早进行性能检查并快速失败
-
事务:
- 合理设置事务超时时间
- 只读查询标记为@Transactional(readOnly=true)
- 根据业务场景选择合适的传播行为和隔离级别
5. 高级主题与扩展
5.1 多数据源事务管理
当项目需要同时操作多个数据库时,常规的事务管理不再适用。可以通过JTA或ChainedTransactionManager实现:
java复制@Configuration
public class MultiDataSourceConfig {
@Bean
@Primary
public DataSource dataSource1() {
// 配置第一个数据源
}
@Bean
public DataSource dataSource2() {
// 配置第二个数据源
}
@Bean
public PlatformTransactionManager transactionManager() {
return new ChainedTransactionManager(
new DataSourceTransactionManager(dataSource1()),
new DataSourceTransactionManager(dataSource2())
);
}
}
5.2 响应式事务探索
在Spring WebFlux项目中,传统的事务模型不再适用。可以使用R2DBC实现响应式事务:
java复制@Transactional
public Mono<Void> reactiveTransfer(Long fromId, Long toId, BigDecimal amount) {
return userRepository.findById(fromId)
.flatMap(fromUser -> userRepository.findById(toId)
.flatMap(toUser -> {
fromUser.setBalance(fromUser.getBalance().subtract(amount));
toUser.setBalance(toUser.getBalance().add(amount));
return userRepository.save(fromUser)
.then(userRepository.save(toUser));
})
);
}
5.3 自定义事务拦截器
通过实现TransactionInterceptor接口,可以完全控制事务行为:
java复制public class CustomTransactionInterceptor implements TransactionInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 自定义事务逻辑
TransactionStatus status = transactionManager.getTransaction(definition);
try {
Object result = invocation.proceed();
transactionManager.commit(status);
return result;
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
在实际项目中,我发现很多开发者只停留在表面使用这些技术,而忽略了它们之间的协同效应。比如结合AOP和JdbcTemplate可以实现自动重试机制,或者在事务切面中加入自定义的日志记录。真正掌握这些技术的关键在于理解它们的实现原理,这样才能在复杂场景下灵活运用。
