SPRING对DAO层的统一封装

1. 异常:spring对dao层进行了统一的封装 首先解决各个持久层jdbc,hibernate,jdo,ibatis等的异常处理的问题。

所以spring提供了一套和技术实现无关的,面向DAO的运行时异常体系,并通过异常转换器转换为spring的异常。

针对jdbc的异常SQLException 抛出的错误码和错误状态 SQLExceptionTranslator|SQLErrorcodeSQLExceptionTranslator |SQLStateSQLExceptionTranslator 进行转换

其他持久层框架 类同。

 

2.JdbcTemplate,TransactionTemplate,通过模版加回调的方式进行封装 将整个jdbc操作中固定不变的和可变的进行分开将固定不变的放到JdbcTemplate中可变的通过回调的方式开放给程序员调用,也可以继承JdbcDaoSupport。并保证JdbcTemplate线程安全性(Dao都是一份代码共享JdbcTemplate的)。spring为各个持久层提供了模版类 封装异常相关信息。

TransactionTemplate和JdbcTemplate一样使用模版加回调的方式实现 但是这个是控制事务的一般加载service层上面的,统一能保证线程安全。内部实现是spring事务管理封装来实现的参考3事务管理。

(先说下原理 内部使用的都是ThreadLocal中存放的connection,所以在service层TransactionTemplate开启的事务使用的连接和JdbcTemplate使用的都是同一个数据库连接,在事务管理细说)。

 

3.事务管理:

============================补充start=======================

扫描二维码关注公众号,回复: 732166 查看本文章

事务隔离级别 一般应用事务隔离级别都是Read Commited,这个级别能解决很多数据库的并发问题 比如脏读,撤销事务时候把已提交的事务数据覆盖,但是

  如果两个事务同事执行都需要修改金额字段  a事务读取金额100元  在a读取完还没有执行操作 b事务读取100并修改为90  a事务这个时候再加10 原本以为是110的但是现在还是100

  Read Commited不能解决这个问题 这个时候要么查询出来使用select ...forupdate(不利于并发)  要么在执行增加的sql语句同时进行查询合入一个sql语句,防止其他事务参入,主要因为读取时候并没有把数据锁定。

  

spring如何实现上面说的线程安全性以及事务管理

  在web开发中一般每个请求对应一个线程,每个请求一般都会经历controller->service->dao =>返回 其中涉及到一个有状态的对象connection。  这个时候你需要保证线程安全 要么涉及到的bean都是无状态的,或者都创建多例(耗内存不考虑,springmvc controller默认实现都是单例的)。 还有种办法就是采用锁机制保证并发,但是connection参数在方法之间的传递又存在问题了。

对比之下Theadlocal是最好的选择能保证线程安全又能方便参数的传递。

一个请求中:

如果service层有事务 在每个事务开启时候获取到connection放到Threadlocal 在dao操作时候通过Threadlocal 获取到这个相同的connection,事务结束释放connection连接

(注意只在事务期间占用连接的,connection  很宝贵的。即使你下面代码又开启事务还得重新获取)。

如果service层没事务 在dao操作时候获取连接放到Threadlocal中去执行完成释放connection连接。

如果是多数据源事务也一样在service层通过事务管理器开启事务就决定了使用那个数据源的连接,所以dao也使用这个连接。(数据源是注入到事务管理器里面的比如DataSourceTransactionManager)

spring这样实现了事务的功能 又保证了连接的线程安全,可以参考spring TransactionSynchronizationManager定义的很多ThreadLocal都是为了维护无状态bean用的。

============================补充end =======================

 

3.1

spring事务管理功能的实现主要三个类:

PlatformTransactionManager    持久层jdbc,hibernate,jdo,jta事务管理器的抽象 spring为各个不同持久层体提供不同的实现 这个只有三个方法开启事务 提交事务 回滚事务 都是针对connection 直接通过ThreadLocal获取不就行了为什么要各自有自己的实现呢?比如hibernate session 开启事务不光 setAutoConnection(false)那么简单 做了好多自己的事情在里面。提交回滚也一样。 涉及到缓存之类的东西。

TransactionDefinition                对应在xml文件中对事务的配置属性 比如事务隔离级别,超时时间,只读状态啊 传播特性之类的

TransactionStatus                    当前事务的运行状态 比如事务回滚 事务savepoint。。。

 

3.2

事务的7种传播特性:一般应用程序不建议service层调用service层的方法,但是如果调用了就可能存在嵌套事务的可能,如果各个service层方法定义了自己的事务,那么在调用过程中事务之间如何相互配合呢?

                                1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启

                                2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行

                                3. PROPAGATION_MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

                                4. PROPAGATION_REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

                                5. PROPAGATION_NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务。

                                6. PROPAGATION_NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常

                                7. PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。

1,2,3,6很容易理解就是字面的意思 7是个jdbc3.0里面支持的savepoint的概念,能回滚到保存点上。4,5里面都有个事务挂起的操作到底什么是事务的挂起呢?

PROPAGATION_REQUIRES_NEW :首先挂起当前事务 挂起的意义是将所有的资源进行保存,以便进行恢复,挂起后开启一个新事务 在新事务中允许,新事务运行结束 老事务恢复继续允许 新老事务互不干涉。

PROPAGATION_NESTED:开启一个嵌套的事务,是一个已经存在事务的子事务,嵌套事务执行会获取保存点savepoint如果失败conn.roolback(savepoint)回滚。 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.  

总结:PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back. 外部事务能决定嵌套事务的提交回滚。而内部事务如果执行失败能回滚到指定的点上去。

 

3.3

声明式事务三种配置方式:

A:TransactionProxyFactoryBean方式(侵入式)

        <bean id="txManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource" />

</bean>

<bean id="bbtForumTarget" 

     class="com.test.service.impl.BbtForumImpl"

     p:forumDao-ref="forumDao"

     p:topicDao-ref="topicDao"

     p:postDao-ref="postDao"/>

<bean id="bbtForum"

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"(类似aop ProxyFactoryBean)

p:transactionManager-ref="txManager"(类似aop 增强)

p:target-ref="bbtForumTarget"(类似aop 目标对象)>

<property name="transactionAttributes">

<props>

<prop key="addTopic">

PROPAGATION_REQUIRED,+PessimisticLockingFailureException

                </prop>

<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>

<prop key="*">PROPAGATION_REQUIRED,-tion</prop>

</props>

</property>

</bean>

B:tx/aop Schema方式(非侵入式)

       <bean id="bbtForum"

class="com.test.service.impl.BbtForumImpl"

p:forumDao-ref="forumDao"

p:topicDao-ref="topicDao"

p:postDao-ref="postDao"/>

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager"

p:dataSource-ref="dataSource"/>

<aop:config>

<aop:pointcut id="serviceMethod"

expression="execution(* com.test.service.*Forum.*(..))" />

<aop:advisor pointcut-ref="serviceMethod"

advice-ref="txAdvice" />

</aop:config>

<tx:advice id="txAdvice" transactionManager=“transactionManager”>

        <tx:attributes> 

            <tx:method name="get*" read-only="false"/>

            <tx:method name="add*" rollback-for="PessimisticLockingFailureException"/>

            <tx:method name="update*"/>         

        </tx:attributes>

    </tx:advice>

C:注解方式

       <bean id="txManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager"

p:dataSource-ref="dataSource"/>

        <!--事务注解扫描器-->          

<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />

        在需要加入事务的类上面加入@Transactional注解就可以了

和spring aop实现对比模拟实现,<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />可以在后处理bean里面完成代理对象的生成。

类似实现:

织入切面 

@Aspect //1,开启事务提交事务

public class Transaction{

   @Around("@annotation(XXXXXXXXXXX.Transactional)")

   public void transaction(ProcessdingJoinPoint pjp){

          PlatformTransactionManager tx = null;//获取配置到<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />里面的事务管理器txManager

          tx.getTransaction();

          pjp.proceed();

          tx.commit();
   }

}

@Aspect //2,回滚事务

public class ThrowsTransaction{

   @AfterThrowing(value="@annotation(value="XXXXXXXXXXX.Transactional",throwing="iae"))

   public void ThrowsTransaction(Exception iae){

          PlatformTransactionManager tx = null;//获取配置到<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />里面的事务管理器txManager

        tx.rollback();

   }

}

 

最后加上注解扫描 <aop:aspectj-autoproxy />

 

区分下: <aop:aspectj-autoproxy />这个是aop注解切面的扫描 扫描标注@Aspect切面 生成代理。

                   <tx:annotation-driven/>这个是事务注解扫描 专门为事务注解提供的扫描@Transactional会被扫描到至于切面实现类上面的实现spring已经实现隐藏起来了。

 

 

如果在service层需要保存一个大文件 保存完成后需要修改数据库几个字段的值,这个时候就需要在service层做个异步处理 就是启动一个新线程来保存文件 保存后修改数据库几个字段这个时候的事务又是什么样的呢?

分析:

启动一个新的线程调用service层的方法来修改数据库,如果这个方法需要事务支持那么首先会从threadlocal中获取到connection连接 如果没有(肯定是没有的因为是新启动的线程)

就去连接池中获取开启事务并绑定。执行到dao操作修改数据库字段再从当前线程的threadlocal获取到数据库连接  完成后事务提交释放连接,整个过程中和开启线程的service方法事务没有任何的关系 ,他们的事务都是相互独立运行的没有任何关系。

所以不用担心多线程下会导致事务失效的问题,只是在多线程下 传播特性不能传播到开启的线程上面去的而已。

 

 

spring把connection连接都管理好了 我们一般不需要自己获取到连接 如果项目中一定要用怎么弄呢?

肯定要从线程的threadlocal中获取不然就达不到spring事务控制的效果了,如何获取呢,spring提供了DataSourceUtils类能让你很方便的获取到线程threadlocal绑定的数据库连接。

 

 

DataSourceUtils获取到的连接是否需要进行是否呢?

分情况:

如果是有事务的那么就不要释放了 因为如果你释放了 spring怎么拿这个连接去提交事务呢。

如果是没有事务那么就一定要是否的 不让会导致 连接泄漏的问题。

无论是释放还是获取都使用DataSourceUtils提供的方法。

其他持久层 都有类似的Util。

 

 

猜你喜欢

转载自wuhuajun.iteye.com/blog/1873569