《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中的传播行为
-
REQUIRED
==>当方法调用时,如果不存在当前事务,那么就创建事务;如果之前的方法已经存在事务了,那么就沿用之前的事务;Spring的默认行为 -
SUPPORTS
==>当方法调用时,没事务就不启用事务;如果存在当前事务,就沿用当前事务 -
MANDATORY
==>方法必须在事务内运行,如果不存在当前事务就抛出异常 -
REQUIRES_NEW
==>无论是否存在当前事务,方法都会在新的事务中运行,也就是事务管理器会打开新的事务去运行该方法 -
NOT_SUPPORTED
==>不支持事务,不存在事务也不会创建,如果存在事务,则挂起它,直至该方法结束后才恢复当前事务,适用于那些不需要事务的SQL -
NEVER
==>不支持事务,只有在没有事务的环境中才能运行它,如果方法存在当前事务,则抛出异常 -
NESTED
==>嵌套事务,也就是调用方法如果抛出异常,只回滚自己内部执行的SQL,而不回滚主方法的SQL。它的实现存在两种情况,1.如果当前数据库支持保存点(savepoint),那么它就会在当前事务上使用保存点技术;如果发生异常,则将方法内执行的SQL回滚到保存点上,而不是全部回滚;2.不支持保存点的haunted,就等同于REQUIRES_NEW创建新的事务运行方法代码
Spring中通过一个枚举去定义的
org.springframework.transaction. annotation.Propagation
企业级应用中主要关注的是REQUEIRES_NEW和NESTED
@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
- SQLSessionFactory可以被认为是一个数据库连接池,它的作用是
创建SqlSession接口对象
- MyBatis的本质就是Java对数据库的操作,所以SQLSessionFactory的生命周期存在于整个Mybatis应用中
- 由于SQLSessionFactory是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个SQLSessionFactory就存在多个数据库连接池,不利于数据库资源的控制,容易导致资源被消耗光
- 一般以单例存在,在应用中共享
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查询中传递多个参数
- 使用map————键值集合需要阅读键才能明白其作用,其次无法限定传递的值得数据类型,因此业务性质不强,可读性差
- 使用注解(至于mapper接口的方法的入参定义上)
@Param
————无法处理多个参数 - 使用JavaBean————没有@Param注解的方式直观
- 混合使用(注解加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