zoukankan      html  css  js  c++  java
  • Mybatis源码分析

    一、Mybatis环境快速入门

    1、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>

    2、创建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>

    3、Mapper配置文件

    <?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>

    4、实体类

    public class UserEntity {
        private Integer id;
        private Date birdate;
        private String name;
    }

    5、mapper接口

    public interface UserMapper {
        public UserEntity getUser(int id);
    }

    6、运行Mybatis代码

    public class TestMyBatis {
    
        public static void main(String[] args) {
            try {
                //配置文件
                String configXml = "mybatis_config.xml";
                //加载配置获取流
                Reader reader = Resources.getResourceAsReader(configXml);
                //获取sqlSessionFactory工厂
                SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
                //获取sqlSession
                SqlSession sqlSession = sqlSessionFactory.openSession();
                //获取对应的mapper
                UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
                //执行方法
                UserEntity user = userMapper.getUser(1);
                System.out.println("name:" + user.getName());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }

    7、数据表结构

    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核心配置文件

    1、Properties(属性)

        Java属性文件可以配置直观的。

    如:

    <properties>
            <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="jdbc.url" value="jdbc:mysql:///mybatis"/>
            <property name="jdbc.username" value="root"/>
            <property name="jdbc.password" value="root"/>
    </properties>

    或者通过直接引入属性文件,例如:

    <properties resource="db.properties"></properties>

    然后db.properties文件中的配置就是:

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

    2、typeAliases(类型别名)

    类型别名是Java类型的简称。

    逐个设置,例如:

    <typeAliases>
    
       <typeAlias type="com.mayikt.entity.User" alias="user"/>
    
    </typeAliases>

    3、Plugins

    分页插件配置

    四、Mybatis大体架构流程分析

    1、流程图

          1、读取resources获取对应的Reader对象。

                reader = Resources.getResourceAsReader(resources);

           2、使用SqlSessionFactoryBuilder获取SqlSessionFactory源码分析

               SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); 

    源码分析:

    1. Reader reader = Resources.getResourceAsReader(resources);

           调用javaioAPI  读取resources配置文件,获取InputStreamReader

         2、SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

      3、使用XMLConfigBuilder 解析mybatis配置文件

    SqlSessionFactoryBuilder使用XMLConfigBuilder解析配置文件,封装成Configuration对象。

    4、因为在构造函数设置了parsed 为fasle,XMLConfigBuilder 只能被使用一次。

    调用该方法解析mybatis_config文件

    通过反射机制匹配接口

    注意:XMLConfigBuilder运行之后,只能被解析一次 否则会抛出异常。

    xml转换程bean对象 configuration

    XMLConfigBuilder的作用是:解析mybatis配置文件文件 得到configuration

    XMLMapperBuilder的作用是什么: 解析mybatisMapper文件

    建议很多源码中设置值都是采用构造函数形式

    loadedResource 存放都是mybatis映射的文件路由地址 使用set集合存放

    1.protected final Set<String> loadedResources = new HashSet<String>();

    注意:mapper文件配置的namespace一定要和接口对应 否则情况查找失败!

    2mapperRegistry作用存放dao层mapper接口 底层使用过map集合存放。

     

    解析配置文件完成了之后,都会装配到configuration

    Configuration作用:mybatis核心的配置文件内容 ,使用xml转换bean

    Mybatis扫包方式有两种一种是 写package、和resource

    明白了 mapperRegistry 注册我们的Mapper文件。

    使用configuration获取默认的DefaultSqlSessionFactory

    五、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);
        }
    
    }

    接口绑定源码分析:

    解析节点信息封装到Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(),然后添加到configuration里面

    转换成Java类就是一个MappedStatement(存sql信息)

    使用Configuration的getMappedStatement方法来获取MappedStatement对象

    获取的方式key的组成为命名空

     getMapper接口

    从mapperRegistry获取查询接口对应的绑定接口

    1、检查是否已经注册过Mapper接口
    2、使用MapperProxyFactory 创建代理类MapperProxy

     

    UserMapper.getUser方法的时候 调用MapperProxy的invoke方法

    因为mapperRegister中 key:mapper接口 value MapperProxyFactory
    使用MapperProxyFacotory创建MapperProxy代理
    Mybatis基于多个不同的接口生成代理类 不同接口肯定不同的invoke方法
    相同的接口,不同的方法肯定是走同一个invoke方法。

    UserMapper.getUser()执行原理分析
    1、调用MapperPrxoxy的invoke方法()
    2、实现接口方法与配置文件sql语句关联

    底层使用的是:result = sqlSession.selectOne(command.getName(), param);

    大致原理分析:

      SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。

     射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,

    从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询

    (也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。

    五、总结:

    1. 获取本地InputStreamReader对象(mybatis配置文件)
    2. 调用SqlSessionFactoryBuilder 
    3. ###在使用XMLConfigBuilder解析mybatis配置文件,装配到Configuration中。
    4. 将配置文件中的Mapper添加到Configuration mapperRegistry实现注册。

            备注:mapperRegistry存放当前所有的mapper文件。

           5.使用Configuration获取默认的DefaultSqlSessionFactory

    六、MybatisMapper SQLSession源码分析

    1、SQLSession的作用

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

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

    2、Executor执行器原理分析

    1.openSessionFromDataSource,首先是从 Configuration 中取出相关的配置,生成 Transaction,接着又创建了一个 Executor,最后返回了 DefaultSqlSession 对象。

    2.SimpleExecutor: 默认的 Executor,每个 SQL 执行时都会创建新的 Statement

    ResuseExecutor: 相同的 SQL 会复用 Statement

    BatchExecutor: 用于批处理的 Executor

    CachingExecutor: 可缓存数据的 Executor,用代理模式包装了其它类型的 Executor

    默认情况下使用缓存的CachingExecutor

    创建的openSession源码分析:
    1、创建事务管理器
    2、创建执行器、
    默认是创建简单的执行器会变成缓存执行器呢
    最好交给DefaultSqlSession
    默认创建SimpleExecutor执行器 ,判断是否开启二级缓存,如果开启了二级缓存
    CachingExecutor执行器构造函数传递SimpleExecutor

     如果二级缓存没有,走简单执行器

    3、SelectOne底层原理查询分析

    1. 当查询单条数据的时候,最终还是调用selectList查询多个结果集包装程单个对象。

     

    1. 从configuration中获取到MappedStatement(对应的sql语句配置),调用executor的query方法实现执行。

      先查询二级缓存,是否有缓存,没有的话调用delegate.<E> query

    如果一级缓存中没有该结果,会调用queryFromDatabase查询数据库得到数据让后在缓存到一级缓存中,下次查询的时候相同的sql语句直接走一级缓存不会查询数据库。

    一级(sqlSession缓存)和二级缓存(sessionFactory)

    为什么CachingExecutor需要找到SimpleExecutor创建缓存key呢? 方便实现缓存key代码重构

    mybatis缓存控制 先查找二级缓存(硬盘、redis)、二级缓存没有的情况在查找一级缓存。

    一级缓存绝对是有的 ,但是二缓存可以没有。

    PerpetualCache 指的就是我们的一级 一级缓存属于本地缓存 存放在内存中 使用map集合存放

    4、Mybatis一级与二级缓存

    一级缓存实现原理

    相同查询sql语句和参数

    第一次查询的时候 会调用数据库的查询 ,缓存到本地内存中
    第二次查询的时候 直接走本地内存 不会查询数据库。

    sqlSession缓存为了防止脏数据,增加、修改、删除的时候 都会清楚所有本地一级缓存。

    mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,

    在对象中有一个HashMap用于存储缓存数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象无法访问。

    具体流程:

    1.第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来

    2.第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率

    注意事项:

    1. 如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,

               这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读

    1. 当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
    2. 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响对应的:
    SimpleStatementHandler,这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理; 存在sql注入攻击问题

    PreparedStatementHandler,这个对应JDBC中的PreparedStatement,预编译SQL的接口;

    防止sql注入

    CallableStatementHandler,这个对应JDBC中CallableStatement,用于执行存储过程相关的接口;

    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;

    八、流程图总结

  • 相关阅读:
    Python是如何进行内存管理的?
    scrapy利用set()对数据进行去重
    python快速抓取新闻标题及内容
    python爬虫第三方库
    Spring使用@Scheduled定时调度
    redis启动失败
    视图系统
    路由系统
    Django框架 part 2
    Django框架
  • 原文地址:https://www.cnblogs.com/cxyyh/p/11080646.html
Copyright © 2011-2022 走看看