事务(Transaction):是数据库的一种安全机制、一个操作序列。事务将所有DML命令作为一个整体向系统提交或撤销操作请求,这些命令要么都执行、要么都不执行。
数据库查询语言(DQL):SELECT
数据库操纵语言(DML):INSERT、UPDATE、DELETE
数据库控制语言(DCL):GRANT、ROLLBACK、COMMIT
ACID原则:原子性、一致性、隔离性、持久性。
原子性(Atomicity):一个事务的操作,要么全部完成,要么全部都不完成。
一致性(Consistency):在事务开始之前和事务结束后,数据库的完整性没有破坏。
隔离性(Isolation):数据库允许多个事务同时对数据惊醒读写和修改的能力(四个隔离级别)
持久性(Durability):事务结束以后,对数据的修改是永久的,即使系统故障也不会丢失
实现对数据库中表student的增删查改功能。
@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; } }
public interface StudentMapper { List<Student> GetAll(); int Insert(Student student); int Delete(int id); int Update(Student student); }
<?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>
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); } }
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>
@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) */
@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就报错了。
再来查下数据库,看上面的添加操作对数据库是否造成了影响。
可以看到,虽然删除操作报错了,但添加操作依然对数据库造成了影响,这完全不符合事务的原子性!
使用 MyBatis-Spring 的主要原因之一是它允许 MyBatis 参与 Spring 事务。MyBatis-Spring 并没有创建特定于 MyBatis 的新事务管理器,而是利用了 Spring 中现有的DataSourceTransactionManager
。
Spring支持编程式事务管理和声明式事务管理。
编程式事务管理:
声明式事务管理:
声明式事务管理比编程式事务管理更加可取。
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; }
该接口中定义了事务的状态,主要内容如下:
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
:该方法返回该事务的名称。
事务隔离级别也定义在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;
传播类型也定义在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;
原来的代码几乎不用删除,我们在此基础上加入事务的功能。
我们在Beans.xml文件中加入事务。
前提条件:添加事务的约束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>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="datasource"/> </bean>
<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
<aop:config> <aop:pointcut id="pointcut" expression="execution(* com.Map.*.*(..))"/> <aop:advisor advice-ref="interceptor" pointcut-ref="pointcut"/> </aop:config>
@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); } }
事务的功能时通过AOP织入实现的。
如果想了解MyBatis有关的事务,可以看下sqlSessionFactory.openSession();
的源码。
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>