MyBatis

MyBatis实战教程

Session

SqlSession 代表和数据库的一次会话,用完必须关闭
SqlSession 和 connection 一样都是非线程安全的。每次使用都应该去获取新的对象
mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象

两个重要配置文件:

  • mybatis的全局配置文件,包含数据库连接信息,事务管理器信息等。系统运行环境信息
  • sql映射文件:保存了每一个sql语句的映射信息,将sql抽取出来
多数据库支持

全局配置文件中配置databaseIdProvider属性
sql映射文件中,为sql语句标签添加databaseId属性

获取自增主键

mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGeanreatedKeys() 获取主键值策略
sql映射文件中,为sql语句标签添加useGeneratedKeys="true"属性
keyProperty:指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性

Oracle不支持自增,使用序列来模拟自增

<!-- 
	keyProperty:查出的主键值封装给javaBean的哪个属性
	order=“BEFORE”:当前sql在插入sql之前运行
	resultType:查出的数据的返回值类型
	BEFORE运行顺序:
		先运行selectKey查询id的sql,查出id值封装给javaBean的id属性
		再运行插入的sql,就可以取出id属性对应的值
	AFTER运行顺序:
		...
-->
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
	<!-- 编写查询主键的sql语句 -->
	select EMPLOYEES_SQL.nextval from dual
</selectKey>
参数处理

单个参数:mybatis不会做特殊处理
#{参数名},取出参数值(其实参数名可以随便写)

多个参数:mybatis会做特殊处理,多个参数会被封装成一个map

默认
key:param1 … paramN,或者参数的索引(0…n)
value:参数值

直接填写参数名会报异常:org.apache.ibatis.binding.BindingException

可以在方法参数前添加@Param("") 注解

key:@Param指定的值
value:参数值

POJO
如果多个参数正好是业务逻辑的数据模型,我们就可以直接传入pojo
#{属性名}:取出传入的pojo的属性值

Map
如果多个参数不是业务模型中的数据,没有对应的pojo,为了方便,我们也可以传入map
#{key}:取出map中对应的值

TO
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象

Page {
	int index;
	int size;
}

特别注意:如果是Collection(List、Set)类型或者是数组,也会特殊处理。也是把传入的list或者数组封装在map中
key:Collection(collection),如果是List还可以使用key(list),数组(array)

public Employee getEmpById(List<Integer> ids);

取值:取出第一个id的值:#{list[0]}

源码分析
names:{0=id, 1=lastName}:构造器的时候就确定了
确定流程

  1. 获取每个标了param注解的参数的@Param的值:id,lastName:赋值给name;
  2. 每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
    name的值:
    标注了param注解,注解的值
    没有标注:
    1. 全局配置:useActuralParamName(jdk1.8):name=参数名
    2. name=map.size():相当于当前元素的索引
#{} 和 ${} 区别

区别

#{}:是以预编译的形式,将参数设置到sql语句中,PreparedStatement;防止sql注入
${}:取出的值直接拼装在sql语句中,会有安全问题

select * from tbl_employee where id = ${id} and last_name = #{lastName}
Preparing:select * from tbl_employee where id = 2 and last_name = ?

大多情况中,我们取参数的值都应该去使用#{};
原生jdbc不支持占位符的地方我们就可以使用${}进行取值
比如分表:按照年份分表拆分

select * from ${year}_salary where xxx;

按字段排序

select * from tbl_employee order by ${f_name} ${order};

#{} 更丰富的用法:
规定参数的一些规则:
javaType,jdbcType,mode(存储过程),numericScale,resultMap,typeHandler,jdbcTypeName,expression

jdbcType通常需要在某些特定的条件下被设置:在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错):JdbcType OTHER:无效的类型:因为mybatis对所有的null都映射的是原生jdbc的OTHER类型
由于全局配置中:jdbcTypeForNull=OTHER;oracle不支持

  1. #{email, jdbcType=null}
  2. jdbcTypeForNull = NULL
select

返回list集合,resultType填写元素的类型
返回一条记录的map:key:列名,value:对应的值。resultType填写map
多条记录封装一个map:Map<Integer,Employee>:key:这条记录的主键,value:记录封装后的javaBean。resultType填写元素的类型,在接口方法上指定封装map的时候key @MapKey("id")

自动映射
  1. 全局设置
  • autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致
  • 如果autoMappingBehavior设置为null则会取消自动映射
  • 数据库字段命名规范,POJO属性符合驼峰命名法,如 A_COLUMN -> aCloumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true
  1. 自定义resultMap,实现高级结果集映射
    查询员工的同时查出员工的部门信息
  • 联合查询:级联属性封装结果集

    <result column="dept_name" property="dept.departmentName" />
    
  • association指定联合javaBean对象(一对一),property指定那个属性是联合的对象,javaType指定属性的类型

    <association property="dept" javaType="...">
    	<id column="id" property="id" />
    	...
    </association>
    

    使用association进行分步查询:select:表明当前属性是调用select指定的方法查出的结果,column:指定将哪一列的值传给这个方法,并封装给 property

    <association property="dept" select="com.antherd.mybatis.dao.DepartmentMapper.getDeptById" column="d_id">
    	<id column="id" property="id" />
    	...
    </association>
    

    可以使用延迟加载:每次查询Employee对象的时候,都将一起查询起来。部门信息在我们使用的时候再去查询。分段查询的基础之上加上两个全局配置:lazyLoadingEnable = true,aggressiveLazyLoading = false

  • collection定义关联集合类型的属性封装规则(一对多

    <collection property="emps" ofType="...">
    	<id cloumn="eid" property="id" />
    	...
    </collection>
    

    collection:定义关联集合类型的属性的封装规则
    ofType:指定集合里面元素的类型

    使用collection进行分步查询:

    <collection
     property="emps"
     select="com.antherd.mybatis.dao.EmployeeMapperPlus.getEmpsByDid"
     column="id">
    </collection>
    

    拓展:多列值传递给关联查询
    将多列的值封装map传递
    column="{key1=columnName1, key2=columnName2}"

    fetchType属性:表示使用延迟加载

    • lazy:延迟
    • eager:立即
  • discriminator鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
    封装Employee:
    如果查出的是女生,就把部门信息查询出来,否则不查询
    如果是男生,把last_name这一列的值赋值给email

    <discriminator javaType="string" column="gender">
    	<case value="0" resultType="com.antherd.mybatis.bean.Employee">
    		<association ...>
    	</case>
    	<case value="1" resultType="com.antherd.mybatis.bean.Employee">
    		<id ...>
    		<result ...>
    	</case>
    </discriminator>
    
动态sql
  • if:判断
    拼装问题解决

    1. where 后面加 1=1
    2. 使用where标签把所有的查询条件包裹在内,只会去掉开头的and或or
    3. 使用trim标签把所有条件包裹在内。
      • prefix:trim标签体中是整个字符串拼串后的结果,prefix给拼串后的整个字符串加一个前缀,如prefix = ”where“
      • prefixOverrides:前缀覆盖,去掉整个字符串前面多余的字符串
      • suffix:后缀
      • suffixOverrides:后缀覆盖,去掉整个字符串后面多余的字符,如prefix = ”and“
  • choose:选择 <choose><when></when></choose>

  • trim:去除

  • foreach:循环,open:遍历所有结果拼接一个开始的字符,close:遍历所有结果拼接一个结束的字符,sqparator:元素之间的分隔符,index:遍历list的时候是索引,遍历map的时候表示map的key,item表示map的值

  • set:去除多余”,“

数据库连接属性中添加 allowMultiQueries=true允许一条语句中,使用”;“来分割多条查询

Oracle数据库批量保存:
Oracle不支持values (), (), ()
Oracle支持批量方法

  1. 多个insert放在begin - end里面
  2. 利用中间表
insert into employees(id, name, email)
	select id, name email from (
		select "1" id, "name1" name, "email1" email from dual
		union
		select "2" id, "name2" name, "email2" email from dual
		...
	)
两个内置参数

mybatis默认还有两个内置参数:
_parameter:代表整个参数
单个参数:_parameter就是这个参数
多个参数:参数会被封装为一个map:_parameter就是代表这个map
_databaseId:如果配置了databaseIdProvider标签
_databaseId就是代表当前数据库的别名oracle

<select id="getEmpsTestInnerParamter" resultType="com.antherd.mybatis.dao.Employee">
	<if test="_databaseId=='mysql'">
		select * from tbl_employee
		<if test="_parameter !=null">
			where last_name = #{_parameter.lastName}
		</if>
	</if>
	<if test="_databaseId=='oracle'">
		select * from tbl_employee
	</if>
</select>

bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值

<bind name="_lastName" value="'%' + lastName + '%'" />

抽取可重用sql片段,方便后面引用

  1. sql抽取: 经常将要查询的列名,或者插入用的列名抽取出来方便引用
  2. include来引用已经抽取的sql
  3. include还可以自定义一些property,sql标签内部就能使用自定义属性
    include-property取值的正确方式${prop}, #{不能使用这种方式}
<sql id="insertColumn">
	id, name, email
</sql>
<include refid="insertColumn">
	<property name="testColumn" value="1234" />
</include>
缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率

MyBatis系统中默认定义了两级缓存

一级缓存二级缓存(全局缓存)

  • 一级缓存(本地缓存):与数据库同一次会话期间查询到的数据会放在本地缓存中。以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
  • 二级缓存(全局缓存):工作机制:1. 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中 2. 如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容 3. sqlSession EmployeeMapper -> Employee DepartmentMapper -> Department 不同namespace查出的数据会放在自己对应的缓存中(map中)效果:数据会从二级缓存中获取,查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
  1. 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。SqlSession级别缓存是一个Map
  2. 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。基于namespace对应一个二级缓存。
  3. 为了提高拓展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询)

  1. sqlSession不同
  2. sqlSession相同,查询条件不同(当前一级缓存中还没有这个数据)
  3. sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
  4. sqlSession相同,手动清除了一级缓存(缓存清空)

二级缓存使用:

  1. 开启全局二级缓存配置:cacheEnabled=true
  2. 在mapper.xml的mapper标签中添加cache标签配置二级缓存
    cache标签参数
  3. POJO需要实现序列化接口
  • eviction(缓存的回收策略):LRU(默认)、FIFO、SOFT、WEAK
  • flushInterval(缓存刷新间隔):缓存多长时间清空一次,默认不清空,设置一个毫秒值
  • readOnly(是否只读):true(只读):mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就将数据在缓存中的引用交给用户。不安全,速度快。 false(非只读):mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列化的技术克隆一份新的数据给用户。安全,速度慢
  • size(缓存存放多少元素):
  • type(指定自定义缓存的全类名):实现Cache接口即可

缓存相关的设置/属性:

  1. cacheEnable=true/false:关闭缓存(二级缓存关闭)(一级缓存一直可用)
  2. 每个select标签都有useCache=“true”:false:不使用缓存(一级缓存依然使用,二级缓存不使用)
  3. 每次增删改标签的:flushCache=“true”:(一级二级都会清除)增删改执行完成后就会清除缓存。一级缓存清空,二级也会被缓存清空
  4. 查询标签:flushCache=“false”:如果flushCache=true,每次查询之后都会清空缓存;缓存是没有被使用的
  5. sqlSession.clearCache():只是清除当期session的一级缓存
  6. localCacheScope:本地缓存作用域(一级缓存SESSION):当前会话的所有数据保存在会话缓存中。STATEMENT:可以禁用一级缓存

缓存
Cache接口提供了实现类,方便第三方缓存实现具体缓存细节

第三方缓存整合原理&ehcache

mybatis 提供了与第三方整合的工具包
包含redis-cache、spring、ehcache-cache等

ehcache-core

mapper中开启cache

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

ehcache.xml

<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
 <!-- 磁盘保存路径 -->
 <diskStore path="D:\ehcache" />
 <defaultCache
 	maxElementInMemory="10000"
 	maxElementsOnDisk="100000000"
 	eternal="false"
 	overflowToDisk="true"
 	timeToIdleSeconds="120"
 	tiemToLiveSeconds="120"
 	diskExpiryThreadIntervalSeconds="120"
 	memoryStoreEvictionPolicy="LRU">
 </defaultCache>
</ehcache>

引用缓存:namespace 指定和哪个名称空间缓存一致

<cache-ref namespace="" />

第三方缓存整合:
1. 导入第三方缓存包即可
2. 导入与第三方缓存整合的适配包:官方有
3. mapper.xml 中使用自定义缓存

第三方缓存

运行原理
  1. 获取sqlSessionFactory对象
    解析文件中的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession
    注意:【MappedStatement】代表一个增删改查的详细信息
  2. 获取SqlSession对象
    返回一个DefaultSQLSession对象,包含Executor和Configuration
    这一步会创建Executor对象
  3. 获取接口的代理对象(MapperProxy)
    getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象 代理对象里面包含了DefaultSqlSession(Executor)
  4. 执行增删改查方法

总结:

  1. 根据配置文件(全局,sql映射)初始化出Configuration对象
  2. 创建一个DefaultSqlSession对象
    里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
  3. DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy
  4. MapperProxy里面有DefaultSqlSession
  5. 执行增删改查方法
    1. 调用DefaultSqlSession的增删改查
    2. 会创建一个StatementHandler对象(同时也会创建出ParameterHandler和ResultSetHandler)
    3. 调用StatementHandler预编译参数以及设置参数值
      使用ParameterHandler来给sql设置参数
    4. 调用StatementHandler的增删改查方法
    5. ResultSetHandler封装结果

注意:
四大对象每个创建的时候都有一个interceptorChain.pluginAll流程
获取到所有的Interceptor(拦截器),返回target包装后的对象
插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)
我们的插件可以为四大对象创建出代理对象
代理对象就可以拦截到四大对象的每一个执行

插件编写:
  1. 编写Interceptor的实现类
  2. 使用@Intercepts注解完成插件签名
  3. 将写好的插件注册到全局配置文件中<plugins / >标签
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值