一、MySQL事务

简单了解一下 MySQL 事务,参考文章:


MySQL事务主要用于处理操作量大,复杂度高的数据,事务中可能包含一个或多个SQL语句,这些语句要么不执行,要么全部执行成功。

  • 事务必须满足四个条件(ACID)
    (1)原子性(Atomicity, 或称不可分割性);
    (2)一致性(Consistency);
    (3)隔离性(Isolation);
    (4)持久性(Durability)。
  • 事务并发问题
    (1)脏读:一个事务读到另外一个事务还未提交(可能被回滚)的脏数据.
    (2)不可重复读: 一个事务执行期间另一事务提交修改,导致第一个事务前后两次查询结果不一致.
    (3)幻读: 一个事务执行期间另一事务提交添加数据,导致第一个事务前后两次查询结果到的数据条数不同.
  • 事务的隔离级别
    (1)读未提交:可能发生脏读、不可重复读、幻读。
    (2)读已提交:可以避免发生脏读,但可能发生不可重复读、幻读。
    (3)可重复读:可以避免发生脏读、不可重复读,但可能发生幻读。
    (4)串行化:可以避免发生脏读、不可重复度、幻读。

二、Spring 整合 MySQL 事务

在执行一组数据库操作时,若在中间环节发生异常或其他中断现象,导致前一部分操作完成,但后一部分操作不能完成,为应对这些中断现象的发生,采用 MySQL 中的事务的概念,事务开始时启动事务,当出现中断现象时,将前面已经完成的操作再回退回去,仿佛一切都没有发生一样,若没有中断现象发生,则在事务的最后提交事务,将对数据库的更新持久化到数据库中。简单的说就是事务中的一组操作,要么全部执行成功,有么全部不执行。

  • XML 方式整合 MySQL 事务:
  1. 导包:
    com.springsource.com.mchange.v2.c3p0-0.9.1.2.jar
    com.springsource.org.apache.commons.logging-1.1.1.jar
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    mysql-connector-java-8.0.11.jar
    spring-aop-5.2.0.RELEASE.jar
    spring-aspects-5.2.0.RELEASE.jar
    spring-beans-5.2.0.RELEASE.jar
    spring-context-5.2.0.RELEASE.jar
    spring-core-5.2.0.RELEASE.jar
    spring-expression-5.2.0.RELEASE.jar
    spring-jdbc-5.2.0.RELEASE.jar
    spring-test-5.2.0.RELEASE.jar
    spring-tx-5.2.0.RELEASE.jar
  2. Spring 配置文件中添加头部信息,主要还需要添加 aop 和 tx 的信息。
  3. 配置事务管理器:管理数据源的事务。
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
  1. 配置事务通知:
<tx:advice id="advice" transaction-manager="transactionManager">
            <tx:attributes>
            <!--
				name:指定方法名,给指定的方法添加事务通知;
				isolation:设置事务隔离级别;
				propagation:设置;事务传播级别;
				read-only:若为true表示只读,若为false表示可以读写。
				rollback-for:设置默认回滚的异常
			-->
            	<tx:method name="zhuanzhang" isolation="READ_UNCOMMITTED" 
						   propagation="REQUIRED" read-only="false"
						   rollback-for="java.lang.Exception"/>
				...
       	    </tx:attributes>
   	</tx:advice>

注意点:最好手动设置 rollback-for 属性为 Exception,这样不管在遇到抛出何种异常时都能回滚事务。因为 Spring 默认抛出了未检查的 unchecked 异常(继承自 RuntimeException 的异常)或者 Error 才回滚事务,其他异常不会触发回滚事务。

  1. 配置 aop,为事务通知指定切入点:
<aop:config>
           <aop:pointcut id="pc" expression="execution(public void com.gec.service.Impl.AccountServiceImpl.zhuanzhang(..))"/>
           <aop:advisor advice-ref="advice" pointcut-ref="pc"/>
    </aop:config>
  • 注解方式整合 MySQL 事务
  1. 前三步与 xml 的方式一样;
  2. 使用注解:@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_UNCOMMITTED,propagation = Propagation.REQUIRED,readOnly = false),若是放在类上,则表示全局配置,类中的方法都遵循该规则;若是放在方法上,则表示值针对该方法。
    注意:这里以抛出 IOException 异常为例,这个异常属于 checked exceptions,若没有手动的设置 rollbackFor = Exception.class 的话,当方法抛出异常时,不会进行事务回滚,而这里因为手动设置的 rollbackFor = Exception.class ,所以能正常进行事务回滚。
//放在方法上表示只针对该方法
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRED, readOnly = false)
    @Override
    public void zhuanzhang(int fromId, int toId, double money) throws IOException {
        ad.lossMoney(money, fromId);
        //减钱

        //手动停电
        File f = new File("D://test4.txt");//该文件实际不存在
        BufferedReader br = new BufferedReader(new FileReader(f));
        String s = null;
        while((s = br.readLine()) != null){
            System.out.println(s);
        }

        //加钱
        ad.addMoney(money, toId);
    }
  1. 在 xml 中开启注解事务支持:< tx:annotation-driven transaction-manager=“transactionManager”/>
<tx:annotation-driven transaction-manager="transactionManager"/>

三、Spring整合MyBatis框架

  1. 导包:
    Spring包及依赖包:
    com.springsource.org.apache.commons.logging-1.1.1.jar
    com.springsource.org.aopalliance-1.0.0.jar
    com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
    commons-logging-1.1.1.jar
    commons-pool-1.5.6.jar
    commons-dbcp-1.4.jar
    mysql-connector-java-8.0.11.jar
    spring-aop-5.1.2.RELEASE.jar
    spring-aspects-5.1.2.RELEASE.jar
    spring-beans-5.1.2.RELEASE.jar
    spring-context-5.2.0.RELEASE.jar
    spring-core-5.2.0.RELEASE.jar
    spring-expression-5.2.0.RELEASE.jar
    spring-jdbc-5.2.0.RELEASE.jar
    spring-tx-5.2.0.RELEASE.jar
    mybatis-spring-1.2.2.jar(Spring与MyBatis包)
    MyBatis包及依赖包:
    asm-3.3.1.jar
    cglib-2.2.2.jar
    javassist-3.17.1-GA.jar
    log4j-1.2.17.jar
    log4j-api-2.0-rc1.jar
    log4j-core-2.0-rc1.jar
    mybatis-3.2.7.jar
    slf4j-api-1.7.5.jar
    slf4j-log4j12-1.7.5.jar
  2. Spring 配置文件中添加头部信息,主要还需要添加 context、aop、tx 的信息。
  3. 配置数据源,这里使用 dbcp 方式连接 MySQL 数据库:
<bean name="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
           <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
           <property name="url" value="jdbc:mysql://localhost:3306/test"/>
           <property name="username" value="root"/>
           <property name="password" value="1234"/>
    </bean>
  1. 配置 SqlSessionFactoryBean,相当于原先 MyBatis 中的 SqlSessionFactory 对象。
<bean name="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
	   <!-- 获取数据库连接-->
       <property name="dataSource" ref="dataSource"/>
	   <!-- 读取 MyBatis 配置文件-->
       <property name="configLocation" value="classpath:mybatis-config.xml"/>
	   <!-- 配置 Mapper 映射文件,也可直接在 MyBatis 配置文件中配置-->
       <property name="mapperLocations">
           <list>
               <value>classpath:com/gec/mapper/UserMapper.xml</value>
           </list>
       </property>
    </bean>
  1. 配置 SqlSessionTemplate,相当于原先 MyBatis 中的 SqlSession 对象。
<bean name="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
	   <!--需要以构造方法注入 SqlSessionFactory 对象-->
       <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
    </bean>

配置完这一步后,就可以在测试类中获取 SqlSessionTemplate 对象,执行增删改查了。

  1. 为优化在 DAO 层过多的重复获取 SqlSessionTemplate 对象的操作,可以让 DAO 层的实现类继承 SqlSessionDaoSupport 类,通过父类的 getSession 方法获取 SqlSessionTemplate 对象。只需要给 DAO 的对象注入 SQLSessionFactoryBean 对象即可。
  2. Mapper 层实际上已经能实现对数据库的操作,所以为了删除 DAO 层可以做以下优化,通过 xml 中配置 MapperFactoryBean 对象,管理 Mapper 层接口,为 Mapper 层接口生成实现类。
<bean name="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
	<!-- 指定管理的 Mapper 接口-->
        <property name="mapperInterface" value="com.gec.mapper.UserMapper"/>
	<!-- 注入 SqlSessionFactory-->
        <property name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
    </bean>
  1. 步骤 7 的代码一次只能管理一个 Mapper 接口,为了避免写过多重读代码,可以替换为配置 MapperScannerConfigurer 对象,指定包路径,就会自动管理包下的所有 Mapper 接口,为每个 Mapper 接口 创建实现类,实现类的名称一般为首字母小写的接口名。
<!-- 扫描 mapper 包,自动为 mapper 创建实例化对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.gec.mapper"/>
    </bean>