Mybatis源码剖析
基础环境搭建
-
JDK8
-
Maven3.6.3(别的版本也可以…)
-
MySQL 8.0.28 --> MySQL 8
-
Mybatis 3.4.6
-
准备jar,准备数据库数据
把依赖导入pom.xml
中
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- <dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.2.18</version>
</dependency>-->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
<build></build>
数据库中放置俩张表
CREATE TABLE `t_user` (
`id` int DEFAULT NULL,
`name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
往里面加入数据
CREATE TABLE `t_account` (
`id` int DEFAULT NULL,
`accountNo` varchar(255) DEFAULT NULL,
`balance` double DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
往里面加入数据
2. 准备配置文件
a. 基本配置文件 mybatis-config.xml
- 数据源的设置 environments
- 类型别名
- mapper文件的注册
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- <settings>
<setting name="cacheEnabled" value="true"/>
</settings>-->
<typeAliases>
<typeAlias type="com.baizhiedu.entity.User" alias="User"/>
<typeAlias type="com.baizhiedu.entity.Account" alias="Account"/>
</typeAliases>
<!-- <plugins>-->
<!-- <!–<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor">-->
<!-- <property name="test" value="111111"/>-->
<!-- </plugin>–>-->
<!-- <!–<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor2"/>–>-->
<!-- <!–<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor3"/>–>-->
<!-- <!– <plugin interceptor="com.baizhiedu.plugins.PageHelperInterceptor1">-->
<!-- <property name="queryMethodPrefix" value="query"/>-->
<!-- <property name="queryMethodSuffix" value="ByPage"/>-->
<!-- </plugin>–>-->
<!--<!– <plugin interceptor="com.baizhiedu.plugins.LockInterceptor"/>–>-->
<!-- </plugins>-->
<environments default="default">
<environment id="default">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/suns?useSSL=false"></property>
<property name="username" value="root"></property>
<property name="password" value="123xxx"></property>
</dataSource>
</environment>
<!-- <environment id="oracle">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"></property>
<property name="username" value="hr"/>
<property name="password" value="hr"/>
</dataSource>
</environment>-->
</environments>
<mappers>
<!--<package name=""-->
<mapper resource="UserDAOMapper.xml"/>
<mapper resource="AccountDAOMapper.xml"/>
</mappers>
</configuration>
-默认IDEA配置,MySQL环境搭建大家都懂,略过,不会的可以关注私聊评论-
Mybatis回顾
1. Mybatis做什么?
Mybatis是一个ORM类型框架,解决的数据库访问和操作的问题,对现有JDBC技术的封装。
2. 核心代码分析
首先看看项目结构
interface
public interface AccountDAO {
public void save(Account account);
}
public interface UserDAO {
//public void save(User user); //SqlSession.insert()
public void save(User user);
public List<User> queryAllUsersByPage();//SqlSesson.select()
public User queryUserById(@Param("id") Integer id);
public void update(User user);
}
entity
package com.baizhiedu.entity;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String accountNo;
private double balance;
public Account() {
System.out.println("---------account----------");
}
public Account(Integer id, String accountNo, double balance) {
this.id = id;
this.accountNo = accountNo;
this.balance = balance;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", accountNo='" + accountNo + '\'' +
", balance=" + balance +
'}';
}
}
package com.baizhiedu.entity;
import java.io.Serializable;
public class User implements Serializable {
private Integer id;
private String name;
private Integer version;
public User() {
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
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 getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
现在让我们测试一下是否可以查到数据
import com.baizhiedu.dao.UserDAO;
import com.baizhiedu.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class TestMybatis {
// 方式一
@Test
public void test1() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
User user = userDAO.queryUserById(4);
System.out.println(user);
}
// 方式二
@Test
public void test2() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
// User user = userDAO.queryUserById(4);
//System.out.println( "类型是" + sqlSession.selectOne("com.baizhiedu.dao.UserDAO.queryUserById", 4).getClass());
User user = (User)sqlSession.selectOne("com.baizhiedu.dao.UserDAO.queryUserById", 4);
System.out.println(user);
}
}
测试类中,哪一种方法好?
功能 :两种方式功能等价
实现效果: 都有耦合性,更改sql字段,俩者都需要更改
那种方式好:第一种方式好 表达概念更清晰 ,第一种封装定义类型,第二种字符串不能表示类型,就好比 String name = "张三"
和 new User().getName()
第一个可以表示人,也可以表示狗,但第二个表示人清晰可见
第一种开发,本质上就是对第二种开发的封装。(代理设计模式)后续再聊
Mybatis核心对象
大家先看这张图有个印像
对于我们来讲。我们在对mybatis进行定义或者进行初步接触的过程当中,我们一直且反复都在强调的一个概念是什么呢?就是mybatis
它是一个JDBC的封装。它是通过什么来进行的封装?它是通过sqlsession这个对象。来进行的封装。那封装的是什么呢?那既然封装的是JDBC,那JDBC无外乎也就会涉及到这么几个核心的类型,一个是connection。一个是statement,一个是resultset。所以在这块儿呢,我们自然而然的就会得到这样一个信息,就是mybits这个框架。
通过sqlsession封装了JDBC。那封装了JDBC的连接connection。封装了statement,当然这个statement就包括我们所说的三种statement,一种是最普通的statement。一种是预编译statement,一种是coablestatement,而coablestatement,它主要应用在哪呢?主要应用在存储过程层面上。那通过这些statement与我们的数据库进行交互,最后它的结果由result进行封装,进而返回给我。所以circlesession它实际上应该封装的是这些东西。那这是我们最初在接触mybatis的时候,
给大家的一个最最基本的概念。但是如果我们仔细分析的话,你就会发现,作为mybatis来讲,它其实不仅仅包括sqlsession。它还包括什么呢?它还包括sqlsession。前面的什么?他的父亲,也就是他的工厂。sqlsessionfactory.它还包括什么?还包括mybatis-config.xml以及我们的mapper.xml这些东西。所以你如果仅认为它封装了JDBC进行使用的话,那实际上理解上是没有问题的。但是细节有很多的偏差。那它至少要包括的是四个环节。sqlsession封装了JDBC的使用。而它还提供了sqlsession factory来创建sqlsession。那还需要我们在配置文件当中去书写相关的配置,进而最终由sqlsession帮我们基于mapper文件。生成dao。哎,那么这一套东西才构成了mybatis,所以显然我们曾经的分析是不到位的,是不透彻的。是有问题的。
它实际上是有两大类核心对象的一类,我们叫做数据存储类对象。一类我们叫做的是操作类型的对象。哎,这是整个mybatis的两块儿内容。那什么是数据存储类的对象呢?它的概念是什么呢?它最为核心的概念就是在JAVA中。或者说,在虚拟机当中。对。mybatis.相关的配置信息。进行封装。因为我们知道文件,它存了很多东西。它存了很多配置的内容,我们不可能用点儿就读一次文件,用点儿就读一次文件,因为什么呢?因为它会频繁产生IO。而你要知道,作为IO来讲,它是操作系统层面上的资源,它的一个创建绝不是虚拟机单独来完成的。它一定是要虚拟机来与操作系统进行交互和交流来完成的。所以注定IO在我们的开发过程当中一定是越少越好,能复用最好。那所以我们说这些mybatis的相关的配置信息,它不可能是随用随读的,它一定是一次性读取。进而封装在JAVA的对象当中。这是火星。能听得明白我的意思吗?好,那这就涉及到了两个问题了,哪两个问题呢?第一个问题就是它的配置信息要封装对象,它有几种配置信息呢?两种一种,刚才我们说过了,叫做mybatis-config.xml,另外一种,我们叫做xxxdaomapper.xml。
由这两个。那这两种信息最终都要进行JAVA的封装,那么这个mybatis-config.xml封装成了什么呢?封装成了一个叫做configuration的对象。那换句话说,我们可以认为configuration对象。它封装的就是mybatis相关的内容呃。而这个xxxDaomapper.xml,它对应的是怎么进行的封装呢?它对应的是一个叫做mappedstatement。对象的风格。当然这块儿仅仅是一个形象上的认知。呃,那不准确。后面呢,我们还会再剖析。那所以呢,首先第一个层面上,我们就要去验证,什么验证我们所说的。这个configuration这个类是对mybatis.config.xml的封装。那怎么来验证呢?
这个是开启二级缓存,我们后续会继续剖析
1. 数据存储类对象
概念:在Java中(JVM)对Mybatis相关的配置信息进行封装
mybatis-config.xml ----> Configuration
Configuration
1. 封装了mybatis-config.xml
2. 封装了mapper 文件 MappedStatement
3. 创建Mybatis其他相关的对象
XXXDAOMapper.xml ----> MappedStatement(形象的认知,不准确)
操nt对象
对应的就是 Mapper文件中的一个一个的 配置标签
<select id. -----> MappedStatement
<insert id. -----> MappedStatement
注定 一个Mybatis应用中 N个MappedStament 对象
MappedStatment ---> Configuration
MappedStatment 中 封装SQL语句 ---> BoundSql
2. 操作类对象 (SqlSession) ---> 门面
Excutor
Excutor 是Mybatis中处理功能的核心
1. 增删改update 查query
2. 事务操作
提交 回滚
3. 缓存相关的操作
Excutor接口 (适配器模式) 操作相关都要设计成接口
BatchExcutor
JDBC中批处理的操作, BatchExcutor
ReuseExcutor
目的:复用 Statement (需要sql一样)
insert into t_user(ID,name)values(1,‘孙帅’);
insert into t_user(ID,name)values(2,‘孙帅1’);
SimpleExcutor
常用Excutor Mybatis推荐 默认
Configuration protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
StatmentHandler
StatementHandler是Mybatis封装了JDBC Statement,真正Mybatis进行数据库访问操作的核心
功能:增删改差
StatementHandler接口
SimpleStatementHandler
JDBC 操作
PreparedStatementHandler
CallableStatementHandler
ParameterHandler
目的:Mybatis参数 ---》 JDBC 相关的参数
@Param ---> #{} --- > ?
ResultSetHandler
目的:对JDBC中查询结果集 ResultSet 进行封装
TypeHandler
Java程序操作 数据库
Java类型 数据库类型
String varchar
int number
int int
excutor和statementhandler都用到了适配器模式
在configuration里面,它是不是专门有这么一个内容?是来存所有的mappedstatement的。也就是configuration是可以找到谁的。是可以找到所有的mappedstatement的。那同样按照我们刚才所关注的mappedstatement里面是不是也存了configuration啊?那也就是mappedstatement是不是也可以找到对应的configuration,因为configuration只是一个,所以它就存了一个,所以它们两个人的关系是什么是?是双向的关联关系,你中有我,我中有你,我既可以通过configuration找到所有的mappedstatement。
那么当然,我也可以通过mappedstatement找到对应的configuration,这样的话它会方便后续mybatis内部在运行的过程当中。可以去解决一些核心的问题。所以。在这儿你一定要注意,它封装的是这些标签,那这些标签\的内容是和mybatis的mappedstatement一一对应的,而且哎。在一个mybatis应用当中,它可以有n个mappedstatement,并且mappedstatement.它是可以找到什么呢?
可以找到configuration。这样我们就把mybatis当中所涉及到的所有的配置文件的数据通过。这两个类型彻底都封装完成了。那换句话说,日后你想要这些相关的内容,比如说mybatis-config.xml,想要所有的mappedstatement。和其他相关的对象,你用configuration就可以了,你要想获得某一个具体的标签,它相关的内容你是不是有map pedstatement对象就够了?而且他们彼此是可以互相找到对方的,那你在编程的时候灵活度就更高了。这就是我们所说的在mybatis核心对象当中的第一类对象数据存储类对象。那这也就是在整个我的这张图里面。这块的内容。任何一个mybatis应用都有configuration和n个mappedstatement,而每一个mappedstatement,它对应的就是一个一个的标签至此,核心对象数据存储这块的内容,我就给大家分析完了。当然,这块还是死的。就是比如说什么时候创建configuration?什么时候创建mappedstatement以及这些数据和mybatis核心的功能,它该怎么交互啊?这都是我们后续要讲解的内容。