spring 事务
一、事务简介
1、事务作用:在数据层保障一系列的数据库操作同步成功同步失败
2、Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败,其是使用JDBC的事务管理器 实现的,如果数据层使用的是JDBC,则可以使用Spring事务
其是通过内部接口和实现类实现的
// 接口 public interface PlatformTransactionManager { void begin(); void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; } // 实现类 public class DataSourceTransactionManager { public void begin() { System.out.println("开始事务"); } public void commit() { System.out.println("提交事务"); } public void rollback() { System.out.println("回滚事务"); } }
3、注意事项:
* Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合
* 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务
二、快速开始Spring事务
1、注入相关依赖
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.29</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.19</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.12</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.29</version> </dependency> </dependencies>
2、设置相关配置
创建config包,用于存储配置相关文件。
1) 配置Spring,创建SpringConfig类文件
package com.itheima.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.PropertySource; import org.springframework.transaction.annotation.EnableTransactionManagement; // 告知这是个配置类 @Configuration // 告知扫描哪些包 @ComponentScan("com.itheima") // 告知加载哪个配置文件 @PropertySource("classpath:jdbc.properties") // 告知加载哪些配置文件 @Import({JdbcConfig.class,MybatisConfig.class}) //开启注解式事务驱动 @EnableTransactionManagement public class SpringConfig { }
2)配置JDBC,创建JdbcConfig文件
package com.itheima.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; @Configuration public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String userName; @Value("${jdbc.password}") private String password; @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } //配置事务管理器,mybatis使用的是jdbc事务 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
3)配置jdbc数据源,在resources下面创建jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false jdbc.username=root jdbc.password=123456
4)配置Mybatis,在config文件夹下创建MybatisConfig
package com.itheima.config; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; import org.springframework.context.annotation.Bean; import javax.sql.DataSource; public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){ // 获取SqlSessionFactoryBean对象 SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); // 别名包 ssfb.setTypeAliasesPackage("com.itheima.domain"); // 数据源 ssfb.setDataSource(dataSource); return ssfb; } @Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.itheima.dao"); return msc; } }
3、创建实体类
在domain文件夹下创建Account类文件
package com.itheima.domain; import java.io.Serializable; // 创建实体类 public class Account implements Serializable { private Integer id; private String name; private Double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
4、创建数据dao层
在dao文件夹下创建AccountDao接口文件
package com.itheima.dao; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; public interface AccountDao { @Update("update tbl_account set money = money + #{money} where name = #{name}") void inMoney(@Param("name") String name, @Param("money") Double money); @Update("update tbl_account set money = money - #{money} where name = #{name}") void outMoney(@Param("name") String name, @Param("money") Double money); }
5、创建service
1)创建service接口文件
在service文件夹下创建AccountService接口文件
package com.itheima.service; import org.springframework.transaction.annotation.Transactional; import java.io.FileNotFoundException; import java.io.IOException; public interface AccountService { /** * 转账操作 * @param out 传出方 * @param in 转入方 * @param money 金额 */ //配置当前接口方法具有事务 @Transactional public void transfer(String out,String in ,Double money) ; }
2)创建service接口实现类文件
在service文件夹下创建impl文件夹,并在其下面创建AccountServiceImpl实现类文件
package com.itheima.service.impl; import com.itheima.dao.AccountDao; import com.itheima.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.*; @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void transfer(String out,String in ,Double money) { accountDao.outMoney(out,money); // int i = 1/0; accountDao.inMoney(in,money); } }
6、创建测试文件,进行测试
在test文件夹下创建测试文件
package com.itheima.service; import com.itheima.config.SpringConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.io.IOException; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer() throws IOException { accountService.transfer("Tom","Jerry",100D); } }
7、最终文件目录
三、为什么开启spring事务
如上图所示,第二版块的示例子中执行了两条sql语句,每条语句都是单独的数据库层面的事务(T1,T2),但是如果是业务层中发生了报错,则不会自动回滚,所以,需要在Spring中建立事务(T)。通过Spring中的事务(T1)进行管理数据库的事务(T1、T2)。
事务角色:
* 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法,如本文例子中的(事务T)
* 事务协调员:加入事务方,在Spring中通常指代数据层方法(如:T1、T2),也可以是业务层方法
四、事务相关配置
1、事务的传播行为
为了在一个service方法里存在多个事务管理,而不是,所有的数据库操作都必须是全部成功或全部失败。比如,上面将的存钱例子,不论改变账户金额是否成功都进行日志记录。
1)添加日志记录的dao文件
package com.itheima.dao; import org.apache.ibatis.annotations.Insert; public interface LogDao { @Insert("insert into tbl_log (info,createDate) values(#{info},now())") void log(String info); }
2)添加日志记录的service接口类,并设置事务传播
package com.itheima.service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; public interface LogService { //propagation设置事务属性:传播行为设置为当前操作需要新事务 @Transactional(propagation = Propagation.REQUIRES_NEW) void log(String out, String in, Double money); }
3)实现日志记录接口的实现类
package com.itheima.service.impl; import com.itheima.dao.LogDao; import com.itheima.service.LogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; public void log(String out,String in,Double money ) { logDao.log("转账操作由"+out+"到"+in+",金额:"+money); } }
4)修改转账service,修改AccountServiceImpl文件
package com.itheima.service.impl; import com.itheima.dao.AccountDao; import com.itheima.service.AccountService; import com.itheima.service.LogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.io.*; @Service public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; @Autowired private LogService logService; // 使用try{}finally{}结构,为了保证日志记录一定会执行 public void transfer(String out,String in ,Double money) { try{ accountDao.outMoney(out,money); int i = 1/0; accountDao.inMoney(in,money); }finally { logService.log(out,in,money); } } }
2、事务传播行为包含: