一、事务
1.概念
在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
2.特性
事务是恢复和并发控制的基本单位。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务总结一句话:要么都成功!要么都失败!
3.为什么需要事务?
如果不配置事务,可能存在数据不一致的情况(例如下面的二、不开启事务的项目案例)。
如果我们不在spring中去配置声明式事务,我们就需要在代码中手动去实现(try-catch)。
注意:事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题!不容马虎。
二、不开启事务的案例
还是使用上一篇使用过的项目,不过增加了一些东西。
注意:这个项目会故意制造一个错误。
1.项目结构
项目数据库原本数据为:
项目结构
2.pom.xml依赖
需要导入的依赖。
使用事务需要导入spring-tx的包,不过新版本spring中的spring-jdbc下导入了spring-tx,就不用再单独导入了。
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- spring要操作数据库需要导入这个spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!-- mybatis核心包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
<!-- mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
3.配置文件
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"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 一、导入 -->
<import resource="spring-dao.xml"/>
<!-- 创建继承了SqlSessionDaoSupport的实现类userMapperImpl -->
<bean id="userMapperImpl" class="com.shengjava.demo.mapper.impl.UserMapperImpl">
<!-- 需要注入sqlSessionFactory -->
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
</beans>
spring-dao.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
http://www.springframework.org/schema/beans/spring-beans.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">
<!-- 创建 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- 数据源DataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/demo"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
mybatis-config.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 映射器 -->
<mappers>
<!--加载映射文件-->
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
4.实体类
实体类User
public class User {
private int id;
private String name;
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
5.接口和实现类
接口UserMapper
public interface UserMapper {
User selectUser(int id);
int insUser(User u);
int delUser(int id);
}
实现类UserMapperImpl
注意:测试的时候会调用selectUser()方法,该方法执行了新增,删除,查询。其中删除语句因为故意写错,所以会抛出异常!
/** 第二种方式:继承SqlSessionDaoSupport */
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override
public User selectUser(int id) {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
User user = new User(4, "name4");
mapper.insUser(user);
mapper.delUser(4);
return mapper.selectUser(id);
}
@Override
public int insUser(User u) {
return getSqlSession().getMapper(UserMapper.class).insUser(u);
}
@Override
public int delUser(int id) {
return getSqlSession().getMapper(UserMapper.class).delUser(id);
}
}
映射文件UserMapper.xml
注意:新增方法是正确的。但里面的删除方法,是故意写错。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shengjava.demo.mapper.UserMapper">
<select id="selectUser" resultType="com.shengjava.demo.pojo.User">
select * from user where id = #{id};
</select>
<insert id="insUser" parameterType="com.shengjava.demo.pojo.User">
insert into user(user_id, user_name) values (#{id}, #{name});
</insert>
<delete id="delUser" parameterType="int">
/* 故意将delete语句写错 */
deleteAAA from user where id=#{id};
</delete>
</mapper>
6.测试
测试类UserMapperTest
public class UserMapperTest {
@Test
public void selectUserTest() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapperImpl", UserMapper.class);
User user = userMapper.selectUser(1);
System.out.println(user);
}
}
抛出异常信息为:说明我们刚刚故意写错语句成功了。
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'deleteAAA from user where id=4' at line 2
查看数据库:
可以看到,我们刚刚测试调用的selectUser(1)方法抛出了异常。但是,插入却成功了(方法抛出异常时不应该插入数据),这违背了事务的:要么都成功!要么都失败!这会导致数据出现不一致问题。
怎么解决呢?使用spring的生命式事务。
三、Spring 声明式事务
1.增加声明式事务
我们只需要修改上面案例中的spring-dao.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
http://www.springframework.org/schema/beans/spring-beans.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">
<!-- 创建 SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!-- 数据源DataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/demo"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- 配置声明式事务:要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 DataSourceTransactionManager 对象 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 使用构造器添加数据源 -->
<constructor-arg ref="dataSource" />
</bean>
<!-- 结合AOP实现事务的织入 -->
<aop:config>
<!-- 切点,expression:mapper包下所有类,所有方法 -->
<aop:pointcut id="txPointcut" expression="execution(* com.shengjava.demo.mapper.*.*(..))"/>
<!-- 配置通知 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给哪些方法配置事务,配置事务的传播特性 -->
<tx:attributes>
<!-- name:给所有的xxx方法配置事务。propagation="REQUIRED"为默认的传播特性 -->
<!--
<tx:method name="ins"/>
<tx:method name="del"/>
<tx:method name="upd"/>
<tx:method name="sel" propagation="REQUIRED" read-only="true"/>
-->
<!-- *表示给所有的方法配置事务,-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
</beans>
2.测试
我们将上面的数据删除,重新运行测试类UserMapperTest的测试方法selectUserTest()。
运行还是按照希望的一样,抛出异常。这时候去刷新数据库,可以看到数据中该方法中的插入语句没有成功。实现了事务的:要么都成功!要么都失败!
可以看到在报错的情况下,插入操作没有成功,这样就符合事务的原则了!
备注:如果想要项目不报错,直接只要把UserMapper.xml文件中的delete语句修改正确就可以了。
相关
我的该分类的其他相关文章,请点击:【Spring + Spring MVC + MyBatis】文章目录