MyBatis 总结

什么是MyBatis

  1. Mybatis 是一个半 自动的ORM(Object Relational Mapping 对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。
  2. 它可以使用 XML 或 注解 来 配置和映射 原生信息,将 POJO 映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  3. 通过 xml 文件或注解的方式 将 需要执行的各种Mapper Statement(四个主要的statement: insert select update delete) 配置起来,并将java 对象和 statement 中 sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行,并将结果映射为 java 对象并返回。(从执行 sql 到返回 result 的过程)。

Mybatis的优点

  1. 基于 SQL 语句编程,相当灵活。不会对应用程序或者数据库的现有设计造成任 何影响,SQL 写在 XML 里,解除 sql与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
  2. 与 JDBC 相比,减少了50%以上的代码量,消除了 JDBC 大量冗余的代码,不 需要手动开关连接; 很好的与各种数据库兼容(因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持)。
  3. 能够与 Spring 很好的集成;
  4. 提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射 标签,支持对象关系组件维护。

Mybatis的缺点

  1. SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL 语句的功底有一定要求。
  2. SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

Mybatis 的工作流程

MyBatis 的一个完整的工作流程需要三要素:1、数据源,即数据库信息(驱动、地址、用户名、密码);2、SQL语句;3、具体操作(Connection、PrepareStement、ResultSet)

Mybatis核心类:

SqlSessionFactory

每个基于 MyBatis 的应用都是以一个SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或通过Java的方式构建出SqlSessionFactory 的实例。SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,建议使用单例模式或者静态单例模式。一个SqlSessionFactory对应配置文件中的一个环境(environment),如果你要使用多个数据库就配置多个环境分别对应一个SqlSessionFactory。

SqlSession

SqlSession是一个接口,它有2个实现类,分别是DefaultSqlSession(默认使用)以及SqlSessionManager(弃用)。SqlSession通过内部存放的执行器(Executor)来对数据进行CRUD。此外SqlSession不是线程安全的,因为每一次操作完数据库后都要调用close对其进行关闭,官方建议通过try-finally来保证总是关闭SqlSession。

Executor

Executor(执行器)接口有两个实现类,其中BaseExecutor有三个继承类分别是BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statement,跟Simple的唯一区别就是内部缓存statement),SimpleExecutor(默认,每次都会创建新的statement)。以上三个就是主要的Executor。通过下图可以看到Mybatis在Executor的设计上面使用了装饰器模式,我们可以用CachingExecutor来装饰前面的三个执行器目的就是用来实现缓存。
在这里插入图片描述

MappedStatement

MappedStatement就是用来存放我们SQL映射文件中的信息包括sql语句,输入参数,输出参数等等。一个SQL节点对应一个MappedStatement对象。MappedStatement与Mapper配置文件中的一个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中,主要用途是描述一条SQL语句。它会存储在Configuration对象的mappedStatements属性中,mappedStatements 是一个HashMap,存储时key = 全限定类名 + 方法名,value = 对应的MappedStatement对象。

Mybatis工作流程

在这里插入图片描述

  1. 生成SqlSessionFactory

SqlSessionFactory需要XMLconfigBuilder对象支持。Mybatis的几乎所有配置文件信息都是存储在由XMLConfigBuilder构建的Configuration对象中。它会把配置文件全部转成JAVA对象
在这里插入图片描述基本上配置文件中的每个标签,如:databaseIdProvider, 都有一个JAVA对象与之对应。Configuration对象构建成功后,通过parse()方法,返回SqlSessionFactory对象,注意这里返回的是DefaultSqlSessionFactory,它是SqlSessionFactory接口的一个实现类。
在这里插入图片描述
在这里插入图片描述

  1. SqlSession的对象的创建

由于前面所创建的SqlSessionFactory是一个接口,因此SqlSession对象的创建交给了SqlSessionFactory的实现类DefalutSqlSessionFactory完成。
在这里插入图片描述
在这里插入图片描述
这里我们需要重点关注的是Executor对象,这是SqlSession对象去访问数据库的执行器,每一个SqlSession对象都维持着这样一个Executor。到此SqlSession对象创建完成,可以开始对数据库进行访问了。

  1. SqlSession的执行过程

由于SqlSession同样是一个接口,因此具体的任务交给它的实现类DefaultSqlSession完成,这里同样用到了Configuration对象的信息,因为我们xxxMapper.xml中的select delete update insert 标签都被解析成Mapper Statement封装在了Configuration。
在这里插入图片描述
可以发现,这里使用了MapperProxyFactory,因为我么定义的Mapper文件都是接口,没有具体的实现,这里使用的是JDK的动态代理为接口生成代理类,最后在exucte()方法中执行的SQL。
在这里插入图片描述

MyBatis初始化时对接口的处理

MapperRegistry是Configuration中的一个属性,又是Configuration对象,很重要。它内部维护一个HashMap用于存放mapper接口的工厂类,每个接口对应一个工厂类。mappers标签中可以配置接口的包路径,或者某个具体的接口类。
当解析mappers标签时,它会判断解析到的是mapper配置文件时,会再将对应配置文件中的增删改查标签一 一封装成MappedStatement对象,存入mappedStatements中。当判断解析到接口时,会创建此接口对应的MapperProxyFactory对象,存入HashMap中,key = 接口的字节码对象,value = 此接口对应的MapperProxyFactory对象。
在这里插入图片描述

操作mapper接口过程

a、调用getMapper()方法,最终执行mapperProxyFactory.newInstance(sqlSession)方法创建代理类MapperProxy;
b、比如当我们调用mapper.getUser()方法的时候,就会去执行MapperProxy代理类的invoke()方法;
c、判断mapper接口是否有实现类,显然我们没有实现类,则调用cacheMapperMethod()方法去缓存中获取要代理的方法method;
d、进入cacheMapperMethod()方法先去查找缓存中有没有,没有的话将mapper配置文件中配置的SQL语句和对应的mapper接口方法进行关联并放入map缓存中,后期直接走缓存了,最后执行execute()方法;
e、执行execute()方法,最终调用selectOne()方法;
f、进入selectOne()方法,底层还是查询所有的,但是取第一个,查询多个的话会抛出异常;
g、进入selectList()方法,调用getMapperStatement()方法获取对应的SQL语句;
h、执行query()方法进行查询,判断如果开启了二级缓存并且配置了二级缓存存储介质(Redis,EhCache…)则先走二级缓存中查询数据,第一次查询是没有缓存数据的,则刷新缓存配置,清除缓存。
i、二级缓存(sessionFactory)中没有查询到数据,就回去执行BaseExecutor去查询 HashMap一级缓存中(sqlSession)是否有缓存数据,一级缓存(PerpetualCache)存放在内存中的,同理也是没有的,最后查询数据库DB
j、将从数据库查询出来的数据缓存到一级缓存中,再把一级缓存中的数据同步到二级缓存,添加到二级缓存之前先添加到getTransactionalCache的entritiesToAddOnCommit的map集合中临时缓存起来;
k、调用executor.close()方法循环迭代TransactionCache,最后将临时map缓存数据提交到二级缓存中,如果事务回滚,则会将缓存数据清除掉
l、再次查询的话,就有缓存了,会直接查询到缓存数据返回,不会去查询数据库DB

MyBatis中使用到的设计模式

1、Builder模式:
例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
2、工厂模式:
例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
单例模式:例如ErrorContext和LogFactory;
3、代理模式:Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
4、组合模式:例如SqlNode和各个子类ChooseSqlNode等;
5、模板方法模式: 例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;
6、适配器模式: 例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
7、装饰者模式: 例如cache包中的cache.decorators子包中等各个装饰者的实现;
8、迭代器模式: 例如迭代器模式PropertyTokenizer;

Mybatis 为什么不要用二级缓存

mybatis的二级缓存是基于application为生命周期的,范围是按照每个namepace一个缓存来存贮和维护,同一个namespace放到一个缓存对象中,当这个namaspace中执行了!isselect语句的时候,整个namespace中的缓存全部清除掉。首先不管多表操作写到那个namespace下,都会存在某个表不在这个namespace下的情况。
例如两个表:role和user_role,如果我想查询出某个用户的全部角色role,就一定会涉及到多表的操作。

select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}

像上面这个查询,不管是写到RoleMapper.xml还是UserRoleMapper.xml,或者是一个独立的XxxMapper.xml中。如果使用了二级缓存,都会导致上面这个查询结果可能不正确。如果你正好修改了这个用户的角色,上面这个查询使用缓存的时候结果就是错的。
Mybatis 的插件运行原理,以及如何编写一个插件
Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、
StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代
理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种
接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke()
方法,当然,只会拦截那些你指定需要拦截的方法。
编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给
插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文
件中配置你编写的插件。例如PageHelper插件原理:
PageHelper源码
@Intercepts(@Signature(type = Executor.class, method = “query”, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageHelper implements Interceptor {
//sql工具类
private SqlUtil sqlUtil;
//属性参数信息
private Properties properties;
//配置对象方式
private SqlUtilConfig sqlUtilConfig;
//自动获取dialect,如果没有setProperties或setSqlUtilConfig,也可以正常进行
private boolean autoDialect = true;
//运行时自动获取dialect
private boolean autoRuntimeDialect;
//多数据源时,获取jdbcurl后是否关闭数据源
private boolean closeConn = true;
//缓存
private Map<String, SqlUtil> urlSqlUtilMap = new ConcurrentHashMap<String, SqlUtil>();
private ReentrantLock lock = new ReentrantLock();
// …
}
拦截了Executor接口,实现了Interceptor接口

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值