MyBatis+Spring MVC开发指南(二)

96
张丰哲 595a1b60 08f6 4beb 998f 2bf55e230555
0.1 2017.06.25 20:26* 字数 2457

前言

《MyBatis+Spring MVC开发指南(一)》,本篇博客将涵盖MyBatis高级映射(一对一,一对多、多对多)、延迟加载、缓存原理分析(一级缓存、二级缓存)、MyBatis和Spring的整合、逆向工程等主题。


高级映射

由于在上一篇博客中已经带大家了解了MyBatis的一些基础知识,这里的高级映射将采用Mapper代理开发方式,主要分析Mapper.xml的高级映射写法。

表:

表之间的关系

用户表User和订单表Orders是一对多的关系;

订单表Orders和订单明细表OrderDetail是一对多关系;

订单明细表OrderDetail和商品表Items是多对一关系;

一对一映射

分析:把订单以及该订单的用户信息也查询出来。

SQL层面好说,就是User和Orders的关联查询,关键是查询出来的结果如何映射?是用resultType,还是用resultMap呢?

如果使用resultType的话,显然我们的实体bean(User仅仅包含用户信息;Orders仅仅包含订单信息,没有User的引用)并不能接受关联查询的结果集,那么我们可以考虑使用OrdersQueryVO(比如说让它extends Orders,然后在加上一些User的属性)作为输出结果类型。

从上面你大致可以发现,resultType适应较简单的输出结果映射,MyBatis其实还提供了resultMap做复杂输出结果映射,比如数据库column列与字段名称不一致的映射,比如延迟加载,比如一对一,一对多,多对多等高级映射特性。


一对一映射

注意点:

第一,Orders实体对象需要有User的引用

第二,注意<association>标签中javaType必须要明确指明类型!

一对多

把订单及订单明细查询出来。

我们就考虑使用resultMap,显然这次是一对多关系(一个订单有多个订单明细)。

看一对多XML片段:

一对多映射

第一,在Orders中存在List<OrderDetail>属性。

第二,一对多使用的是<collection>标签,需要特别注意的是ofType属性,也就是需要明确指明集合对象中的类型。

多对多

其实多对多,就是<collection>和<association>的综合应用。

第一,User实体拥有List<Orders>,Orders实体拥有List<OrderDetail>,OrderDetail实体拥有List<Items>;

第二,ResultMap的编写,其实就是<collection>里面嵌套<collection>,<collection>里面嵌套<association>而已。

这里仅仅分析下映射思路,就不贴XML片段了。我想只要思路清晰,那么就会很简单。


延迟加载

所谓延迟加载,就是需要的时候才发出SQL去查询,在Hibernate中有延迟加载,MyBatis同样提供了这个功能。延迟加载需要借助<resultMap>标签完成。我们先从思路上分析下MyBatis的延迟加载:

第一,MyBatis是默认开启延迟加载的么?如果不是,那么显然应该进行延迟加载配置。

在MyBatis的全局核心配置文件SqlMapConfig.xml的<settings>中可以设置lazyLoadingEnabled以及aggressiveLazyLoading属性值。

第二,要实现延迟加载,就得进行SQL拆分。(你想想,如果我们的SQL都写在一起,DB要么执行,要么不执行,根本做不到按需查询,所以要延迟加载,就得拆分SQL。)那么怎么进行拆分呢?

在<resultMap>中的<collection>以及<association>标签中有select属性,也就是说当使用到了<collection>/<association>的时候才发出select属性对应的SQL。

第三,我们其实可以借助MyBatis去完成延迟加载,也可以自己实现延迟加载。怎么做呢?一句话,需要的时候,我们自己调用相应的Statement完成即可。


查询缓存

MyBatis是要和DB打交道的,那么自然涉及到数据查询缓存的问题,这有利于提高系统的查询效率。思考几个问题:

第一,我们知道操作DB的接口是SqlSession,那么当我们创建了SqlSession后,在这个SqlSession生命周期中,应该是可以缓存数据。比如在同一个SqlSession内部,我们发出2条相同的Statement,就可以考虑从缓存中取得数据。

第二,针对上面的SqlSession缓存,如果你稍微看一下源码,你会知道其实就是一个HashMap,KEY主要就是SqlSession+StatementId构成。

第三,既然是缓存,那么涉及到缓存和DB的数据一致问题,如果处理不好,很可能带来脏读!那么显然不仅仅是<select>需要操作SqlSession缓存呢,对于<insert>/<update>/<delete>也需要处理缓存。那么怎么处理呢?对于<insert>/<update>/<delete>的处理思路,有2种:要么更新缓存,要么清空缓存。MyBatis采取了很简单的做法,就是清空缓存!

第四,随着SqlSession的关闭,SqlSession缓存也就失效了,这也是所谓的一级缓存。还有一种缓存,是跨SqlSession的,即二级缓存。想一想,如果我们把namespace+statementId作为KEY呢?这将意味着,多个SqlSession来发出同一个Statement,可以从缓存中拿取数据。

一级、二级缓存原理图:

一级缓存、二级缓存

对于一级缓存而言,MyBatis是默认支持的,无需配置开启。而二级缓存是需要开启的,首先来说,需要在全局配置文件中指明(在<setting>中的cacheEnabled属性,全局性缓存开关),其次在需要开启二级缓存的XXXMapper.xml中指明(<cache>标签)。

MyBatis还提供了更加细粒度的缓存控制,比如在<select>标签中,就提供了useCache属性,默认为true,即使用二级缓存。(同理,<insert>标签中提供flushCache配置,显然,我们应该使用默认的true,去清空缓存)

虽然如此,但是MyBatis的二级缓存仍然不是很好用,为什么这么说呢?

假设,二级缓存中,有商品列表的数据,如果我们仅仅更新了其中一个商品,那么意味着二级缓存的清空。而我们真正想要的是刷新该商品的缓存信息而不要影响其他商品的缓存信息。

思考一个问题:缓存到哪里去呢?

缓存到本机的内存?磁盘?能够支持分布式缓存么,如ehcache、Redis?

如果你是MyBatis的作者,显然你应该提供一个Cache接口,让使用方可以自己选择缓存的具体实现!

这里,我们还需要特别注意的是,缓存对象的序列化、反序列问题(如果缓存到Redis中,那么显然涉及网络通信IO呢),应该实现Serializable接口。

看一眼MyBatis提供的Cache接口:

Cache接口

MyBatis的默认实现类:

默认Cache实现类

假设,我们需要整合ehcache的话,那么具体需要做什么呢?

第一,提供ehcache以及ehcache与MyBatis整合的依赖

第二,在<cache/>标签中,type属性指明ehcache实现Cache接口的实现类

第三,提供相关的ehcache配置文件


MyBatis和Spring整合

整合的思路(需要MyBatis-Spring整合的依赖,由MyBatis提供):

第一,SqlSessionFactory需要交给Spring管理(单例)

注意到SqlSessionFactory的创建显然需要数据库连接相关的信息,因此需要数据库连接池;除此之外还需要MyBatis的主配置文件。

第二,如果采用原始DAO的开发方法的话,那么我们是需要向Dao的实现类(交给Spring管理)中注入SqlSessionFactory,然后在各个方法中得到SqlSession进行操作的。可以让Dao的实现类extends SqlSessionDaoSupport,而SqlSessionDaoSupport类中已经存在setSqlSessionFactory()方法,因此我们可以直接向Dao的实现类注入SqlSessionFactory;另外SqlSessionDaoSupport中有SqlSession,因此使得操作更加简单呢;而且都交给Spring管理了,我们自然不必担心SqlSession的关闭忘了。

第三,如果采用Mapper代理的方式开发,那么我们需要Spring做的就是管理Mapper动态代理实现。

基于Mapper代理开发的XML片段:

Spring配置文件

我们可以指定一个包路径,告诉Spring,将这个路径下的Mapper接口+Mapper.xml都注册一下生成代理对象进行管理。


逆向工程

什么是逆向工程,说白了,就是MyBatis为我们提供了一个自动代码生成工具,这个工具可以根据数据库表信息,帮助我们生成Mapper.java/Mapper.xml/POJO实体类。

Mybatis提供了多种生成的方式,比较普遍的就是根据一个XML配置文件进行生成。在这个XML配置文件中指明DB连接信息,表信息,生成到哪里,包路径是什么等。

具体的例子,大家可以参考:http://www.mybatis.org/generator/ 进行操作即可。


到这里,这个系列文章,就写了一半了,下一篇将是关于Spring MVC~

Good Night!

日记本
Web note ad 1