跨出CURD程序员(一)数据库事务

  跨出第一步,从被动变主动。

       本文主要记录:事务的四大特性;事务的隔离级别;spring事务的7种传播属性;spring事务配置方式(编程式,声明式)

  • 事务的四大特性(ACID)
    1. 原子性(atomicity)
      一体,里面所有的操作要么全部成功,要么全部失败。
    2. 一致性(consistency)
      一致性是对数据可见性的约束,保证在一个事务中的多次操作的数据中间状态对其他事务不可见的。看网上好多讲解找到了一个比较通俗易懂的例子,转账:一个事务里面包括一个人钱增加,另一个人减少,在这个事务里两个操作有个中间状态一个人减钱了另一个人还没加钱的中间状态,一致性就是指的这个中间状态在此时其他事务调用这个表是是不会看到这种状态的,一定是只能看到最初没转的状态和转完钱的状态。
    3. 隔离性(isolation)
      隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。衍生出隔离级别
    4. 持久性(durability)
      事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
      tips:原子性和一致性的的侧重点不同:原子性关注状态,要么全部成功,要么全部失败,不存在部分成功的状态。而一致性关注数据的可见性,中间状态的数据对外部不可见,只有最初状态和最终状态的数据对外可见
  • 事务的隔离级别
      
    四种问题:
    1. 两个事务同时更新一条数据(丢失更新)
    2. 一个更新事务完成未提交,数据被回滚了,一个读取事务读取读到的会是更新的脏数据。(脏读)
    3. 当同一个事务执行两次及以上相同的查询时,每次都得到不同的数据。一般因为另一并发事务在两次查询期间进行了更新(不可重复读)
    4. 第一个事务读取了一些数据,此时第二个事务在该表中插入了一些新数据,这是第一个事务再读取相同的数据就会多几行(幻读)

       四种隔离级别:

    1. Read Uncommitted(读取未提交内容)
      允许事务读取未被其他事务提交的更改。脏读,不可重复读,幻读都可能会出现

    2. Read Committed(读取提交内容)
      只允许事务读取已经被其他事务提交的更改,可以避免脏读,但不可重复读和幻读问题仍然可能出现

    3. Repeatable Read(可重读)

      确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但是幻读的问题依然存在

    4. Serializable(可串行化)
      确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新,删除。所有的并发问题都能避免,但是性能较低。

               
  • spring事务的传播属性

    Propagation.REQUIRED(required):支持当前事务,如果当前有事务, 那么加入事务, 如果当前没有事务则新建一个(积极主动的选手,相互影响回滚)
    Propagation.NOT_SUPPORTED(not_supported) : 以非事务方式执行操作,如果当前存在事务就把当前事务挂起,执行完后恢复事务(表面一套背后一套,原事务正常执行);
    Propagation.SUPPORTS (supports) :如果当前有事务则加入,如果没有则不用事务。(顺其自然,有就有没有就没有,被动型选手)
    Propagation.MANDATORY (mandatory) :只支持事务方式执行,如果当前没有事务,则抛出异常。(坚决拥护领导决策)
    PROPAGATION_NEVER (never) :只支持非事务方式执行,如果当前存在事务,则抛出异常。(唱反调的)
    Propagation.REQUIRES_NEW (requires_new) :支持当前事务,当前有事务,则挂起当前事务新创建一个事务,如果当前没有事务,则自己创建一个事务。(独立自主,互不影响)
    Propagation.NESTED (nested 嵌套事务) :如果当前存在事务,则嵌套在当前事务中。如果当前没有事务,则新建一个事务自己执行(和required一样)。嵌套的事务使用保存点作为回滚点,当内部事务回滚时不会影响外部事物的提交;但是外部回滚会把内部事务一起回滚回去。(积极主动型选手,不麻烦别人,但是别人会麻烦你)

    注意:事务必须在同一个线程中才有效,

  • spring事务配置方式(编程式,声明式)
    编程式:通过系统提供的API或者封装方法在程序中调用,不接触最终实现的底层持久化框架代码。

    声明式(主要使用):

    第一步:在spring配置中配置事务管理器

    第二步:在需要使用事务的方法前面加上@Transactional注解
    例子:

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory">
    <ref bean="mySessionFactory"/>
    </property>

    <!-- 配置事务传播特性 -->
    <tx:advice id="TestAdvice" transaction-manager="transactionManager">
    <tx:attributes>
    <tx:method name="save*" propagation="REQUIRED"/>
    <tx:method name="del*" propagation="REQUIRED"/>
    <tx:method name="update*" propagation="REQUIRED"/>
    <tx:method name="add*" propagation="REQUIRED"/>
    <tx:method name="find*" propagation="REQUIRED"/>
    <tx:method name="get*" propagation="REQUIRED"/>
    <tx:method name="apply*" propagation="REQUIRED"/>
    </tx:attributes>
    </tx:advice>
    <!-- 配置参与事务的类 -->
    <aop:config>
    <aop:pointcut id="allTestServiceMethod" expression="execution(* com.test.testAda.test.model.service.*.*(..))"/>
    <aop:advisor pointcut-ref="allTestServiceMethod" advice-ref="TestAdvice" />
    </aop:config>
    需要注意的地方:

    (1) advice(建议)的命名:由于每个模块都会有自己的Advice,所以在命名上需要作出规范,初步的构想就是模块名+Advice(只是一种命名规范)。

    (2) tx:attribute标签所配置的是作为事务的方法的命名类型。

             如<tx:method name="save*" propagation="REQUIRED"/>

            其中*为通配符,即代表以save为开头的所有方法,即表示符合此命名规则的方法作为一个事务。

            propagation="REQUIRED"代表支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。

    (3) aop:pointcut标签配置参与事务的类,由于是在Service中进行数据库业务操作,配的应该是包含那些作为事务的方法的Service类。

           首先应该特别注意的是id的命名,同样由于每个模块都有自己事务切面,所以我觉得初步的命名规则因为 all+模块名+ServiceMethod。而且每个模块之间不同之处还在于以下一句:

           expression="execution(* com.test.testAda.test.model.service.*.*(..))"

           其中第一个*代表返回值,第二*代表service下子包,第三个*代表方法名,“(..)”代表方法参数。

    (4) aop:advisor标签就是把上面我们所配置的事务管理两部分属性整合起来作为整个事务管理。

    图解:跨出CURD程序员(一)数据库事务


上一篇:springboot2.X 使用spring-data组件对MongoDB做CURD


下一篇:Java-JDBC-PreparedStatement进行CURD