- 映射器
[toc]
映射器
由一个接口和XML文件(注解不推荐)组成。映射器中可以配置参数、各类SQL语句、存储过程、缓存、级联等内容,并通过简易的映射规则映射到指定的POJO上,有效消除JDBC底层代码。
1. 概述
有以下元素:
- select:查询,可以自定义参数、返回结果集等
- insert:插入,返回一个整数,代表插入的条数
- update:更新,返回整数,代表更新的条数
- delete:删除,返回整数,代表删除的条数
- parameterMap:定义参数映射关系,但即将被删除,不推荐使用
- sql:定义一部分SQL,其他地方引用
- resultMap:描述从结果集中加载对象,提供映射规则
- cache:给定命名空间的缓存配置
- chche-ref:其他命名空间缓存配置的引用
2. select 查询
2.1 属性
1. id:和Mapper的命名空间namespace组合起来是唯一的,供MyBatis调用
- parameterType:类全名/别名
- parameterMap:即将废弃
- resultType:定义类全路径,允许自动匹配时结果集自动映射;或int double等;也可用别名,但不能和resultMap同时使用
- resultMap:映射集的引用,和resultType二选一使用,能提供自定义映射规则
- flushCache:调用SQL后是否清空之前查询本地缓存和二级缓存,默认false
- useCache:启动二级缓存的开关,是否要将此次结果缓存,默认true
- timeout:超时参数,s,超时异常
- fetchSize:获取记录的总条数设定
- statementType:使用JDBC的哪个Statement工作,有STATEMENT(Statement)、PREPARED(PreparedStatement)、CALLABLE(CallableStatement),默认PREPARED
- resultSetType:对JDBC的resultSet接口而言,包括FORWARD_ONLY(游标允许向前访问)、SCROLL_SENSITIVE(双向滚动,但不及时更新,若库中数据修改过,不在resultSet中反映)、SCROLL_INSENSITIVE(双向滚动,及时跟踪库更新)
- databaseId:参考databaseIDProvider
- resultOrdered:仅适用于嵌套结果select,true表示假设包含了嵌套结果集或是分组了,当返回一个主结果行时,就不能引用前面结果集了,确保在获取嵌套的结果集时不至于爆内存;默认false
- resultSets:适用于多个结果集的情况,列出执行SQL后每个结果集的名称,逗号分隔
用的最多的是id、parameterType、resultType、resultMap;若要设置缓存还用到flushCache、useCache;其他不常用。
2.2 简单应用
eg:统计表中同一姓氏的用户数量:接口中声明方法:public Integer countUserByFirstName(String firstName);
然后mapper.xml中:
1 | <select id="countUserByFirstName" parameterType="string" resultType="int"> |
id:配合主元素mapper的namespace属性,联合成为唯一标识
parameterType:接受的参数类型,可以是别名或全名
resultType:返回类型,同样可以全名或别名
#{firstName}:传入的参数
2.3 自动映射
1. MyBatis提供自动映射功能,默认开启,能有效减少大量的映射配置
2. 基础配置文件中的setting元素中有两个可配置选项autoMappingBehavior或mapUnderscoreToCamelCase,用于控制自动映射和驼峰映射的开关,前者使用的多,灵活,后者严苛,使用不广
3. autoMappingBehavior的取值:
1. NONE:不进行自动映射
2. PARTIAL:默认,只对没有嵌套结果集进行自动映射
3. FULL:对所有结果集进行自动映射
4. 编写POJO中的属性,如果和表列名一致,就能自动映射;不一致也可在SQL中通过`as`改为一致
5. 驼峰映射:表列名 user_name,POJO属性名userName,此时可以映射
2.4 传入多个参数
-
使用map接口传递:此时将接口的方法定义为
public List<Book> findBooksByMap(Map<String, Object> parameterMap);
,此时传入映射器的就是一个map对象,使用这个map在SQL中设置参数: -
<select id="findBooksByMap" parameterType="map" resultType="book"> select id, book_name as bookName from books where book_name like concat('%', #{bookName}, '%') and id like concat('%', #{id}, '%') </select>
此时接口方法定义:`public List<Book> findBooksByBean(BookParams bookParam);`;映射器中parameterType指定该JavaBean全名,花括号内用bean的属性即可1
2
3
4
5
6
7
8
9
10
11
12
13
SQL中#{bookName}是键,要在调用方法前填充map传入。但因map可读性差,不能限定传入参数类型,故使用不多
3. 使用注解传递:`public List<Book> findBooksByAnnotation(@Param("roleName") String roleName, @Param("id") String id);`,可读性大大提高,在SQL中使用参数时,花括号内填入注解的内容即可,且此时不用指定parameterType属性,MyBatis可以自动探索
4. 使用JavaBean传递:参数过多时注解繁琐,可用一个POJO代表参数
```java
public class BookParams {
private String bookName;
private String id;
// getter setter
}
2.5 resultMap映射结果集
自动映射规则简单,无法定义多的属性。复杂映射使用resultMap,首先需要定义resultMap元素,然后可在select中使用。
1 | <mapper> |
resultMap元素定义了一个roleMap,id是其标识,type标识要映射的类。
子元素id是主键,result代表其他属性;property代表POJO的属性名,column是SQL的列名,如此便建立了映射联系。
2.6 分页参数RowBounds
- 对查询到的结果取部分。RowBounds类内有offset和limit两个属性。
- offset表示偏移量,即从第几行开始读取,默认0。
- limit表示限制条数,默认Integer.MAX。
- 要使用RowBounds,只需在接口参数最后增加一个
RowBounds rowBounds
即可,在映射文件中无需特别设置,MyBatis自动识别应用。 - 小数据量适用,大量时用插件。
3. insert 插入
3.1 属性
- id:SQL标号,标识
- parameterType:参数类型
- flushCache:是否刷新缓存,默认true表示插入后刷新一二级缓存
- timeout:s
- StatementType:STATEMENT(Statement), PREPARED(PreparedStatement, CALLABLE(CALLABLEStatement),默认PREPARED
- useGeneratedKeys:是否启用Jdbc的getGeneratedKeys方法来取出数据库自动生成的主键(如MySQL的自增主键),默认false
- keyProperty:(仅insert和update)唯一标记一个恶属性,通过useGeneratedKeys或insert的selectKey子元素设置它的键值
- keyColumn:(仅insert和update)
- databaseId
insert后,返回一个整数表示影响了个条数。
3.2 简单应用
1 | <insert id="insertRole" parameterType="role"> |
3.3 主键回填
- 意义:MySQL中对主键有自增功能,故上述例子中没有插入id主键。但有时需要利用插入的元组的主键,就需要获取到库自增的主键的值。
- JDBC中Statement执行插入后,可以通过getGeneratedKeys方法获得生成的主键。MyBatis中的insert有开关属性useGeneratedKeys,默认false,打开后需要配置属性keyProperty或keyColumn,用于告诉系统将回填的主键填入哪个属性
1 | <insert id="" parameterType="" useGeneratedKeys="true" keyProperty="id"> |
如此,就将主键id回填到POJO的id属性内。
3.4 自定义主键
- 意义;有时主键不需自增,而是依赖某些规则,如本例的表空时id为1,不空时id为当前id+3
- MyBatis中此功能依赖于selectKey元素
- selectKey有order属性,其值BEFORE/AFTER表示此主键定义是在SQL前或后执行
1 | <insert id parameterType> |
4. update和delete
属性类似上面,且同样返回整数表示影响的行数。
5. sql元素
定义一条SQL的一部分,随后可以引用,方便后续编写SQL。
如表中列多,可将所有列名设为一个sql元素,随后SQL语句中便可通过include引用。
1 | <mapper> |
甚至支持变量传递,将SQL中的变量传给sql元素,然后sql元素引入SQL执行:
1 | <sql id="roleCols"> |
本例中,include元素将表名t_role赋值给alias变量。
6. 参数
《Java EE互联网轻量级框架整合开发》 MyBatis 5.6,暂不展开。
7. resultMap元素
定义映射规则、级联的更新、定制类型转化器等。主要是一个结果集的映射关系,将SQL映射到JavaBean。MyBatis暂只支持resultMap查询,不支持更新或保存、级联的更新删除修改。
7.1 resultMap元素构成
1 | <resultMap> |
id表示主键。
result配置POJO到SQL列名的映射关系。
Constructor用于配置构造方法。POJO可能没有无参构造方法,就需要把有参构造方法配置进此,如public RoleBean(Integer id, String roleName)
:
1 | <constructor> |
result和idArg的属性:
- property:映射到列结果的字段或属性
- column:对应SQL的列
- javaType
- jdbcType
- typeHandler
7.2 使用map存储结果集
map支持任何select的结果集存储,只需在select元素中配置属性resultType="map"
即可。但可读性差,一般使用POJO存储。
7.3 使用POJO存储结果集
POJO存储,就可以使用自动映射,或自定义更复杂的映射,当然这就要先创建一个resultMap元素(映射),见[2.5 resultMap映射结果集](###2.5 resultMap映射结果集)
8. 级联
8.1 MyBatis中的级联
分为三种:
- 鉴别器discriminator:根据某些条件决定采用具体实现类级联的方案
- 一对一association
- 一对多collection
本例以学生Student、任务表Task、任务安排表ST、男女体检表Female/MaleForm、学生证表ID说明,学生与任务表一对多、与男女体检表属于鉴别器类型、与学生证一对一。
8.2 建立POJO
为每个实体建立POJO。
男女体检表,多数项目相同,可建立父类后继承:
1 | public abstract class HealthForm { |
学生证表:
1 | public class IDCard { |
任务表:
1 | public class Task { |
任务安排表:
1 | public class ST { |
学生表:建立父类,并继承男女学生,以适配男女体检单
1 | public class Student { |
综上可见,MyBatis中级联,在POJO中的属性就应体现出级联。
8.3 配置mapper文件
级联核心,对每个实体类创建接口后配置mapper文件。
Task
1 | <mapper namespace="xxx.TaskMapper"> |
IDCard
1 | <mapper namespace="xxx.IDCardMapper"> |
ST 任务安排表:association元素代表一对一级联的开始。
1 | <mapper namespace="xxx.STMapper"> |
男女体检表:
1 | <mapper namespace="xxx.MaleFormMapper"> |
**学生映射:**本例核心,与其他实体类关联,resultMap元素也可继承
1 | <mapper namespace="xxx.StudentMapper"> |
discriminator:column属性代表用此列进行鉴别,resultMap属性表示采用哪个resultMap映射
8.4 N+1问题
以上级联方式,在使用时会引发所有级联共同执行,如只需要部分数据,多余的SQL会浪费资源。如已有N个关联关系完成了级联,只要再加入一个关联关系,就变成了N+1个关联,所有级联SQL都会被执行,但并非所有数据都需要。
为解决此问题,MyBatis提出延迟加载功能,一开始取学生信息时,不将学生证表、体检表、任务表取出,只取学生表和任务安排表信息。当通过学生POJO访问学生证、体检表、任务表时才通过相应SQL取出。
8.5 延迟加载
希望一次性把常用的级联数据取出,而不常用的级联数据等到用时才取出。对于这些不常用的级联表采用延迟加载。
在settings配置中有以下两子元素配置级联:
- lazyLoadingEnabled:全局开关,默认false,开启后所有关联对象都延迟加载;特定关联关系中可设置fetchType覆盖该全局开关
- aggressiveLazyLoading:启用时,对任意延迟属性的调用会使带有延迟加载属性的对象完整加载;反之,每种属性按需加载;默认值从3.4.2后变为false;
前者决定是否开启延迟加载,后者控制是否采用层级加载,都是全局级别配置。
另有fetchType属性,可以针对某个信息设置,本属性在association和collection中可以设置,有两个值:
- eager:获得当前POJO后立即加载对应数据
- lazy:获得当前POJO后延迟加载对应数据
当开启延迟加载,关闭层级加载后,使用此属性即可自定义设置。
8.6 另一种级联
基于SQL表连接的基础上,再次设计。虽能消除N+1问题,但较为复杂,浪费内存,不甚推荐,暂不展开。
8.7 多对多级联
程序中将此拆分成两个一对多级联来处理。
本例中,用户可以是多个角色,一个角色可以有多个用户。
POJO:
1 | public class Role { |
Mapper:
1 | <mapper namespace="xxx.RoleMapper"> |
9. 缓存
MyBatis支持缓存,一般放在内存中,提高性能。数据库位于磁盘,速度慢。一般只将命中率高的数据放在缓存。
9.1 一级二级缓存
MyBatis分为一级缓存和二级缓存。
一级缓存是在SqlSession上的缓存,二级缓存是在SqlSessionFactory上的缓存。默认时MyBatis开启一级缓存,且一级缓存不需要POJO可序列化。
一级缓存:用一个SqlSession获取mapper,用这个mapper先后get同一数据,实际上是用一个SqlSession获取同一数据;此时只执行一次SQL语句;当一个SqlSession第一次通过SQL和参数获取对象后,会将其缓存,若下次SQL和参数都没有变化,并且缓存未超时或声明不需刷新时,就可以从缓存中读取数据。
二级缓存:一级缓存位于SqlSession层面,不同SqlSession缓存不能共享;如需共享就要开启二级缓存,位于Factory层,更高一层;只需在映射文件中加入<cache/>
即可。查询结束后需要SqlSession.commit()提交,才会缓存对象到Factory层面。此时需要可序列化。
9.2 缓存配置与使用
-
只配置
<cache/>
后,MyBatis会将select的结果缓存,而增删改之后会刷新缓存。 -
<cache/>
配置属性:
- blocking:默认false,不开启阻塞性缓存,读写时不加入JNI的锁;开启后能保证读写安全性,但降低性能
- readOnly:缓存内容是否只读,默认false;若开启,不会因为多线程读写造成不一致
- eviction:缓存策略,LRU最近最少使用的,即移除最长时间不用的对象;FIFO先入先出;SOFT软引用,移除基于垃圾回收器状态和软引用规则的对象;WEAK弱引用,移除垃圾收集器状态和弱引用规则的对象;默认LRU
- flushInterval:ms,默认null,即不主动刷新,只有增删改后才刷新
- type:自定义缓存类,要实现接口Cache
- size:缓存对象个数,默认1024
-
如需自定义缓存类就要实现Cache接口,但一般可使用Redis、MongoDB等常用缓存。如有一个Redis实现类RedisCache,则如下配置:
1
2
3<cache type="xxx.RedisCache">
<property name="host" value="localhost"/>
</cache> -
可对SQL单独配置以自定义是否缓存
1
2<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>此为默认配置,flushCache对四种SQL都有效;useCache仅select特有。
如其他mapper文件需要使用本mapper文件的cache配置,可以引用:
<cache-ref namespace="xxx.Mapper"/>
10. 存储过程
数据库概念,是库预先编译好的,放在库内存中的程序片段,具有高性能,可重用的特点。定义了3种类型参数:输入、输出、输入输出。
暂不展开。