什么是事务?
事务是数据库的核心概念之一,它代表数据库一系列操作的集合,这些操作必须在一个事务当中,要么全部执行成功,要么全部不执行
ACID特性
原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响
一致性(Consistency)
一个事务执行之前和执行之后都必须处于一致状态,例如转账,假设用户A和用户B二者的钱加起来一共是50000,那么不管A和B之间如何转账,事务结束之后二个用户的钱加起来一共是5000,这就是事务的一致性
隔离性(Lsolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
<!-- 装配JDBC事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 启用事务注解驱动, transaction-manager引用事务管理器的id, 如果事务管理器的id是transactionManager,那么transaction-manager可以不用指定。 proxy-target-class设置为true,表示强制使用CGLIB创建事务代理--> <tx:annotation-driven proxy-target-class="true"/>
案例:
entity层:
import lombok.Data; @Data public class Account { private Integer aid; private Integer money; }
dao层引用
public interface AccountDao { /** * 根据账号id查询账号信息 * @param aid * @return */ Account getAccountById(int aid); /** * 更新账户信息 * @param count */ void updateAccount(Account count); }
resource文件中的mapper
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="edu.nf.ch03.dao.AccountDao"> <resultMap id="accountMap" type="account"> <id property="aid" column="a_id"/> <result property="money" column="money"/> </resultMap> <select id="getAccountById" parameterType="int" resultMap="accountMap"> select a_id, money from account_info where a_id = #{aid} </select> <select id="updateAccount" parameterType="account"> update account_info set money = #{money} where a_id = #{aid} </select> </mapper>
applicationContext配置xml文件
<?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" xmlns:mybatis="http://mybatis.org/schema/mybatis-spring" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 启用包扫描 --> <context:component-scan base-package="edu.nf.ch03"/> <!-- 1. 整合druid连接池,其实也就是将druid的数据源纳入spring的ioc容器中, init-method指定数据源的初始化方法,destroy-method指定数据源的close方法--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 注入数据库连接属性 --> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/city?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="root"/> <!-- 注入连接池配置属性--> <!-- 最大连接池数量--> <property name="maxActive" value="20"/> <!-- 初始化连接池时创建的连接个数--> <property name="initialSize" value="5"/> <!-- 最小连接池数量,建议和初始化大小一致--> <property name="minIdle" value="5"/> <!-- 获取连接最大等待时间,超时则抛异常。单位:毫秒 --> <property name="maxWait" value="2000"/> <!-- 连接保持空闲而不被驱逐出连接池的最小时间,单位毫秒--> <property name="minEvictableIdleTimeMillis" value="300000"/> <!-- 销毁连接的线程检测的间隔时间,单位:毫秒--> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 检测连接是否有效--> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="true"/> <!-- 归还连接时检测连接是否有效--> <property name="testOnReturn" value="true"/> <!-- 是否缓存PreparedStatement,mysql建议关闭 --> <property name="poolPreparedStatements" value="false"/> <!-- 定义一条伪sql,用于检查连接的可用性 --> <property name="validationQuery" value="select 1"/> </bean> <!-- 2. 整合mybatis, 其核心就是将SqlSessionFactory纳入spring的IOC容器 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据源, 引用上面配置数据源的bean的id --> <property name="dataSource" ref="dataSource"/> <!-- 指定实体的别名--> <property name="typeAliasesPackage" value="edu.nf.ch03.entity"/> <!-- 指定mapper映射文件的目录--> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <!-- 配置文件插件--> <property name="plugins"> <!-- 装配分页的拦截器 --> <bean class="com.github.pagehelper.PageInterceptor"> <!-- 注入分页属性 --> <property name="properties"> <props> <!-- 数据库方言 --> <prop key="helperDialect">mysql</prop> <!-- 启用分页注解支持--> <prop key="supportMethodsArguments">true</prop> <!-- 分页合理化--> <prop key="reasonable">true</prop> </props> </property> </bean> </property> </bean> <!-- 3. 扫描dao的接口包。这样会利用动态代理的机制在运行时创建所有dao接口的代理实现--> <mybatis:scan base-package="edu.nf.ch03.dao"/> <!-- 装配JDBC事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 启用事务注解驱动, transaction-manager引用事务管理器的id, 如果事务管理器的id是transactionManager,那么transaction-manager可以不用指定。 proxy-target-class设置为true,表示强制使用CGLIB创建事务代理--> <tx:annotation-driven proxy-target-class="true"/> </beans>
service层
/** * @author wangl * @date 2022/10/18 * 转账接口 */ public interface TransferService { /** * 转账服务 * @param money 转账金额 * @param fromUser 转账人信息 * @param toUser 接收人信息 */ void transfer(int money, Account fromUser, Account toUser); }
impl
@Service @Slf4j @RequiredArgsConstructor /** * @Transactional事务注解,可以标注在类上或者方法上,如果同时存在,则优先方法的事务 * * rollbackFor指定遇到什么类型的异常就回滚; * noRollbackFor指定遇到什么类型的异常不进行回滚; * readOnly指定是否是只读的事务 * propagation属性用来指定事务传播级别,传播级别请参照文档说明 * 如果不指定propagation,默认传播级别就是REQUIRED */ @Transactional(rollbackFor = RuntimeException.class) public class TransferServiceImpl implements TransferService { private final AccountDao dao; @Override public void transfer(int money, Account fromUser, Account toUser) { //先查询转账人的余额 fromUser = dao.getAccountById(fromUser.getAid()); //检查余额是否充足 if(fromUser.getMoney() < money) { throw new TransferException("对不起,余额不足"); } //查询接收人的账号信息 toUser = dao.getAccountById(toUser.getAid()); //先扣除转账人的余额 fromUser.setMoney(fromUser.getMoney() - money); //添加接受人的余额 toUser.setMoney(toUser.getMoney() + money); //更新转账人和接收人的信息 dao.updateAccount(fromUser); System.out.println(10/0); dao.updateAccount(toUser); } }
exception自定义异常
public class TransferException extends RuntimeException{ public TransferException(String message) { super(message); } public TransferException(String message, Throwable cause) { super(message, cause); } public TransferException(Throwable cause) { super(cause); } }
测试层
public class TransferServiceTest { @Test public void testTransfer() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); TransferService service = context.getBean(TransferService.class); //创建转账人 Account fromUser = new Account(); fromUser.setAid(1); //创建接收人 Account toUser = new Account(); toUser.setAid(2); //转账 service.transfer(500, fromUser, toUser); } @Test public void testPropagation() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); TestService service = (TestService) context.getBean("testService"); service.methodA(); } @Test public void testReadOnly() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Account account = new Account(); account.setAid(1); account.setMoney(1500); TestService service = (TestService) context.getBean("testService"); service.find(account); } }
所要用到的依赖(所有spring中的版本号都要一致,要不会报错)
这里没有版本号是因为我们已经在父模块中已经进行按需依赖
<dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies>