五、Spring事务原理详解
5.1、事务基本概念
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
特点:
事务是恢复和并发控制的基本单位。
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
5.2、事务的基本原理
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:
获取连接Connectioncon=DriverManager.getConnection()
开启事务con.setAutoCommit(true/false);
执行CRUD
提交事务/回滚事务con.commit()/con.rollback();
关闭连接conn.close();
使用Spring的事务管理功能后,我们可以不再写步骤2和4的代码,而是由Spirng自动完成。
那么Spring是如何在我们书写的CRUD之前和之后开启事务和关闭事务的呢?解决这个问题,也就可以从整体上理解Spring的事务管理实现原理了。下面简单地介绍下,注解方式为例子
配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
spring在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
真正的数据库层的事务提交和回滚是通过binlog或者redolog实现的。
5.3、Spring事务的传播属性
所谓spring事务的传播属性,就是定义在存在多个事务同时存在的时候,spring应该如何处理这些事务的行为。这些属性在TransactionDefinition中定义,具体常量的解释见下表:
常量名称 | 常量解释 |
---|
PROPAGATION_REQUIRED | 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是Spring默认的事务的传播。 | PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作 | PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 | PROPAGATION_MANDATORY | 支持当前事务,如果当前没有事务,就抛出异常。 | PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 | PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 | PROPAGATION_NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。 |
5.4、数据库隔离级别
隔离级别 | 隔离级别的值 | 导致的问题 |
---|
Read-Uncommitted | 0 | 导致脏读 | Read-Committed | 1 | 避免脏读,允许不可重复读和幻读 | Repeatable-Read | 2 | 避免脏读,不可重复读,允许幻读 | Serializable | 3 | 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重 |
脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。
总结:
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
大多数的数据库默认隔离级别为ReadCommited,比如SqlServer、Oracle
少数数据库默认隔离级别为:RepeatableRead比如:MySQLInnoDB
5.5、Spring中的隔离级别
常量 | 解释 |
---|
ISOLATION_DEFAULT | 这是个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。 | ISOLATION_READ_UNCOMMITTED | 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。 | ISOLATION_READ_COMMITTED | 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 | ISOLATION_REPEATABLE_READ | 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 | ISOLATION_SERIALIZABLE | 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 |
5.6、事务的嵌套
通过上面的理论知识的铺垫,我们大致知道了数据库事务和spring事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解spring事务传播的机制。
假设外层事务ServiceA的MethodA()调用内层ServiceB的MethodB()
PROPAGATION_REQUIRED(spring默认)
如果ServiceB.MethodB()的事务级别定义为PROPAGATION_REQUIRED,那么执行ServiceA.MethodA()的时候spring已经起了事务,这时调用ServiceB.MethodB(),ServiceB.MethodB()看到自己已经运行在ServiceA.MethodA()的事务内部,就不再起新的事务。假如ServiceB.MethodB()运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.MethodA()或者在ServiceB.MethodB()内的任何地方出现异常,事务都会被回滚。
PROPAGATION_REQUIRES_NEW
比如我们设计ServiceA.MethodA()的事务级别为PROPAGATION_REQUIRED,ServiceB.MethodB()的事务级别为PROPAGATION_REQUIRES_NEW。那么当执行到ServiceB.MethodB()的时候,ServiceA.MethodA()所在的事务就会挂起,ServiceB.MethodB()会起一个新的事务,等待ServiceB.MethodB()的事务完成以后,它才继续执行。
他与PROPAGATION_REQUIRED的事务区别在于事务的回滚程度了。因为ServiceB.MethodB()是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.MethodB()已经提交,那么ServiceA.MethodA()失败回滚,ServiceB.MethodB()是不会回滚的。如果ServiceB.MethodB()失败回滚,如果他抛出的异常被ServiceA.MethodA()捕获,ServiceA.MethodA()事务仍然可能提交(主要看B抛出的异常是不是A会回滚的异常)。
PROPAGATION_SUPPORTS
假设ServiceB.MethodB()的事务级别为PROPAGATION_SUPPORTS,那么当执行到ServiceB.MethodB()时,如果发现ServiceA.MethodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.MethodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。
PROPAGATION_NESTED
现在的情况就变得比较复杂了,ServiceB.MethodB()的事务属性被配置为PROPAGATION_NESTED,此时两者之间又将如何协作呢?ServiceB#MethodB如果rollback,那么内部事务(即ServiceB#MethodB)将回滚到它执行前的SavePoint而外部事务(即ServiceA#MethodA)可以有以下两种处理方式:
捕获异常,执行异常分支逻辑 这种方式也是嵌套事务最有价值的地方,它起到了分支执行的效果,如果ServiceB.MethodB失败,那么执行ServiceC.MethodC(),而ServiceB.MethodB已经回滚到它执行之前的SavePoint,所以不会产生脏数据(相当于此方法从未执行过),这种特性可以用在某些特殊的业务中,而PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW都没有办法做到这一点。
b、外部事务回滚/提交代码不做任何修改,那么如果内部事务(ServiceB#MethodB)rollback,那么首先ServiceB.MethodB回滚到它执行之前的SavePoint(在任何情况下都会如此),外部事务(即ServiceA#MethodA)将根据具体的配置决定自己是commit还是rollback另外三种事务传播属性基本用不到,在此不做分析。
5.7、Spring事务API架构图
5.8、SpringAOP设计原理及应用场景
4.7.1、SpringAOP应用示例
AOP是OOP的延续,是AspectOrientedProgramming的缩写,意思是面向切面编程。可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
我们现在做的一些非业务,如:日志、事务、安全等都会写在业务代码中(也即是说,这些非业务类横切于业务类),但这些代码往往是重复,复制------粘贴式的代码会给程序的维护带来不便,AOP就实现了把这些业务需求与系统需求分开来做。这种解决的方式也称代理机制。
先来了解一下AOP的相关概念
切面(Aspect):官方的抽象定义为"一个关注点的模块化,这个关注点可能会横切多个对象"。"切面"在ApplicationContext中<aop:aspect>来配置。
连接点(Joinpoint):程序执行过程中的某一行为,例如,MemberService.get的调用或者MemberService.delete抛出异常等行为。
通知(Advice):“切面"对于某个"连接点"所产生的动作。其中,一个"切面"可以包含多个"Advice”。
切入点(Pointcut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联。切面中的所有通知所关注的连接点,都由切入点表达式来决定。
目标对象(TargetObject):被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,SpringAOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
AOP代理(AOPProxy):在SpringAOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将<aop:config>的proxy-target-class属性设为true。
通知(Advice)类型:前置通知(Beforeadvice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect中的doBefore方法。
后置通知(Afteradvice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,ServiceAspect中的returnAfter方法,所以Teser中调用UserService.delete抛出异常时,returnAfter方法仍然执行。
返回后通知(Afterreturnadvice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
环绕通知(Aroundadvice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,ServiceAspect中的around方法。抛出异常后通知(Afterthrowingadvice):在方法抛出异常退出时执行的通知。ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,ServiceAspect中的returnThrow方法。
注:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。
使用SpringAOP可以基于两种方式,一种是比较方便和强大的注解方式,另一种则是中规中矩的xml配置方式。
先说注解,使用注解配置SpringAOP总体分为两步,第一步是在xml文件中声明激活自动扫描组件功能,同时激活自动代理功能(来测试AOP的注解功能): 第二步是为Aspect切面类添加注解: 测试代码 控制台输出如下: 可以看到,正如我们预期的那样,虽然我们并没有对MemberService类包括其调用方式做任何改变,但是Spring仍然拦截到了其中方法的调用,或许这正是AOP的魔力所在。
再简单说一下xml配置方式,其实也一样简单:
个人觉得不如注解灵活和强大,你可以不同意这个观点,但是不知道如下的代码会不会让你的想法有所改善: 以下是MemberService的代码: 应该说学习SpringAOP有两个难点,第一点在于理解AOP的理念和相关概念,第二点在于灵活掌握和使用切入点表达式。概念的理解通常不在一朝一夕,慢慢浸泡的时间长了,自然就明白了,下面我们简单地介绍一下切入点表达式的配置规则吧。
通常情况下,表达式中使用"execution"就可以满足大部分的要求。表达式格式如下: modifiers-pattern:方法的操作权限
ret-type-pattern:返回值
declaring-type-pattern:方法所在的包
name-pattern:方法名
parm-pattern:参数名
throws-pattern:异常
其中,除ret-type-pattern和name-pattern之外,其他都是可选的。上例中,execution(*com.spring.service.*.*(…))表示com.spring.service包下,返回值为任意类型;方法名任意;参数不作限制的所有方法。
最后说一下通知参数
可以通过args来绑定参数,这样就可以在通知(Advice)中访问具体参数了。例如,<aop:aspect>配置如下: 上面的代码args(msg,…)是指将切入点方法上的第一个String类型参数添加到参数名为msg的通知的入参上,这样就可以直接使用该参数啦。
访问当前的连接点
在上面的Aspect切面Bean中已经看到了,每个通知方法第一个参数都是JoinPoint。其实,在Spring中,任何通知(Advice)方法都可以将第一个参数定义为org.aspectj.lang.JoinPoint类型用以接受当前连接点对象。JoinPoint接口提供了一系列有用的方法,比如getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标)、getSignature()(返回正在被通知的方法相关信息)和toString()(打印出正在被通知的方法的有用信息)。
4.7.2、SpringAOP设计原理及源码分析
开始之前先上图,看看Spring中主要的AOP组件 Spring提供了两种方式来生成代理对象:JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码: 那这个其实很明了,注释上我也已经写清楚了,不再赘述。
下面的问题是,代理对象生成了,那切面是如何织入的?
我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下SpringAOP是如何织入切面的。 主流程可以简述为:获取可以应用到此方法上的通知链(InterceptorChain),如果有,则应用通知,并执行joinpoint;如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。
首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:
可以看到实际的获取工作其实是由AdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。下面来分析下这个方法的实现: 这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor. 接下来我们再看下得到的拦截器链是怎么起作用的。 从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码
5.9、基于SpringJDBC开发ORM框架
使用Spring进行基本的JDBC访问数据库有多种选择。Spring至少提供了三种不同的工作模式:JdbcTemplate,一个在Spring2.5中新提供的SimpleJdbc类能够更好的处理数据库元数据;还有一种称之为RDBMSObject的风格的面向对象封装方式,有点类似于JDO的查询设计。我们在这里简要列举你采取某一种工作方式的主要理由.不过请注意,即使你选择了其中的一种工作模式,你依然可以在你的代码中混用其他任何一种模式以获取其带来的好处和优势。所有的工作模式都必须要求JDBC2.0以上的数据库驱动的支持,其中一些高级的功能可能需要JDBC3.0以上的数据库驱动支持。
JdbcTemplate-这是经典的也是最常用的Spring对于JDBC访问的方案。这也是最低级别的封装,其他的工作模式事实上在底层使用了JdbcTemplate作为其底层的实现基础。JdbcTemplate在JDK1.4以上的环境上工作得很好。
NamedParameterJdbcTemplate-对JdbcTemplate做了封装,提供了更加便捷的基于命名参数的使用方式而不是传统的JDBC所使用的"?"作为参数的占位符。这种方式在你需要为某个SQL指定许多个参数时,显得更加直观而易用。该特性必须工作在JDK1.4以上。
SimpleJdbcTemplate-这个类结合了JdbcTemplate和NamedParameterJdbcTemplate的最常用的功能,同时它也利用了一些Java5的特性所带来的优势,例如泛型、varargs和autoboxing等,从而提供了更加简便的API访问方式。需要工作在Java5以上的环境中。
SimpleJdbcInsert和SimpleJdbcCall-这两个类可以充分利用数据库元数据的特性来简化配置。通过使用这两个类进行编程,你可以仅仅提供数据库表名或者存储过程的名称以及一个Map作为参数。其中Map的key需要与数据库表中的字段保持一致。这两个类通常和SimpleJdbcTemplate配合使用。这两个类需要工作在JDK5以上,同时数据库需要提供足够的元数据信息。
RDBMS对象包括MappingSqlQuery,SqlUpdateandStoredProcedure-这种方式允许你在初始化你的数据访问层时创建可重用并且线程安全的对象。该对象在你定义了你的查询语句,声明查询参数并编译相应的Query之后被模型化。一旦模型化完成,任何执行函数就可以传入不同的参数对之进行多次调用。这种方式需要工作在JDK1.4以上。
1.异常处理异常结构如下: SQLExceptionTranslator是一个接口,如果你需要在SQLException和org.springframework.dao.DataAccessException之间作转换,那么必须实现该接口。转换器类的实现可以采用一般通用的做法(比如使用JDBC的SQLStatecode),如果为了使转换更准确,也可以进行定制(比如使用Oracle的errorcode)。
SQLErrorCodeSQLExceptionTranslator是SQLExceptionTranslator的默认实现。该实现使用指定数据库厂商的errorcode,比采用SQLState更精确。转换过程基于一个JavaBean(类型为SQLErrorCodes)中的errorcode。这个JavaBean由SQLErrorCodesFactory工厂类创建,其中的内容来自于"sql-error-codes.xml"配置文件。该文件中的数据库厂商代码基于DatabaseMetaData信息中的DatabaseProductName,从而配合当前数据库的使用。
SQLErrorCodeSQLExceptionTranslator使用以下的匹配规则:
首先检查是否存在完成定制转换的子类实现。通常SQLErrorCodeSQLExceptionTranslator这个类可以作为一个具体类使用,不需要进行定制,那么这个规则将不适用。
接着将SQLException的errorcode与错误代码集中的errorcode进行匹配。默认情况下错误代码集将从SQLErrorCodesFactory取得。错误代码集来自classpath下的sql-error-codes.xml文件,它们将与数据库metadata信息中的databasename进行映射。
使用fallback翻译器。SQLStateSQLExceptionTranslator类是缺省的fallback翻译器。
2.config模块
NamespaceHandler接口,DefaultBeanDefinitionDocumentReader使用该接口来处理在springxml配置文件中自定义的命名空间。 在jdbc模块,我们使用JdbcNamespaceHandler来处理jdbc配置的命名空间,其代码如下: 其中,EmbeddedDatabaseBeanDefinitionParser继承了AbstractBeanDefinitionParser,解析<embedded-database>元素,并使用EmbeddedDatabaseFactoryBean创建一个BeanDefinition。顺便介绍一下用到的软件包org.w3c.dom。
软件包org.w3c.dom:为文档对象模型(DOM)提供接口,该模型是JavaAPIforXMLProcessing的组件API。该DocumentObjectModelLevel2CoreAPI允许程序动态访问和更新文档的内容和结构。
Attr:Attr接口表示Element对象中的属性。
CDATASection:CDATA节用于转义文本块,该文本块包含的字符如果不转义则会被视为标记。
CharacterData:CharacterData接口使用属性集合和用于访问DOM中字符数据的方法扩展节点。
Comment:此接口继承自CharacterData表示注释的内容,即起始’<!–‘和结束’–>'之间的所有字符。
Document:Document接口表示整个HTML或XML文档。
DocumentFragment:DocumentFragment是"轻量级"或"最小"Document对象。
DocumentType:每个Document都有doctype属性,该属性的值可以为null,也可以为DocumentType对象。
DOMConfiguration:该DOMConfiguration接口表示文档的配置,并维护一个可识别的参数表。
DOMError:DOMError是一个描述错误的接口。
DOMErrorHandler:DOMErrorHandler是在报告处理XML数据时发生的错误或在进行某些其他处理(如验证文档)时DOM实现可以调用的回调接口。
DOMImplementation:DOMImplementation接口为执行独立于文档对象模型的任何特定实例的操作提供了许多方法。
DOMImplementationList:DOMImplementationList接口提供对DOM实现的有序集合的抽象,没有定义或约束如何实现此集合。
DOMImplementationSource:此接口允许DOM实现程序根据请求的功能和版本提供一个或多个实现,如下所述。
DOMLocator:DOMLocator是一个描述位置(如发生错误的位置)的接口。
DOMStringList:DOMStringList接口提供对DOMString值的有序集合的抽象,没有定义或约束此集合是如何实现的。
Element:Element接口表示HTML或XML文档中的一个元素。
Entity:此接口表示在XML文档中解析和未解析的已知实体。
EntityReference:EntityReference节点可以用来在树中表示实体引用。
NamedNodeMap:实现NamedNodeMap接口的对象用于表示可以通过名称访问的节点的集合。
NameListNameList接口提供对并行的名称和名称空间值对(可以为null值)的有序集合的抽象,无需定义或约束如何实现此集合。
Node:该Node接口是整个文档对象模型的主要数据类型。
NodeList:NodeList接口提供对节点的有序集合的抽象,没有定义或约束如何实现此集合。
Notation:此接口表示在DTD中声明的表示法。
ProcessingInstruction:ProcessingInstruction接口表示"处理指令",该指令作为一种在文档的文本中保持特定于处理器的信息的方法在XML中使用。
Text:该Text接口继承自CharacterData,并且表示Element或Attr的文本内容(在XML中称为字符数据)。
TypeInfo:TypeInfo接口表示从Element或Attr节点引用的类型,用与文档相关的模式指定。
UserDataHandler:当使用Node.setUserData()将一个对象与节点上的键相关联时,当克隆、导入或重命名该对象关联的节点时应用程序可以提供调用的处理程序。
3.core模块
3.1JdbcTeamplate对象,其结构如下: 3.2RowMapper 3.3元数据metaData模块
本节中spring应用到工厂模式,结合代码可以更具体了解。 CallMetaDataProviderFactory创建CallMetaDataProvider的工厂类,其代码如下: TableMetaDataProviderFactory创建TableMetaDataProvider工厂类,其创建过程如下: 3.4使用SqlParameterSource提供参数值
使用Map来指定参数值有时候工作得非常好,但是这并不是最简单的使用方式。Spring提供了一些其他的SqlParameterSource实现类来指定参数值。我们首先可以看看BeanPropertySqlParameterSource类,这是一个非常简便的指定参数的实现类,只要你有一个符合JavaBean规范的类就行了。它将使用其中的getter方法来获取参数值。
SqlParameter封装了定义sql参数的对象。CallableStateMentCallback,PrePareStateMentCallback,StateMentCallback,ConnectionCallback回调类分别对应JdbcTemplate中的不同处理方法。 3.5simple实现
4.DataSource
spring通过DataSource获取数据库的连接。Datasource是jdbc规范的一部分,它通过ConnectionFactory获取。一个容器和框架可以在应用代码层中隐藏连接池和事务管理。
当使用spring的jdbc层,你可以通过JNDI来获取DataSource,也可以通过你自己配置的第三方连接池实现来获取。流行的第三方实现由apacheJakartaCommonsdbcp和c3p0. TransactionAwareDataSourceProxy作为目标DataSource的一个代理,在对目标DataSource包装的同时,还增加了Spring的事务管理能力,在这一点上,这个类的功能非常像J2EE服务器所提供的事务化的JNDIDataSource。
Note
该类几乎很少被用到,除非现有代码在被调用的时候需要一个标准的JDBCDataSource接口实现作为参数。这种情况下,这个类可以使现有代码参与Spring的事务管理。通常最好的做法是使用更高层的抽象来对数据源进行管理,比如JdbcTemplate和DataSourceUtils等等。
注意:DriverManagerDataSource仅限于测试使用,因为它没有提供池的功能,这会导致在多个请求获取连接时性能很差。
5.object模块 6.JdbcTemplate是core包的核心类。
它替我们完成了资源的创建以及释放工作,从而简化了我们对JDBC的使用。它还可以帮助我们避免一些常见的错误,比如忘记关闭数据库连接。JdbcTemplate将完成JDBC核心处理流程,比如SQL语句的创建、执行,而把SQL语句的生成以及查询结果的提取工作留给我们的应用代码。它可以完成SQL查询、更新以及调用存储过程,可以对ResultSet进行遍历并加以提取。它还可以捕获JDBC异常并将其转换成org.springframework.dao包中定义的,通用的,信息更丰富的异常。
使用JdbcTemplate进行编码只需要根据明确定义的一组契约来实现回调接口。PreparedStatementCreator回调接口通过给定的Connection创建一个PreparedStatement,包含SQL和任何相关的参数。CallableStatementCreateor实现同样的处理,只不过它创建的是CallableStatement。RowCallbackHandler接口则从数据集的每一行中提取值。
我们可以在DAO实现类中通过传递一个DataSource引用来完成JdbcTemplate的实例化,也可以在Spring的IOC容器中配置一个JdbcTemplate的bean并赋予DAO实现类作为一个实例。需要注意的是DataSource在Spring的IOC容器中总是配制成一个bean,第一种情况下,DataSourcebean将传递给service,第二种情况下DataSourcebean传递给JdbcTemplatebean。
7.NamedParameterJdbcTemplate类为JDBC操作增加了命名参数的特性支持,而不是传统的使用(’?’)作为参数的占位符。NamedParameterJdbcTemplate类对JdbcTemplate类进行了封装,在底层,JdbcTemplate完成了多数的工作。
5.10、浅谈分布式事务
现今互联网界,分布式系统和微服务架构盛行。一个简单操作,在服务端非常可能是由多个服务和数据库实例协同完成的。在一致性要求较高的场景下,多个独立操作之间的一致性问题显得格外棘手。基于水平扩容能力和成本考虑,传统的强一致的解决方案(e.g.单机事务)纷纷被抛弃。其理论依据就是响当当的CAP原理。往往为了可用性和分区容错性,忍痛放弃强一致支持,转而追求最终一致性。
分布式系统的特性
在分布式系统中,同时满足CAP定律中的一致性Consistency、可用性Availability和分区容错性PartitionTolerance三者是不可能的。在绝大多数的场景,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性。
分布式事务服务(DistributedTransactionService,DTS)是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。
CAP理论告诉我们在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的,所以我们只能在一致性和可用性之间进行权衡。
为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性。
数据一致性理解:
强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据CAP理论,这种实现需要牺牲可用性。
弱一致性:系统并不保证后续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。
最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS是一个典型的最终一致性系统。
第四章
Spring 案例分享篇
六、Spring5新特性及应用举例
依赖JDK 8+和Java EE7+以上版本
首次采用反应式编程模型
支持使用注解进行编程
新增函数式编程
支持使用REST断点执行反应式编程
支持HTTP 2.0
新增Kotlin和Spring WebFlux
可使用Lambda表达式注册Bean
Spring WebMVC支持最新的API
使用JUnit5执行条件和并发测试
使用Spring WebFlux执行集成测试
核心容器优化
|