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;}@Overridepublic 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;}@Overridepublic 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 {// 方式一@Testpublic 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);}// 方式二@Testpublic 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 ----> ConfigurationConfiguration 1. 封装了mybatis-config.xml2. 封装了mapper 文件 MappedStatement3. 创建Mybatis其他相关的对象 XXXDAOMapper.xml ----> MappedStatement(形象的认知,不准确)操nt对象 对应的就是 Mapper文件中的一个一个的 配置标签 <select id. -----> MappedStatement<insert id. -----> MappedStatement 注定 一个Mybatis应用中 N个MappedStament 对象 MappedStatment ---> Configuration MappedStatment 中 封装SQL语句 ---> BoundSql
2. 操作类对象 (SqlSession) ---> 门面 ExcutorExcutor 是Mybatis中处理功能的核心1. 增删改update 查query2. 事务操作 提交 回滚3. 缓存相关的操作Excutor接口 (适配器模式) 操作相关都要设计成接口BatchExcutorJDBC中批处理的操作, 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;StatmentHandlerStatementHandler是Mybatis封装了JDBC Statement,真正Mybatis进行数据库访问操作的核心功能:增删改差StatementHandler接口SimpleStatementHandlerJDBC 操作 PreparedStatementHandlerCallableStatementHandler ParameterHandler目的:Mybatis参数 ---》 JDBC 相关的参数 @Param ---> #{} --- > ?ResultSetHandler目的:对JDBC中查询结果集 ResultSet 进行封装 TypeHandlerJava程序操作 数据库Java类型 数据库类型String varcharint numberint 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核心的功能,它该怎么交互啊?这都是我们后续要讲解的内容。