掷一枚硬币 答案不在硬币里 mybatis复习整理二
mybatis 复习整理(仗剑走天涯)
源码分析
图片来源于网络
API接口层
:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
数据处理层
:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
基础支撑层
:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑
核心成员
Configuration
:**MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中
SqlSession
:作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
Executor
:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler
:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等
ParameterHandler
:负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
ResultSetHandler
:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler
:负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
MappedStatement
:MappedStatement维护一条<select|update|delete|insert>节点的封装
SqlSource
:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql
:表示动态生成的SQL语句以及相应的参数信息
图片来源于网络
动态Sql
MyBatis提供了对SQL语句动态的组装能力,大量的判断都可以在 MyBatis的映射XML文件里面配置,以达到许多我们需要大量代码才能实现的功能,大大减少了我们编写代码的工作量
元素
元素 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose、when、otherwise | 相当于Java中的 case when语句 | 多条件分支判断 |
trim、where、set | 辅助元素 | 用于处理一些SQL拼装问题 |
foreach | 循环语句 | 在in语句等列举条件常用 |
上代码 再解释
-
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 声明-->
<properties>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db_mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="xiaobo"/>
</properties>
<settings>
<!-- 设置日志打印格式-->
<setting name="logImpl" value="SLF4J"/>
<setting name="logPrefix" value="mybatis.sql."/>
<!-- 驼峰式命名-->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
<!-- 别名配置-->
<typeAliases>
<!-- <typeAlias type="com.dream.xiaobo.entity.User" alias="user"/>-->
<!-- <typeAlias type="com.dream.xiaobo.datasources.DruidDatasourcesFactory" alias="DRUID"/>-->
<package name="com.dream.xiaobo.entity"/>
</typeAliases>
<!-- 设置默认读取哪个环境-->
<environments default="test">
<!-- id 多环境的名称-->
<environment id="development">
<!-- 数据源-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<!-- 数据源 这里的type可以使用别名 或者使用全类名-->
<dataSource type="com.dream.xiaobo.datasources.DruidDatasourcesFactory">
<property name="druid.driverClassName" value="${driver}"/>
<property name="druid.url" value="${url}"/>
<property name="druid.username" value="${username}"/>
<property name="druid.password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 映射Mapper-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
<!-- <mapper class="com.dream.xiaobo.dao.UserMapper"/>-->
<mapper resource="mapper/GoodsMapper.xml"/>
<mapper resource="mapper/TypeMapper.xml"/>
<mapper class="com.dream.xiaobo.dao.AdminMapper"/>
</mappers>
</configuration>
<setting name="mapUnderscoreToCamelCase" value="true"
:驼峰式命名
<typeAlias type="com.dream.xiaobo.entity.User" alias="user"/>
:设置别名
<package name="com.dream.xiaobo.entity"/>
给包下的设置别名
-
userMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--映射地址-->
<mapper namespace="com.dream.xiaobo.dao.UserMapper">
<resultMap id="userMap" type="User">
<id column="id" property="id" />
<result column="user_name" property="username" />
<result column="age" property="age" />
<result column="version" property="version" />
<result column="is_delete" property="isDelete" />
<result column="gmt_create" property="gmtCreate" />
<result column="gmt_update" property="gmtUpdate" />
</resultMap>
<sql id="sql">
id,user_name,age,version,is_delete,gmt_create,gmt_update
</sql>
<insert id="insertUser" parameterType="list">
INSERT INTO t_user(user_name,age,version,is_delete,gmt_create,gmt_update) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.username},#{user.age},#{user.version},#{user.isDelete},#{user.gmtCreate},#{user.gmtUpdate})
</foreach>
</insert>
<update id="updateUser">
UPDATE `t_user`
<set>
<trim suffixOverrides="," suffix="">
<if test="username != null and username != ''">
user_name = #{username} ,
</if>
<if test="age != null">
age = #{age} ,
</if>
<if test="gmtUpdate != null and gmtUpdate != ''">
gmt_update = #{gmtUpdate} ,
</if>
</trim>
</set>
WHERE id = #{id}
</update>
<delete id="deleteUser">
DELETE FROM `t_user` WHERE id IN
<foreach collection="users" item="user" open="(" close=")" separator=",">
#{user.id}
</foreach>
</delete>
<select id="selectUser" resultType="com.dream.xiaobo.entity.User">
SELECT <include refid="sql" />
FROM `t_user`
<where>
<if test="id != null">
id = #{id}
</if>
<if test="username != null and username != ''">
and user_name = #{username}
</if>
<if test="age != null">
and age = #{age}
</if>
<if test="version != null">
and version = #{version}
</if>
<if test="isDelete != null">
and is_delete = #{isDelete}
</if>
<if test="gmtCreate != null and gmtCreate != ''">
and gmt_create = #{gmtCreate}
</if>
<if test="gmtUpdate != null and gmtUpdate != ''">
and gmt_update = #{gmtUpdate}
</if>
</where>
</select>
<select id="selectUserById" resultType="com.dream.xiaobo.entity.User">
SELECT <include refid="sql" />
FROM `t_user`
<where>
<if test="id != null">
id = #{id}
</if>
</where>
</select>
</mapper>
resultMap
:结果集映射 使其字段与对象属性一一对应
if
:if元素相当于Java中的if语句,它常常与test属性联合使用
where
:where元素相当于WHERE关键字 可以解决第一个拼接问题
trim
:有时候我们要去掉一些特殊的SQL语法,比如常见的and、or,此时可以使用trim元素。trim元素意味着我们需要去掉一些特殊的字符串,prefix代表的是语句的前缀,而prefixOverrides代表的是你需要去掉的那种字符串,suffix表示语句的后缀,suffixOverrides代表去掉的后缀字符串
set
:在update语句中,如果我们只想更新某几个字段的值,这个时候可以使用set元素配合if元素来完成。注意:set元素遇到,会自动把,去掉
foreach
:foreach元素是一个循环语句,它的作用是遍历集合,可以支持数组、List、Set接口
-
collection
配置的是传递进来的参数名称
-
item
配置的是循环中当前的元素
-
index
配置的是当前元素在集合的位置下标
-
open close
配置的是以什么符号将这些集合元素包装起来
-
separator
各个元素的间隔符
sql
:增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用
<include refid="sql"></include>
:引用sql片段
-
userMapper
public interface UserMapper {
List<User> selectUser(User user);
List<User> selectUserById(@Param("id") Integer id);
Integer insertUser(@Param("users") List<User> users);
Integer updateUser(User user);
Integer deleteUser(@Param("users") List<User> users);
}
嵌套查询
因为实际当中表存在一对多的关系 所以我们需要进行相应的多表操作
-
Goods
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods implements Serializable {
private static final Long serialVersionUID = 1L;
private Integer id;
private String name;
// private Type type;
// private List<Type> list;
}
-
Type
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Type implements Serializable {
private static final Long serialVersionUID = 1L;
private Integer id;
private String typeName;
private List<Goods> list;
}
-
goodsMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dream.xiaobo.dao.GoodsMapper">
<resultMap id="goodsMap" type="com.dream.xiaobo.entity.Goods">
<id column="id" property="id"/>
<result column="name" property="name"/>
<association property="type" column="id" javaType="Type" select="com.dream.xiaobo.dao.TypeMapper.selectAll"/>
</resultMap>
<resultMap id="goodsMap2" type="com.dream.xiaobo.entity.Goods">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!-- <association property="type" javaType="Type">-->
<!-- <id column="id" property="id"/>-->
<!-- <result column="type_name" property="typeName"/>-->
<!-- </association>-->
</resultMap>
<sql id="sql">
id,`name`
</sql>
<sql id="sql2">
g.id,g.name,t.id,t.type_name
</sql>
<sql id="sql3">
id,name,type_id,type_name
</sql>
<select id="selectAll" resultMap="goodsMap">
SELECT <include refid="sql" />
FROM `t_goods`
</select>
<select id="selectAll2" resultMap="goodsMap2">
SELECT <include refid="sql2" />
FROM `t_goods` g LEFT JOIN `t_type` t ON g.type_id = t.id
</select>
<select id="selectById" resultMap="goodsMap2">
SELECT <include refid="sql" />
FROM `t_goods` WHERE id = #{id}
</select>
</mapper>
-
typeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dream.xiaobo.dao.TypeMapper">
<resultMap id="typeMap" type="com.dream.xiaobo.entity.Type">
<id column="id" property="id"/>
<result column="type_name" property="typeName"/>
</resultMap>
<resultMap id="goodMap3" type="com.dream.xiaobo.entity.Type">
<id column="id" property="id"/>
<result column="type_name" property="typeName"/>
<collection property="list" javaType="list" ofType="Goods" column="id" select="com.dream.xiaobo.dao.GoodsMapper.selectById"/>
</resultMap>
<resultMap id="goodsMap4" type="com.dream.xiaobo.entity.Type">
<id column="id" property="id"/>
<result column="type_name" property="typeName"/>
<collection property="list" javaType="list" ofType="Goods" column="id">
<id column="id" property="id"/>
<result column="name" property="name"/>
</collection>
</resultMap>
<sql id="sql">
id,type_name
</sql>
<sql id="sql2">
g.id,g.name,t.id,t.type_name
</sql>
<select id="selectAll" resultMap="typeMap">
SELECT <include refid="sql" />
FROM t_type id = #{id}
</select>
<select id="selectById" resultMap="goodMap3">
SELECT <include refid="sql"/>
FROM `t_type`
</select>
<select id="selectAll3" resultMap="goodsMap4">
SELECT <include refid="sql2"/>
FROM `t_type` t LEFT JOIN `t_goods` g ON g.type_id = t.id
</select>
</mapper>
collection 级联操作时使用
-
按查询嵌套
对象属性与实体一一对应
property
:实体对应的对象
javaType
: 实体对象对应的类型
ofType
: 实体对象对应的实体对象的类型
column
:主键
-
按结果嵌套
property
:实体对应的对象
javaType
: 实体对象对应的类型
ofType
: 实体对象对应的实体对象的类型
column
:主键
select
:根据哪个Mapper方法得到哪个查询结果
association 嵌套查询时使用
-
按结果嵌套
property
:实体对象字段
column
:字段
javaType
:字段类型
select
:根据哪个Mapper方法得到哪个查询结果
-
按查询嵌套
对象属性与实体一一对应
property
:实体对象字段
column
:字段
javaType
:字段类型
collection 和 association具备延迟加载功能
懒加载(延迟加载)
按需加载,即当使用时候再去查询 先从单表查询、需要时再从关联表去关联查询、大大提高数据库性能,因为查询单表要比关联查询多张表速度要快
SELECT orders., user.username FROM orders, USER WHERE orders.user_id = user.id
延迟加载相当于:
SELECT orders.,
(SELECT username FROM USER WHERE orders.user_id = user.id)username FROM orders
关联查询分两次来做,而不是一次性查出所有的。第一步只查询单表orders,必然会查出orders中的一个user_id字段,然后我再根据这个user_id查user表,也是单表查询
<!-- 开启懒加载配置 -->
<settings>
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
获取递增主键
-
配置文件方式
<insert id="insertUser" parameterType="list" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
INSERT INTO t_user(user_name,age,version,is_delete,gmt_create,gmt_update) VALUES
<foreach collection="users" item="user" separator=",">
(#{user.username},#{user.age},#{user.version},#{user.isDelete},#{user.gmtCreate},#{user.gmtUpdate})
</foreach>
</insert>
useGeneratedKeys
:是否开启获取递增主键
keyColumn
:数据库字段
keyProperty
:对应对象字段
-
注解配置方式
@Insert("INSERT INTO `t_user`(user_name,age,version,is_delete,gmt_create,gmt_update)\n" +
" values(#{username},#{age},#{version},#{isDelete},#{gmtCreate},#{gmtUpdate})")
@Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id")
public Integer insert(User user);
mybatis缓存
如果缓存中有数据,就不用从数据库获取,大大提高系统性能
mybatis提供一级缓存和二级缓存
一级缓存
第一次发起查询sql查询用户id为1的用户,先去找缓存中是否有id为1的用户,如果没有,再去数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
如果sqlsession执行了commit操作(插入,更新,删除),会清空sqlsession中的一级缓存,避免脏读
第二次发起查询id为1的用户,缓存中如果找到了,直接从缓存中获取用户信息
MyBatis默认支持并开启一级缓存
一级缓存失效
sqlSession不同
当sqlSession对象相同的时候,查询的条件不同,原因是第一次查询时候,一级缓存中没有第二次查询所需要的数据
当sqlSession对象相同,两次查询之间进行了插入的操作
当sqlSession对象相同,手动清除了一级缓存中的数据
一级缓存的生命周期
MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用
一级缓存的意义
当前会话产生的共同数据
二级缓存
二级缓存是Mapper级别的缓存
多个sqlsession去操作同一个mapper的sql语句,多个sqlsession可以共用二级缓存,所得到的数据会存在二级缓存区域
二级缓存是跨sqlsession的
二级缓存相比一级缓存的范围更大(按namespace来划分),多个sqlsession可以共享一个二级缓存
二级缓存配置
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 需要将映射的javabean类实现序列化 -->
-
mapper
<!--开启本Mapper的namespace下的二级缓存-->
<cache eviction="LRU" flushInterval="100000"/>
❝eviction回收策略(缓存满了的淘汰机制),目前MyBatis提供以下策略
LRU(Least Recently Used)
最近最少使用的,最长时间不用的对象
FIFO(First In First Out)
先进先出,按对象进入缓存的顺序来移除他们
SOFT
软引用,移除基于垃圾回收器状态和软引用规则的对象,当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉,也就是只有内存不足,JVM才会回收该对象
WEAK
弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。弱引用的特点是不管内存是否足够,只要发生GC,都会被回收
❝flushInterval:刷新间隔时间,单位为毫秒
如果你不配置它,那么当SQL被执行的时候才会去刷新缓存
❝size:引用数目
一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出
❝readOnly:只读
意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有
办法修改缓存,它的默认值是false,不允许我们修改
❝禁用二级缓存
在mapper的crud中设置useCache=false,禁用当前select语句的二级缓存,默认情况为true
实际开发中,针对每次查询都需要最新的数据sql,要设置为useCache="false" ,禁用二级缓存
❝flushCache标签:刷新缓存(清空缓存)
执行完commit操作都需要刷新缓存,flushCache="true 表示刷新缓存,可以避免脏读
❝二级缓存应用场景
根据需求设置相应的flushInterval:刷新间隔时间
二级缓存的意义
多个会话产生共享的数据
第三方缓存--EhCache充当二级缓存
第三方缓存组件很多 常用的有ehcache,Memcached、redis等
ehcache三方缓存依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
-
mapper中使用ehcache
<mapper namespace = “com.ydlclass.entity.User” >
<cache type="org.mybatis.caches.ehcache.EhcacheCache" eviction="LRU" flushInterval="10000" size="1024" readOnly="true"/>
</mapper>
-
ehcache.xml独立配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
插件使用
@Slf4j
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "query",
args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
log.debug("[{}]","拦截之前");
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
log.debug("[{}]","拦截之后");
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
-
mybatis-config.xml
<plugins>
<plugin interceptor="com.dream.xiaobo.plugins.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>
pageHelper分页插件
https://pagehelper.github.io/docs/howtouse/
你知道的越多 你不知道的越多 嘿 我是小博 带你一起看我目之所及的世界......