一、SQL查询结果填充的几种方式
1.介绍
MyBatis会根据句映射关系把查询到的结果填充到指定结果集类型中。支持方式:
-
auto mapping:自动映射。当列名或列的别名与实体类属性名相同时不需要做额外配置。
-
resultMap:手动定义映射关系。
-
camel case:驼峰命名规则。
2.Auto Mapping
自定映射方式,只要保证数据库中列名和属性名相同,就可以自动进行映射。也可以给列起别名,让别名和属性名对应。对应时不用区分大小写。
3.resultMap
当数据库列名和属性名不同时是无法进行自动映射的,这时需要手动指定映射关系。
1.创建表
在数据库中创建表。
表名:无论哪种方式,表名和实体类名称是否相同都没有关系。
列名:列名和实体类名称不对应。
2.创建实体类
实体类的类名和表名不是必须相同的。
实体类中属性名和列名也不相同。
3.修改映射文件
在映射文件中需要注意的是<select>
标签不再使用resultType属性,而是使用resultMap属性。resultMap属性表示使用哪个<resultMap>
作为结果映射配置
1.<resultMap>
标签的子标签的property属性必须和类的属性名严格对应,区分大小写。如果没有对应上,会出现反射异常2.column属性对应数据库列名,由于数据库是不区分大小写的,所以column的值也是不区分大小写的。
3.resultMap可以存在多个。只要id属性不重名就可以。
4.驼峰转换
通过全局配置文件开启驼峰转换功能后,就可以让xxx_yyy自动映射到xxxYyy上。例如:列名叫做peo_id,可以自动映射到peoId的属性上。转换时去掉列中的下划线,把下划线后面单词首字母变大写。
1.修改全局配置文件
<!-- 按照DTD顺序要求,settings需要定义在typeAliases上面,properties标签下面 -->
<settings>
<!-- 开启驼峰转换 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
2.修改映射文件
<mapper namespace="a.b.c">
<!-- 直接使用resultType标签进行配置 -->
<select id="myid" resultType="People2">
select * from tb_people
</select>
</mapper>
二、多表查询
1.介绍
只要是关系型数据库,在设计表时都需要按照范式进行设计,为了减少数据冗余,都会拆成多个表。当需要多张表中数据时,需要进行联合查询。
在学习MyBatis多表查询时其实就是在学习<association>
标签和<collection>
标签。
如果一个实体类关联另一个实体类的一个对象使用
<association>
。如果一个实体类关联一个实体类的List集合对象,需要使用
<collection>
。所以分析的思路是:先分析需求->分析数据库设计对应关系->创建实体类->根据实体类关联属性类型决定使用哪个标签。
两种方式优缺点:
-
联合查询方式:
优点:一次查询。
缺点:SQL相对复杂。不支持延迟加载。
-
业务装配
优点:手动实现,灵活度高。
缺点:代码复杂。
-
N+1方式:
优点:SQL简单。支持延迟加载。
缺点:多做N次查询。
思路:
MyBatis多表查询时一定需要使用<resultMap>
标签,因为<association>
标签和<collection>
标签是<resultMap>
的子标签。
2.业务装配
所谓的业务装配是不使用MyBatis进行装配。而是使用Java代码进行装配。具体体现在Web项目中,是在service里面通过Java代码实现结果的填充。
这种方式理解起来简单,写持久层时也简单。但是service的代码写起来更多了。属于N+1方式的另一种写法。需要保证持久中提供上对于两个表的单表查询方法。
3.N+1查询方式
N+1查询方式命名由来:当查询Emp表中N条数据时,需要编写1条查询全部的SQL和N条根据外键列值作为另一张表主键查询条件的N条SQL语句。
N+1查询方式,在进行操作时需要先分析出最终想要的结果需要包含对于两张表的单表查询语句是什么。
注意:条件的字段必须作为查询的字段,否则不生效。
编写EmpMapper.xml实现SQL和接口方法绑定。
<resultMap id="empMap2" type="Emp"
<id column="e_id" property="id"/>
<result column="e_name" property="name"/>
<!-- 此处依然使用association填充单个对象属性值.property和javaTye依然需要写 -->
<!-- select 调用另一个查询的路径,同一个映射文件中,前面namespace可以省略-->
<!-- column: 当前SQL查询结果哪个列值当做参数传递过去。如果是多个参数{"key":column,"key2":column2}-->
<association property="dept" javaType="Dept" select="com.bjsxt.mapper.DeptMapper.selectById" column="e_d_id"></association>
</resultMap>
<select id="selectAllN1" resultMap="empMap2">
select e_id,e_name,e_d_id from emp
</select>
三、延迟加载
延迟加载只能出现在多表联合查询的N+1方式中。
表示当执行当前方法时,是否立即执行关联方法的SQL。
1.启用延迟加载
配置延迟加载有两种方式:
全局配置。整个项目所有N+1位置都生效。
局部配置。只配置某个N+1位置。
两种方式需要选择其中一种,如果两种方式都使用了,局部配置方式生效。
1.全局配置方式
属性名 | 解释说明 | 可取值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
2.局部配置方式
局部配置方式需要在collection或association标签中配置fetchType属性。fetchType可取值:lazy(延迟加载)和earge(立即加载)。
当配置了fetchType属性后,全局settings的配置被覆盖,对于当前标签以fetchType属性值为准。
<resultMap id="empMap2" type="Emp">
<id column="e_id" property="id"/>
<result column="e_name" property="name"/>
<association property="dept" javaType="Dept"
select="com.bjsxt.mapper.DeptMapper.selectById" column="e_d_id"
fetchType="lazy"></association>
</resultMap>
<select id="selectAllN1" resultMap="empMap2">
select e_id,e_name,e_d_id from emp
</select>
四、缓存
1.缓存介绍
缓存是一种临时存储少量数据至内存或者是磁盘的一种技术。减少数据的加载次数,可以降低工作量,提高程序响应速度,缓存的重要性是不言而喻的。
MyBatis分为一级缓存和二级缓存,同时也可配置关于缓存设置。
-
一级存储是SqlSession上的缓存。
-
二级缓存是在SqlSessionFactory(namespace)上的缓存。
-
默认情况下,MyBatis开启一级缓存,没有开启二级缓存。当数据量大的时候可以借助一些第三方缓存框架或Redis缓存来协助保存Mybatis的二级缓存数据。
2.一级缓存
一级缓存是SqlSession级缓存。只要是同一个SqlSession对象(必须是同一个)调用同一个<select>
标签相同参数值时(不同<select>
完全相同的SQL不会走同一个缓存),将直接使用缓存数据,而不会访问数据库。
注意
1.一级缓存想要生效,必须同时满足3个条件:
1. 同一个SqlSession对象。
2. 同一个select标签。本质为底层同一个JDBC的Statemen对象。
3. 完全相同的SQL,包含SQL的参数值也必须相同。
2. insert、delete、update操作会清空一级缓存数据。
3. close(),commit也会清空缓存。
命中缓存:从Map中查询是否存在指定key。如果存在表示命中缓存,如果不存在这个key,需要访问数据库。
3.二级缓存
二级缓存是以namespace为标记的缓存,可能要借助磁盘,磁盘上的缓存,可以由一个SqlSessionFactory创建的SqlSession之间共享缓存数据,默认并不开启。
注意:
1.二级缓存生效条件:
同一个SqlSessionFactory对象。
同一个方法(<select>)。
SQL完全相同。
2.二级缓存默认不开启,需要手动开启。
3.只有当SqlSession执行commit或close时才会存储到二级缓存中。
4.MyBatis的二级缓存的缓存介质有多种多样,而并不一定是在内存中,所以需要对JavaBean对象实现序列化接口。
5.二级缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响。
6.查询数据顺序 二级-->一级--->数据库--->把数据保存到一级,当sqlsession关闭或者提交的时候,把一级缓存中数据刷新到二级缓存中。
7.执行了DML操作,会清空一级缓存,所以数据变更不可能到达二级缓存中。
8.cache 有一些可选的属性 type, eviction, flushInterval, size, readOnly, blocking。
<cache type="" readOnly="" eviction=""flushInterval=""size=""blocking=""/>
属性 | 含义 | 默认值 |
---|---|---|
type | 自定义缓存类,要求实现org.apache.ibatis.cache.Cache接口 | null |
readOnly | 是否只读true:给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全 | false |
eviction | 缓存策略LRU(默认) – 最近最少使用的:移除最长时间不被使用的对象。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。 | LRU |
flushInterval | 刷新间隔,毫秒为单位。 | null |
size | 缓存对象个数。 | 1024 |
五、四大核心接口介绍及执行流程
1.四大核心接口介绍
MyBatis执行过程中涉及到非常重要的四个接口,这四个接口为MyBatis的四大核心接口:
-
Executor执行器,执行器负责整个SQL执行过程的总体控制。默认SimpleExecutor执行器。
-
StatementHandler语句处理器,语句处理器负责和JDBC层具体交互,包括prepare语句,执行语句,以及调用ParameterHandler.parameterize()。默认是PreparedStatementHandler。
-
ParameterHandler参数处理器,参数处理器,负责PreparedStatement入参的具体设置。默认使用DefaultParameterHandler。
-
ResultSetHandler结果集处理器,结果处理器负责将JDBC查询结果映射到java对象。默认使用DefaultResultSetHandler
2.四大核心接口执行顺序图
执行流程
(1)使用执行器Executor控制整个执行流程
(2)实例化StatementHandler,进行SQL预处理
(3)使用ParameterHandler设置参数
(4)使用StatementHandler执行SQL
(5)使用ResultSetHandler处理结果集
六、执行器类型
MyBatis的执行器都实现了Executor接口。作用是控制SQL执行的流程。
-
SimpleExecutor:默认的执行器类型。每次执行query和update(DML)都会重新创建Statement对象。
-
ReuseExecutor:执行器会重用预处理语句。不会每一次调用都去创建一个新的 Statement 对象,而是会重复利用以前创建好的(如果SQL相同的话)。
-
BatchExecutor:用在update(DML)操作中。所有SQL一次性提交。适用于批量操作。
1.SimpleExecutor
SimpleExecutor 是MyBatis默认的执行器类型。在没有明确设置执行器类型时,默认就是这个类型。
2.ReuseExecutor
ReuseExecutor主要用在执行时,重用预编译SQL。在同一个SqlSession对象中下次调用已经预编译的SQL直接设置参数。
3.BatchExecutor
BatchExecutor底层使用JDBC的批量操作。每一条SQL都不会立即执行,而是放到了List<Statement>中,最终统一提交。
由于底层的批量操作只支持DML操作,所以BatchExecutor也主要用在批量新增、批量删除、批量修改中。
七、MyBatis执行原理
对于MyBatis执行原理来说,不同的情况有不同的执行过程,大致可以分下面几种情况:
(1)接口绑定方式、使用SqlSession执行方法。
(2)是否有插件。
(3)不同的执行器。
首先加载全局配置文件为输入流,交给XPathParser解析器解析为Document文档对象,然后使用DOM解析Document文档对象,把解析结果存放在Configuration配置类中。
通过DefaultSqlSessionFactory实例化工厂,实例SqlSession的对象。创建了SqlSession接口的实现类DefaultSqlSession对象,在创建过程中,会同时创建Transaction事务对象、Executor执行器对象。如果当前项目有Interceptor拦截器,创建执行器时会执行拦截器。
通过JDK提供的Proxy创建接口的动态代理对象。
可以通过接口的代理对象调用方法。在调用方法时MyBatis会根据方法的类型判断调用SqlSession的哪个方法。例如:selectList、selectOne、update、insert等。
确定好具体调用SqlSession的哪个方法后,会按照执行器类型执行MyBatis四大核心接口,执行时也会触发拦截器Interceptor。最终会返回SQL的执行结果。
执行完方法后需要提交事务,提交时清空缓存、清除存储的Statement对象。
最后关闭SqlSession对象,释放资源。