首页 > 数据库 > MySQL > 正文

MySQL与Spring事务管理

2024-07-24 12:59:34
字体:
来源:转载
供稿:网友

数据库事务是保证在并发情况下能够正确执行的重要支撑,MySQL常见的数据库引擎中支持事务的是InnoDB,事务就是一系列操作,正确执行并提交,如果中途出现错误就回滚。事务要保证能够正常的执行,就必须要保持ACID特性,这在前面的文章当中有提到,本文也偏重sPRing的事务管理配置demo因此不做过多的概念介绍,网上的资料已经比较丰富。

这是HeidiSQl截图查看不同引擎的特性: 这里写图片描述

数据库的事务的隔离性是有级别的,不同的级别具有不同的特性,应该在合适的条件下选择合适的隔离级别,不同的数据库产品支持的隔离级别可能不同,甲骨文支持三种,MySQL数据库支持四种隔离级别,分别是:读未提交,读已提交,可重复读,可串行。不同的隔离级别分别可以避免脏读,不可重复读,幻读的情况。值得一提的是,避免不可重复读和幻读都是进行加锁,不同的是一个是对行进行加锁,避免幻读是对表进行加锁。还有就是锁可以分为共享锁和独占锁,一般来说为了避免事务的更新丢失,读写之间会进行加锁,分为悲观锁与乐观锁,悲观锁就是普通的锁,乐观锁就是不加锁,在更新的时候再去验证是否有被修改,如果被修改则读取的数据为脏数据不能修改。这样在修改比较多的情况下比较适合悲观锁,在读取比较多的情况下就比较适合乐观锁。

spring作为开发平台,对数据库的访问支持的很完善,对事务的支持也比较完善,主要分为两种编程式事务与声明式事务,编程式事务能够更细的控制事务回滚与提交的粒度,但是需要在代码中编写,耦合性更高,并且与业务代码混合在一起,这与spring的无入侵的特性相违背,因此一般会采用声明式事务的方式进行事务管理,因为在很多需要操作数据的逻辑都需要进行事务管理,抛开不同开发者写的代码差异性不谈,单从重复性的代码导致系统难以维护,内聚性很差这点就足以证明这种方式不可取,但是这不正是AOP的使用场景吗,因此使用切面编程技术生成动态代理完成事务管理,业务代码更加的单纯,。主要分为三大部分,数据源,事务管理器,代理机制。其中处于核心地位的接口为PlatformTransactionManager,它的定义比较简单:// 获取spring配置的事务传播机制 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;// 提交 void commit(TransactionStatus status) throws TransactionException;// 或则回滚 void rollback(TransactionStatus status) throws TransactionException;

getTransaction定义了事务的开启范围,这个可以在网上找到更详细的讲解,这里不累述。commit与rollback就是基本的事务控制机制。 AbstractPlatformTransactionManager实现了该接口,这个抽象类定义一些基本的对事务支持的操作,但是抽象了不同的orm框架对事务管理的细节。jdbc是通过dataSource Connection来进行事务管理,而hibernate是通过Sqlsession进行事务管理。使用jdbc进行事务管理,就注入DataSourceTransactionManager即可。并制定对应的数据源,然后只需要制定需要代理的类即可。 数据源的配置:

<bean id="driver" class="com.mysql.jdbc.Driver"></bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"> <property name="username" value="${username}" /> <property name="passWord" value="${password}" /> <property name="url" value="${url}" /> <property name="driver" ref="driver" /> </bean>

首先我们制定事务管理器:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>

然后我们定义需要事务传播机制,我们来创建一个常见,也可以采用其他的方式创建advice,这里的advice通知就是定义了需要做什么,这里就是 如果有事务加入事务,没有的话创建新事务:

<tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <!-- 如果有事务加入事务,没有的话创建新事务 --> <tx:method name="*" propagation="REQUIRED" timeout="3"/> </tx:attributes> </tx:advice>

做什么定义完了,就需要指定在哪里做(pointcut)即切点,代理的方式有很多种,可以使用默认的代理DefaultAdvisorAutoProxyCreator。也可以使用注解的方式,很灵活,主要就是明白定义好切点(一般spring支持较好的是方法级别的拦截)即可。这里我们使用比较方便的schema标签支持aop代理:

<aop:config> <aop:pointcut id="txPointcut" expression="execution(* micro.test.spring.service..*Service*.*(..))" /> <aop:advisor advice-ref="advice" pointcut-ref="txPointcut"/> </aop:config>

很多时候我们引入新的xml标签会报错,这个时候检查xml文件头是否引入了相关的schema即可,这点是初学者很容易犯错的地方。

<!-- 扫描到advice --> <context:component-scan base-package="micro.test.spring" /> <!-- mybatis中sqlSession由这个类来完成 --> <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations"> <list> <value>classpath:/sqlmap/ComputerMapper.xml</value> </list> </property> </bean>

其他必要的配置跟本文不太相关就不贴了。 前面execution(* micro.test.spring.service..Service.*(..))就表示在这个包下面的所有方法都是切点,会被代理。这是service的一个方法,执行它:

public void testTransaction2() { Computer computer = new Computer(); computer.setBrand("test"); computerMapper.insert(computer);//事务会回滚 ComputerExample computerExample = new ComputerExample(); computerExample.or().andBrandEqualTo(computer.getBrand()); int a = 1; a /= 0; // 制造异常 computerMapper.insert(computer); }

如果没事务管理机制,会插入一条记录。引入了事务管理机制,抛出异常(spring要求运行时异常)就不会插入记录,因为遇到了异常,当然如果去掉异常,两条都会插入进去,值得一提的是我数据库设置的默认应该不是读未提交,因为在调试到还没抛异常的时候也不会看到一条记录,如果是读未提交应该是看到出现了一条记录然后又消失了(被回滚),测试了几次,要么有0条,要么有两条(正常执行)。没有造成脏读的情况至少应该是读已提交。查询了一下: 这里写图片描述 果然可重复读,是能够不免脏读和不可重复度两个问题的。但是无法避免幻读。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表