回顾一下事务的特性:
事务:即多条对数据的操作组成的集序列
事务的四大特性(ACID):
原子性:将多条对数据进行操作的序列视为一个整体,要么全部执行成功,要么全部执行失败
持久性:事务一旦提交成功,数据的改变是永久的,即使数据库发生故障也不会影响数据!
一致性:事务的提交前后,数据完整必须保持一致
隔离性:数据库面对多个访问时候要为每个访问开启一个事务,且多条事务独立执行,不能相互影响
事务的隔离级别:
产生的问题:
脏读:就是读取到事务没有提交的数据!
解决方案:对当前表加上表级别的读锁
不可重复读:两次读取结果不一致:
解决方案:对当前操作的表加上行级写锁(其实大多时候改变只是针对行的,必要时可以加表级写锁)
幻读:就是数据库表中数据操作时候,读取数据显示没有,但是插入该数据却显示存在!
解决方案:加上表级别的写锁 :
注意:并不是隔离级别越高越好,串行化(表级写锁)是一个重量级操作,会影响数据库的执行效率!
Spring的事务管理
1 Spring提供的事务管理器: PlatformTransactionManager
主要用到就是该接口的实现类:
DataSourceTransactionManager 用于Spring JDBC或MyBatis
HibernateTransactionManager 适用于Hibernate,对这个框架不熟,暂时不做描述
接口中的三个主要方法:(提交事务,获取事务状态,回滚)
2 TransactionDefinition接口,主要定义事务的一些基本的信息
获取事务定义名称
String getName()
获取事务的读写属性
boolean isReadOnly()
获取事务隔离级别
int getIsolationLevel()
获事务超时时间
int getTimeout()
获取事务传播行为特征
int getPropagationBehavior()
3 TransactionStatus接口事务的状态:
boolean isNewTransaction();是否新开启 boolean hasSavepoint();是否具有回滚存储点 void setRollbackOnly();设置事务处于回滚状态 boolean isRollbackOnly();获取事务是否处于回滚状态 void flush();刷新事务 boolean isCompleted();获取事务是否处于完成状态
事务实现
编程式:
maven:
<dependencies>
<!--Spring基本-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--Spring管理jdbc基本-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- MyBatis基本-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--Druid数据库连接控制池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!--Spring控制MyBatisd基本-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!--支持AOP编程-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
主配置文件:
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--引入配置的properties文件-->
<context:property-placeholder location="classpath:*.properties"/>
<!--数据库连接池配置信息-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="service" class="service.imp.ServiceImp">
<property name="dao" ref="dao"/>
<!--不使用AOP创建事务管理器时候需要用到-->
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--javabean测试阶段没有构造-->
<property name="typeAliasesPackage" value="domain"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="dao"/>
</bean>
<!--AOP-->
<!--通知类-->
<bean id="txAdvice" class="aop.Advice">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:config>
<aop:pointcut id="pt" expression="execution(public void service.imp.ServiceImp.addDate(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="AOP001" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
</beans>
jdbc配置文件:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db jdbc.username=root jdbc.password=root
mybatis的mapper:注意文件名字必须和接口,名字相同,否则找你不到映射文件,同时需要和接口文件在同一个目录下!也就是在idea的配置文件目录下创建和接口文件相同的包级别的文件目录!
<?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="dao.Dao">
<update id="addDate">
update message set money = money + #{money} where id= #{id}
</update>
<!--
<update id="inMoney">
update account set money = money + #{money} where name = #{name}
</update>
<update id="outMoney">
update account set money = money - #{money} where name = #{name}
</update>
-->
</mapper>
AOP通知类(这块为了方便,其实可以不写)
public class Advice {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Object AOP001(ProceedingJoinPoint pjp) throws Throwable {
//开启事务
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
//事务定义
TransactionDefinition td = new DefaultTransactionDefinition();
//事务状态
TransactionStatus ts = ptm.getTransaction(td);
System.out.println("AOP......Runing");
//执行原始方法!
Object ret = pjp.proceed(pjp.getArgs());
//提交事务
ptm.commit(ts);
return ret;
}
}
Dao:
public interface Dao {
void addDate(@Param("id") String id, @Param("money") double money);
}
Service:
public interface ServiceTest {
void addDate(String id, double money);
void addDateNoAOP(String id, double money);
}
实现:
public class ServiceImp implements ServiceTest {
private Dao dao;
private DataSource dataSource;
public void setDao(Dao dao) {
this.dao = dao;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void addDate(String id, double money) {
//使用Aop开启事务处理
dao.addDate(id, money);
}
@Override
public void addDateNoAOP(String id, double money) {
//获取事务管理器
PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource);
//事务定义
TransactionDefinition td = new DefaultTransactionDefinition();
//事务状态
TransactionStatus ts = ptm.getTransaction(td);
//执行原始方法
dao.addDate(id, money);
//提交事务时候将事务的状态信息放入作为参数即可
ptm.commit(ts);
}
}
主要加载类:
public class APP {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//强转向父类转,使用子类类型强转会出现类型适配错误
ServiceTest serviceImp = (ServiceTest) ctx.getBean("service");
//serviceImp.addDateNoAOP("001",200);
serviceImp.addDate("001",200);
}
}
编程式基本就是这样!
声明式(xml)
参考这个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:context="http://www.springframework.org/schema/context"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--引入主要的配置文件-->
<context:property-placeholder location="classpath:*.properties"/>
<!--数据库连接池的基本配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--MyBatis的包扫描-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.domain"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.dao"/>
</bean>
<bean id="accountService" class="com.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--引入事务的管理类-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--定义事务管理的通知类-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!--定义控制的事务-->
<tx:attributes>
<!--将所有的方法进行拦截-->
<tx:method name="*" read-only="false"/>
<!--将拦截后的方法进行再次的过滤,根据情况进行相关事务-->
<!--1:以get/find开头的查询数据的方法,设置为只读-->
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!--2:满足这类方法通配的执行事务为写(会改变数据)-->
<tx:method name="a" read-only="false" propagation="REQUIRED"/>
<tx:method name="b" read-only="false" propagation="NEVER"/>
<!--准确锁定方法-->
<!--解释:read-only 是否只读
timeout 超时时间,单位秒
isolation 事物的隔离级别
no-rollback-for 设置事务中不回滚的异常(多个之间用逗号隔开)
rollback-for 设置事务中必须回滚的异常
propagation 事物的传播方式(7种)
-->
<tx:method
name="transfer"
read-only="false"
timeout="-1"
isolation="DEFAULT"
no-rollback-for=""
rollback-for=""
propagation="REQUIRED"
/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--主要业务涉及到综合业务的,应该在servicec层进行相关的操作,所以service和dao/mapper都应该进行监控-->
<aop:pointcut id="pt" expression="execution(* com.service.*Service.*(..))"/>
<aop:pointcut id="pt2" expression="execution(* com.dao.*.b(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt2"/>
</aop:config>
</beans>
注意小点:
aop:advice与aop:advisor区别:
AOP:advice可以是任意的Java对象,但是advisor必须要实现advice的五个接口:
MethodBeforeAdvice
AfterReturningAdvice
ThrowsAdvice...
简言之:一个用于事务。一个就是普通的AOP编程。想了解更多,打开源码集合百度即可
半注解形式实现:
其实只是看xml文件,正常加载配置文件,改变的只是接口上加了注解标注(下面接口实现示例一部使用的就是注解标注),xml中开启事务注解,了解xml文件和注解关系的几乎秒懂,在Spring家族中注解和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:context="http://www.springframework.org/schema/context"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="classpath:*.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.itheima.domain"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itheima.dao"/>
</bean>
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- TX格式 -->
<!-- TX格式 -->
<!-- TX格式 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
<!--<tx:advice id="txAdvice" transaction-manager="txManager">-->
<!--<tx:attributes>-->
<!--<tx:method-->
<!--name="transfer"-->
<!--read-only="false"-->
<!--timeout="-1"-->
<!--isolation="DEFAULT"-->
<!--no-rollback-for=""-->
<!--rollback-for=""-->
<!--propagation="REQUIRED"-->
<!--/>-->
<!--</tx:attributes>-->
<!--</tx:advice>-->
<!--<aop:config>-->
<!--<aop:pointcut id="pt" expression="execution(* com.itheima.service.*Service.*(..))"/>-->
<!--<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>-->
<!--</aop:config>-->
</beans>
声明式(注解)
service接口:
示例一:注解操作其实就是替换xml中的注释部分的配置
/对当前接口的所有方法添加事务
@Transactional(isolation = Isolation.DEFAULT)
public interface AccountService {
/**
* 转账操作
* @param outName 出账用户名
* @param inName 入账用户名
* @param money 转账金额
*/
//对当前方法添加事务,该配置将替换接口的配置
@Transactional(
readOnly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {}, //java.lang.ArithmeticException.class, IOException.class
noRollbackFor = {},
propagation = Propagation.REQUIRED
)
public void transfer(String outName, String inName, Double money);
}
示例二:
public interface AccountService {
@Transactional
public void transfer(String outName, String inName, Double money);
}
Spring的主配置文件:
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
涉及到的核心注解:
@Transactional 类注解方法注解,一般用于接口,标注该接口上开启事务
@EnableTransactionManagement 类注解,Spring的核心配置文件注解,开启事务!
到这基本上完成了Spring事务实现的总结!