spring06——spring中使用事务
事务是什么
事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败
事务四个特性:ACID
• 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保
动作要么全部完成,要么完全不起作用。
• 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所
建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被
破坏。
• 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该
与其他事务隔离开来,防止数据损坏。
• 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该
受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持
久化存储器中
如果不考虑隔离级别引发的安全性问题
• 脏读 :一个事务读到了另一个事务的未提交的数据,如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
• 不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致. 这通常是因为另一个并发事务在两次查询期间进行了更新。
• 幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致
解决读问题:设置事务隔离级别
先介绍这些,我们进行一下事务管理
新建一个java项目:实现转账的事务管理
先导包,这次是25个
aopalliance-1.0.jar spring aop实现的
asm-3.3.1.jar
aspectjweaver-1.8.7.jar 也是关于aop的
c3p0-0.9.2.1.jar c3p0数据库连接池的
cglib-2.2.2.jar 应该是进行spring动态代理的
commons-logging-1.1.1.jar 文本处理的
javassist-3.17.1-GA.jar
junit-4.9.jar 用于测试的
log4j-1.2.17.jar 三个关于日志记录的
log4j-api-2.0-rc1.jar 日志
log4j-core-2.0-rc1.jar 日志
mchange-commons-java-0.2.3.4.jar c3p0数据库连接池分离出来的一个辅助包,没有会抛出异常
mybatis-3.2.7.jar mybatis的包
mybatis-spring-1.2.2.jar mybatis和spring整合的包
mysql-connector-java-5.1.7-bin.jar 数据库的mysql驱动
slf4j-api-1.7.5.jar 这个和下面这个应该是有关于日志文件的
slf4j-log4j12-1.7.5.jar
spring-aop-4.1.3.RELEASE.jar 也是做aop的
spring-aspects-4.1.3.RELEASE.jar Spring提供对AspectJ(另一种AOP的实现)的支持。
spring-beans-4.1.3.RELEASE.jar 配置bean的
spring-context-4.1.3.RELEASE.jar 扩展了大量功能的一个jar...
spring-core-4.1.3.RELEASE.jar 核心包
spring-expression-4.1.3.RELEASE.jar expression表达式
spring-jdbc-4.1.3.RELEASE.jar Spring对JDBC的封装支持。如果你的项目对性能要求比较高,不妨用用Spring.JDBC。
spring-tx-4.1.3.RELEASE.jar 做spring事务管理的
这次加入的是spring-tx.jar和spring-jdbc.jar
我们的事务管理器就放在了spring-jdbc.jar里。
上述有一些参考了https://blog.****.net/tanjiayqqq/article/details/38331951
https://blog.****.net/zwj1030711290/article/details/51735869
是对asm包的详解,里面也有一些很令人感兴趣的内容,包括java语言和其他语言的一些对比
Javassist是一款字节码编辑工具,可以直接编辑和生成Java生成的字节码,以达到对.class文件进行动态修改的效果。熟练使用这套工具,可以让Java编程更接近与动态语言编程。这个出处被我不小心关了,因为开太多,不好找,所以没写出处
Spring 的声明式事务管理可以通过两种方式来实现,一种是基于 XML 的方式,另一种是基于 Annotation 的方式
正式做项目(xml的方式)
把mybatis和spring整合项目中的所有东西(除了test包和已经不管用的mybatis.xml文件)都复制过来 前面那篇文章找就可以
如下
一直没给你们数据库的表。。。。现在给一个
开始做功能
UserService.java里面加一个方法,原本那个不需要删,留着就行
package com.cbb.service;
import com.cbb.pojo.User;
public interface UserService {
/**
* 方法描述:根据id查询用户
* @param id 用户id
* @return
*/
public User selectById(int id);
/**
* 方法描述:转账业务
* @param in 转入账户
* @param out 转出账户
* @param money 交易金额
* @return
*/
public int transfer(String in ,String out ,double money);
}
再实现这个方法
@Override
public int transfer(String in, String out, double money) {
//经过两步
//1.给账户扣钱
userMapper.update(out,-money);
//用于测试事务的出错回滚功能
//int a = 1/0;
//给账户加钱
userMapper.update(in,money);
return 0;
}
方法还没写,先用着——不管你是加钱还是减钱,用一个方法就行.
我们再写方法
package com.cbb.mapper;
import com.cbb.pojo.User;
public interface UserMapper {
/**
* 方法描述:根据id查询用户信息
* @param id 用户id
* @return
*/
public User selectById(int id);
/**
* 方法描述:修改用户金额
* @param in
* @param out
* @param money
*/
public void update(String userName, double money);
}
写Usermapper的映射文件
<?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.cbb.mapper.UserMapper">
<select id="selectById" parameterType="int" resultType="User">
select * from user where id = #{id}
</select>
<select id="update">
update account set balance = (balance + #{1}) where username = #{0}
</select>
</mapper>
我么写一个测试用例
package com.cbb.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.cbb.service.UserService;
/**
* 类描述:
* 作者: 地铁与人海
* 创建日期:2019年3月14日
* 修改人:
* 修改日期:
* 修改内容:
* 版本号: 1.0.0
*/
public class TXTest {
@Test
public void test() {
ApplicationContext app =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService us = app.getBean(UserService.class);
us.transfer("jack", "rose", 800);
}
}
现在整个项目就能运行起来
现在我们可以将上面那句
int a = 1/0;
取消注释,然后就会发现,现在转账只会扣钱,不加钱了。这就是我们使用事务的原因
现在我们来加事务
首先要修改applicationContext.xml文件
文件头要添加一条
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.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">
<!--1.数据源 2.扫描包:注册一些bean 3.会话工厂SqlSessionFactory 4.mapper的代理对象bean -->
<!-- 加载外部的资源文件 -->
<context:property-placeholder location="db.properties"/>
<!-- 配置数据库连接池 c3p0的 --><!-- 必须是这个,很多地方默认使用这个 -->
<bean id = "dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value = "${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 扫描注解自动注册bean -->
<context:component-scan base-package="com.cbb"></context:component-scan>
<!-- 配置会话工厂,让spring容器来管理会话工厂,单例模式。mybatis-spring.jar下的类 --><!-- 没必要起其他的名字 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入属性:mybatis的配置文件、数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- mybatis的配置文件 -->
<!-- <property name="configLocation" value="mybatis.xml"></property> -->
<!-- 别名 -->
<property name="typeAliasesPackage" value="com.cbb.pojo"></property>
</bean>
<!-- mapper的地理bean,使用包扫描加载方式,批量生成mapper的代理bean --><!-- bean的名字就是接口的名字,首字母小写 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定mapper接口的包路径 -->
<property name="basePackage" value="com.cbb.mapper"></property>
</bean>
</beans>
加了哪一条呢?自己对比着看吧,提示是有tx的那一条
然后我们写配置文件
是在上一个基础上添加。
这里是重点、重点、重点,想说的都在注释里面
<!-- spring 事务管理的配置 -->
<!-- 首先配置一个事务管理器,id要配置成这个,有些地方会默认使用这个名字查找事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务的通知(类似于使用AOP通知,不过这已经封装好了) --><!-- 看一下事务的aop实现 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--name配置的是需要进行事务管理的方法 *是通配符
propagation传播行为:
REQUIRED:默认值(缺省) 当前的方法必须运行在事务中,如果当前没有事务则会开启一个新事务。如果当前有事务,则方法会在该事务中运行
SUPPORTS:表示该方法不需要事务上下文,如果有事务则使用当前事务,如果没有事务则无需进行事务管理,比较适合查询方法
还有很多,在后面会讲 -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
然后将事务配置到aop中
<!-- 事务的aop配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.cbb.service..*.*(..))" id="txCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txCut"/>
</aop:config>
上面有两个地方标识为什么方法添加事务,一个是切点,也是范围大的那个,一个是<tx:method name="*" propagation="REQUIRED"/>
,name是需要进行事务管理的方法,*代表所有方法。
三步:配置事务管理器、配置事务管理器的通知、配置事务的aop配置。都完成了,就可以测试了
使用注解的方式进行开发
因为我们是通过读配置文件的方法运行的,那我们完全可以用上面那个项目,读另一个配置文件即可。
我们命名为spring.xml文件
其他的都一样,我们先把我们写的关于事务的删掉
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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/context http://www.springframework.org/schema/context/spring-context.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">
<!--1.数据源 2.扫描包:注册一些bean 3.会话工厂SqlSessionFactory 4.mapper的代理对象bean -->
<!-- 加载外部的资源文件 -->
<context:property-placeholder location="db.properties"/>
<!-- 配置数据库连接池 c3p0的 --><!-- 必须是这个,很多地方默认使用这个 -->
<bean id = "dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value = "${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 扫描注解自动注册bean -->
<context:component-scan base-package="com.cbb"></context:component-scan>
<!-- 配置会话工厂,让spring容器来管理会话工厂,单例模式。mybatis-spring.jar下的类 --><!-- 没必要起其他的名字 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入属性:mybatis的配置文件、数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- mybatis的配置文件 -->
<!-- <property name="configLocation" value="mybatis.xml"></property> -->
<!-- 别名 -->
<property name="typeAliasesPackage" value="com.cbb.pojo"></property>
</bean>
<!-- mapper的地理bean,使用包扫描加载方式,批量生成mapper的代理bean --><!-- bean的名字就是接口的名字,首字母小写 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定mapper接口的包路径 -->
<property name="basePackage" value="com.cbb.mapper"></property>
</bean>
</beans>
然后现在我们只需要配置两个就可以了(不是上面的三个)
一个是事务管理器,一个是事务的注解驱动就可以了
添加进去
<!-- spring 事务管理的配置 (注解)-->
<!-- 首先配置一个事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务的注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
其实transaction-manager="transactionManager"
也不用加,他默认找transactionManager
这个id,我们起的就是这个,但还是写上了。
我们给需要进行事务管理的方法添加上注解
然后读取我们所写的配置文件(新写了一个test2)
package com.cbb.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.cbb.service.UserService;
public class TXTest {
@Test
public void test() {
ApplicationContext app =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService us = app.getBean(UserService.class);
us.transfer("jack", "rose", 800);
}
@Test
public void test2() {
ApplicationContext app =
new ClassPathXmlApplicationContext("spring.xml");
UserService us = app.getBean(UserService.class);
us.transfer("jack", "rose", 800);
}
}
注解的使用
@Transactional
注解可以放到方法上,也可以放到类上,表示对这个类上所有的方法进行事务管理
我们还可以对注解添加一些配置,格式是
@Transactional( 配置放在这里面 )
主要有:
我们测试一个noRollbackFor,noRollbackFor 是不对哪些异常进行回滚
我们的设定的错误是java.lang.ArithmeticException异常,也就是算术异常
package com.cbb.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.cbb.mapper.UserMapper;
import com.cbb.pojo.User;
import com.cbb.service.UserService;
/**
* 类描述:service类
* 作者: 地铁与人海
* 创建日期:2019年3月12日
* 修改人:
* 修改日期:
* 修改内容:
* 版本号: 1.0.0
*/
//事务管理的注解,可以放在需要注解的方法上,也可以放到类上,放到类上代表类所有的方法都需要事务管理。
@Transactional(noRollbackFor = java.lang.ArithmeticException.class)
//noRollbackFor 是不对哪些异常进行回滚 所以证据好意思就是对这个java.lang.ArithmeticException异常(我们设计的算术异常)不回滚
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public User selectById(int id) {
return userMapper.selectById(id);
}
@Override
public int transfer(String in, String out, double money) {
//经过两步
//1.给账户扣钱
userMapper.update(out,-money);
//用于测试事务的出错回滚功能
//int a = 1/0;
//给账户加钱
userMapper.update(in,money);
return 0;
}
}
这样这个异常就不会回滚了。
注意
如果我们把会出现异常的地方try-catch起来,我们的事务不会进行回滚,因为我们的异常通知是捕捉到异常才会使事务进行回滚(事务更接近于通知中的异常通知),现在都捕捉不到,所以不会回滚。
所以需要被spring进行事务管理,则应该不能自己处理异常,应该把异常往外抛。
事务补充知识点
事务传播行为
补充说明:事务管理核心接口(底层的东西)
依赖包:spring-tx-4.*.RELEASE
事务管理器:PlatformTransactionManager
Spring 并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给
Hibernate、Mybatis 或者 JTA 等持久化机制所提供的相关平台框架的事务来实现。
Spring 事务管理器的接口是
org.springframework.transaction.PlatformTransactionManager
➢ getTransaction:用户获取事务状态信息
➢ commit:用于提交事务
➢ rollback:用于回滚事务
实现类
重要的一个实现类为
org.springframework.jdbc.datasource.DataSourceTransactionManager,用于配置 JDBC数据源的事务管理器
事务属性
➢ getPropagationBehavior:获取事务的传播行为
➢ getIsolationLevel:获取事务的隔离级别,事务管理器根据它来控制另外
一个事务可以看到本事务内的哪些数据
➢ getTimeout:获取事务的超时时间
➢ isReadOnlys:获取是否只读
➢ getName:获取事务对象名称##### 事务状态 TransactionStatus
TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。
事务状态
TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。
事务管理的方式
Spring 中的事务管理分为两种方式:一种是传统的的编程式事务管理,另一种是声明式事
务管理。
➢ 编程式事务管理:通过编写代码实现事务管理,包括定义事务的开始、正常执行后的
事务提交和异常时的事务回滚。
➢ 声明式事务管理:通过 AOP 技术实现事务管理,其主要的思想是讲事务管理作为一
个‘切面’代码单独编写,然后通过 AOP 技术将事务管理的‘切面’代码植入到业
务目标类中。
显而易见,声明式事务管理最大的优点在于开发者无须通过编程的方式来管理事务,
只需在配置文件中进行相关的事务规则声明,就可以将事务规则应用到业务逻辑中。
这样的开发效率高,所以在实际开发中,推荐使用声明式事务管理。
我们上面使用的就是声明式事务管理
至此spring的学习告一段落
END