日常提交

This commit is contained in:
2022-09-01 01:18:37 +08:00
parent c0b417cb35
commit 8516c31fcc

View File

@@ -496,3 +496,336 @@ public class User {
- typeHandler
- resultMap
- select
### association元素
association元素通常用来标识“has-a”的关系通过关联有两种方式将数据加载到关联中去
- 嵌套select执行另一个sql statement并且返回预期的复杂对象
- 嵌套result通过嵌套的result mapping来处理连接结果的重复子集
#### 嵌套select
嵌套select,其会先返回查询的主表数据,对关联的子表数据会通过association标签指定的select属性值来再次执行sql查找预期数据并组装城复杂对象
当使用嵌套select来处理association时,association标签具有如下属性
- column数组库表的列名该列保存的值将会被传递给嵌套的statement作为输入参数。
> 当想要传递复合key作为输入参数时可以通过column="{prop1=col1,prop2=col2}"的形式来传递参数这会导致传递给嵌套statement的参数对象的prop1和prop2属性被设置
- select会返回预期复杂类型的其他select statement标签的id
- fetchType可选参数可以为“lazy”或者“eager”会覆盖全局配置中的lazyLoadingEnable
```xml
<!-- association内层嵌套样例 -->
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
```
> 对于嵌套select方法来加载association,其会导致***N+1***的问题
> 例如,当一条select语句返回一个数据集时,通过嵌套select语句来加载association,其会:
> #### 对数据集中的每一条数据,都会再次执行一个select语句来为该条数据加载association信息,当数据集中数据条数很多时,如果未设置延迟加载,那么成百上千的sql语句被执行,会严重影响性能
#### 嵌套result
不同于嵌套select先查主表信息后再次执行sql语句查询关联子表数据,嵌套result会通过A JOIN B关联来通过查询需要的主表信息和子表信息
嵌套result不需要像嵌套select一样分次执行sql语句
> #### 使用嵌套result时association可以使用的属性
> - resultMap:指定用于将results加载到association的resultMap id
> - columnPrefix:可以将具有某一个前缀的列映射到外部的resultMap中,从而即使在select语句中重新为列名定义了alias(添加了前缀,如"co_"等),还是能复用外部的同一个resultMap
> - notNullColumn:该属性可以指定多个列,只有当指定的多个列中任何一个不为空时,mybatis
> 才会创建该子对象
> - autoMapping:开启或关闭自动映射,该属性对外部映射无效,故而不能搭配select或者resultMap使用
```xml
<!-- 通过left join连接两个表 -->
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
<!-- resultMap,映射主表信息 -->
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<!-- association的resultMap属性指定为其他resultMap的id,嵌套resultMap -->
<association property="author" resultMap="authorResult" />
</resultMap>
<!-- resultMap,映射子表信息 -->
<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>
```
> 在嵌套result中,id元素(被标识为id的result)非常重要,应该指定一个或多个属性标识为id来唯一标识该对象.
> 当没有指定id result时,mybatis也会正常运行,但是会产生严重的性能问题
> 应尽可能的将能唯一标识该结果的最小字段集标识为id(可以选择主键或符合主键)
对于嵌套result,除了上述的办法,还可以将association对应的resultMap直接嵌套写入association的子标签中,样例如下:
```xml
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</association>
</resultMap>
```
共同作者:通过columnPrefix属性复用外部resultMap的情况
```xml
<!-- 连接author表两次,为author表字段添加不同前缀 -->
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
CA.id as co_author_id,
CA.username as co_author_username,
CA.password as co_author_password,
CA.email as co_author_email,
CA.bio as co_author_bio
from Blog B
left outer join Author A on B.author_id = A.id
left outer join Author CA on B.co_author_id = CA.id
where B.id = #{id}
</select>
<!-- resultMap,将results映射为Author类,可被复用 -->
<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>
<!-- 复用外部的authorResult resultMap -->
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author"
resultMap="authorResult" />
<!-- 通过columnPrefix指定前缀,将添加前缀的results仍然映射到resultMap -->
<association property="coAuthor"
resultMap="authorResult"
columnPrefix="co_" />
</resultMap>
```
### collection元素
collection元素用来处理A类中含有List&lt;B&gt;属性的情况,其用法基本和association元素相同
```xml
<collection property="posts" ofType="domain.blog.Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
```
和association元素相同,coolection元素也可以通过嵌套select或者嵌套result来映射复杂类型
#### collection元素的嵌套select
```xml
<resultMap id="blogResult" type="Blog">
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
```
#### collection元素的嵌套result
```xml
<!-- 关联查询 -->
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>
<!-- 嵌套result -->
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>
<!-- 嵌套result并复用外部resultMap -->
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>
<resultMap id="blogPostResult" type="Post">
<id property="id" column="id"/>
<result property="subject" column="subject"/>
<result property="body" column="body"/>
</resultMap>
```
## discriminator
某些情况下,单条sql查询语句会返回具有不同类型的结果集(如不同的Car数据,但是有卡车和轿车的区别).
discriminator可以通过某些字段的值来选择性采用某些resultMap,并且支持继承层次.
```xml
<!--
通过vehicle_type字段的值来选择采用那些resultMap
当某行数据vihicle_type字段值为1时,只有carResult的resultMap会被使用,外面如vin等属性都不会被映射
如果想要
-->
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>
<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>
```
如果想要在使用carResult时也执行vehicleResult中的映射,可以在carResult中指定extends属性
```xml
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>
```
## automapping
对于automapping,其会获取resultset中的字段名并且在java对象中查找同名的property(忽略大小写,如果想将数据表字段名的underscore形式映射到java类的camelCase属性,可以在mybatis config文件中添加mapUnderscoreToCamelCase配置).
即使手动指定了resultMap,automapping一样会发挥作用.对于每个指定的resultMap,所有在resultSet中出现过的字段如果没有在resultMap中手动为其配置一个映射,那么会对该字段执行automapping.
```xml
<!-- 如下列中,password会被手动映射,而id和userName都会被自动映射 -->
<select id="selectUsers" resultMap="userResultMap">
select
user_id as "id",
user_name as "userName",
hashed_password
from some_table
where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
<result property="password" column="hashed_password"/>
</resultMap>
```
> 自动映射有三种等级,可以在mybatis config文件中进行配置
> - NONE:禁用自动配置
> - PARTIAL:会对除嵌套result映射以外的属性进行自动行摄
> - FULL:会对所有属性执行自动映射
```xml
<!--
对于该映射,当automapping level为full时,由于Blog和Author类中都具有id属性,故而在automapping时会将Blog
对象中的id和Author对象中的id都填充为B.id,该行为是非预期的
而对于automapping level为partial时,该automapping不会填充嵌套result映射中的属性,故而只会填充Blog类中
的id属性,而不会填充Author类中的id属性
-->
<select id="selectBlog" resultMap="blogResult">
select
B.id,
B.title,
A.username,
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<association property="author" resultMap="authorResult"/>
</resultMap>
<resultMap id="authorResult" type="Author">
<result property="username" column="author_username"/>
</resultMap>
```
默认情况下,automapping级别为partial,并且可以为resultMap元素指定autoMapping属性为false来为该resultMap关闭autoMapping
```xml
<resultMap id="userResultMap" type="User" autoMapping="false">
<result property="password" column="hashed_password"/>
</resultMap>
```
## mybatis缓存
默认情况下,mybatis只启用了会话级别的缓存,其缓存数据只在sqlsession期间有效.
如果想要开启二级缓存,只需要在接口对应的mapper文件中添加一行
```xml
<cache/>
```
该行语句的效果如下:
- 所有select语句的执行结果都会被缓存
- 所有insert/update/delete语句都会清除缓存
- 缓存会通过LRU算法来作为淘汰策略
- 缓存不定期的刷新
- 缓存会存储1024个引用指向列表或者对象(不管查询方法会返回哪种类型)
- 缓存被设计为可读写缓存,通过缓存获取到的对象并不会和其他线程共享,故而可以安全的对获取到的对象进行共享
> 缓存只会作用于位于mapper文件中的语句,如果使用java api和xml混合的方式,那么在共用接口声明的sql语句并不会被缓存.
可以自定义二级缓存
```xml
<!--
该二级缓存通过FIFO策略进行淘汰
刷新缓存周期为60000ms
缓存大小可以保存512个结果(类型为对象或列表:queryForOne或queryForList)
该缓存是只读的,对该缓存的写操作可能会产生线程安全问题
-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
```
对于二级缓存,可以使用的淘汰策略有:
- LRU:默认策略
- FIFO
- SOFT:基于垃圾回收器状态和软引用规则移除对象
- WEAK:弱引用,基于垃圾回收器状态和弱引用规则移出对象
flushInterval:
- 定义cache的刷新间隔,单位为ms,默认没有刷新间隔
size:
- 定义缓存大小,为引用数目,默认为1024
readOnly:
- true:只读,会给所有调用者返回相同的缓存对象实例,调用者如果进行修改可能会产生线程安全问题
- false:可读写,会给所有调用者返回缓存对象的拷贝,调用者可以安全的对返回对象进行修改,默认情况下缓存类型是可读写的
> ### 二级缓存的事务性
> 当sqlsession完成并且提交,或完成并回滚时,即使没有执行flushCache=true的insert/delete/update,缓存也会进行刷新
> select/update/insert/delete
> sql语句都会有flushCache属性,指定是否在执行完成后刷新cache,默认情况下,select标签的flushCache属性为false,insert/update/delete语句的flushCache属性为true
### cache-ref
默认情况下,某一命名空间中的语句只会对当前命名空间中的cache进行刷新.但是,如果想在多个命名空间(Mapper)之间共享缓存,可以通过cache-ref来引用其他命名空间中的缓存.
```xml
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
```