Mybatis总结

mybatis学习笔记

mybatis解决JDBC的问题:

1.数据库连接创建, 释放频繁造成系统资源的浪费

2.sql语句在代码中硬编码, 造成代码不易维护, 实际使用中sql变化比较大, 改变sql需要改变java代码.

3.使用preparedStatement向有占位符传参存在硬编码

4.对结果集封装也存在硬编码.

解决: mybatis 提供连接池, 通过配置文件解决硬编码, 通过反射内省自动封装结果集

自定义框架设计:

大概流程:

1.加载xml配置文件,封装成configuration对象(封装了数据库连接池的相关信息,以及所有配置文件的信息)

2.将配置文件中,以namespace.statementId为唯一表示,存放到map中,value为具体需要执行的,自己封装的mappedStatement(封装了数据库的连接)

3.在执行相对应的方法,通过传入的namespace.statementId获取对应的mappedStatement,注册驱动,获取连接,解析sql,填充参数,最后执行. 或者使用mapper的动态代理对象,直接调用相对应的方法执行.这里需要满足statementId与接口的方法名相同

框架核心所运用的设计模式:

构建者设计模式( 当一个复杂对象在初始化过于复杂时,我们使用该设计模式,一步一步构建小对象,最后构成复杂对象)

工厂设计模式(创建对象的工作在工厂方法代码里面,根据传入参数的不同,获取不通的对象)

代理模式(对象的方法执行由代理对象完成)

JDK动态代理:由代理模式衍生出来. 需要满足, 代理的对象实现了接口. 最终生成的代理对象,每次执行方法,都会去调用invoke(),这样就可以在方法执行前后,我们对方法进行增长.

mybatis动态sql:

mybatis动态sql是当我们的业务逻辑比较复杂时,需要将sql动态变化,根据我们传入的实体类或者参数,使用不同的sql语言进行查询.

​ 动态sql:

  1. if 语句 (简单的条件判断)

  2. choose(when, otherwize)

  3. trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)

  4. where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or )

  5. set (用于update)

  6. foreach (用于 in语句)

    原理:

    mybatis将xml的sql语句封装成一个个节点,每个节点都是一种动态sql类型的描述,例如 IfSqlNode, 每个动态sql有多个SqlNode构成,都需要实现内部定义的抽象方法apply(), 在sql执行的时候, 这个apply方法会依次执行子节点的apply(), 这样递归执行下去, 构建动态sql中prepareStatement的参数, 并保存最终生成sql的StringBuilder对象. 最终执行 sql

mybatis映射:

一对一:

创建映射实体类:

1
2
3
4
5
6
7
8
9
10
11
12
public class Order {
private int id;
private Date ordertime;
private double total;
private User user;
}
public class User {
private int id;
private String username;
private String password;
private Date birthday;
}

创建OrderMapper接口

1
2
3
public interface OrderMapper {
List<Order> findAll();
}

配置xml文件

1
2
3
4
5
6
7
8
9
10
11
<mapper namespace="com.lagou.mapper.OrderMapper">
<resultMap id="orderMap" type="com.lagou.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>

或者

1
2
3
4
5
6
7
8
9
10
11
<resultMap id="orderMap" type="com.lagou.domain.Order">
<result property="id" column="id"></result>
<result property="ordertime" column="ordertime"></result>
<result property="total" column="total"></result>
<association property="user" javaType="com.lagou.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>
一对多(多对多类似):

修改实体

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Order {
private int id;
private Date ordertime;
private double total;
private User user;
}
public class User {
private int id;
private String username;
private String password;
private Date birthday;
private List<Order> orderList;
}

创建UserMapper接口

1
2
3
public interface UserMapper {
List<User> findAll();
}

配置XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<mapper namespace="com.lagou.mapper.UserMapper">
<resultMap id="userMap" type="com.lagou.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>
<collection property="orderList" ofType="com.lagou.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>

mybatis注解开发(代码不做演示)

@Insert 新增
@Update 更新
@Delete 删除
@Select 查询
@Result 结果映射
@Results 与 @Result 封装多个结果集
@One 一对一
@Many 一堆多或者多对多

mybatis缓存(一级缓存与二级缓存):

存储结构: 一级缓存跟二级缓存底层的数据结构都是hashMap, 其中key是mybatis自己封装的CacheKey是, 生成方式主要由MappedStatement RowBounds(分页相关对象) BoundSql 构成. 一级缓存的value主要是保存sql查询之后的结果(包括具体的对象), 重复执行玩一次sql,都获取到同一个对象. 二级缓存缓存的是结果的数据(不能具体到对象), 即获取到不同的对象,但是对象的值是相等的. 如果二级缓存配置的是redisCache,则使用到的是redis中的哈希数据结构.

​ 范围: 一级缓存是针对同一个sqlSession而言,同一个sqlSession查询的内容,缓存共享. 二级缓存是针对整个namespace, 多个sqlSession共享同一个二级缓存

​ 失效场景: 一级缓存和二级缓存每次查询都会进行数据的缓存. 在进行 insert update delet等数据库写操作的时候会清空缓存. 除此之外, 一级缓存,可以手动调用缓存的 clearCache方法清空缓存, 后续有查询操作缓存可以继续使用; 调用 sqlSession.close()方法之后,也会清空缓存,后续缓存不可用.

mybatis插件:

mybatis四大组件(Executor, StatementHandler, ParameterHandler, ResultSetHandler) 允许对其内部的方法进行拦截, mybatis插件的原理就是拦截器对这些对象内部方法进行拦截.

​ 具体原理:

在四大组件的对象创建出来后, 每个对象都不是直接返回,而是优先经过interceptorChain.pluginAll(parameterHandler)

获取到所有的拦截器,调用interceptorChain.pluginAll(parameterHandler)

为目标对象利用动态代理创建代理对象; 面向切面的方式,拦截到每一个需要拦截的方法,加入业务逻辑,以达到插件的目的.

mybatis架构原理:

分三层:

(1) API接口层:对外提供使用的接口API, 开发人员通过这些API操作数据库. 接口层收到调用请求就会调用数据处理曾来完成数据的处理

Mybatis提供两种方式调用API:

a.使用传统方式(传入namespace.方法id)

b.通过Mapper代理方式(getMapper())

(2) 数据处理层: 复制具体的sql操作, sql解析, sql执行,以及执行结果的映射.

(3) 基础支撑层: 负责最基础的功能支撑,包括连接管理, 事务管理, 配置加载, 缓存处理.

主要构建及其相互关系
组件 描述
SqlSession 作为mybatis工作的主要顶层api,表示和数据库交互的会话,完成数据库操作
Executor 执行器,是调度的核心,负责sql语句的生成和查询缓存
StatementHandler 封装了JDBC Statement操作,设置参数已经封装结果为list
ParameterHandler 负责设置参数,被StatementHandler调用
ResultSetHandler 负责封装结果, 被StatementHandler调用
TypeHandler 负责数据库类型与java类型映射
MappedStatement 封装了sql语句的节点
SqlSource 根据用户传递的parameterObject,动态生成sql语句,将信息封装到BoundSql中
BoundSql 表示动态生成的sql语句已经相对应的参数信息
mybatis执行器:

最基本的有三种:

​ SimpleExecutor, ReuseExecutor, BathExecutor

​ 严格来讲在mybatis源码中还有 BaseExecutor, CachingExecutor, ClosedExecutor(方法都抛出异常)

​ 区别:

​ SimpleExecutor 每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

​ ReuseExecutor 执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。

​ BathExecutor 执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

懒加载:

仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。

​ 原理: 使用CGLIB动态代理,当调用目标方法时,实际上调用的是动态代理对象的invoke方法, 在法相目标方法返回的是null值,那么久会单独执行一次事先保存好的关联相对应对象的sql,将执行结果按照配置文件设置到对一个的字段上,接着完成 相对应的逻辑.