事务可以看做是数据库的若干操作组成的一个单元。
我们在开发企业级应用时,用户的一个操作实际却是对数据读写多步操作的结合。由于数据操作在顺序执行中,任何一步都有可能发生异常,导致后续的操作无法完成,此时由于业务逻辑全部完成,之前操作的数据并不可靠,需要在这种情况下进行回退(回滚),将数据恢复到用户操作之前。
事务的作用就是为了保证用户的每一个操作都是可靠的,事务的每一步操作都必须完成,只要发生异常就回退到事务未开始操作的状态,这些操作要么全部完成,要么全部取消,从而保证数据满足一致性的要求。
原子性(Atomicity):强调事务的不可分割性。
一致性(Consistency):事务的执行前后数据完整性保持一致。
隔离性(Isolation):一个事务在执行中不会受到其它事务的影响。
持久性(Durability):事务一旦结束,数据变化会持久到数据库。
1.编程式事务,这类事务管理形式在项目中很少使用,这种方式需要注入一个事务管理对象TransactionTemplate,然后再我们的代码中需要提交事务或回滚事务时由我们自己写代码实现。
2.声明式事务:这类事务管理建立在AOP的基础上,本质式对方法前后进行切面式的拦截,所以声明式事务是方法级别的。声明式事务管理方式有两种:分别是基于xml配置实现和基于注解的方式实现。
由于事务是数据库的操作集合,因此我们对事务的管理都是基于数据库源操作的。
(1)在pom.xml中导入Spring-jdbc的相关jar包,这里我们使用阿里的德鲁伊数据库源
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.16version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.10version>
dependency>
(2)在Spring文件中配置数据库源,将数据库源交给spring进行管理
<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
https://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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.cwd.spring6pro.demo1"> context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_db?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai">property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver">property>
<property name="username" value="root">property>
<property name="password" value="root">property>
bean>
beans>
Spring针对不同的dao框架,提供了不同的实现类,JDBC和MyBtais的事务管理实现类是DataSourceTransactionManager。
配置spring事务管理器,并注入数据源。
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
(1)配置事务传播行为
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
(2)基于aop为不同的类和方法开启事务管理,aop部分不了解的可以看SpringAOP(面向切面编程)
<aop:config>
<aop:pointcut expression="execution(* com.cwd.spring6pro.demo1.dao.UserDao.*(..))" id="allmethod"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="allmethod"/>
aop:config>
(1)在配置好事务管理器后,开启注解事务管理。
<tx:annotation-driven transaction-manager="dataSourceTransactionManager">tx:annotation-driven>
(2)在service层中通过注解控制事务
import com.cwd.spring6pro.demo1.dao.UserDao;
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(value = "userService")
@Transactional(propagation = Propagation.REQUIRED)
//@Transactional注解如果使用在类名上,那么这个类的所有方法都在事务中运行,也可以在方法上
public class UserService {
@Autowired//注入注解
private UserDao userDao;
public void UserSave() {
userDao.saveUser();
}
public void saveAdd() {
userDao.save();
userDao.add();
}
}
即然是传播,那么至少有两个东西才可以产生传播。单体是不存在传播的。事务传播行为(Propagation Behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何继续进行。事务传播行为是Spring框架独有的事务增强特性,它不属于事务的实际提供方行为,即不属于数据库行为。
举个例子,方法A事务调用方法B事务时,方法B是在方法A的事务中运行呢?还是自己新开一个事务运行呢?这就由方法B的事务传播行为决定。
我们使用传播行为有两种方式,一种是基于xml配置,一种是注解方式:
· 基于xml配置
<tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
· 基于注解方式
@Transactional(propagation = Propagation.REQUIRED)//事务传播行为
public class XXX{}
指定的方法必须在事务内执行,若当前存在事务,就加入到当前事务中,若当前没有事务,则自行创建一个新的事务,这种传播行为是最常见的,也是Spring框架默认的传播行为。
如果方法A调用方法B时,方法B配置了此传播行为,方法A有事务时,方法B就加入方法A的事务,如果方法A没有事务,方法B就以非事务的方式执行。
总是新建一个事务,如果当前已经存在了一个事务,就把当前事务挂起,直到新建事务的结束。
总是加入到当前事务,如果当前没有事务,就会抛出异常
总是以非事务的方式执行操作,如果当前存在一个事务,就把当前事务挂起。
总是以非事务的方式执行操作,如果当前存在一个事务,就抛出异常。
如果当前存在事务,则在嵌套的事务内执行。如果当前没有事务,就自行创建一个事务。
关于MyBtais的学习,这里推荐几篇文章:
1.一篇文章告诉你什么是Mybatis+Mybatis的搭建及使用
2.MyBatis的日志+参数传递+常用注解
3.详解MyBatis查询结果处理(简单类型,POJO,resultMap输出映射)
4.MyBatis多表关联查询处理结果集、懒加载、association(一对一)、collection(一对多)、案例详解
5.MyBtais动态sql和特殊符号处理(案例集锦:不定条件查询+模糊查询+批量查询+批量删除…)
6.MyBatis缓存看这一篇就够了(一级缓存+二级缓存+缓存失效+缓存配置+工作模式+测试)
Spring集成MyBtais的核心操作就是将SqlSessionFactory交给Spring进行管理,并由Spring管理对dao接口的代理实现。
Spring结合MyBatis插件包
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.2version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>1.3.1version>
dependency>
由于上面文章中事务那一部分已经配置好了数据库源如下所示,因此这里我们就直接使用。
(1)数据库源
<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
https://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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.cwd.spring6pro.demo1"> context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_db?characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai">property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver">property>
<property name="username" value="root">property>
<property name="password" value="root">property>
bean>
beans>
(2)配置SqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="configLocation" value="MyBatisConfig.xml">property>
<property name="mapperLocations" value="mapper/*Mapper.xml"> property>
bean>
<bean id="mapperFactory" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cwd.springmybatis">property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory">property>
bean>
我们此时就可以在Service中直接注入Dao的代理对象,此接口由Spring代理实现。
package com.cwd.ssm.service;
import com.cwd.ssm.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserService {
@Autowired
UserDao userDao;
}