SpringMVC和事务和Mybatis

《JavaEE互联网轻量级应用整合开发》

  • JavaEE互联网轻量级应用整合开发这本书的读书笔记

SpringMVC从入门到放弃

控制器开发

  • 控制器开发是SpringMVC的核心内容
  • 获取请求参数==>处理业务逻辑==>绑定模型和视图(前后分离的话就是返回JSON数据)

获取请求参数

  • 在SpringMVC中接收参数的方法很多,建议不要使用Servlet容器所给予的API,这样控制器就会依赖于Servlet容器
  • SpringMVC会自动解析请求中的参数,通过在控制器入参中声明属性名,如果请求中包含这个属性名,SpringMVC就会自动将属性赋值
  • @RequestParam声明的参数默认不能为空
  • @SessionAttribute从Session中获取对应的数据

Ajax发送POST请求

$.post({
    url:"...",
    //此处需要告知传递参数类型为JSON,不能缺少
    contentType:"application/json",
    //将JSON对象转换为字符串传递
    data:JSON.stringify(data),
    success:function(result){}
});
  • 后台需要用@RequestBody去接受参数为JSON对象的请求

深入Spring事务管理

  • 互联网系统时时面对着高并发,在互联网系统中同时跑成百上千条进程都是十分常见的,常见的商品秒杀环节,很多个用户同时请求一个商品的购买,多线程访问网站,进而导致数据库在一个多事务访问的环境中
  • 数据库处于多事务环境中,就有可能引发数据库丢失更新(Lost Update)数据一致性问题,同时也给服务器带来很大压力,甚至可能发生数据库死锁和瘫痪进而导致数据库宕机
  • 为了解决这些问题,互联网开发者需要了解数据库一些特性,进而规避一些存在的问题,避免数据的不一致,提高系统性能
  • 大部分情况下一个数据库事务是要么同时成功,要么同时失败的,但是也存在的不同的要求
  • 例如信用卡还款,有个跑批量的事务,而整个批量事务包含了对多个信用卡的还款业务的处理,我们不能因为一张卡的事务失败了,就把其他卡的事务也会滚,造成多个客户还款失败,Spring事务的传播行为带来了比较方便的解决方案
  • 声明性事务和编程式事务,如今编程式事务几乎不用了,因为它会产生冗余,代码可读性较差
  • 用Java配置的方式实现Spring事务,需要在配置类中实现接口TransactionManagementConfigurer的annotationDrivenTransactionManager方法,Spring会把annotationDrivenTransactionManager返回的事务管理器作为程序中的事务管理器

声明式事务

  • 声明式事务是一种约定性的事务,在大部分的情况下,使用数据库事务时,大部分场景是在代码中发生了异常时,需要回滚事务,而不发生异常时则是提交事务,从而保证数据库数据的一致性
  • 从这点出发,Spring给了一个约定(类似于AOP开发给的约定),你的业务方法不发生异常,事务管理器就提交事务,发生异常则让事务管理器回滚事务
  • ACID:Atomicity原子性,Consistency一致性,Isolation隔离性,Durability持久性

隔离级别

  • 例子:一对夫妇使用同一张卡账户消费,老公喜欢刷卡,老婆喜欢通过移动支付
  • 第一类丢失更新==>同时开始事务,获取账户初始金额,老公消费成功提交,老婆取消购买,事务回滚到初始金额,目前已经被大部分数据库消灭
  • 第二类丢失更新==>由于在不同的事务中,无法探知其他事务的操作
  • 为了克服事务之间协助的一致性,数据库标准规范中定义了事务之间的隔离级别,来在不同程度上减少出现丢失更新的可能性

SQL规范事务隔离级别

  • DrityRead(脏读)
  • ReadCommit(读/写提交)
  • RepeatableRead(可重复读)
  • Serializable(序列化)

隔离级别解释

  • 脏读是最低的隔离级别,其含义是允许一个事务去读取另一个事务中未提交的数据
  • 读/写提交,其含义是一个事务只能读取另一个事务已经提交的数据
  • 可重复读,是针对数据库同一条记录而言的,换句话说,可重复读会使得同一条数据记录的读/写按照一个序列化进行操作,不会产生交叉情况
  • 很多时候数据库并不只能针对一条数据进行读写操作,在很多场景需要同时对多条记录进行读/写,这个时候就会产生幻读

选择隔离级别和传播行为

  • 在互联网应用中,不但要考虑数据库数据的一致性,而且还要考虑系统的性能,一般而言,从脏读到序列化,系统性能直线下降
  • 设置高的级别,比如序列化,会严重压制并发,从而引发大量的线程挂起,知道获得锁才能进一步操作,而恢复时又需要大量的等待时间
  • 在一般的在购物类应用中,通过隔离级别来控制事务一致性的方式又被排除了,而对于脏读又风险过大,在大部分场景下,企业会选择读/写提交的方式设置事务,这样既有助于提高并发,又压制了脏读,但是对于数据一致性问题并没有解决
  • 注解@Transaction的默认隔离级别是Isolation.DEFAULT,其含义是默认的,随数据库的默认值而变化
  • 不同的数据库支持的隔离级别不同,MySQL支持全部四种隔离级别,而Oracle只支持读/写提交和序列化,默认读写提交

传播行为

  • 传播行为是指方法之间的调用事务策略问题

以信用卡批量还款为例

//记录还款成功的总卡数和对应完成的信息
@Transaction RepaymentBatchService.batch
//每张卡的还款
@Transaction RepaymentService.repay
  • 当batch方法调用repay方法时,它为repay方法创建一条新的事务,当这个方法产生异常时,只会回滚它自身的事务,而不会影响主事务其他事务
  • 类似这样一个方法调度另外一个方法时,可以对事务的特性进行传播配置,我们称之为传播行为

Spring中的传播行为

  1. REQUIRED==>当方法调用时,如果不存在当前事务,那么就创建事务;如果之前的方法已经存在事务了,那么就沿用之前的事务;Spring的默认行为
  2. SUPPORTS==>当方法调用时,没事务就不启用事务;如果存在当前事务,就沿用当前事务
  3. MANDATORY==>方法必须在事务内运行,如果不存在当前事务就抛出异常
  4. REQUIRES_NEW==>无论是否存在当前事务,方法都会在新的事务中运行,也就是事务管理器会打开新的事务去运行该方法
  5. NOT_SUPPORTED==>不支持事务,不存在事务也不会创建,如果存在事务,则挂起它,直至该方法结束后才恢复当前事务,适用于那些不需要事务的SQL
  6. NEVER==>不支持事务,只有在没有事务的环境中才能运行它,如果方法存在当前事务,则抛出异常
  7. NESTED==>嵌套事务,也就是调用方法如果抛出异常,只回滚自己内部执行的SQL,而不回滚主方法的SQL。它的实现存在两种情况,1.如果当前数据库支持保存点(savepoint),那么它就会在当前事务上使用保存点技术;如果发生异常,则将方法内执行的SQL回滚到保存点上,而不是全部回滚;2.不支持保存点的haunted,就等同于REQUIRES_NEW创建新的事务运行方法代码

Spring中通过一个枚举去定义的org.springframework.transaction. annotation.Propagation
企业级应用中主要关注的是REQUEIRES_NEWNESTED

@Transactional的自调用失效问题

  • 注解@Transactional底层的实现是SpringAOP技术,而SpringAOP技术使用的是动态代理。这就意味着对于静态(static)方法和非public方法,注解@Transactional是失效的
  • 自调用==>一个类的方法调用自身另外一个方法的过程
  • @Transactional实现原理是AOP,而AOP的实现原理是动态代理,类中的方法调用自己的另一个方法,并不存在代理对象的调用,这样就不会产生AOP去为我们设置@Transactional配置的参数,这样就出现了自调用注解失效的问题

MVC模型中Controller中调用Service的问题

  • 当一个Controller使用Service方法时,如果这个Service标注有@Transactional,那么它就会启用一个事务,而一个Service方法完成后,它就会释放该事务
  • 当一个Controller声明的方法中,调用了两次Service方法,这两个方法是有各自单独的事务的。如果多次调用,且不在同一个事务中,这会造成不同时提交和回滚不一致的问题

防止过长时间占用事务

  • 大型互联网系统中,一个数据库的链接可能也就是50条左右。(PS:这么点吗?)
  • @Transactional的Service类中的方法代码,作为一个事务整体,与数据库没有交互的代码,如网络请求,文件上传也会占用事务时间,因为只有方法运行完成后,返回Result后才会关系数据库资源

错误异常捕获语句

  • MVC模型中服务类分为原子服务类和组合服务类
  • ...方法已经存在异常了,由于开发者不了解Spring的事务约定,在两个操作方法里加入自己的try...catch..语句,就可能造成数据库操作发生异常的时候,被代码中的try...catch..所捕获,Spring在事务约定的流程中再也得不到任何异常信息,此时Spring就会提交事务,造成数据不一致
  • 解决方法时将捕获的异常向上抛出

Mybatis和Spring

  • 大部分Java互联网项目是使用Spring+Mybatis搭建平台,并且经受住了大数据量和大批量请求的考验,在互联网项目中得到了广泛的应用
  • 使用SpringIOC可以有效管理各类Java资源,达到即插即拔的功能
  • 通过AOP框架,数据库事务可以委托给Spring处理,消除很大一部分事务代码
  • 通过Mybatis的高灵活,可配置,可优化SQL等特性,完全可以构建高性能的大型网站

SqlSessionTemplate

  • 每运行一次SqlSessionTemplate就会获取一个SqlSession,所以每个方法都是独立的SqlSession,这意味着它是安全的线程
  • SqlSession目前应用不多,它需要使用字符串表明运行那个SQL,字符串不包含业务含义,只是功能性代码,并不符合面向对象的规范

Mybatis

生命周期论

  • 我们已经掌握了MyBatis组件的创建及其基本应用,但是远远不够
  • 生命周期是组件的重要问题,尤其是是在多线程的环境中,比如互联网应用,Socket请求等,而MyBatis也常用于多线程环境中,错误使用会造成严重的多线程并发问题
  • 为了正确编写MyBatis的应用程序,我们需要掌握Mybatis组件的生命周期
  • 所谓生命周期就是每一个对象应该存活的时间,比如一些对象用完一次后就要关闭,使它们被Java虚拟机(JVM)销毁,以避免继续占用资源。所以我们会根据每一个组件的作用去确定其生命周期

SQLSessionFactoryBuilder

  • SQLSessionFactoryBuilder的作用在于创建SQLSessionFactory,创建成功后就失去了作用,不需要长期存在

SQLSessionFactory

  1. SQLSessionFactory可以被认为是一个数据库连接池,它的作用是创建SqlSession接口对象
  2. MyBatis的本质就是Java对数据库的操作,所以SQLSessionFactory的生命周期存在于整个Mybatis应用中
  3. 由于SQLSessionFactory是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个SQLSessionFactory就存在多个数据库连接池,不利于数据库资源的控制,容易导致资源被消耗光
  4. 一般以单例存在,在应用中共享

SqlSession

  • 如果说SqlSessionFactory相当于数据库连接池,那么SqlSession就相当于一个数据库连接(Connection对象)
  • 你可以在一个事务里执行多条SQL,然后通过它commit,rollback等方法,提交或者回滚事务
  • SqlSession应当存活于一个业务请求中,处理完整个请求后,应该关闭整个连接,让它归还给SqlSessionFactory,否则数据库资源就很快被耗光了

Mapper

  • Mapper是一个接口,它由SqlSession创建,所以它的最大生命周期至多和SqlSession保持一致
  • Mapper代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它

Mybatis配置

  • 别名重名会导致扫描失败进而导致启动失败,可以通过@Alias("[value]")来为设置扫描包下的某一个类设置单独别名

typeHandler类型转换器

  • 在JDBC中,需要在PreparedStatement对象中设置那些已经预编译过的SQL语句的参数
  • 执行SQL后,会通过ResultSet对象获取得到数据库的数据,而这些数据的类型是通过tyeHandler来实现的
  • 在typeHandler中,分为jdbcType和javaType,其中jdbcType用于定义数据库类型,而javaType用于定义Java类型
  • typeHandler的作用就是承担jdbcType和javaType之前的相互转换
  • 多数情况Mybatis会通过Result的数据类型去匹配具体的Java类型,但是一些使用特殊数据类型的场景(使用自定义枚举或是数据库使用特殊数据类型)可以使用自定义的typeHandler去处理类型之间的转换问题

ObjectFactory(对象工厂)

  • 当创建结果集时,会使用一个对象工厂来完成这个结果集实例的创建,在默认情况下,Mybatis会使用其定义的对象工厂———DefaultObjectFactory来完成对应的工作

Mybatis插件

  • 插件是Mybatis中最强大和灵活的组件,同时也是最复杂,最难以使用的组件
  • 因为插件会覆盖Mybatis底层对象的核心的方法和属性,所以使用十分注意,如果操作不当会造成严重后果

映射器

  • 映射器是Mybatis中最复杂且最重要的组件,它由一个接口加上XML文件组成
  • 在映射器中可以配置参数,各类的SQL语句,存储过程,缓存,级联等复杂的内容,并且通过简单的映射规则将从数据库获取的结果集映射到指定的POJO或者其他对象
  • 在Mybatis应用程序开发中,映射器的开发工作量占全部工作量的80%

映射器配置元素

  • select:查询语句,最常用最复杂的元素之一,可以自定义参数,返回结果集
  • insert:插入语句,执行后返回一个整数,代表插入的条数
  • update:更新语句,执行后返回一个整数,代表更新的条数
  • delete:删除语句,执行后返回一个整数,代表删除的条数
  • sql:定义一部分SQL来在XMl中重用
  • resultMap:用来描述从数据库结果集中来加载对象,它是最复杂,最强大的元素,提供了结果集和程序中DTO类的映射规则
  • cahce:给定命名空间的缓存配置

SELECT元素

  • id:和Mapper的命名空间组合起来构成唯一标识,供Mybatis调用
  • parameterType:可以给出类的全命名,或是定义好的别名,可以选择JavaBean,Map等简单的参数类型传递给SQL
  • resultType:依据SQL查询结果设置对应的Java程序类型,可以是全类名或别名,也可以是常见数据类型(int,double等)
  • resultMap:映射集的引用,提供自定义映射规则,级联,TypeHandler等
  • flushCache:调用完SQL后,是否要Mybatis清空之前查询的本地缓存和二级缓存

SELEC查询中传递多个参数

  1. 使用map————键值集合需要阅读键才能明白其作用,其次无法限定传递的值得数据类型,因此业务性质不强,可读性差
  2. 使用注解(至于mapper接口的方法的入参定义上)@Param————无法处理多个参数
  3. 使用JavaBean————没有@Param注解的方式直观
  4. 混合使用(注解加JavaBean)

简单分页参数Bean-RowBounds

  • Mybatis内置专门用于处理分页类,主要属性石offet,limit
  • offset属性是偏移量,即从第几行开始读取记录
  • limit是限制条数
  • 使用它只需要给mapper接口中的查询方法增加一个RowBounds参数即可
  • RowBounds分页原理是执行SQL查询后,按照偏移量和限制条数返回查询结构,对于大量的数据查询,它的性能并不好

INSERT语句

  • 举个例子:新增用户的时候,插入用户后需要插入用户和角色关系表,需要插入后获取用户的主键
  • userGenerateKeys代表采用JDBC的Statement对象的getGeneratedKeys方法返回主键
  • keyProperty代表将用POJO的那个属性去匹配这个主键

插入中的自定义规则

  • 使用下的标签

ResultMap中的级联配置

  • 级联是一个数据库实体概念
  • Mybatis中还有一种称为鉴别器的级联
  • 级联不是必须的,级联的好处是获取关键数据十分便捷,但是级联过多会增加系统的复杂度,同时降低系统的性能
  • 当级联的层级超过三层时,推荐不考虑级联。多个数据库表对象的关联导致系统的耦合,复杂和难以维护

级联配置


    
    
    

  • 在association代表一对一级联的开始
  • property属性代表映射到POJO属性上,上例中为Gril中的引用类型属性task

你可能感兴趣的:(SpringMVC和事务和Mybatis)