在讲解MyBatis之前,先说下传统JDBC连接数据库的弊端:

1.JDBC底层没有实现连接池,从而导致操作数据库需要频繁的创建和释放,影响性能;

2.JDBC的代码散落在Java代码中,如果需要修改SQL语句,需要重新编译Java类;

3.使用PreparedStatement设置参数繁,占位符和参数需要一一对应;

4.处理返回的结果集解析也很麻烦。

所以,在实际开发中,基本不会使用原生的JDBC来操作数据库。

然后,说一下Hibernate和MyBatis的区别,现在互联网公司基本上都是以MyBatis为主,因MyBatis能够书写比较复杂的SQL语句,比较灵活。

MyBatis的核心概念

类名 描述
Configuration 描述mybatis-config.xml全局配置关系类
SqlSessionFactory Session管理工厂接口
SqlSession SqlSession接口。 SqlSession中提供了操作数据库的方法,完成一次数据库的访问和结果的映射.不是线程安全
Executor 执行器。SqlSession通过执行器操作数据库,调用StatementHandler房屋数据库,并缓存查询结果
MappedStatement 底层封装对象。对操作数据库存储封装,包括sql语句、输入输出参数
StatementHandler 具体操作数据库相关的handler接口
ResultSetHandler 具体操作数据库返回结果的handler接口

下面我们新建一个项目,

首先创建一个表,并插入数据:

CREATE TABLE STUDENTS
(
stud_id int(11) NOT NULL AUTO_INCREMENT,
name varchar(50) NOT NULL,
email varchar(50) NOT NULL,
dob date DEFAULT NULL,
PRIMARY KEY (stud_id)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = UTF8; insert into students(stud_id, name, email, dob)
values (1, 'Student1', 'student1@gmail.com', '1990-06-25');
insert into students(stud_id, name, email, dob)
values (2, 'Student2', 'student2@gmail.com', '1990-06-25');

新建一个Student类:

package com.mybatis3.domain;

import java.util.Date;

public class Student {
private Integer studId;
private String name;
private String email;
private Date dob; public Student() { } public Student(Integer studId) {
this.studId = studId;
} public Student(Integer studId, String name, String email, Date dob) {
this.studId = studId;
this.name = name;
this.email = email;
this.dob = dob;
} @Override
public String toString() {
return "Student [studId=" + studId + ", name=" + name + ", email="
+ email + ", dob=" + dob + "]";
}
// todo getter and setter
}

新建一个mapper类:

import java.util.List;

import com.mybatis3.domain.Student;

public interface StudentMapper {
Student findStudentById(Integer id);
}

新建Service类:

public class StudentService {
private Logger logger = LoggerFactory.getLogger(getClass()); public Student findStudentById(Integer studId) {
logger.debug("Select Student By ID :{}", studId);
SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.findStudentById(studId);
//return sqlSession.selectOne("com.mybatis3.StudentMapper.findStudentById", studId);
} finally {
sqlSession.close();
}
}
}

新建一个SqlSessionFactory工具类:

package com.mybatis3.util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties; import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class MyBatisSqlSessionFactory {
private static SqlSessionFactory sqlSessionFactory; private static final Properties PROPERTIES = new Properties(); static {
try {
InputStream is = DataSourceFactory.class.getResourceAsStream("/application.properties");
PROPERTIES.load(is);
} catch (IOException e) {
e.printStackTrace();
}
} public static SqlSessionFactory getSqlSessionFactory() {
if (sqlSessionFactory == null) {
try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
throw new RuntimeException(e.getCause());
}
}
return sqlSessionFactory;
} public static SqlSession getSqlSession() {
return getSqlSessionFactory().openSession();
} public static Connection getConnection() {
String driver = PROPERTIES.getProperty("jdbc.driverClassName");
String url = PROPERTIES.getProperty("jdbc.url");
String username = PROPERTIES.getProperty("jdbc.username");
String password = PROPERTIES.getProperty("jdbc.password");
Connection connection = null;
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
throw new RuntimeException(e);
}
return connection;
}
}

编写StudentMapper.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"> <mapper namespace="com.mybatis3.mappers.StudentMapper">
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id"/>
<result property="name" column="name"/>
<result property="email" column="email"/>
<result property="dob" column="dob"/>
</resultMap> <select id="findStudentById" parameterType="int" resultType="Student">
select stud_id as studId, name, email, dob
from Students
where stud_id = #{studId}
</select>
</mapper>

最后编写test类:

package com.mybatis3.services;

import java.util.Date;
import java.util.List; import org.junit.AfterClass; import static org.junit.Assert.*; import org.junit.BeforeClass;
import org.junit.Test; import com.mybatis3.domain.Student; public class StudentServiceTest {
private static StudentService studentService; @Test
public void testFindStudentById() {
Student student = studentService.findStudentById(1);
assertNotNull(student);
}
}

整个代码结构如下:

现在正式调试testFindStudentById方法,调用到 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream)方法,

在这里设置断点,进入MyBatis的源码里,

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

这行代码没有必要看,接着往下看:

return build(parser.parse());

parse方法点进去:

parseConfiguration(parser.evalNode("/configuration"));

进入到parseConfiguration方法里面:

这些属性就是对应mybatis-config.xml里面的参数,这个方法就是解析xml里面所有的内容。

点击进入mapperElement方法,这个方法,就是规定了mapper标签的加载优先级顺序:

如果配置重复,会抛出异常。

回到上面的bulid方法:

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

可以看到就是返回我们SqlSessionFactory(是个接口)的实例对象,其参数就是我们的Configuration全局配置类。

上面到此为止告一段落,接下来看下sqlSessionFactory.openSession()方法,点进去:

 public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

上面的getDefaultExecutorType方法就是返回执行器类型,是个枚举,默认是SIMPLE:

public enum ExecutorType {
SIMPLE, REUSE, BATCH
}

点进去openSessionFromDataSource方法,这个方法就是获取环境信息,创建一个事务,再获取一个执行器:

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

点进newExecutor方法:根据条件创建不同的执行器。

此方法里有个非常重要的一段代码:

 if (cacheEnabled) {
executor = new CachingExecutor(executor, autoCommit);
}

CachingExecutor是创建一级缓存的执行器。

到目前为止,已经拿到SqlSession,并对SimpleExecutor执行器进行初始化。

第三步:执行我们编写的return sqlSession.selectOne("com.mybatis3.mappers.StudentMapper.findStudentById", studId);方法。

点进去selectOne,selectOne会调用DefaultSqlSession.selectList方法,点进去:

看下statement的参数值,就是StudentMapper.xml里 mapper标签名称 + select标签id的名称,所以这里的id是要唯一的。

这里出现了一个新的核心类MappedStatement,对应的StudentMapper.xml里的select,update,insert,delete语句。

下面代码:

      List<E> result = executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

进入query方法:

 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

getBoundSql就是绑定sql语句的参数,具体值如下:

createCacheKey方法创建缓存key,点进去,一直找到createCacheKey方法,包括缓存参数的更新,给sql创建了一个缓存,缓存可以是由id + sql + limit + offset组成,具有相同的key会做缓存。

query方法会先拿到缓存,缓存如果不为空的话,判断是否要清空缓存,如果使用缓存,且不是脏数据,则使用读写锁进行加锁,从不同的缓存策略中获取缓存列表;

缓存为空的话,会查询数据库,

进入 List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);方法,

在真正查询数据库之前,会再次判断已经存在一级缓存,如果没有,执行queryFromDatabase方法,执行里面的doQuery方法:

 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

这里又出现了一个核心类StatementHandler,这个接口是设计具体数据库的操作。 进入 return handler.<E>query(stmt, resultHandler);方法:

 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}

这里出现的JDBC的PreparedStatement 类,不用解释了吧,已经调到Java JDBC的这层API了。

进入到handleResultSets方法,执行此方法,就已经返回数据集的结果了。如下图:

现在总结一下:

至此,基本上就是整个MyBatis查询数据的一个过程。

MyBatis源码解析的更多相关文章

  1. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  2. Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别

    XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法其会调用生成DynamicSqlSource和RawSqlSource这两个帮助类,本文将对此作下简单的简析 应用 ...

  3. mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)

    目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...

  4. mybatis源码-解析配置文件(四)之配置文件Mapper解析

    在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ...

  5. mybatis源码-解析配置文件(三)之配置文件Configuration解析

    目录 1. 简介 1.1 系列内容 1.2 适合对象 1.3 本文内容 2. 配置文件 2.1 mysql.properties 2.2 mybatis-config.xml 3. Configura ...

  6. Mybatis源码解析,一步一步从浅入深(一):创建准备工程

    Spring SpringMVC Mybatis(简称ssm)是一个很流行的java web框架,而Mybatis作为ORM 持久层框架,因其灵活简单,深受青睐.而且现在的招聘职位中都要求应试者熟悉M ...

  7. Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码

    在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...

  8. Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)

    在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...

  9. Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例

    在Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们看到了XMLConfigBuilder(xml配置解析器)的实例化.而且这个实例化过程在文章:Mybatis源码解析,一步一步从浅 ...

  10. Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析

    在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...

随机推荐

  1. day29 Pyhton 面向对象 多态 封装

    # coding:utf-8 # py2中的经典类 # class D:#没有继承object是经典类# pass # # def func(self): # # print('d') # class ...

  2. DFS深度优先搜索算法

    Lake Counting(POJ No.2386) 有一个大小为N*M的园子,雨后积起了水.八连通的积水被认为是在一起的.请求出园子里共有多少个水洼?(八连通是指下图中相对w的*部分) * * * ...

  3. linux(centos8):查看操作系统的当前版本(os/kernel/bash)

    一,查看redhat系操作系统的版本: 适用于centos/fedora/rhel等 [root@centos8 ~]# cat /etc/redhat-release CentOS Linux re ...

  4. 第一章 Linux操作系统及其历史介绍

    一.什么是操作系统 1.基本含义: 简称OS 是计算机系统中必不可少的基础系统软件,是应用程序运行和用户操作必备的基础环境 操作系统就是一个人与计算机之间的中介 2.组成方式: 操作系统的组成: 计算 ...

  5. GDB常用调试命令(一)

    GDB是UNIX及UNIX-like下的调试工具,通常gdb使用前置条件:编译时加入debug信息,这里指的是C++. gcc/g++调试选项   gcc/g++是在编译时加入-g,-g分4个等级: ...

  6. .net core autofac asyncinterceptor 异步拦截器帮助包

    autofac使用拦截器实现AOP,是基于Castle.Core的.然而Castle.Core并未提供原生异步支持.所以需要使用帮助类实现,这在autofac官方文档的已知问题中有详细说明: http ...

  7. 12 个设计 API 的安全建议,不要等出事儿了“捶胸顿足”

    原文地址:API Security Best Practices 原文作者:Mark Michon 译者 & 校正:HelloGitHub-小鱼干 & HelloGitHub-鸭鸭 虽 ...

  8. 从零造就JVM大牛(一)

    引言 从事java的小伙伴大家好,如果你是一名从事java行业的程序员,无论你是小白还是工作多年的老司机,我相信这篇文章一定会给你带来 不同程度的收货不敢说你看完我的文章从此精通jvm打遍天下无对手, ...

  9. vue 404

    问题描述:前端同事使用Vue.js框架,利用vue-route结合webpack编写了一个单页路由项目,运维协助在服务器端配置nginx.部署完成后,访问首页没问题,从首页里打开二级页面没问题,但是所 ...

  10. JUC---06线程间通信(二)

    二.线程间定制化调用通信 要使多线程之间按顺序调用,实现A->B->C按顺序输出,使用Lock锁实现,通过Lock锁创建三个Condition实例(三把钥匙),通过不同的条件,调用不同钥匙 ...