一、为什么要使用事务及事务的特性
事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。
事物的特性:
1、原子性(事务不可分割,一个事务就是一个最小的单元,这里面的操作是一块的执行的。)
2、一致性(因为是一个单元,将相当于一个球,动的时候是一个球在动,里面的东西都是一块的,提交就都提交,撤回就都撤回)
3、隔离性(当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离)
4、持久性(指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作)
二、数据资源并发访问(隔离级别不同)时可能会出现的情况
1.脏读:读到另一个未提交事务的数据
2.不可重复读:B事务读取了两次数据,但在两次中间有另一个事务对数据进行了修改并提交,导致读到的数据两次不一样。(行影响)
3.幻读:B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,B事务的这两次读取出来的集合不一样。(表影响)
**幻读与不可重复读的区别:都是读取了两次数据。不可重复读是针对一行数据,数据内容前后不一致。幻读是针对表,前后读出的行数不一样。
三、数据库的隔离级别
为了解决上面出现的问题,主流数据库都会提供四种事务隔离级别。
1.读未提交(Read Uncommitted)所有事务都能看到其他事务未提交的数据。但这只能防止更新丢失,不能解决脏读,不可重复读和幻读。
2.读已提交(Read Committed)事务只能看到其他事物已提交的数据的改变,这个可以解决脏读问题,但对不可重复读和幻读无效。这是大多数数据库系统的默认隔离级别
3.可重复读(Repeatable Read)读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。Mysql默认使用该隔离级别。这可以通过“共享读锁”和“排他写锁”实现,即事务需要对某些数据进行修改必须对这些数据加 X 锁,读数据时需要加上 S 锁,当数据读取完成并不立刻释放 S 锁,而是等到事物结束后再释放。所以是对修改的数据加锁,所以增加数据并不能禁止,所以不能防止幻读。
4.可串行化(Serializable)事务只能一个接着一个地执行,不能并发执行。但可能导致大量的超时现象和锁竞争,通常数据库不会用这个隔离级别
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | × | × | × |
读已提交 | √ | × | × |
可重复读 | √ | √ | × |
可串行化 | √ | √ | √ |
四、spring实现事务
1.spring_dao.xml:
<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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.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">
<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/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/eiji/mapper/userMapper.xml"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--结合AOP实现事务织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--哪些方法使用事务-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.eiji.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>
</aop:config>
</beans>
2.UserMapperImpl:
public class UserMapperImpl implements UserMapper {
SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public void insertUser(User user) {
sqlSession.getMapper(UserMapper.class).insertUser(user);
}
public void deleteUser(int id) {
sqlSession.getMapper(UserMapper.class).deleteUser(id);
}
public List<User> selectUser() {
return sqlSession.getMapper(UserMapper.class).selectUser();
}
}
3.测试类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper bean = context.getBean(UserMapper.class);
bean.insertUser(new User(14,"果然少年","231424"));
bean.deleteUser(13);
List<User> users = bean.selectUser();
for (User user : users) {
System.out.println(user);
}
}
}