1. 事务
1.1 什么是事务
事务(Transaction):是数据库的一种安全机制、一个操作序列。事务将所有DML命令作为一个整体向系统提交或撤销操作请求,这些命令要么都执行、要么都不执行。
数据库查询语言(DQL):SELECT
数据库操纵语言(DML):INSERT、UPDATE、DELETE
数据库控制语言(DCL):GRANT、ROLLBACK、COMMIT
1.2 ACID原则
ACID原则:原子性、一致性、隔离性、持久性。
原子性(Atomicity):一个事务的操作,要么全部完成,要么全部都不完成。
一致性(Consistency):在事务开始之前和事务结束后,数据库的完整性没有破坏。
隔离性(Isolation):数据库允许多个事务同时对数据惊醒读写和修改的能力(四个隔离级别)
持久性(Durability):事务结束以后,对数据的修改是永久的,即使系统故障也不会丢失
2. 无事务情况
实现对数据库中表student的增删查改功能。
2.1 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private int id;
private String stu_username;
private String stu_password;
public Student(String stu_username, String stu_password) {
this.stu_username = stu_username;
this.stu_password = stu_password;
}
}
2.2 接口Mapper
public interface StudentMapper {
List<Student> GetAll();
int Insert(Student student);
int Delete(int id);
int Update(Student student);
}
2.3 映射Mapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD com.Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.Map.StudentMapper">
<select id="GetAll" resultType="com.POJO.Student">
select * from student
</select>
<insert id="Insert" parameterType="com.POJO.Student">
insert into student(stu_username, stu_password) values(#{stu_username}, #{stu_password})
</insert>
<delete id="Delete" parameterType="java.lang.Integer">
delete from student where id = #{id}
</delete>
<update id="Update" parameterType="com.POJO.Student">
update student set stu_username = #{stu_username}, stu_password = #{stu_password} where id = #{id}
</update>
</mapper>
2.4 接口实现类
public class StudentMapperImpl extends SqlSessionDaoSupport implements StudentMapper{
@Override
public List<Student> GetAll() {
return getSqlSession().getMapper(StudentMapper.class).GetAll();
}
@Override
public int Insert(Student student) {
return getSqlSession().getMapper(StudentMapper.class).Insert(student);
}
@Override
public int Delete(int id) {
return getSqlSession().getMapper(StudentMapper.class).Delete(id);
}
@Override
public int Update(Student student) {
return getSqlSession().getMapper(StudentMapper.class).Update(student);
}
}
2.5 整合sqlSession
Beans.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: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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--配置数据源-->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/student?useSSL=true&useUnicode=true&charsetEncoding=utf8&severTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="root199962"/>
</bean>
<!--配置SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--配置mybatis-config.xml-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--配置映射-->
<property name="mapperLocations" value="classpath*:com/Map/*.xml"/>
</bean>
<!--配置sqlSessionFactory-->
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<!--通过构造器注入(无set方法)-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--导入配置文件beans.xml-->
<import resource="Beans.xml"/>
<!--注册实现类的bean-->
<bean id="studentMapperImpl" class="com.Map.StudentMapperImpl">
<!--利用实现类的set方法注入sqlSessionFactory-->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
2.6 测试
@Test
public void TestGetAll() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapper.class);
List<Student> students = studentMapper.GetAll();
for (Student student : students) {
System.out.println(student);
}
}
/*
输出:
Student(id=1, stu_username=admin, stu_password=123456)
Student(id=15, stu_username=王五, stu_password=123456)
Student(id=16, stu_username=王五, stu_password=123456)
*/
2.7 添加错误测试
@Test
public void TestInsertDelete() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapper.class);
Student stu = new Student("王五", "123456");
studentMapper.Insert(stu);
studentMapper.Delete(5);
List<Student> students = studentMapper.GetAll();
for (Student student : students) {
System.out.println(student);
}
}
可以看到,上面的添加操作成功了,但下面删除了一个表中不存在ID就报错了。
再来查下数据库,看上面的添加操作对数据库是否造成了影响。
可以看到,虽然删除操作报错了,但添加操作依然对数据库造成了影响,这完全不符合事务的原子性!
3. Spring 事务
3.1 Spring 事务管理
使用 MyBatis-Spring 的主要原因之一是它允许 MyBatis 参与 Spring 事务。MyBatis-Spring 并没有创建特定于 MyBatis 的新事务管理器,而是利用了 Spring 中现有的DataSourceTransactionManager
。
3.2 事务分类
Spring支持编程式事务管理和声明式事务管理。
编程式事务管理:
- 将事务和代码嵌套到业务方法中,实现控制事务的提交和回滚。
- 缺点:必须再每个事务操作业务逻辑中包含额外的事务管理代码。
声明式事务管理:
- 将事务管理代码从业务方法中分离出来,以声明式的方式来实现事务管理。
- 将事务作为横切关注点,提供AOP方法模块化。
- Spring中通过Spring AOP框架支持声明式事务管理。
声明式事务管理比编程式事务管理更加可取。
3.3 窥见源码
3.3.1 PlatformTransactionManager
Spring 事务是由 org.springframework.transaction.PlatformTransactionManager
接口定义。
该接口提供了三个方法,内容如下:
public interface PlatformTransactionManager extends TransactionManager {
// 事务管理器通过TransactionDefinition获取事务状态,从而管理事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 根据状态提交事务
void commit(TransactionStatus var1) throws TransactionException;
// 根据状态回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
3.3.2 TransactionDefinition
该接口中定义了事务的状态,主要内容如下:
public interface TransactionDefinition {
default int getPropagationBehavior() {return 0;}
default int getIsolationLevel() {return -1;}
default int getTimeout() {return -1;}
default boolean isReadOnly() {return false;}
@Nullable
default String getName() {return null;}
}
getPropagationBehavior
:该方法返回传播行为。
getIsolationLevel
:该方法返回该事务独立于其他事务的工作程度(隔离级别)。
getTimeout
:该方法返回一秒为单位的时间间隔,事务必须在该时间间隔内完成。
isReadOnly
:该方法返回该是否是只读的。
getName
:该方法返回该事务的名称。
3.3.3 事务隔离级别
事务隔离级别也定义在TransactionDefinition中。
// 这是默认的隔离级别
int ISOLATION_DEFAULT = -1;
// 读不提交,可以发生误读、不可重复读、幻读
int ISOLATION_READ_UNCOMMITTED = 1;
// 读提交,能够阻止误读,可以发生不可重复读、幻读
int ISOLATION_READ_COMMITTED = 2;
// 可重复读,阻止误读和不可重复读,可以发生误读
int ISOLATION_REPEATABLE_READ = 4;
// 可序列化,阻止误读、不可重复读和幻读
int ISOLATION_SERIALIZABLE = 8;
// 默认不超时
int TIMEOUT_DEFAULT = -1;
3.3.4 传播类型
传播类型也定义在TransactionDefinition中。
// 如果当前没有事务,就新建一个事务;如果已存在一个事务中,就加入到这个事务中。(最常见选项)
int PROPAGATION_REQUIRED = 0;
// 支持当前事务,如果没有当前事务,就以非事务方式执行
int PROPAGATION_SUPPORTS = 1;
// 使用当前事务,如果没有当前事务,就抛出异常
int PROPAGATION_MANDATORY = 2;
// 新建事务,如果当前存在事务,把当前事务挂起
int PROPAGATION_REQUIRES_NEW = 3;
// 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
int PROPAGATION_NOT_SUPPORTED = 4;
// 以非事务方式执行擦偶哦在,如果当前事务存在异常则抛出异常
int PROPAGATION_NEVER = 5;
// 如果当前存在事务,则在嵌套事务内执行;如果没有事务,则执行与PROPAGATION_REQUIRED类似的操作
int PROPAGATION_NESTED = 6;
4. 代码实现
原来的代码几乎不用删除,我们在此基础上加入事务的功能。
4.1 配置事务
我们在Beans.xml文件中加入事务。
4.1.1 添加约束
前提条件:添加事务的约束tx
<?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: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/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
4.1.2 配置声明式事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="datasource"/>
</bean>
4.1.3 配置事务的通知
<tx:advice id="interceptor" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
传播特性(propagation):
- REQUIRED、SUPPORTS、MANDATORY、NOT_SUPPORTED、NEVER、NESTED
隔离级别(isolation):
- DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE
只读(read-only):
- false、true
超时(timeout):
- 事务超时值(单位为秒)。
回滚异常(rollback-for):
- 触发回滚的异常;以逗号分隔。例如
com.foo.MyBusinessException, ServletException
不回滚异常(no-rollback-for):
- 不触发回滚的异常;以逗号分隔。例如
com.foo.MyBusinessException, ServletException
4.1.4 配置AOP织入
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.Map.*.*(..))"/>
<aop:advisor advice-ref="interceptor" pointcut-ref="pointcut"/>
</aop:config>
4.2 测试
@Test
public void TestInsertDelete() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
StudentMapper studentMapper = applicationContext.getBean("studentMapperImpl", StudentMapper.class);
Student stu = new Student("王五", "123456");
studentMapper.Insert(stu);
studentMapper.Delete(5);
List<Student> students = studentMapper.GetAll();
for (Student student : students) {
System.out.println(student);
}
}
5. 写在最后
事务的功能时通过AOP织入实现的。
如果想了解MyBatis有关的事务,可以看下sqlSessionFactory.openSession();
的源码。
❤️ END ❤️
ass);
Student stu = new Student(“王五”, “123456”);
studentMapper.Insert(stu);
studentMapper.Delete(5);
List students = studentMapper.GetAll();
for (Student student : students) {
System.out.println(student);
}
}
# 5. 写在最后
事务的功能时通过AOP织入实现的。
如果想了解MyBatis有关的事务,可以看下`sqlSessionFactory.openSession();`的源码。
------
<div align="center">❤️ END ❤️</div>