声明式事务是Spring框架提供的一种管理事务的方式
允许在不修改业务代码的情况下,通过注解或XML配置来管理事务。
声明式:通过注解等方式,告诉框架,我要做什么,框架会帮我做什么。
优点:代码量小。
缺点:封装太多。排错不容易
编程式:通过代码的方式,告诉框架,我要做什么,需要自己写代码实现。
优点:排错容易
缺点:代码量多
1、导入包:spring-boot-starter-data-jdbc、mysql-connector-java
2、配置数据库连接信息:在appLication.properties中spring.datasource.*
3、可以直接使用DataSource/JdbcTemplate
@Transactional
是 Spring 框架中用于声明式事务管理的一个重要注解。它可以应用于类或者方法上,帮助开发者在不编写大量手动事务控制代码的情况下实现对数据库事务的管理。以下是关于 @Transactional
注解的详细笔记:
可以应用于接口、接口方法、类以及类方法上。
当应用于类上时,表示该类的所有公共方法都需要被事务管理。
当应用于方法上时,表示该方法需要被事务管理。
@Service
@Transactional
public class BookService {
// 类中的所有方法都具有事务性
}
@Service
public class BookService {
@Transactional
public void updateBook(Book book) {
// 只有这个方法具有事务性
}
}
在使用 @Transactional
之前,了解事务的基本特性非常重要。事务具有以下四个基本属性(ACID):
@Transactional
注解支持多种属性配置,用于更精细地控制事务行为:
propagation(传播行为):定义事务方法如何与当前事务关联。常见的传播行为有:
REQUIRED
(默认):如果有现成的事务,就加入这个事务;如果没有,则创建一个新事务。REQUIRES_NEW
:无论是否有现成的事务,都创建一个新的事务。SUPPORTS
:如果有现成的事务,就加入;如果没有,就以非事务方式执行。NOT_SUPPORTED
:不支持事务,如果有现成的事务,则暂停它。MANDATORY
:必须在现成事务中执行,否则抛出异常。NEVER
:必须在没有事务的环境中执行,否则抛出异常。NESTED
:如果有现成的事务,则在嵌套的事务中执行。isolation(隔离级别):定义事务与其他事务之间的隔离程度。常见的隔离级别有:
DEFAULT
:使用数据库默认的隔离级别。READ_UNCOMMITTED
:允许脏读、不可重复读和幻读。READ_COMMITTED
:不允许脏读,但允许不可重复读和幻读。REPEATABLE_READ
:不允许脏读和不可重复读,但允许幻读。SERIALIZABLE
:完全串行化的读,防止所有事务问题。true
,则该事务不允许对数据库进行修改操作,优化查询性能。@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
readOnly = false,
rollbackFor = {SQLException.class, DataAccessException.class}
)
public void updateBook(Book book) {
// 业务逻辑
}
@Transactional
通常用于数据库操作较多的服务层(Service Layer),以确保数据操作的原子性和一致性。REQUIRES_NEW
),而有些情况需要加入现有事务(REQUIRED
)。SQLException
)进行回滚,必须在 @Transactional
中指定 rollbackFor
。LazyInitializationException
。因此,确保事务管理的范围足够大,以覆盖所有需要的数据访问操作。@Transactional
的实现依赖于 Spring AOP(面向切面编程)。当一个带有 @Transactional
注解的方法被调用时,Spring 会创建一个代理对象,并在代理对象中添加事务管理逻辑:
方法调用后:
No qualifying bean of type
错误:确保相关类(如 DataSource
、PlatformTransactionManager
)已正确配置,并在 Spring 容器中注册。LazyInitializationException
错误:增加事务范围,确保事务在访问懒加载数据时依然活跃。@Transactional
注解的生效范围仅限于外部方法调用,因此不能在同一个类中直接调用带有 @Transactional
的方法。解决办法是通过代理类或者将事务方法提取到单独的类中。@Service
public class BookService {
@Autowired
private BookDao bookDao;
@Transactional
public void updateBookStock(int bookId, int quantity) {
Book book = bookDao.getBookById(bookId);
if (book.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
book.setStock(book.getStock() - quantity);
bookDao.updateBook(book);
}
@Transactional(readOnly = true)
public Book findBook(int id) {
return bookDao.getBookById(id);
}
}
包含所有 @Transactional
属性的调用演示
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookService {
// 自动注入DAO对象,用于数据库操作
@Autowired
private BookDao bookDao;
/**
* 演示使用所有属性的事务方法
*/
@Transactional(
propagation = Propagation.REQUIRED, // 事务传播行为:如果有现存的事务,则加入;否则新建一个事务
isolation = Isolation.REPEATABLE_READ, // 事务隔离级别:防止脏读和不可重复读
timeout = 30, // 事务超时时间:30秒
readOnly = false, // 是否只读事务:否,允许数据修改
rollbackFor = {RuntimeException.class, SQLException.class}, // 指定哪些异常会导致事务回滚
noRollbackFor = {IllegalArgumentException.class} // 指定哪些异常不会导致事务回滚
)
public void updateBookStock(int bookId, int quantity) {
// 获取指定ID的图书对象
Book book = bookDao.getBookById(bookId);
// 如果库存不足,抛出异常,触发事务回滚
if (book.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
// 更新图书库存
book.setStock(book.getStock() - quantity);
bookDao.updateBook(book);
}
/**
* 演示只读事务
*/
@Transactional(
readOnly = true, // 设置为只读事务,不允许对数据库进行修改操作
propagation = Propagation.SUPPORTS, // 支持现有事务,如果没有事务则以非事务方式执行
isolation = Isolation.READ_COMMITTED // 允许读取已提交的数据,防止脏读
)
public Book findBook(int id) {
// 查找图书信息
return bookDao.getBookById(id);
}
/**
* 演示事务超时和特定异常回滚
*/
@Transactional(
timeout = 10, // 设置事务超时时间为10秒
rollbackFor = {CustomException.class}, // 仅在遇到自定义异常时回滚
propagation = Propagation.REQUIRES_NEW, // 每次调用都创建一个新的事务
isolation = Isolation.SERIALIZABLE // 串行化隔离级别,最严格的隔离方式
)
public void processTransaction() throws CustomException {
// 执行一些数据库操作
try {
// 假设进行某些复杂操作
bookDao.someComplexOperation();
} catch (SQLException e) {
// 抛出自定义异常以触发回滚
throw new CustomException("操作失败", e);
}
}
}
© 著作权归作者所有
本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。