目录
下面介绍如何通过 XML 的方式实现声明式事务管理,步骤如下。
事务概念
1、什么事务
(1)事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败.
(2)典型场景:银行转账
*lucy转账100元给mary
*lucy少100,mary多100
2、事务四个特性(ACID)
(1)原子性:过程中要么都成成功,要么都失败
(2)一致性:操作之前和操作之后总量是不变的,比如转账之前有一百,转账后两个人前加起来还是一百
(3)隔离性:在多事务操作时不会产生影响,比如两个人一同操作同一个事物,他们操作之间不会产生影响
(4)持久性:事务提交会真正发生变化
搭建事务操作环境
我们知道mvc是三层架构,界面层、业务逻辑层、数据访问层,在事务操作时我们先只看业务逻辑层(Srevice)、数据访问层(Dao)。
下面我们先实现一个简单的无事务转账案例
创建service,搭建dao,完成对象创建和注入关系。
@Service
public class UserService {
// 注入dao
@Autowired
private UserDao userdao;
public void accountMoney(){
// lucy少100
userdao.reducwMoney();
// 模拟异常
int i = 10/0;
// marry多100
userdao.addMoney();
}
}
public interface UserDao {
// 多钱
public void addMoney();
// 少钱
public void reducwMoney();
}
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
// 多钱:marry进账100
@Override
public void addMoney() {
String sql = "update t_account set moeny = moeny+? where username=?";
jdbcTemplate.update(sql, 100,"mary");
}
// 少钱,Lucy转账给marry
@Override
public void reducwMoney() {
String sql = "update t_account set moeny=moeny-? where username=?";
jdbcTemplate.update(sql, 100,"lucy");
}
}
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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!--组件扫描-->
<context:component-scan base-package="com.java.spring"></context:component-scan>
<!-- 数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--JdbcTemplate对象,固定模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入dataSource对象,将数据源的 Bean 注入到 JdbcTemplate 中,此时JdbcTemplate 拥有数据库信息固定值-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理器 class是实现类的全路径-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 name是固定的源码中的set方法要求-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
测试
public class Test1 {
@Test
public void testAccount(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
无异常结果
有异常结果:Lucy转给Mary100,但由于异常Mary没有得到,导致丢失100.
解决异常办法:加入事务
Spring 实现声明式事务管理主要有 2 种方式:
- 基于 XML 方式的声明式事务管理。
- 通过 Annotation 注解方式的事务管理。
-
下面介绍如何通过 XML 的方式实现声明式事务管理,步骤如下。
1. 引入 tx 命名空间
Spring 提供了一个 tx 命名空间,借助它可以极大地简化 Spring 中的声明式事务的配置。
想要使用 tx 命名空间,第一步就是要在 XML 配置文件中添加 tx 命名空间的约束。(由于 Spring 提供的声明式事务管理是依赖于 Spring AOP 实现的,因此我们在 XML 配置文件中还应该添加与 aop 命名空间相关的配置。) -
<?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:aop="http://www.springframework.org/schema/aop" 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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
2. 配置事务管理器
接下来,我们就需要借助数据源配置,定义相应的事务管理器实现的 Bean,配置内容如下。
<!--组件扫描--> <context:component-scan base-package="com.java.spring"></context:component-scan> <!-- 数据库连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <!--JdbcTemplate对象--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入dataSource对象,将数据源的 Bean 注入到 JdbcTemplate 中,固定值--> <property name="dataSource" ref="dataSource"></property> </bean> <!--创建事务管理器 class是实现类的全路径--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据源 name是固定的源码中的set方法要求--> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置通知:tx:advice :来声明事务 id:自己起名字--> <tx:advice id="txadvice"> <!-- 配置事务参数--> <tx:attributes> <!--<tx:method> 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,--> <!-- 指定哪种规则上面添加事务,propagation 指定事务的传播行为。具体参数下面会涉及--> <tx:method name="accountMoney" propagation="REQUIRED"/> <!-- 或<tx:method name="account*"/> 表示account开头的方法加上实务操作·--> </tx:attributes> </tx:advice> <!--配置切入点和切面--> <aop:config> <!-- 配置切入点--> <aop:pointcut id="pt" expression="execution(* com.java.spring.service.UserService.*(..))"/> <!-- 配置切面 advice-ref:通知名称--> <aop:advisor advice-ref="txadvice" pointcut-ref="pt"></aop:advisor> </aop:config> </beans>
添加事务在发生异常二者都既不少钱也不多钱其余的实体类和上面的UserService一样,接口和接口实现类以及测试类也一样。
-
上面属性标签解释
-
<tx:method> 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。
propagation:事务传播行为
当一个事务方法被另外一个事务方法调用时候,这个事务方法如何进行
T2并不影响T1,如果1掉完方法2出现了问题它并不影响2,2在自己的事务内运行,可以正常提交
当前A没有事务,当他调用B时,是不在事务中运行的
当前A有了事务,调用B时,B在A的事务内运行
ioslation事务隔离级别
(1)事务有特性成为隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题。
(2)有三个读问题:脏读、不可重复读、虚(幻)读
- 脏读:指事务A读取到了事务B更新了但是未提交的数据,然后事务B由于某种错误发生回滚,那么事务A读取到的就是脏数据。
事务A读取到60000后,那60000进行操作,但事务B还没有提交就开始回滚,以至于A拿到错数据去操作
- 不可重复读:指在数据库访问时,一个事务在前后两次相同的访问中却读到了不同的数据内容。
- 虚度:一个未提交的事务读取到了另一个提交事务添加的数据
- 虚读和不可重复读的本质是一样的,两者都表现为两次读取的结果不一致。但是不可重复读指的是两次读取同一条记录的值不同,而幻读指的是两次读取的记录数量不同
解决办法:通过设置隔离性,解决三种问题
下面介绍如何通过 注解的方式实现声明式事务管理,步骤如下
1.在spring配置文件中配置事务管理器,开启事务注解。
<?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:aop="http://www.springframework.org/schema/aop"
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 http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--组件扫描-->
<context:component-scan base-package="com.java.spring"></context:component-scan>
<!-- 数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 注入dataSource对象,将数据源的 Bean 注入到 JdbcTemplate 中,固定值-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--创建事务管理器 class是实现类的全路径-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 name是固定的源码中的set方法要求-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>
</beans>
2.在Service类(或方法上)上面添加事务注解
- @Transactional这个注解添加到类上面,为这个该方法添加事务。
- 如果这个注解添加到类上面,整个类里面的所有方法都添加事务
- 如果整个主角添加到方法上,为这个该方法添加事务
-
@Service @Transactional public class UserService { // 注入dao @Autowired private UserDao userdao; public void accountMoney(){ // lucy少100 userdao.reducwMoney(); // 模拟异常 int i = 10/0; // marry多100 userdao.addMoney(); } }
其他类和测试类和第一个类一样,注意配置文件名称更改
-
发生异常,但该类添加了事务注解,所以Lucy不会少。Mary不会多
@Transactional注解参数
第一个参数:事务的传播方式(propagation),第二个隔离性(ioslation)
其他参数
timeout:超时时间
(1) 事务需要在一定时间内进行提交,如果不提交进行回滚
(2) 默认值是-1,设置时间以秒单位进行计算
readonly:是否只读
(1)读:查询操作,写:添加修改删除操作
(2)readonly默认值false,表示可以查询,可以添加修改删除操作。
(3)设置eadonly.值是true,设置成tru之后,只能查询
rolbaskor:回滚
(1)设置出现哪些异常进行事务回滚
noRolbackFor:不回滚
(1)设置出现哪些异常不进行事务回滚
完全注解
1,创建配置类,使用配置类替代xml配置文件
2、Spring5.0框架自带了通用的日志封装
(1)Spring5已经移除Log4jConfigListener,官方建议使用Log42
(2)Spring5框架整合Log4j2
第一步引入jar包
第二步创建log4j2.xml配置文件
@EnableTransactionManagement//开启事务
@Configuration//配置类
@ComponentScan(basePackages = "com.java")//组建扫描
@EnableTransactionManagement//开启事务
public class TxConfig {
// 创建数据库连接池
@Bean
public DruidDataSource getDruidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/user_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
// 创建JdbcTempLate对象
// 应为连接池创建时已经生成了dataSource对象所以这里的参数实在连接池中找的对象,而不是我们要传入的参数
@Bean
public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// 注入datasource
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
// 创建事务管理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource((dataSource));
return transactionManager;
}
}
测试类
public void testAccount2(){
ApplicationContext context = new AnnotationConfigApplicationContext("TxConfig.class");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
Spring5框架核心容器支持@Nullable注解
(1)@Nullable注解可以使用在方法上面,属性上面,参数上面,表示方法返回可以为空,属性值可以为空,参数值可以为空
(2)注解用在方法上面,方法返回值可以为空
(3)注解使用在方法参数里面,方法