Mybatis学习笔记,挺全的!

第一部分:自定义持久层框架

在该部分中主要学习到了以下几个关键知识点:
在这里插入图片描述

1.1 分析手动编写JDBC操作面临的问题

  1. 数据库连接频繁创建、销毁造成资源浪费,影响系统性能;
  2. sql语句硬编码,不易维护;
  3. 使用preparedStatement传递sql参数存在硬编码,不易维护;
  4. 对查询结果集解析以及封装成实体类存在硬编码,不易维护;

1.2 针对性的给出问题的解决思路

  1. 针对问题1,可以使用“享元模式”即“池化”技术,使用数据库连接池来维护数据库连接的创建、回收等;
  2. 针对问题2,可以将sql语句从代码中抽离出来,放到单独的xml配置文件中;
  3. 针对问题3和4,可以使用反射、内省等技术自动将查询结果集绑定到实体类上;

1.3 尝试编写自定义框架解决面临的问题

在这里插入图片描述

  1. 配置文件:分为Mybatis全局配置文件和mapper映射文件;
  2. Configuration:上述配置文件对应的Java对象;
  3. SqlSessionFactoryBuilder:拥有一个build()方法,它会将上述的配置文件字节流进行xml解析封装成Configuration对象,再将Configuration对象设置到new出来的SqlSessionFactory并返回;
  4. SqlSessionFactory:拥有一个openSession()方法,用来根据Configuration创建SqlSession对象;
  5. SqlSession:持有Executor的引用,封装了数据库的查询方法;
  6. Executor:真正去实现JDBC的CRUD操作;

上面自定义的框架会发现和工作中使用的Mybatis框架用法有点出入,这是因为考虑到:

  • 自定义框架使用中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方 法,
    关闭 sqlsession);
  • 自定义框架使用中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码;

因此可以针对xml文件定义对应的mapper接口,然后使用动态代理来生成具体的mapper实现类来帮我们自动完成上述操作。

第二部分:Mybatis相关概念

在这里插入图片描述

2.1 简介

Mybatis是一个半自动化的ORM(对象/关系映射)框架,它支持定制化SQL、存储过程以及高级映
射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的
XML或注解来配置和映射原生类型、接口和Java的POJO (Plain Old Java Objects,普通老式Java对 象)
为数据库中的记录。

2.2 历史

原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了
google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11
月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框
架包括SQL Maps和Data Access Objects(DAO)。

2.3 优势

Mybatis是一个半自动化的持久层框架,对开发人员开说,核心sql还是需要自己进行优化,sql和java编
码进行分离,功能边界清晰,一个专注业务,一个专注数据。
分析图示如下:
在这里插入图片描述
相比于Hibernate这种全自动化的ORM框架它更灵活、更轻量。

第三部分:Mybatis基本应用

在这里插入图片描述

3.1 入门

基本开发步骤:

①添加MyBatis的pom依赖
②创建user数据表
③编写User实体类
④编写映射文件UserMapper.xml
⑤编写核心文件SqlMapConfig.xml
⑥编写测试类

3.2 常用开发方式

3.2.1 传统开发方式

  1. 编写Dao接口;
  2. 实现Dao接口,和基本开发步骤类似,主要是通过statementId来定位sql并执行数据库操作;

3.2.2 代理开发方式

Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口
定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper 接口开发需要遵循以下规范:

  1. Mapper.xml文件中的namespace与mapper接口的全限定名相同;
  2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同;
  3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同;
  4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同;

第四部分:Mybatis配置文件深入

在这里插入图片描述

4.1 核心配置文件

SqlMapConfig.xml

4.1.1 层级关系

在这里插入图片描述

4.1.2 常用配置

1)environments标签
数据库环境的配置,支持多环境配置
其中,事务管理器(transactionManager)类型有两种:
JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作
用域。
MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生
命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因
此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。
其中,数据源(dataSource)类型有三种:
UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置
数据源,然后放置一个 JNDI 上下文的引用。
2)mapper标签
该标签的作用是加载映射的,加载方式有如下几种:
•使用相对于类路径的资源引用,例如:

•使用完全限定资源定位符(URL),例如:

•使用映射器接口实现类的完全限定类名,例如:

•将包内的映射器接口实现全部注册为映射器,例如:

3)Properties标签

实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的
properties文件,该标签必须放在配置文件顶部!
4)typeAliases标签
类型别名,是为Java 类型设置一个短的名字。配置方式有:

  • 单个配置,<typeAliases><typeAlias type="com.User" alias="user"></typeAlias></typeAliases>
  • 包配置,<typeAliases><package name="com.jarry.entity"></typeAlias></typeAliases> ,这种方式的别名为类名,不区分大小写

4.2 映射配置文件

xxxMapper.xml

4.2.1 动态sql之if标签

<select id="findByCondition" parameterType="user" resultType="user">
select * from User
<where>
<if test="id!=0">
and id=#{id}
</if>
<if test="username!=null">
and username=#{username}
</if>
</where>
</select>

4.2.2 动态sql之foreach标签

<select id="findByIds" parameterType="list" resultType="user">
select * from User
<where>
<foreach collection="list" open="id in(" close=")" item="id"
separator=",">
#{id}
</foreach>
</where>
</select>

4.2.3 动态sql之SQL片段抽取

<sql id="selectUser" select * from User</sql>

<select id="findById" parameterType="int" resultType="user">
<include refid="selectUser"></include> where id=#{id}
</select>
<select id="findByIds" parameterType="list" resultType="user">
<include refid="selectUser"></include>
<where>
<foreach collection="array" open="id in(" close=")" item="id"
separator=",">
#{id}
</foreach>
</where>
</select>

第五部分:Mybatis复杂映射开发

在这里插入图片描述

5.1 一对一查询

public class Order {
private int id;
private Date ordertime;
private double total;
//代表当前订单从属于哪一个客户
private User user;
} p
ublic class User {
private int id;
private String username;
private String password;
private Date birthday;
}

想要从数据库中查询出Order对象,而Oder对象持有一个User对象。

对应的mapper.xml如下:

<mapper namespace="com.jarry.mapper.OrderMapper">
<resultMap id="orderMap" type="com.jarry.domain.Order">
<result column="uid" property="user.id"></result>
<result column="username" property="user.username"></result>
<result column="password" property="user.password"></result>
<result column="birthday" property="user.birthday"></result>
</resultMap>
<select id="findAll" resultMap="orderMap">
select * from orders o,user u where o.uid=u.id
</select>
</mapper>

或者

<resultMap id="orderMap" type="com.jarry.domain.Order">
<result property="id" column="id"></result>
<result property="ordertime" column="ordertime"></result>
<result property="total" column="total"></result>
<!--该标签为单个对象封装标签,javaType表示实体该字段的类型-->
<association property="user" javaType="com.jarry.domain.User">
<result column="uid" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>

5.2 一对多查询

public class Order {
private int id;
private Date ordertime;
private double total;
//代表当前订单从属于哪一个客户
private User user;
} p
ublic class User {
private int id;
private String username;
private String password;
private Date birthday;
//代表当前用户具备哪些订单
private List<Order> orderList;
}

从数据库中查询User对象,该User对象持有一个Oder对象的list。

对应的mapper.xml如下:

<mapper namespace="com.jarry.mapper.UserMapper">
<resultMap id="userMap" type="com.jarry.domain.User">
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<!--该标签为集合封装标签, ofType表示该实体字段集合的泛型类型-->
<collection property="orderList" ofType="com.jarry.domain.Order">
<result column="oid" property="id"></result>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select *,o.id oid from user u left join orders o on u.id=o.uid
</select>
</mapper>

5.3 多对多查询

双向一对多,参考5.2。

第六部分:Mybatis注解开发

在这里插入图片描述

6.1 常用注解

@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装

6.2 一对一

Mapper接口方法写法如下:

@Select("select * from orders")
@Results({
@Result(id=true,property = "id",column = "id"),
@Result(property = "ordertime",column = "ordertime"),
@Result(property = "total",column = "total"),
@Result(property = "user",column = "uid",
javaType = User.class,
one = @One(select = "com.lagou.mapper.UserMapper.findById"))
})
List<Order> findAll();

6.3 一对多

Mapper接口方法写法如下:

public interface UserMapper {
@Select("select * from user")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "password",column = "password"),
@Result(property = "birthday",column = "birthday"),
@Result(property = "orderList",column = "id",
javaType = List.class,
many = @Many(select =
"com.lagou.mapper.OrderMapper.findByUid"))
})
List<User> findAllUserAndOrder();
} p
ublic interface OrderMapper {
@Select("select * from orders where uid=#{uid}")
List<Order> findByUid(int uid);
}

6.4多对多

双向一对多,参考6.3。

第七部分:Mybatis缓存

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

为了加快查询效率,Mybatis引入了缓存机制。

①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数
据结构(HashMap)用于存储缓存数据。不同的sqlSession
之间的缓存数据区域(HashMap)是互相不影响的。
②、二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession
可以共用二级缓存,二级缓存是跨SqlSession的。

缓存查询顺序:二级缓存------>一级缓存------->数据库

7.1 一级缓存

  • 默认开启。
  • SqlSession级别,不同的SqlSession不共享缓存。
  • 底层是一个HashMap结构。
  • 一级缓存的维护是在Executor 类中执行。
  • 执行SqlSession的commit或者clear会导致一级缓存清空,并转存到二级缓存。
  • 在sql标签上配置flushCache=true,可以使一级缓存失效。

7.2 二级缓存

  • 默认不生效,需要在mapper.xml使用标签,或者在mapper接口上添加@CacheNamespace注解使其生效。
  • Mapper/Namespace级别,和SqlSession无关,不同的SqlSession可以共享同一个Mapper/Namespace的二级缓存。
  • 底层也默认是HashMap结构
  • 开启二级缓存后pojo要实现Serializable接口,使其可以持久化到其他存储介质。
  • 执行增删改,并且提交SqlSession以后会清空对应的二级缓存。

7.3 二级缓存接入Redis

Mybatis提供了一个Cache接口,可以实现自己的缓存策略。

基于HashMap实现换粗无法适用集群或分布式环境,因为他是JVM进程级别的。因此需要整合分布式缓存框架,如Redis。

mybatis提供了一个针对cache接口的redis实现类。

使用步骤:

  1. 添加如下maven依赖:
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency
  1. 在mapper.xml加入二级缓存开启标签或者在mapper接口上添加@CacheNamespace(implementation = RedisCache.class)注解
  2. 添加redis配置文件:

redis.properties

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

第八部分:Mybatis插件

在这里插入图片描述

8.1 Mybatis插件介绍

在这里插入图片描述
mybatis提供四大扩展点:

  • 执行器Executor (update、query、commit、rollback等方法);
  • SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
  • 参数处理器ParameterHandler (getParameterObject、setParameters方法);
  • 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);

8.2 插件原理

  • 基于JDK动态代理;
  • 插件会被保存到interceptorChain;
  • 在执行SQL时,需要先通过DefaultSqlSessionFactory 创
    建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor实例创建完毕后,
    MyBatis会通过JDK动态代理为实例生成代理类。这样,插件逻辑即可在 Executor相关方法被调用前执
    行;

8.3 自定义插件

Intercepts ({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这
个拦截器
@Signature (type = StatementHandler .class , //这是指拦截哪个接口
method = "prepare",//这个接口内的哪个方法名,不要拼错了
args = { Connection.class, Integer .class}),//// 这是拦截的方法的入参,按顺序写到这,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的
})
public class MyPlugin implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
// //这里是每次执行操作的时候,都会进行这个拦截器的方法内
Override
public Object intercept(Invocation invocation) throws Throwable {
//增强逻辑
System.out.println("对方法进行了增强....");
return invocation.proceed(); //执行原方法
}
/**获取配置文件的属性**/
//插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
Override
public void setProperties(Properties properties) {
System.out.println("插件配置的初始化参数:"+properties );
}
}

sqlMapConfig.xml

<plugins>
<plugin interceptor="com.lagou.plugin.MySqlPagingPlugin">
<!--配置参数-->
<property name="name" value="Bob"/>
</plugin>
</plugins>

第九部分:Mybatis架构原理

在这里插入图片描述

9.1 总体架构

在这里插入图片描述
Mybatis的功能架构分为三层:
(1) API接口层:提供给外部使用的接口 API,开发人员通过这些本地API来操纵数据库。接口层一接收到
调用请求就会调用数据处理层来完成具体的数据处理。
MyBatis和数据库的交互有两种方式:
a. 使用传统的MyBati s提供的API ;
b. 使用Mapper代理的方式
(2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根
据调用的请求完成一次数据库操作。
(3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是
共 用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑

9.2 主要组件

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

9.3 总体流程

(1) 加载配置并初始化
触发条件:加载配置文件
配置来源于两个地方,一个是配置文件(主配置文件conf.xml,mapper文件*.xml),—个是java代码中的 注
解,将主配置文件内容解析封装到Configuration,将sql的配置信息加载成为一个mappedstatement 对
象,存储在内存之中
(2) 接收调用请求
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。

(3) 处理操作请求
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程:
(A) 根据SQL的ID查找对应的MappedStatement对象。
(B) 根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
(C) 获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
(D) 根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理
结果。
(E) 释放连接资源。
(4) 返回处理结果
将最终的处理结果返回。

第十部分:Mybatis源码剖析

10.1 延迟加载

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。

  • 优点:
    先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表
    速度要快。
  • 缺点:
    因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时
    间,所以可能造成用户等待时间变长,造成用户体验下降。
  • 在多表中:
    一对多,多对多:通常情况下采用延迟加载
    一对一(多对一):通常情况下采用立即加载
  • 注意:
    延迟加载是基于嵌套查询来实现的

10.1.1 局部延迟加载

在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策
略。

<!-- 开启一对多 延迟加载 -->
<resultMap id="userMap" type="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<!--
fetchType="lazy" 懒加载策略
fetchType="eager" 立即加载策略
-->
<collection property="orderList" ofType="order" column="id"
select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy">
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM `user`
</select>

10.1.2 全局延迟加载

在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。

<settings>
<!--开启全局延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>

局部配置优先于全局配置。

10.2 延迟加载原理

  • 基于动态代理技术,主要使用:Javassist,Cglib实现。
  • 为pojo生成代理,在调用字段的getter方法时,调用代理方法完成sql的延迟执行。

第十一部分:设计模式

在这里插入图片描述
Mybati s至少用到了以下的设计模式:

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

11.1 Builder模式

Builder模式的定义是"将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表
示。”,它属于创建类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范
围,就可以使用工厂模式和Builder模式,相对于工厂模式会产出一个完整的产品,Builder应用于更加
复杂的对象的构建,甚至只会构建产品的一个部分,直白来说,就是使用多个简单的对象一步一步构建
成一个复杂的对象

Mybatis中的体现:
在这里插入图片描述
SqlSessionFactory 的构建过程:
Mybatis的初始化工作非常复杂,不是只用一个构造函数就能搞定的。所以使用了建造者模式,使用了
大 量的Builder,进行分层构造,核心对象Configuration使用了 XmlConfigBuilder来进行构造

在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的
MybatisMapConfig.xml 和所有的 *Mapper.xml 文件,构建 Mybatis 运行的核心对象 Configuration
对 象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。

11.2 工厂模式

在Mybatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂
模式。
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创
建型模式。
在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建
其他类的实例,被创建的实例通常都具有共同的父类

Mybatis 体现:

Mybatis中执行Sql语句、获取Mappers、管理事务的核心接口SqlSession的创建过程使用到了工厂模
式。
有一个 SqlSessionFactory 来负责 SqlSession 的创建
在这里插入图片描述
可以看到,该Factory的openSession ()方法重载了很多个,分别支
持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象。

11.3 代理模式

代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式 的
英文叫做Proxy,它是一种对象结构型模式,代理模式分为静态代理和动态代理,我们来介绍动态代 理。

Mybatis中实现:

代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper.java接
口,不需要实现,由Mybati s后台帮我们完成具体SQL的执行。
当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又
会调用 mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:

public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new
ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} p
ublic Class<T> getMapperInterface() {
return mapperInterface;
} p
ublic Map<Method, MapperMethod> getMethodCache() {
return methodCache;
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new
Class[] { mapperInterface },
mapperProxy);
} p
ublic T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession,
mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}

在这里,先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象,然后调用
newInstance(MapperProxy mapperProxy)生成代理对象然后返回,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。通
过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给
MapperProxy.invoke方法,而该方法则会调用后续的
sqlSession.cud>executor.execute>prepareStatement 等一系列方法,完成 SQL 的执行和返回。


文章来源:拉勾教育Java高新训练营15期

posted @ 2021-05-19 15:31  墨、鱼的blog  阅读(235)  评论(0编辑  收藏  举报