Java学习笔记-全栈-web开发-15-MyBatis

MyBatis

  • 1. 简介
    • 1.1 原生JDBC 缺点
    • 1.2 MyBatis框架核心
    • 1.3 开发步骤
      • 1.3.1 创建model类
      • 1.3.2 创建全局配置文件
      • 1.3.3 编写映射文件
      • 1.3.4 加载映射文件
      • 1.3.5 编写测试程序
  • 2. 常用查询(语法重点)
    • 2.1 模糊查询
    • 2.2 增
    • 2.3 删
    • 2.4 改
    • 2.5 获得自增主键
    • 2.6 获得自增UUID
    • 2.7 语法小结
  • 3. Dao层 —— mapper(核心)
    • 3.1 开发规范
    • 3.2 测试
      • 3.2.1 编写mapper接口以及对应的配置文件
      • 3.2.2 添加配置映射
      • 3.2.3 测试
    • 3.3 全局配置setting(了解)
    • 3.4 别名 typeAliases
      • 3.4.1 mybatis内置别名
      • 3.4.2 自定义别名
    • 3.5 mapper注册
    • 3.6 注解开发
  • 4. 映射文件(重点)
    • 4.1 输入映射ParameterType
      • 4.1.1 简单类型
      • 4.1.2 传递POJO包装对象
      • 4.1.3 传递Map对象
    • 4.2 输出映射 resultType/resultMap
    • 4.2.1 resultType
      • 4.2.2 resultMap
    • 4.3 映射用法总结
  • 5. 高级查询语法
    • 5.1 if和where
    • 5.2 SQL片段
    • 5.3 for-each
    • 5.4 choose(分支查询)
  • 6. 关联查询
    • 6.1 一对一
      • 6.1.1 resultType实现
      • 6.1.2 resultMap实现
      • 小结
    • 6.2 一对多
      • 小结
    • 6.3 多对多
    • 6.4 总结
  • 7. 懒加载
  • 8. 查询缓存
    • 8.1 一级缓存
    • 8.2 二级缓存
      • 8.2.1 二级缓存应用场景:
      • 8.2.2 缓存框架
  • 9. MyBatisPlus
  • 10. 重点笔记(核心)
  • 11. 运行原理(重要)
    • 架构图
    • 11.1 基础点
    • 11.2 解析源码从HelloWorld开始
      • 1. 获取SqlSessionFactory对象
      • 2. 获取sqlSession对象
      • 3. 获取Mapper接口的代理对象(MapperProxy)
      • 4. 执行增删改查方法
      • 总结
    • 11.3 插件机制


1. 简介

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis,实质上Mybatis对ibatis进行一些改进

MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

对jdbc的封装框架有哪些:Hibernate,dbutils,jdbcTemplate[spring],mybatis

原理:
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

1.1 原生JDBC 缺点

Java学习笔记-全栈-web开发-15-MyBatis_第1张图片

以上是普通jdbc数据库操作,缺点

  • 数据库连接频繁开启和关闭,会严重影响数据库的性能。
  • 代码中存在硬编码,分别是数据库部分的硬编码和SQL执行部分的硬编码。

1.2 MyBatis框架核心

  • 1、mybatis配置文件,包括Mybatis全局配置文件和Mybatis映射文件,其中全局配置文件配置了数据源、事务等信息;映射文件配置了SQL执行相关的信息。
  • 2、mybatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。
  • 3、通过SqlSessionFactory,可以创建SqlSession即会话。Mybatis是通过SqlSession来操作数据库的
  • 4、SqlSession本身不能直接操作数据库,它是通过底层的Executor执行器接口来操作数据库的。Executor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)。
  • 5、Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中。该对象包括:SQL语句、输入参数映射信息、输出结果集映射信息。其中输入参数和输出结果的映射类型包括HashMap集合对象、POJO对象类型。

1.3 开发步骤

  • 1、创建model类
  • 2、创建全局配置文件SqlMapConfig.xml;
  • 3、编写映射文件;
  • 4、加载映射文件,在SqlMapConfig.xml中进行加载;
  • 5、编写测试程序,即编写Java代码,连接并操作数据库。
    过程:
    • a)读取配置文件;
    • b)通过SqlSessionFactoryBuilder创建SqlSessionFactory会话工厂。
    • c)通过SqlSessionFactory创建SqlSession。
    • d)调用SqlSession的操作数据库方法。
    • e)关闭SqlSession。

1.3.1 创建model类

Java学习笔记-全栈-web开发-15-MyBatis_第2张图片

1.3.2 创建全局配置文件

SqlMapConfig.xml



<configuration>

<environments default="development">
	<environment id="development">
		
		<transactionManager type="JDBC">transactionManager>
		
		<dataSource type="POOLED">
			<property name="driver" value="com.mysql.jdbc.Driver"/>
			<property name="url" value="jdbc:mysql://localhost:3306/study?serverTimezone=UTC"/>
			<property name="username" value="root"/>
			<property name="password" value="123456"/>
		dataSource>
	environment>
environments>
configuration>

1.3.3 编写映射文件

在classpath下,创建sqlmap文件夹,在其下创建User.xml映射文件




<mapper namespace="test">
	
	<select id="findUserById" parameterType="int" resultType="com.shunxu.model.User">
		SELECT * FROM USER WHERE id = #{id}
	select>
mapper>

1.3.4 加载映射文件

在SqlMapConfig.xml中加载User.xml

<mappers>
	<mapper resource="sqlmap/User.xml"/>
mappers>

1.3.5 编写测试程序

Java学习笔记-全栈-web开发-15-MyBatis_第3张图片

以上测试代码是mybatis单独使用的方法,但实际上会同spring一起使用,对象的操作都会交给bean,比上述代码简单很多。

2. 常用查询(语法重点)

2.1 模糊查询


	<select id="findUserByName" parameterType="String" resultType="com.shunxu.model.User">
		SELECT * FROM USER WHERE username like '%${value}%'
	select>

在这里插入图片描述

2.2 增


<insert id="insertUser" parameterType="com.shunxu.model.User">
	INSERT INTO USER (username,sex,birthday,address)
	VALUES(#{username},#{sex},#{birthday},#{address})
insert>

如果主键的值是mysql自增,则此处不需要给ID赋值
在这里插入图片描述

2.3 删

<delete id="deleteUser" parameterType="int">
	DELETE FROM USER WHERE id=#{id}
delete>

在这里插入图片描述

2.4 改

<update id="updateUser" parameterType="com.shunxu.model.User">
	UPDATE USER SET username=#{username},sex=#{sex} WHERE id=#{id}
update>

Java学习笔记-全栈-web开发-15-MyBatis_第4张图片

2.5 获得自增主键

通过sql函数获得:SELECT LAST_INSERT_ID()

<insert id="insertUser" parameterType="com.gyf.domain.User">
		
		<selectKey keyProperty="id" resultType="int" order="AFTER">
			SELECT LAST_INSERT_ID()
		selectKey>
		
		INSERT INTO USER (username,sex,birthday,address) 
		VALUES(#{username},#{sex},#{birthday},#{address})
insert>

2.6 获得自增UUID

<insert id="insertUser" parameterType="com.gyf.domain.User">
		<selectKey keyProperty="id" resultType="String" order="BEFORE">
			SELECT UUID()
		selectKey>
		INSERT INTO USER (username,sex,birthday,address) 
		VALUES(#{username},#{sex},#{birthday},#{address})
insert>

2.7 语法小结

parameterType和resultType

  • parameterType指定输入参数的java类型,可以填写别名或Java类的全限定名。
    resultType指定输出结果的java类型,可以填写别名或Java类的全限定名。

#{}和${}

  • #{}:相当于预处理中的占位符?。
    #{}里面的参数表示接收java输入参数的名称。
    #{}可以接受HashMap、POJO类型的参数。
    当接受简单类型的参数时,#{}里面可以是value,也可以是其他。
    #{}可以防止SQL注入。
  • ${}:相当于拼接SQL串,对传入的值不做任何解释的原样输出。
    ${}会引起SQL注入,所以要谨慎使用。
    ${}可以接受HashMap、POJO类型的参数。
    当接受简单类型的参数时,${}里面只能是value。

selectOne和selectList

  • selectOne:只能查询0或1条记录,大于1条记录的话,会报错:
  • selectList:可以查询0或N条记录

3. Dao层 —— mapper(核心)

Mapper代理的开发方式,程序员只需要编写mapper接口(相当于dao接口)即可。

Mybatis会自动的为mapper接口生成动态代理实现类。

不过要实现mapper代理的开发方式,需要遵循一些开发规范

3.1 开发规范

  • 1.mapper接口的全限定名要和mapper映射文件的namespace的值相同。
  • 2.mapper接口的方法名称要和mapper映射文件中的statement的id相同;
  • 3.mapper接口的方法参数只能有一个,且类型要和mapper映射文件中statement的parameterType的值保持一致。
  • 4.mapper接口的返回值类型要和mapper映射文件中statement的resultType值或resultMap中的type值保持一致;

3.2 测试

3.2.1 编写mapper接口以及对应的配置文件

Java学习笔记-全栈-web开发-15-MyBatis_第5张图片

3.2.2 添加配置映射

在这里插入图片描述

3.2.3 测试

Java学习笔记-全栈-web开发-15-MyBatis_第6张图片

3.3 全局配置setting(了解)

Java学习笔记-全栈-web开发-15-MyBatis_第7张图片
可配置属性:
Java学习笔记-全栈-web开发-15-MyBatis_第8张图片

3.4 别名 typeAliases

别名是使用是为了在映射文件中,更方便的去指定参数和结果集的类型,不再用写很长的一段全限定名。

3.4.1 mybatis内置别名

Java学习笔记-全栈-web开发-15-MyBatis_第9张图片

3.4.2 自定义别名

Java学习笔记-全栈-web开发-15-MyBatis_第10张图片

3.5 mapper注册

<mapper resource=’’/>
使用相对于类路径的资源
如:<mapper resource="sqlmap/User.xml" />

-----------------------------------------
<mapper class=’’/>
使用mapper接口的全限定名
如:<mapper class="cn.shunxu.mybatis.mapper.UserMapper"/>
注意:此种方法要求mapper接口和mapper映射文件要名称相同,且放到同一个目录下;
-----------------------------------------
<package name=’’/>(推荐)
注册指定包下的所有映射文件
如:<package name="cn.shunxu.mybatis.mapper"/>

注意:此种方法要求mapper接口和mapper映射文件要名称相同,且放到同一个目录下;

3.6 注解开发

可以将xml文件删除,所有的sql语句都在mapper接口方法中写
Java学习笔记-全栈-web开发-15-MyBatis_第11张图片

4. 映射文件(重点)

4.1 输入映射ParameterType

指定输入参数的java类型,可以使用别名或者类的全限定名。它可以接收简单类型,POJO对象、HashMap。

4.1.1 简单类型

基本类型
在这里插入图片描述

传递POJO对象
Java学习笔记-全栈-web开发-15-MyBatis_第12张图片

4.1.2 传递POJO包装对象

开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数

例如:
综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息)。

  1. 定义POJO包装类
    Java学习笔记-全栈-web开发-15-MyBatis_第13张图片
  2. 修改UserMapper.java
    Java学习笔记-全栈-web开发-15-MyBatis_第14张图片
  3. 修改UserMapper.xml
    在这里插入图片描述
  4. 测试
    Java学习笔记-全栈-web开发-15-MyBatis_第15张图片

4.1.3 传递Map对象

  1. mapper
    Java学习笔记-全栈-web开发-15-MyBatis_第16张图片
  2. xml
    在这里插入图片描述
  3. 测试
    Java学习笔记-全栈-web开发-15-MyBatis_第17张图片

4.2 输出映射 resultType/resultMap

输出映射与输出映射的用法基本一致,因此这里针对resultType和resultMap各举一个典型例子



4.2.1 resultType

resultType

  • 使用resultType进行结果映射时,查询的列名和映射的pojo属性名完全一致,该列才能映射成功。
  • 如果查询的列名和映射的pojo属性名全部不一致,则不会创建pojo对象;
  • 如果查询的列名和映射的pojo属性名有一个一致,就会创建pojo对象

输出POJO列表

  1. mapper
    在这里插入图片描述
  2. xml
    在这里插入图片描述


4.2.2 resultMap

如果查询出来的列名和属性名不一致(即数据库字段与model字段不一致),通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。

  • 1、定义resultMap
  • 2、使用resultMap作为statement的输出映射类型
  1. mapper
    Java学习笔记-全栈-web开发-15-MyBatis_第18张图片
  2. xml
    Java学习笔记-全栈-web开发-15-MyBatis_第19张图片
  3. 测试
    Java学习笔记-全栈-web开发-15-MyBatis_第20张图片

4.3 映射用法总结

  • 输出单个pojo对象和pojo列表时,mapper映射文件中的resultType的类型是一样的,mapper接口的方法返回值不同。
  • 同样的mapper映射文件,返回单个对象和对象列表时,mapper接口在生成动态代理的时候,会根据返回值的类型,决定调用selectOne方法还是selectList方法。

5. 高级查询语法

5.1 if和where

if标签:作为判断入参来使用的,如果符合条件,则把if标签体内的SQL拼接上。(test中的参数是property而不是column,且所有特殊字符都需要转义,逻辑运算符也得转义,因此建议使用英文逻辑运算符

注意:

  • 用if进行判断是否为空时,不仅要判断null,也要判断空字符串‘’;
  • 查询时,多个if并联的时候,比如当第一个if不满足时,拼接结果就会多一个"and"导致出错,使用where标签,会去掉条件中的第一个and符号。
  • 同理,update用多个if实现多个set时,也会有逗号可能多余的情况。因此用set标签替代手动的set,能够去掉最后一个多余的逗号

Java学习笔记-全栈-web开发-15-MyBatis_第21张图片

5.2 SQL片段

将某一段查询语句单独抽出来,然后通过引用的方式实现“到处使用”

Java学习笔记-全栈-web开发-15-MyBatis_第22张图片

5.3 for-each

例子:查询指定id用户
java
Java学习笔记-全栈-web开发-15-MyBatis_第23张图片

xml
Java学习笔记-全栈-web开发-15-MyBatis_第24张图片还可以加上index属性,当为list指索引,当为map指key。
测试
Java学习笔记-全栈-web开发-15-MyBatis_第25张图片



或者直接传入id集合
Java学习笔记-全栈-web开发-15-MyBatis_第26张图片

此外,foreach常用于批量保存
Java学习笔记-全栈-web开发-15-MyBatis_第27张图片for-each本质上是拼接sql字符串,因此,凡是可遍历生成的字符串,都可以用foreach,比如连接url设置allowMultiQueries=true,可以通过for-each发起多个sql语句

5.4 choose(分支查询)

需求:如果带了id,就用id查询,如果带了username,就用username查;二选其一。choose相当于带了bread的switch-case(因此是按顺序进入when)

<select id="getByIdOrUsername" resultType="xx.User">
	select * from user
	<where>
		<choose>
			<when test="id!=null">
				id=#{id}
			when>
			<when test="userName!=null">
				username like #{userName}
			when>
			<otherwise>
			
				1=1 
			otherwise>
		choose>		
	where>
select>

6. 关联查询

显然,mybatis是以一个model为基本单位,当查询涉及到多个model时,就需要关联查询

数据库信息如下:
Java学习笔记-全栈-web开发-15-MyBatis_第28张图片
user和orders:

  • User 与orders:一个用户可以创建多个订单,一对多
  • Orders 与 user:多个订单只由一个用户创建,多对一

orders和orderdetail:

  • Orders 与 orderdetail:一个订单包括多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多
  • orderdetail 与orders:多个订单明细包括在一个订单中, 多对一

orderdetail和items:

  • Orderdetail 与 items:多个订单明细只对应一个商品信息,多对一
  • Items 与 orderdetail:一个商品可以包括在多个订单明细 ,一对多

6.1 一对一

需求:
根据商品ID查找订单,包括用户名和地址

SQL语句:

#查找某个定单id的信息,包括用户名字和地址
SELECT o.*,u.username,u.address FROM orders o,user u
WHERE o.user_id = u.id AND o.id = 3


6.1.1 resultType实现

复杂查询时,单表对应的po类已不能满足输出结果集的映射。所以要根据需求建立一个扩展类来作为resultType的类型。

  1. 写一个订单扩展类
    Java学习笔记-全栈-web开发-15-MyBatis_第29张图片
  2. 声明订单接口
    在这里插入图片描述
  3. 声明订单配置文件
    Java学习笔记-全栈-web开发-15-MyBatis_第30张图片
  4. 加载映射文件
    在这里插入图片描述
  5. 测试
    Java学习笔记-全栈-web开发-15-MyBatis_第31张图片

6.1.2 resultMap实现

掌握association用法

  1. OrdersMapper.java
    Java学习笔记-全栈-web开发-15-MyBatis_第32张图片
  2. OrdersMapper.xml
    Java学习笔记-全栈-web开发-15-MyBatis_第33张图片
  3. 测试
    Java学习笔记-全栈-web开发-15-MyBatis_第34张图片

小结

  • resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。如果没有查询结果的特殊要求建议使用resultType。

  • resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的对象属性中。

  • resultMap可以实现延迟加载,resultType无法实现延迟加载。

6.2 一对多

掌握collection用法

需求:根据订单ID查找订单信息、用户信息和订单明细

SQL

Select
			orders.id,
			orders.user_id,
			orders.number,
			orders.createtime,
			orders.note,
			user.username,
			user.address,
			orderdetail.id detail_id,
			orderdetail.items_id,
			orderdetail.items_num
		from 
			orders,user,orderdetail
		where 
			orders.user_id = user.id 
			and orders.id = orderdetail.orders_id
  			and orders.id = #{?};
SELECT 
	o.*,
	u.username,
	u.address,
	od.id detail_id,
  od.items_id,
  od.items_num
FROM 
	orders o,
	user u,
	orderdetail od
WHERE 
	o.user_id = u.id 
  AND o.id = od.orders_id
	AND o.id = 3
  1. 在Orders中指定订单明细
    Java学习笔记-全栈-web开发-15-MyBatis_第35张图片
  2. Mapper
    Java学习笔记-全栈-web开发-15-MyBatis_第36张图片
  3. OrderMapper.xml
    Java学习笔记-全栈-web开发-15-MyBatis_第37张图片
  4. 测试
    Java学习笔记-全栈-web开发-15-MyBatis_第38张图片

小结

  • mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。

使用resultType实现:

  • 需要对结果集进行二次处理。将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。

6.3 多对多

需求:查询用户信息及用户购买的商品信息,要求将关联信息映射到主pojo的pojo属性中

Java学习笔记-全栈-web开发-15-MyBatis_第39张图片
SQL

SELECT 
  u.id,
	u.username,
	u.address,
	o.id order_id,
  o.number,
	o.createtime,
  o.note,
	od.id detail_id,
  od.items_id,
  od.items_num,
  it.name,
  it.price,
  it.detail
FROM 
	user u,
	orders o,
	orderdetail od,
  items it
WHERE 
	o.user_id = u.id 
  AND o.id = od.orders_id
  AND od.items_id = it.id;

思路:

  • 将用户信息映射到user中。
  • 在user类中添加订单列表属性List orderslist,将用户创建的订单映射到orderslist
  • 在Orders中添加订单明细列表属性List detailList,将订单的明细映射到detailList
  • 在Orderdetail中添加Items属性,将订单明细所对应的商品映射到Items
  1. UserMapper
    Java学习笔记-全栈-web开发-15-MyBatis_第40张图片
  2. User/Orders/Orderdetail.java
    Java学习笔记-全栈-web开发-15-MyBatis_第41张图片Java学习笔记-全栈-web开发-15-MyBatis_第42张图片Java学习笔记-全栈-web开发-15-MyBatis_第43张图片
  3. UserMapper.xml
    Java学习笔记-全栈-web开发-15-MyBatis_第44张图片Java学习笔记-全栈-web开发-15-MyBatis_第45张图片
  4. 测试
    Java学习笔记-全栈-web开发-15-MyBatis_第46张图片Java学习笔记-全栈-web开发-15-MyBatis_第47张图片

6.4 总结

  • 一对一:模型里面写模型(association)
  • 一对多:模型里面写集合(collection)
  • resultType:将查询结果按照sql列名pojo属性名一致性映射到pojo中。
  • resultMap:使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
  • association:将关联查询信息映射到一个pojo对象中。
  • collection:将关联查询信息映射到一个list集合中。

7. 懒加载

懒加载又叫延时加载,也叫按需加载。也就是说先加载主信息,需要的时候,再去加载信息。
在mybatis中,resultMap标签 的association标签和collection标签具有懒加载的功能。

Usermapper.xml
在这里插入图片描述
OrdersMapper.xml
Java学习笔记-全栈-web开发-15-MyBatis_第48张图片

开启懒加载
Java学习笔记-全栈-web开发-15-MyBatis_第49张图片

测试
Java学习笔记-全栈-web开发-15-MyBatis_第50张图片

8. 查询缓存

请记住:缓存只针对“查询”操作,其他操作会清除\更新缓存。

Mybatis的缓存,包括一级缓存和二级缓存,一级缓存是默认使用的。二级缓存需要手动开启。

一级缓存指的就是sqlsession,同一个sqlSession的同一条查询语句结果会被缓存。

在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。

一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象

二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是一级缓存区域。

Java学习笔记-全栈-web开发-15-MyBatis_第51张图片

8.1 一级缓存

原理:
Java学习笔记-全栈-web开发-15-MyBatis_第52张图片由于一级缓存是sqlSession级别的,在spring中,也可理解为是一个事务级别的,只有在一个事务中的相同查询,一级缓存才有效。

8.2 二级缓存

不同的namespace缓存放不同的map。

原理:
Java学习笔记-全栈-web开发-15-MyBatis_第53张图片
二级缓存需要手动开启:

  1. 全局开启
    Java学习笔记-全栈-web开发-15-MyBatis_第54张图片

  2. mapper开启二级缓存
    Java学习笔记-全栈-web开发-15-MyBatis_第55张图片

缓存是一种保存操作,对象会被序列化到本地,因此,所有对象都必须实现serializable接口

指定某个方法禁用二级缓存:
在这里插入图片描述

刷新缓存:
Java学习笔记-全栈-web开发-15-MyBatis_第56张图片
注意,二级缓存实际放的是一级缓存,因此,只有当一级缓存的sqlSession关闭后,二级缓存才有数据。

8.2.1 二级缓存应用场景:

应用需求:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术。

注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。

局限性
Mybatis二级缓存对细粒度的数据,缓存实现不好。

应用场景:
对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空。

解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
比如可以对经常变化的 数据操作单独放到另一个namespace的mapper中。

mybatis本身的缓存实现不太好,因此本节没有详细解释用法,仅仅是罗列概念。
在springboot中将学习更好的缓存框架。

8.2.2 缓存框架

mybatis的缓存只是“意思意思”,实际上不会真的用。一般使用第三方缓存框架

  • 如果是单个应用或者对缓存访问要求很高的应用,用ehcache。
  • 如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

9. MyBatisPlus

mybatisplus是对mybatis的封装操作,能够实现更加强大的功能
mybatisplus官网

具体的使用可以查看官网(中文),我会在springboot中将会介绍它的入门使用。

推荐一个不错的学习博文
链接

10. 重点笔记(核心)

mybatis映射文件

  • resultMap,自定义结果集
  • sql,抽取可重用语句块
  • insert、update、delete、select
    • values中的#{}填写的值,直接使用传入的pojo等封装对象的属性
    • 允许增删改直接定义包装类型的返回值
    • 如果是非pojo等封装对象,mybatis会将其封装为map,默认的key是param1~paramN,因此,最好在接口的方法传参中加上@param(“key名”)指定sql语句中的参数
    • 如果不是pojo对象,且不经常使用,则可以将其封装为map,因为mybatis底层本身就是使用map
    • 对于经常使用的多参数模型,创建专用的TO即可
    • 如果传入的是collection或list,虽然也是封装为map,但是map的key指定为collection和list,比如访问数组的第一个值:list[0]
    • 如果返回的是集合,resultType依旧写集合中元素的类型
    • 想要返回Map<主键,Javabean>,则resultType填Javabean,mapper接口方法上加上@MapKey(“主键字段名”)
  • parameterType,传入的参数类型,可以省略
  • cache,命名空间的二级缓存配置
  • cache-ref,其他命名空间缓存配置的引用
  • parameterMap,已废弃,老式的参数映射

#和$的区别:

  • #通过占位符的形式与sql拼接,能防止sql注入
  • $用在原生sql语句不允许使用占位符的时候(非参数的地方),如:
    • 分表:select * from ${year}_salary;
    • 分组:select * from user order by ${orderName}
  • #能够规定参数的一些规则

resultMap详解:

  • 基本用法:如果查询出来的列名和Javabean属性名不一致,可以通过resultMap将列名将Javabean属性名之间作一个映射关系。
    • Java学习笔记-全栈-web开发-15-MyBatis_第57张图片
    • id表示主键列,result表示非主键列
    • column表示字段名,property表示Javabean属性名
  • 高级用法(外键和级联概念在应用层的解决):实现关联查询
    • 查询用户的同时,返回他所属部门(用户的Javabean增加一个dept类型的属性,但是没有dept_id;用户表有dept_id,但是没有加外键约束)
    • 详情请查看第六节的关联查询,以下进行知识补充,(假设User与dept是多对一关系
    • association指定联合对象,最重要的是它可以实现分步查询property指定返回类型,select="mapper接口全方法名"指定调用方法,column指定为该方法传入参数的参数,该参数是第一步的查询结果的某个字段名,如果需要传入多个则使用column={property=column, property=column}(视频中的例子,User的Javabean中没有dept_id,但是数据表有)
      • 查询用户所属部门(一对一)
      <resultMap type="xxx.User" id="testMap">
      	<id column="id" property="id"/>
      	<result column="user_name" property="userName"/>
      	<association property="department" select="xxx.departmentMapper.getById" column="dept_id">
      		<id column="id" property="id"/>
      		<result column="name" property="name"/>
      	association>
      resultMap>
      
      <select id="getByStep" resultMap="testMap">
      	select * from user where id=#{id}
      select>
      
      
      分步查询每步都会发起一次查询语句,因此性能上可能会有问题。
      但是很多公司可能禁止多表联查,而且建立索引之后并不慢,
      而且resultMap支持延迟加载,可以实现分步查询的延迟(也就是说,如果用到了第二步的数据,才会进行第二步的查询)
      lazyLoadingEnabled=true
      aggressiveLazyLoading=false (侵入式懒加载,意思是如果是分步查询,那就将每一步的都加载出来,我们需要关闭这个功能)(有时候默认值就是我们想要的,但是也应该显示的指定我们想要的值,说明我们用到了,也避免版本更新带来的错误
    • collection,嵌套结果集,将部分结果封装为集合(测试时需要在dept中加上List和getter/setter;)凡是column都是指数据字段,property都是指Javabean属性名
      • 查询部门的同时将所有用户用户返回(一对多)
      <resultMap type="xx.Department" id="testMap">
      	<id column="id" property="id"/>
      	<result column="name" property="name"/>
      	
      	<collection property="users" ofType="xx.User">
      		<id column="id" property="id"/>
      		<result column="user_name" property="userName"/>
      	collection>
      resultMap>
      
      <select id="getUsersByDeptId" resultMap="testMap">
      	select * d.id did,d.name dname,u.id uid,u.user_name userName
      	from department d
      	left join user u
      	on u.dept_id=d.id
      	where d.id=#{id}
      	
      select>
      
      collection也有select,支持分步查询.
      全局开启懒加载之后,部分语句不需要懒加载,将collection/association的fetchType改为eager即可。
    • (了解)鉴别器discriminator,根据某列的值改变collection/association的封装规则
      • 查询部门,如果是女生,则查出部门信息
      • 如果是男生,则将username 赋值给email
      <resultMap type="xxx.Department" id="testMap">
      	<id column="id" property="id"/>
      	<result column="name" property="name"/>
      	
      	<discriminator javaType="string" column="gender">
      		
      		<case value="0" resultType="xx.User">
      			<association property="users" select="xxx.UserMapper.getAllByDeptId" column="id">
      				<id column="id" property="id"/>
      				<result column="name" property="name"/>
      			association>
      		case>
      		
      		<case value="1" resultType="xx.User">
      			
      				<result column="username" property="email"/>
      		case>
      	<discriminator>
      resultMap>
      
      <select id="getByStep" resultMap="testMap">
      	select * from user where id=#{id}
      select>
      

11. 运行原理(重要)

架构图

Java学习笔记-全栈-web开发-15-MyBatis_第58张图片

11.1 基础点

底层执行步骤:

  • 所有的mybatis应用都围绕着SqlSessionFactory,因此框架的第一步就是构建SqlSessionFactory
  • 从SqlSessionFactory中拿到sqlSession对象,sqlSession能够通过唯一标识执行已经映射的sql语句(Mapped sql),执行完毕需要关闭。
  • 唯一标识:Mapped sql的id,且包含namespace。
  • Mapped sql:就是包含sql语句的xxMapper.xml,需要将其注册到全局配置文件的Mappers中才能生效。

接口式执行步骤:

  • 配置Mapper接口,写上所需方法。
  • 配置Mapper.xml
  • 通过mapper全类名地址=namespace,将Mapper接口和Mapper.xml实现文件绑定
  • 通过mapper中方法名=xml中sql方法的id,将sql语句和接口方法绑定
  • 创建SqlSessionFactory,获取sqlSession对象,通过sqlSession获取Mapper接口的代理对象,然后用其调用方法,最后关闭sqlSession对象。

非线程安全的对象(如sqlSession),不允许将其作为类属性作为类共享属性,而应该在调用处,每次获取新的对象,否则会出现竞争。

Mapper接口没有实现类,但是mybatis为其生成了代理对象。

mybatis通过TypeHandlers实现Java和mysql对象的转换。

mybatis四大对象:

  • Executor:MyBatis的执行器,用于执行增删改查操作
  • ParameterHandler:处理SQL的参数对象
  • ResultSetHandler:处理SQL的返回结果集
  • StatementHandler:数据库的处理对象,用于执行SQL语句

11.2 解析源码从HelloWorld开始

1. 获取SqlSessionFactory对象

Java学习笔记-全栈-web开发-15-MyBatis_第59张图片

2. 获取sqlSession对象

Java学习笔记-全栈-web开发-15-MyBatis_第60张图片

在这步,拿到了四大对象之一的executor(第七步很重要,利用了插件包装executor)

3. 获取Mapper接口的代理对象(MapperProxy)

Java学习笔记-全栈-web开发-15-MyBatis_第61张图片

4. 执行增删改查方法

Java学习笔记-全栈-web开发-15-MyBatis_第62张图片
Java学习笔记-全栈-web开发-15-MyBatis_第63张图片

总结

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

如下图所示:

Java学习笔记-全栈-web开发-15-MyBatis_第64张图片


11.3 插件机制

MyBatis在四大对象的创建过程中,都会有插件进行介入。

插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。

MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

Java学习笔记-全栈-web开发-15-MyBatis_第65张图片

你可能感兴趣的:(Java-web开发)