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


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 缺点

在这里插入图片描述

以上是普通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类

在这里插入图片描述

1.3.2 创建全局配置文件

SqlMapConfig.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>
<!-- 配置mybatis的环境信息 -->
<environments default="development">
	<environment id="development">
		<!-- 配置JDBC事务控制,由mybatis进行管理 -->
		<transactionManager type="JDBC"></transactionManager>
		<!-- 配置数据源,采用dbcp连接池 -->
		<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映射文件

<?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">
<!-- 
	namespace:命名空间,它的作用就是对SQL进行分类化管理,可以理解为SQL隔离
	注意:使用mapper代理开发时,namespace有特殊且重要的作用
 -->
<mapper namespace="test">
	<!-- 
		[id]:statement的id,要求在命名空间内唯一  
		[parameterType]:入参的java类型
		[resultType]:查询出的单条结果集对应的java类型
		[#{}]: 表示一个占位符?
		[#{id}]:表示该占位符待接收参数的名称为id。注意:如果参数为简单类型时,#{}里面的参数名称可以是任意定义
	 -->
	<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 编写测试程序

在这里插入图片描述

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

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

2.1 模糊查询

<!-- 
		[${}]:表示拼接SQL字符串
	 	[${value}]:表示要拼接的是简单类型参数。
		 注意:
		1、如果参数为简单类型时,${}里面的参数名称必须为value 
		2、${}会引起SQL注入,一般情况下不推荐使用。但是有些场景必须使用${},比如order by ${colname}
	-->
	<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>

在这里插入图片描述

2.5 获得自增主键

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

<insert id="insertUser" parameterType="com.gyf.domain.User">
		<!-- 
			[selectKey标签]:通过select查询来生成主键
			[keyProperty]:指定存放生成主键的属性
			[resultType]:生成主键所对应的Java类型
			[order]:指定该查询主键SQL语句的执行顺序,相对于insert语句
			[last_insert_id]:MySQL的函数,要配合insert语句一起使用 -->
		<selectKey keyProperty="id" resultType="int" order="AFTER">
			SELECT LAST_INSERT_ID()
		</selectKey>
		<!-- 如果主键的值是通过MySQL自增机制生成的,那么我们此处不需要再显示的给ID赋值 -->
		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接口以及对应的配置文件

在这里插入图片描述

3.2.2 添加配置映射

在这里插入图片描述

3.2.3 测试

在这里插入图片描述

3.3 全局配置setting(了解)

在这里插入图片描述
可配置属性:
在这里插入图片描述

3.4 别名 typeAliases

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

3.4.1 mybatis内置别名

在这里插入图片描述

3.4.2 自定义别名

在这里插入图片描述

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接口方法中写
在这里插入图片描述

4. 映射文件(重点)

4.1 输入映射ParameterType

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

4.1.1 简单类型

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

传递POJO对象
在这里插入图片描述

4.1.2 传递POJO包装对象

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

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

  1. 定义POJO包装类
    在这里插入图片描述
  2. 修改UserMapper.java
    在这里插入图片描述
  3. 修改UserMapper.xml
    在这里插入图片描述
  4. 测试
    在这里插入图片描述

4.1.3 传递Map对象

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

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
    在这里插入图片描述
  2. xml
    在这里插入图片描述
  3. 测试
    在这里插入图片描述

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,能够去掉最后一个多余的逗号

在这里插入图片描述

5.2 SQL片段

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

在这里插入图片描述

5.3 for-each

例子:查询指定id用户
java
在这里插入图片描述

xml
在这里插入图片描述还可以加上index属性,当为list指索引,当为map指key。
测试
在这里插入图片描述



或者直接传入id集合
在这里插入图片描述

此外,foreach常用于批量保存
在这里插入图片描述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时,就需要关联查询

数据库信息如下:
在这里插入图片描述
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. 写一个订单扩展类
    在这里插入图片描述
  2. 声明订单接口
    在这里插入图片描述
  3. 声明订单配置文件
    在这里插入图片描述
  4. 加载映射文件
    在这里插入图片描述
  5. 测试
    在这里插入图片描述

6.1.2 resultMap实现

掌握association用法

  1. OrdersMapper.java
    在这里插入图片描述
  2. OrdersMapper.xml
    在这里插入图片描述
  3. 测试
    在这里插入图片描述

小结

  • 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中指定订单明细
    在这里插入图片描述
  2. Mapper
    在这里插入图片描述
  3. OrderMapper.xml
    在这里插入图片描述
  4. 测试
    在这里插入图片描述

小结

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

使用resultType实现:

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

6.3 多对多

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

在这里插入图片描述
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<Orders> orderslist,将用户创建的订单映射到orderslist
  • 在Orders中添加订单明细列表属性List<Orderdetail> detailList,将订单的明细映射到detailList
  • 在Orderdetail中添加Items属性,将订单明细所对应的商品映射到Items
  1. UserMapper
    在这里插入图片描述
  2. User/Orders/Orderdetail.java
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
  3. UserMapper.xml
    在这里插入图片描述在这里插入图片描述
  4. 测试
    在这里插入图片描述在这里插入图片描述

6.4 总结

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

7. 懒加载

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

Usermapper.xml
在这里插入图片描述
OrdersMapper.xml
在这里插入图片描述

开启懒加载
在这里插入图片描述

测试
在这里插入图片描述

8. 查询缓存

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

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

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

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

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

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

在这里插入图片描述

8.1 一级缓存

原理:
在这里插入图片描述由于一级缓存是sqlSession级别的,在spring中,也可理解为是一个事务级别的,只有在一个事务中的相同查询,一级缓存才有效。

8.2 二级缓存

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

原理:
在这里插入图片描述
二级缓存需要手动开启:

  1. 全局开启
    在这里插入图片描述

  2. mapper开启二级缓存
    在这里插入图片描述

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

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

刷新缓存:
在这里插入图片描述
注意,二级缓存实际放的是一级缓存,因此,只有当一级缓存的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属性名之间作一个映射关系。
    • 在这里插入图片描述
    • 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>
      <!--public User getByStep(Integer id);-->
      <select id="getByStep" resultMap="testMap">
      	select * from user where id=#{id}
      </select>
      <!--
      第一步:根据id查询出对应用户,将结果返回到testMap中
      第二步:在association,调用departmentMapper的getById方法,将第一步查询出来的dept_id传入,然后返回department对象放入结果集。
      最终结果:就是getByStep的返回类型User,其中Javabean的User中有个Department对象,查询出的部门也放入其中了
      -->
      
      分步查询每步都会发起一次查询语句,因此性能上可能会有问题。
      但是很多公司可能禁止多表联查,而且建立索引之后并不慢,
      而且resultMap支持延迟加载,可以实现分步查询的延迟(也就是说,如果用到了第二步的数据,才会进行第二步的查询)
      lazyLoadingEnabled=true
      aggressiveLazyLoading=false (侵入式懒加载,意思是如果是分步查询,那就将每一步的都加载出来,我们需要关闭这个功能)(有时候默认值就是我们想要的,但是也应该显示的指定我们想要的值,说明我们用到了,也避免版本更新带来的错误
    • collection,嵌套结果集,将部分结果封装为集合(测试时需要在dept中加上List<User>和getter/setter;)凡是column都是指数据字段,property都是指Javabean属性名
      • 查询部门的同时将所有用户用户返回(一对多)
      <resultMap type="xx.Department" id="testMap">
      	<id column="id" property="id"/>
      	<result column="name" property="name"/>
      	<!--指定property是Dept中的List名
      		ofType指集合内元素类型
      	-->
      	<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"/>
      	<!--
      	javaType指的是鉴别字段的类型
      	column是鉴别字段
      	-->
      	<discriminator javaType="string" column="gender">
      		<!--
      		value=鉴别字段的值
      		resuleType是封装结果的元素类型,rersultMap二选一
      		-->
      		<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>
      		<!--男生情况下没有association,因此不会查出部门-->
      		<case value="1" resultType="xx.User">
      			<!--将数据库的username赋给Javabean的email-->
      				<result column="username" property="email"/>
      		</case>
      	<discriminator>
      </resultMap>
      <!--public Department getByStep(Integer id);-->
      <select id="getByStep" resultMap="testMap">
      	select * from user where id=#{id}
      </select>
      

11. 运行原理(重要)

架构图

在这里插入图片描述

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对象

在这里插入图片描述

2. 获取sqlSession对象

在这里插入图片描述

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

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

在这里插入图片描述

4. 执行增删改查方法

在这里插入图片描述
在这里插入图片描述

总结

 * 	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);

如下图所示:

在这里插入图片描述


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)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值