近期深入学习我们工作中基本都会使用到的Mybatis框架,从mybatis的框架设计上收获良多,下面用一篇简单易懂的文章分享一下学习心得,文末会结合阅读的源码结构进行模拟手写一个简易版的mybatis调用代码加深理解。
一.什么是Mybatis?
根据官网定义:mybatis – MyBatis 3 | Introduction
MyBatis 是一款优秀的持久层ORM框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
二.Mybatis作为ORM框架,和传统的JDBC有什么区别?
ORM全称是Object-Relaction-Mapper,顾名思义就是将对象-数据库关系映射,就是通过Java的POJO类(类似Model层的VO)直接与数据库的表/字段建立关联关系,让使用者可以直接通过Java去直接直观地操作数据库表/字段。
ORM框架是为了解决JDBC一系列存在问题而诞生,那么传统JDBC存在哪些弊端呢?
(1)JDBC底层没有使用连接池,操作数据库需要不断创建连接,关闭连接,会带来比较大的性能消耗;
(2)JDBC的sql在Java原生方式编写,如果改动需要重新编译不利于系统维护;
(3)JDBC使用PrepareStatement预编译方式,对变量赋值时需要维护索引号,需要使用setInt、setString等硬编码写法,不利于拓展;
(4)JDBC返回的结果集ResultSet解析结果时,也不得不进行硬编码。
而Mybatis针对这些问题都封装了一系列工具类来解决,使得使用者不需要过多关注JDBC层的连接和硬编码问题。
三.快速开始
下面以我自己使用的配置,对mybatis的简单使用进行介绍,更多使用可以查阅官网。
Maven配置添加mybatis依赖:
org.mybatis
mybatis
3.4.1
resources目录下mybatis-config.xml全局文件:
resources目录下config.properties文件:
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mysql
username=root
password=root
resources/com/test/mapper目录下的BlogMaper.xml文件:
model目录下Blog.java:
package com.test.model;
public class Blog {
private Integer id;
private String name;
private Integer amount;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
@Override
public String toString() {
return "Blog{" +
"id=" + id +
", name='" + name + '\'' +
", amount=" + amount +
'}';
}
}
Main方法:
public class TestMybatis {
public static void main(String[] args) throws IOException {
//解析配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
Properties properties = new Properties();
properties.load(Resources.getResourceAsStream("config.properties"));
//得到SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is, properties);
//得到SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过namesace获取
Blog blog = (Blog) sqlSession.selectOne("com.test.model.BlogMapper.selectTestById", 2);
System.out.println(blog);
}
通过上面简单的配置,就可以通过mybaits对数据库进行查询等操作了。从上述配置可以看到,mybaits对于XML配置模式的整个调用过程,可以理解为以SqlSession作为中转,向上解析配置,向下连接JDBC进行数据库连接调用。
四.Mybatis调用简图和简要分析
Mybatis相当于在Java和Mysql等数据库之间作为桥梁,Mybatis内部会提前解析一系列配置信息。Java通过简单的配置请求到Mybatis,Mybatis内部通过对请求的解析,最终还是会使用原生JDBC对数据库进行操作。
那么我们就可以对Mybatis内部设计做一个简单的分层猜想,SqlSession作为中间层分别向上和向下连接配置Configuration及JDBC执行器Executor,负责根据不同请求情况在Configuration中找到合适的执行语句,交给Executor去连接操作JDBC,最终返回结果。
(1)首先,Mybatis对全局配置文件的解析,主要包括mybatis-config.xml,Mapper.xml等配置文件的解析。
核心入口在:
//得到SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is, properties);
这个入口会使用xpath对mybatis-config.xml和mapper.xml等配置文件进行的解析,将每个节点的配置封装为一个个的Configuration内部属性当中。
org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)
-->org.apache.ibatis.builder.xml.XMLConfigBuilder.parser
----->org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration
------->org.apache.ibatis.builder.xml.XMLConfigBuilder.propertiesElement
------->org.apache.ibatis.builder.xml.XMLConfigBuilder.environmentsElement
------->org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement
---------->org.apache.ibatis.session.Configuration.setEnvironment
对于mapper.xml扫描在上面org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement步骤中,执行流程如下:
org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement
----->org.apache.ibatis.builder.xml.XMLMapperBuilder.parse
------>org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement
--------->org.apache.ibatis.builder.xml.XMLMapperBuilder.parameterMapElement
--------->org.apache.ibatis.builder.xml.XMLMapperBuilder.resultMapElements
--------->org.apache.ibatis.builder.xml.XMLMapperBuilder.sqlElement
--------->org.apache.ibatis.builder.xml.XMLMapperBuilder.buildStatementFromContext
------------>org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode
------>org.apache.ibatis.builder.xml.XMLMapperBuilder.bindMapperForNamespace
------>org.apache.ibatis.builder.xml.XMLMapperBuilder.parsePendingResultMaps
------>org.apache.ibatis.builder.xml.XMLMapperBuilder.parsePendingCacheRefs
------>org.apache.ibatis.builder.xml.XMLMapperBuilder.parsePendingStatements
--------->org.apache.ibatis.session.Configuration.addMappedStatement
上述源码的调用过程,核心目的就是解析配置文件,将配置文件的信息封装到Configuration当中。
(2)SqlSession调用Executor
从源码中我们可以看到,sqlSession是一个接口,定义了诸多操作数据库的方法,同时还提供了获取Configuration配置,以及通过@Mapper获取Mapper接口代理对象的方法:
package org.apache.ibatis.session;
import java.io.Closeable;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
public interface SqlSession extends Closeable {
T selectOne(String statement);
T selectOne(String statement, Object parameter);
List selectList(String statement);
List selectList(String statement, Object parameter);
List selectList(String statement, Object parameter, RowBounds rowBounds);
Map selectMap(String statement, String mapKey);
Map selectMap(String statement, Object parameter, String mapKey);
Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
Cursor selectCursor(String statement);
Cursor selectCursor(String statement, Object parameter);
Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List flushStatements();
void close();
void clearCache();
//获取配置和根据Mpper注解获取代理Mapper对象
Configuration getConfiguration();
T getMapper(Class type);
Connection getConnection();
}
我们进一步查看其实现子类DefaultSqlSession.java:
查看DefaultSqlSession.java中的Executor的属性对应子类实现:
可以看到sqlSession的实现当中是封装了Configuration和Executor的,而Executor就是对JDBC的执行器。也就是说sqlSession实际上并不直接操作数据库,而是在配置层和JDBC执行层进行了一层包装和转发而已。这也印证了上面的猜测,但是实际上每一层的实现逻辑还是很复杂的,考虑的场景很多,包括缓存,事务,动态sql,以及Spring整合点等等。
五.Mybatis使用了哪些设计模式?
(1)Builder 模 式 : 最常看到的设计模式了,核心思想都是使用内部Builder类对传入的参数构建出一个目标对象返回。例 如 SqlSessionFactoryBuilder 、 XMLConfigBuilder 、 XMLMapperBuilder 、XMLStatementBuilder、CacheBuilder;
(2)工厂模式:例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
(3)单例模式:例如ErrorContext和LogFactory;
(4)代理模式:Mybatis实现的核心,比如MapperProxy、ConnectionLogger用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;
(5)组合模式:例如SqlNode和各个子类ChooseSqlNode等;
(6)模 板 方 法 模 式 : 例 如 BaseExecutor 和 SimpleExecutor , 还 有 BaseTypeHandler 和 所 有 的 子 类 例 如IntegerTypeHandler;
(7)适配器模式:例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
(8)装饰者模式:例如Cache包中的cache.decorators子包中等各个装饰者的实现;
(9)迭代器模式:例如迭代器模式PropertyTokenizer;
.......
六.手写模拟实现一个简易Mybatis,支持XML和注解方式查询
文章单独一篇展开,链接为:手写模拟实现一个简易Mybatis,支持XML和注解方式查询_喜欢火影的木易杨的博客-CSDN博客