源码分析之Mybatis

为什么要使用Mybatis

Mybatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(PlanOld Java Objects,普通的Java对象)映射成数据库中的记录。

Mybatis环境快速入门

Maven依赖信息

 <dependencies>
    <!-- mybatis核心包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.3.0</version>
    </dependency>
    <!-- mysql驱动包 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.29</version>
    </dependency>
    <!-- junit测试包 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
</dependencies>


创建Mybatis配置文件configuration

<?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>
    <!-- 环境配置 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 数据库连接相关配置 ,这里动态获取config.properties文件中的内容-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mapping文件路径配置 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>

</configuration>

 
<?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,namespace的值习惯上设置成包名+sql映射文件名,这样就能够保证namespace的值是唯一的
例如namespace="com.mayikt.mapper.UserMapper"就是com.mayikt.mapper(包名)+userMapper(userMapper.xml文件去除后缀)
 -->
<mapper namespace="com.mayikt.mapper.UserMapper">
    <!-- 在select标签中编写查询的SQL语句, 设置select标签的id属性为getUser,id属性值必须是唯一的,不能够重复
    使用parameterType属性指明查询时使用的参数类型,resultType属性指明查询返回的结果集类型
    resultType="com.mayikt.entity.User"就表示将查询结果封装成一个User类的对象返回
    User类就是users表所对应的实体类
    -->
    <!--
        根据id查询得到一个user对象
     -->
    <select id="getUser" parameterType="int"
            resultType="com.mayikt.entity.UserEntity">
        select * from user where id=#{id}
    </select>
</mapper>



运行Mybatis代码

try {
    // 1.mybatis配置文件
    String resources = "mybatis.xml";
    // 2.获取Reader对象
    Reader resourceAsReader = Resources.getResourceAsReader(resources);
    // 3.获取SqlSessionFactoryBuilder
    SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsReader);
    // 4.创建对应的session
    SqlSession sqlSession = build.openSession();
    // 5.获取对应的mapper
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    // 6.执行方法
    UserEntity user = userMapper.getUser(1);
    System.out.println("name:" + user.getName());
} catch (Exception e) {
    e.printStackTrace();
}
 

数据库表结构

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


Mybatis核心配置文件

Properties(属性)

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis
jdbc.username=root
jdbc.password=root

Mybatis大体架构流程分析

1. 读取resource获取对应的Reader对象
reader = Resources.getResourceAsReader(resources);
2. 使用SqlSessionFactoryBuilder获取SqlSessionFactory源码分析
SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
源码分析:
1.进入到build传递reader有参构造函数
在这里插入图片描述
最终执行:
在这里插入图片描述
2.SqlSessionFactoryBuilder使用XMLConfigBuilder解析配置文件,封装成Configuration对象。
在这里插入图片描述
注意:XMLConfigBuilder运行之后,只能被解析一次 否则会抛出异常。
在这里插入图片描述
3.将配置文件中的mapper注册到configuration的mapperRegistry中
在这里插入图片描述
所以在configuration中的mapperRegistry注册mapper接口
4.使用configuration获取默认的DefaultSqlSessionFactory
在这里插入图片描述

Mybatis代理模式原理分析

在这里插入图片描述

MybatisMapper接口绑定原理

Mapper既然是接口,没有被初始化如何被调用的?

答案:使用动态代理技术

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    UserMapper userMapper = getMapper(UserMapper.class);
    UserEntity user = userMapper.getUser(1);
    System.out.println("user:" + user.toString());
}

//1.获取对应的Mapper接口
public static <T> T getMapper(Class<T> clas)
        throws IllegalArgumentException, InstantiationException, IllegalAccessException {
    return (T) Proxy.newProxyInstance(clas.getClassLoader(), new Class[]{clas},
            new MyBatisJdkInvocationHandler(clas));
}

public class MyBatisJdkInvocationHandler implements InvocationHandler {
    /**
     * 目标对象
     */
    private Object target;

    public MyBatisJdkInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return new UserEntity(1000l, "蚂蚁课堂", 20);
    }

    /**
     * 获取代理对象接口
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

}


MybatisMapper接口绑定原理分析

大致原理分析:
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。
射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询(也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。

MybatisMapper SQLSession源码分析

SQLSession的作用

  1. SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法

  2. 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的。

  3. SqlSession 因为线程不安全 所以不会共享

Executor执行器原理分析

1. openSessionFromDataSource:首先是从Configuration中取出相关的配置,生成Transaction,接着又创建一个Executor,最后返回了DefaultSqlSession对象。
2. SimpleExecutor: 默认的 Executor,每个 SQL 执行时都会创建新的 Statement
3. ResuseExecutor: 相同的 SQL 会复用 Statement
4. BatchExecutor: 用于批处理的 Executor
5. CachingExecutor: 可缓存数据的 Executor,用代理模式包装了其它类型的 Executor
在这里插入图片描述
在这里插入图片描述
默认情况下使用缓存的CachingExecutor

SelectOne底层原理查询分析

在这里插入图片描述
1.当查询单条数据的时候,最终还是调用selectList查询多个结果集包装程单个对象。
在这里插入图片描述
2.从configuration中获取到MappedStatement(对应的sql语句配置),调用executor的query方法实现执行。
在这里插入图片描述
3.先查询二级缓存,是否有缓存,没有的话调用delegate. query
在这里插入图片描述
如果一级缓存中没有该结果,会调用queryFromDatabase查询数据库得到数据让后在缓存到一级缓存中,下次查询的时候相同的sql语句直接走一级缓存不会查询数据库。

Mybatis一级与二级缓存

一级缓存

mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。

具体流程:

  1. 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来
  2. 第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率

注意事项:

  1. 如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
  2. 当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
  3. mybatis的缓存是基于[namespace:sql语句:参数]来进行缓存的,意思就是,SqlSession的HashMap存储缓存数据时,是使用[namespace:sql:参数]作为key

注意:服务器集群的时候,每个sqlSession有自己独立的缓存相互之间不存在共享,所以在服务器集群的时候容易产生数据冲突问题.

配置以下配置可以实现开启日志打印

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="cacheEnabled" value="false"/>
</settings>

如何禁止一级缓存

方案1 在sql语句上 随机生成 不同的参数 存在缺点:map集合可能爆 内存溢出的问题
方案2 开启二级缓存
方案3 使用sqlSession强制清除缓存
方案4 创建新的sqlSession连接。

二级缓存SessionFactory

二级缓存是mapper级别的缓存,也就是同一个namespace的mappe.xml,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域,二级缓存默认是没有开启的。
需要在setting全局参数中配置开启二级缓存

Config.配置

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

在UserMapper配置

<!-- 以下两个<cache>标签二选一,第一个可以输出日志,第二个不输出日志 -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
<!-- <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> -->
二级缓存回收策略
LRU:最近最少使用的策略,移除最长时间不被使用的对象   
FIFO:先进先出策略,按对象进入缓存的顺序来移除它们。
SOFT:软引用策略,移除基于垃圾回收器状态和软引用规则的对象。 
WEAK:弱引用策略,更积极地移除基于垃圾收集器状态和弱引用规则的对象。

软引用与弱引用的区别:
软引用: 软引用是用来描述一些有用但并不是必需的对象, 对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象
弱引用: 弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象

TransactionalCache

TransactionalCache:继承自Cache接口,主要作用是保存SqlSession在事务中需要向某个二级缓存提交的缓存数据(因为事务过程中的数据可能会回滚,所以不能直接把数据就提交二级缓存,而是暂存在TransactionalCache中,在事务提交后再将过程中存放在其中的数据提交到二级缓存,如果事务回滚,则将数据清除掉)

TransactionalCacheManager

TransactionalCacheManager:用于管理CachingExecutor使用的二级缓存对象,只定义了一个transactionalCaches字段
private final Cache delegate;	//对应的二级缓存对象
private boolean clearOnCommit;	//是否在commit时清除二级缓存的标记
// 需要在commit时提交到二级缓存的数据
private final Map<Object, Object> entriesToAddOnCommit;
// 缓存未命中的数据,事务commit时,也会放入二级缓存(key,null)
private final Set<Object> entriesMissedInCache;

StatementHandler

StatementHandler接口的实现大致有四个,其中三个实现类都是和JDBC中的Statement响对应的:

  1. SimpleStatementHandler,这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理; 存在sql注入攻击问题
  2. PreparedStatementHandler,这个对应JDBC中的PreparedStatement,预编译SQL的接口;
    防止sql注入
  3. CallableStatementHandler,这个对应JDBC中CallableStatement,用于执行存储过程相关的接口;
  4. RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用。

ResultSetHandler

就是将Statement实例执行之后返回的ResultSet结果集转换成我们需要的List结果集

一级缓存与二级缓存区别

①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(sqlHashMap)是互相不影响的。
②、二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。


注意:sqlSession缓存底层存在线程安全问题。

Mybatis使用常用设计模式

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值