zoukankan      html  css  js  c++  java
  • Mybatis3.3——源码阅读笔记

    Mybatis——Source阅读笔记

    安利一个工具,可以直接生产单表的mapper xml,直接有GUI。可以直接运行

    https://github.com/zouzg/mybatis-generator-gui

    兵马未动,日志先行

    ​ 上面是mybatis自己做的一个日志小框架,主要使用了适配器模式之对象适配器。

    http://www.cnblogs.com/liuling/archive/2013/04/12/adapter.html

    ​ 对象适配器的关键:

    • 理解三个对象:

      • 目标:客户需要的接口
      • 被适配者:是一个已经存在的接口或者抽象类。
      • 适配器:适配器是一个类,这个类实现了目标接口并且含有被适配者的引用。
    • 通过操作是适配器来使得被适配者能符合目标接口需求

      主要流程:

    • LogFactory直接通过扫这几个日志框架的包装类,通过调用构造函数的方式来判断是否引入对应日志框架所依赖的全部jar包。

    • 如果加载了其中一个日志框架,就不再使用其他日志框架。尝试加载的顺序是串行的,并没有使用多线程,所以最好避免使用同时使用多个日志框架。

    异常

    ​ 其实ExeceptionFactory就是简单的包装了普通的异常,抛出成Mybatis的异常PersistenceException

    public class ExceptionFactory {
    
        private ExceptionFactory() {
            // Prevent Instantiation
        }
    
        //把普通异常包装成mybatis自己的PersistenceException
        public static RuntimeException wrapException(String message, Exception e) {
        //查找错误上下文,得到错误原因,传给PersistenceException
        //每个线程都会有一个ErrorContext,所以可以得到,  .message(message).cause是典型的构建器模式
        return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);
        }
    
    }
    

    ​ 因为Mybatis是要处理多线程抛出的错误信息或者异常,所以编写了一个简单的ErrorContent来存储错误上下文。

    ​ 主要储存的信息就是:

    private ErrorContext stored;
    
    private String resource;
    
    private String activity;
    
    private String object;
    
    private String message;
    
    private String sql;
    
    private Throwable cause;
    

    ​ 然后主要使用了ThreadLocal这个多线程安全的变量,可以理解为一个Map,Key是线程id,Value是这线程下的局部变量。

    //每个线程给开一个错误上下文,防止多线程问题
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
    

    缓存

    52542923228

    ​ mybatis使用了缓存机制,可以减轻对数据库的压力,提高数据库的性能。这边文档有简单介绍。

    https://www.cnblogs.com/moongeek/p/7689683.html

    ​ mybatis这边缓存的时间还是主要使用了一个适配器模式。通过适配器实现Cache接口来配合Mybatis使用,通过引用Cache接口来引入不同种的缓存(自己实现,或者使用redis等等)

    ​ 然后Cache的实现全部使用了装饰者模式,非常的地道,首先建立一个最基础的缓存(例如PerpetualCache),然后上面全部的实现,都是一层一层的套进去的,也就是说理论上可以上面全部的特性。very nice

    ​ 缓存都是通过key、value形式存入的。Mybatis实现了一个类CacheKey来作为key值,主要是通过一个算法来计算一个值作为Key,如果重复了再通过每个对象的类型来一一比对进行区分。

    ​ 然后这边的适配器有很多种,主要可以分为4类。

    回收机制适配器

    1. 堵塞队列缓存BlockingCache

      ​ 通过可重入锁(ReentrantLock)来实现堵塞,只有当前线使用完这个缓存之后才会释放资格锁,其他线程才能进行竞争。

      ​ 实现的时候可以指定等待的超时时间,超过时间后,Mybatis直接抛出异常,表示老子拿不到缓存,直接拜拜了~。

      ​ 可重入锁简单介绍:

      https://blog.csdn.net/yanyan19880509/article/details/52345422

    2. 先进先出缓存FifoCache

      ​ 通过Deque这个双向链表来实现先进先出的机制,缓存长度默认是1024。

    private void cycleKeyList(Object key) {
        //增加记录时判断如果记录已超过1024条,会移除链表的第一个元素,从而达到FIFO缓存效果
        keyList.addLast(key);
        if (keyList.size() > size) {
            Object oldestKey = keyList.removeFirst();
            delegate.removeObject(oldestKey);
        }
    }
    
    1. 最近最少使用缓存LruCache

      ​ 额外又使用了一个Map来做lru机制,所以使用这种缓存机制会占用2倍的内存。

      ​ 使用的Map是使用了LinkedHashMap,这个Map就是会每次访问或者插入一个新元素都会把元素放到链表的末尾。

    public void setSize(final int size) {
        keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
            private static final long serialVersionUID = 4267176411845948333L;
    
            //核心就是覆盖 LinkedHashMap.removeEldestEntry方法,
            //返回true或false告诉 LinkedHashMap要不要删除此最老键值
            //LinkedHashMap内部其实就是每次访问或者插入一个元素都会把元素放到链表末尾,
            //这样不经常访问的键值肯定就在链表开头啦
            @Override
            protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
                boolean tooBig = size() > size;
                if (tooBig) {
                    //这里没辙了,把eldestKey存入实例变量
                    eldestKey = eldest.getKey();
                }
                return tooBig;
            }
        };
    }
    
    1. 定时调度缓存ScheduledCache

      每隔固定的clearInterval就清理一次缓存。

    private boolean clearWhenStale() {
        //如果到时间了,清空一下缓存
        if (System.currentTimeMillis() - lastClear > clearInterval) {
            clear();
            return true;
        }
        return false;
    }
    

    回收机制优化缓存

    1. 软引用缓存

      这边大概要介绍一下引用:https://www.jianshu.com/p/b56731447179

      主要使用分为强引用、软引用、弱引用。主要区别就是进行垃圾回收的回收优先级不同。

      • 强引用:存在绝对不回收
      • 软引用:除非内存空间接近临界值,jvm即将OOM时才回收
      • 弱引用:不管内存空间是否足够,只要被垃圾回收器扫到就回收。

      这边再介绍ReferenceQueue,使用这个队列,垃圾回收器会把将要回收的对象放入到这个队列中。
      https://blog.csdn.net/u012332679/article/details/57489179

       @Override
       public void putObject(Object key, Object value) {
           removeGarbageCollectedItems();
           //putObject存了一个SoftReference,这样value没用时会自动垃圾回收
           delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
       }
    
       @Override
       public Object getObject(Object key) {
           Object result = null;
           @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
                   SoftReference<Object> softReference = (SoftReference<Object>)delegate.getObject(key);
           if (softReference != null) {
               //核心调用SoftReference.get取得元素
               result = softReference.get();
               if (result == null) {
                   delegate.removeObject(key);
               }
               else {
                   // See #586 (and #335) modifications need more than a read lock
                   synchronized (hardLinksToAvoidGarbageCollection) {
                       //存入经常访问的键值到链表(最多256元素),防止垃圾回收
                       hardLinksToAvoidGarbageCollection.addFirst(result);
                       if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
                           hardLinksToAvoidGarbageCollection.removeLast();
                       }
                   }
               }
           }
           return result;
       }
    
       private void removeGarbageCollectedItems() {
               SoftEntry sv;
               //查看被垃圾回收的引用队列,然后调用removeObject移除他们
               while ((sv = (SoftEntry)queueOfGarbageCollectedEntries.poll()) != null) {
                   delegate.removeObject(sv.key);
           }
       }
    
       private static class SoftEntry extends SoftReference<Object> {
           private final Object key;
    
           SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
                   super(value, garbageCollectionQueue);
                   this.key = key;
            
           }
       }	
    

    ​ 可以看出来,这个主要是为了优化内存来使用的,之暂存最近256个object,其他都是弱引用。加快垃圾回收。

    1. 弱引用缓存

      作用跟软引用缓存类似,但是回收的优先级更快。

    2. 序列化缓存

      这个主要就是缓存前把对象转成了二进制,然后取出再转换,好处是省内存,坏处减缓了速度。

    private byte[] serialize(Serializable value) {
        try {
            //序列化核心就是ByteArrayOutputStream
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(value);
            oos.flush();
            oos.close();
            return bos.toByteArray();
        }
        catch (Exception e) {
            throw new CacheException("Error serializing object.  Cause: " + e, e);
        }
    }
    
    private Serializable deserialize(byte[] value) {
        Serializable result;
        try {
            //反序列化核心就是ByteArrayInputStream
            ByteArrayInputStream bis = new ByteArrayInputStream(value);
            ObjectInputStream ois = new CustomObjectInputStream(bis);
            result = (Serializable)ois.readObject();
            ois.close();
        }
        catch (Exception e) {
            throw new CacheException("Error deserializing object.  Cause: " + e, e);
        }
        return result;
    }
    

    事务缓存

    ​ 主要就是一次存入多个缓存。或者移除多个缓存。

    //多了commit方法,提供事务功能
    public void commit() {
        if (clearOnCommit) {
            delegate.clear();
        }
        flushPendingEntries();
        reset();
    }
    
    public void rollback() {
        unlockMissedEntries();
        reset();
    }![](https://images2018.cnblogs.com/blog/657755/201805/657755-20180515145210617-785205161.png)
    
    
    private void reset() {
        clearOnCommit = false;
        entriesToAddOnCommit.clear();
        entriesMissedInCache.clear();
    }
    
    private void flushPendingEntries() {
        for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
            delegate.putObject(entry.getKey(), entry.getValue());
        }
        for (Object entry : entriesMissedInCache) {
            if (!entriesToAddOnCommit.containsKey(entry)) {
                delegate.putObject(entry, null);
            }
        }
    }
    
    private void unlockMissedEntries() {
        for (Object entry : entriesMissedInCache) {
            delegate.putObject(entry, null);
        }
    }
    

    ​ 这边主要是还搭配了一个事务缓存管理器进行统一的管理。

    ​ 主要实现就是通过一个HashMap进行统一的管理。

    public void clear(Cache cache) {
        getTransactionalCache(cache).clear();
    }
    
    //得到某个TransactionalCache的值
    public Object getObject(Cache cache, CacheKey key) {
        return getTransactionalCache(cache).getObject(key);
    }
    
    public void putObject(Cache cache, CacheKey key, Object value) {
        getTransactionalCache(cache).putObject(key, value);
    }
    
    //提交时全部提交
    public void commit() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
            txCache.commit();
        }
    }
    
    //回滚时全部回滚
    public void rollback() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
            txCache.rollback();
        }
    }
    
    private TransactionalCache getTransactionalCache(Cache cache) {
        TransactionalCache txCache = transactionalCaches.get(cache);
        if (txCache == null) {
            txCache = new TransactionalCache(cache);
            transactionalCaches.put(cache, txCache);
        }
        return txCache;
    }
    

    调试型缓存——日志缓存

    ​ 这边就是来计算一下去缓存的命中率。从而进行相关sql的优化之类的操作。

    //目的就是getObject时,打印命中率
    @Override
    public Object getObject(Object key) {
        //访问一次requests加一
        requests++;
        final Object value = delegate.getObject(key);
        //命中了则hits加一
        if (value != null) {
            hits++;
        }
        if (log.isDebugEnabled()) {
            //就是打印命中率 hits/requests
            log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
        }
        return value;
    }
    

    解析

    ​ 主要分两个模块,一个是字符串替换解析的模块,例如${var}的字符串替换。实现方式就是GenericTokenParser这个类提供了parse方法来进行处理,通过传入的TokenHandler的实现类不同来处理不同的标记。

    public class GenericTokenParser {
    
        //有一个开始和结束记号
        private final String openToken;
    
        private final String closeToken;
    
        //记号处理器
        private final TokenHandler handler;
    
        public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
            this.openToken = openToken;
            this.closeToken = closeToken;
            this.handler = handler;
        }
    
        public String parse(String text) {
            StringBuilder builder = new StringBuilder();
            if (text != null && text.length() > 0) {
                char[] src = text.toCharArray();
                int offset = 0;
                int start = text.indexOf(openToken, offset);
                //#{favouriteSection,jdbcType=VARCHAR}
                //这里是循环解析参数,参考GenericTokenParserTest,比如可以解析${first_name} ${initial} ${last_name} reporting.这样的字符串,里面有3个 ${}
                while (start > -1) {
                    //判断一下 ${ 前面是否是反斜杠,这个逻辑在老版的mybatis中(如3.1.0)是没有的
                    if (start > 0 && src[start - 1] == '\') {
                        // the variable is escaped. remove the backslash.
                        //新版已经没有调用substring了,改为调用如下的offset方式,提高了效率
                        //issue #760
                        builder.append(src, offset, start - offset - 1).append(openToken);
                        offset = start + openToken.length();
                    }
                    else {
                        int end = text.indexOf(closeToken, start);
                        if (end == -1) {
                            builder.append(src, offset, src.length - offset);
                            offset = src.length;
                        }
                        else {
                            builder.append(src, offset, start - offset);
                            offset = start + openToken.length();
                            String content = new String(src, offset, end - offset);
                            //得到一对大括号里的字符串后,调用handler.handleToken,比如替换变量这种功能
                            builder.append(handler.handleToken(content));
                            offset = end + closeToken.length();
                        }
                    }
                    start = text.indexOf(openToken, offset);
                }
                if (offset < src.length) {
                    builder.append(src, offset, src.length - offset);
                }
            }
            return builder.toString();
        }
    
    }
    

    ​ 第二个模块主要就是XML文件的读取和解析,使用的模块是org.w3c.dom.Node以及XPath这两个模块。 主要是用Node来解析XML文件的接口,然后使用XPath来替换字符串里面的数据。例如${var}这种的。

    public String evalString(Object root, String expression) {
        //1.先用xpath解析
        String result = (String)evaluate(expression, root, XPathConstants.STRING);
        //2.再调用PropertyParser去解析,也就是替换 ${} 这种格式的字符串
        result = PropertyParser.parse(result, variables);
        return result;
    }
    

    ​ 这边提供一下XPATH 和 NODE一些简单参考链接。

    XPATH :http://www.w3school.com.cn/xpath/

    XNode:https://baike.baidu.com/item/org.w3c.dom/5427193?fr=aladdin

    还有就是提供一篇博客参考:http://www.cnblogs.com/sunzhenchao/p/3161093.html

    类型处理器

    ​ 博客参考(不过这个博客mybatis版本比较老)

    http://www.cnblogs.com/sunzhenchao/archive/2013/04/09/3009431.html

    ​ ORM框架最重要功能是将面向对象方法中的对象和关系型数据库中的表关联了起来,在关联过程中就必然涉及到对象中的数据类型和数据库中的表字段类型的转换,Mybatis中的org.apache.ibatis.type包主要就是实现这个功能。

    ​ 当MyBatis为PreparedStatement 设置参数时或者从ResultSet中获取数据时,会根据Java类型使用TypeHandler 去获取相应的值。官网中也列出了每一个TypeHandler用来处理对应的JDBC类型和JAVA类型。

    ​ 实现转换的接口逻辑很清晰,先定义了TypeHandler接口规定了set方法,给PreparedStatement对象对应的列设置参数,以及get方法来获取对应返回对象列的值。

    ​ 然后再定义了一个抽象类BaseTypeHandler来实现这个接口,把一些公共代码写在抽象类里面,然后通过新定义的几个abstract函数把具体的执行操作下放到集成的子类来实现。

    ​ 另外一个部分就是类型别名的注册是在这边做了实现,主要就是上图右侧的来管理和实现的。主要就是给类设置别名,简化代码,数据维护在一个map里面。

    IO

    VFS

    ​ 虚拟文件系统,主要是用来读取服务器里的资源,这边主要是用来读取jar内的文件内容。VFS是宏定义的一个抽象类,通过一个单例的方式加载一种具体的VFS实现类,默认提供两种

    • DefaultVFS:默认的VFS,提供读取jar方法
    • Jboss6

    Resource

    ​ 这边就是mybatis封装好的经常使用的文件相关操作的一个工具类。主要用来加载xml、props以及获取类文件的url等等。

    ResolverUtil

    ​ 里面主要两个check方法

    • IsA:是否是type或者是其父类

    • AnnotatedWith:这个元素上是否含有这个注解

      以及一个查询方法

      //主要的方法,找一个package下满足条件的所有类,被TypeHanderRegistry,MapperRegistry,TypeAliasRegistry调用
      public ResolverUtil<T> find(Test test, String packageName) {
          String path = getPackagePath(packageName);
      
          try {
              //通过VFS来深入jar包里面去找一个class
              List<String> children = VFS.getInstance().list(path);
              for (String child : children) {
                  if (child.endsWith(".class")) {
                      addIfMatching(test, child);
                  }
              }
          }
          catch (IOException ioe) {
              log.error("Could not read package: " + packageName, ioe);
          }
      
          return this;
      }
      

    数据源

    ​ 这边就是mybatis做的数据库连接池的具体实现。对外统一提供的接口是:DataSourceFactory

    /**
     * 数据源工厂
     * 有三种内建的数据源类型 UNPOOLED POOLED JNDI
     */
    public interface DataSourceFactory {
    
        //设置属性,被XMLConfigBuilder所调用
        void setProperties(Properties props);
    
        //生产数据源,直接得到javax.sql.DataSource
        DataSource getDataSource();
    
    }
    

    unpooledDataSource

    ​ 首先是提供了非池化的数据库连接,这边主要用的就是常见的JDBC代码。

    private Connection doGetConnection(Properties properties) throws SQLException {
        initializeDriver();
        //属性的前缀是以“driver.”开 头的,它 是 通 过 DriverManager.getConnection(url,driverProperties)方法传递给数据库驱动
        Connection connection = DriverManager.getConnection(url, properties);
        configureConnection(connection);
        return connection;
    }
    
    private synchronized void initializeDriver() throws SQLException {
        //这里便是大家熟悉的初学JDBC时的那几句话了 Class.forName newInstance()
        if (!registeredDrivers.containsKey(driver)) {
            Class<?> driverType;
            try {
                if (driverClassLoader != null) {
                    driverType = Class.forName(driver, true, driverClassLoader);
                }
                else {
                    driverType = Resources.classForName(driver);
                }
                // DriverManager requires the driver to be loaded via the system ClassLoader.
                // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
                Driver driverInstance = (Driver)driverType.newInstance();
                DriverManager.registerDriver(new DriverProxy(driverInstance));
                registeredDrivers.put(driver, driverInstance);
            }
            catch (Exception e) {
                throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
            }
        }
    }
    
    private void configureConnection(Connection conn) throws SQLException {
        if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
            conn.setAutoCommit(autoCommit);
        }
        if (defaultTransactionIsolationLevel != null) {
            conn.setTransactionIsolation(defaultTransactionIsolationLevel);
        }
    }
    

    PooledDataSource

    ​ 然后就是比较重点的Mybatis自己维护的数据库连接池代码,其实也比较简单。

    ​ 首先是池化的连接其实是使用了一个JDK动态代理。可以传入任意一个数据库连接(也可以是刚才未池化的连接),然后一切的数据操作使用被代理对象,但是close方法做了重写,不是释放数据库连接,而是返回到数据库池中。

    ​ JAVA动态代理:https://www.cnblogs.com/LCcnblogs/p/6823982.html

    class PooledConnection implements InvocationHandler {
        /*
         * Required for InvocationHandler implementation.
         *
         * @param proxy  - not used
         * @param method - the method to be executed
         * @param args   - the parameters to be passed to the method
         * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            //如果调用close的话,忽略它,反而将这个connection加入到池中
            if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
                dataSource.pushConnection(this);
                return null;
            }
            else {
                try {
                    if (!Object.class.equals(method.getDeclaringClass())) {
                        // issue #579 toString() should never fail
                        // throw an SQLException instead of a Runtime
                        //除了toString()方法,其他方法调用之前要检查connection是否还是合法的,不合法要抛出SQLException
                        checkConnection();
                    }
                    //其他的方法,则交给真正的connection去调用
                    return method.invoke(realConnection, args);
                }
                catch (Throwable t) {
                    throw ExceptionUtil.unwrapThrowable(t);
                }
            }
        }
    
    }
    

    ​ 然后再用一个PoolState来维护活动和空闲的连接,也是简单通过List来管理的。并且在这里记录一些统计数据

    //----------以下是一些统计信息----------
    //请求次数
    protected long requestCount = 0;
    
    //总请求时间
    protected long accumulatedRequestTime = 0;
    
    protected long accumulatedCheckoutTime = 0;
    
    protected long claimedOverdueConnectionCount = 0;
    
    protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
    
    //总等待时间
    protected long accumulatedWaitTime = 0;
    
    //要等待的次数
    protected long hadToWaitCount = 0;
    
    //坏的连接次数
    protected long badConnectionCount = 0;
    

    ​ 然后最后的数据库连接池实现类是PooledDataSource,主要完成了数据库连接的管理工作。

    • forceCloseAll() 清空全部存在的连接
      • 清空全部活动的连接
      • 清空全部空闲的连接
      • 如果不是autoCommit的连接,要执行rollback
    • pushConnection() 添加一个连接

    • popConnection(String username, String password)

    • pingConnection(PooledConnection conn)

    JndiDataSource

    ​ 这个数据源的实现是为了使用如 Spring 或应用服务器这类的容器, 容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

    ​ JNDI参考: https://yq.aliyun.com/articles/270917

    事务

    ​ 文章参考:https://my.oschina.net/u/657390/blog/663080

    ​ 在MYbatis中一共有两种事务管理器类型JDBC以及MANAGED,其中JDBC直接利用JDBC的commit和rollback来进行事务管理,依赖于从数据源获得的连接来管理事务范围。

    ​ 另外一种是托管事务,就是交给容器来管理事务

    /**
     * 托管事务,交给容器来管理事务
     * MANAGED – 这个配置几乎没做什么。
     * 它从来不提交或回滚一个连接。
     * 而它会让 容器来管理事务的整个生命周期(比如 Spring 或 JEE 应用服务器的上下文) 
     * 默认 情况下它会关闭连接。
     * 然而一些容器并不希望这样, 因此如果你需要从连接中停止 它,将 closeConnection 属性设置为 false。
     * 如果使用mybatis-spring的话,不需要配置transactionManager ,因为mybatis-spring覆盖了mybatis里的逻辑
     */
    

    反射

    对象工厂

    ​ 对象工厂,所有对象都要由工厂来产生,Mybatis提供了一个默认的对象工厂,主要使用create接口

    @Override
    public <T> T create(Class<T> type) {![](https://images2018.cnblogs.com/blog/657755/201805/657755-20180515145520053-1622403118.png)
    
        return create(type, null, null);
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
        //根据接口创建具体的类
        //1.解析接口
        Class<?> classToCreate = resolveInterface(type);
        // we know types are assignable
        //2.实例化类
        return (T)instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
    }
    

    调用者

    ​ 主要就是封装了一下get、set、method等的反射方法。

    wrapper

    ​ 这边对象包装器,封装了反射相关的操作。抽出统一的抽象接口ObjectWrapper

    ​ 然后对于集合类,Mybatis暂时不提供支持,所以仅仅是在方法里面抛出异常,但是又在BaseWrapper里面简单支持了集合类的get、set方法。

    ​ 最重要的实现类就是BeanWrapper,基本提供了初始化bean,set、get等相关操作。其中主要的实现是使用了MetaObject以及MetaClass来实现。

    ​ 首先来看下MetaClass,可以看到这个类,其实基本所有实现托管到reflector来实现

    private Reflector(Class<?> clazz) {
        type = clazz;
        //加入构造函数
        addDefaultConstructor(clazz);
        //加入getter
        addGetMethods(clazz);
        //加入setter
        addSetMethods(clazz);
        //加入字段
        addFields(clazz);
        readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
        writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
        for (String propName : readablePropertyNames) {
            //这里为了能找到某一个属性,就把他变成大写作为map的key。。。
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
        for (String propName : writeablePropertyNames) {
            caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
    }
    

    ​ 可以看出这个类,维护了一个bean的class对象的全部信息。这个要求bean是非常严格正确的bean对象才能使用,否则会直接抛出异常。

    ​ 这边也使用了一个缓存来提高速度。但是主要的一个假设,是类是不会变化的。

    /*
     * Gets an instance of ClassInfo for the specified class.
     * 得到某个类的反射器,是静态方法,而且要缓存,又要多线程,所以REFLECTOR_MAP是一个ConcurrentHashMap
     *
     * @param clazz The class for which to lookup the method cache.
     * @return The method cache for the class
     */
    public static Reflector forClass(Class<?> clazz) {
        if (classCacheEnabled) {
            // synchronized (clazz) removed see issue #461
            //对于每个类来说,我们假设它是不会变的,这样可以考虑将这个类的信息(构造函数,getter,setter,字段)加入缓存,以提高速度
            Reflector cached = REFLECTOR_MAP.get(clazz);
            if (cached == null) {
                cached = new Reflector(clazz);
                REFLECTOR_MAP.put(clazz, cached);
            }
            return cached;
        }
        else {
            return new Reflector(clazz);
        }
    }
    

    ​ 最后是元对象MetaObject这边主要是再次封装了一次,特地来解决xml中类似person[0].birthdate.year的bean的set和get操作。所以这边就需要引入原来的对象,对象包装器,对象工厂,对象包装器工厂。

    public class MetaObject {
    
        //有一个原来的对象,对象包装器,对象工厂,对象包装器工厂
        private Object originalObject;
    
        private ObjectWrapper objectWrapper;
    
        private ObjectFactory objectFactory;
    
        private ObjectWrapperFactory objectWrapperFactory;
    
        private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory) {
            this.originalObject = object;
            this.objectFactory = objectFactory;
            this.objectWrapperFactory = objectWrapperFactory;
    
            if (object instanceof ObjectWrapper) {
                //如果对象本身已经是ObjectWrapper型,则直接赋给objectWrapper
                this.objectWrapper = (ObjectWrapper)object;
            }
            else if (objectWrapperFactory.hasWrapperFor(object)) {
                //如果有包装器,调用ObjectWrapperFactory.getWrapperFor
                this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
            }
            else if (object instanceof Map) {
                //如果是Map型,返回MapWrapper
                this.objectWrapper = new MapWrapper(this, (Map)object);
            }
            else if (object instanceof Collection) {
                //如果是Collection型,返回CollectionWrapper
                this.objectWrapper = new CollectionWrapper(this, (Collection)object);
            }
            else {
                //除此以外,返回BeanWrapper
                this.objectWrapper = new BeanWrapper(this, object);
            }
        }
    }
    

    ​ 这边学习其主要方法getValuesetValue

    //取得值
    //如person[0].birthdate.year
    //具体测试用例可以看MetaObjectTest
    public Object getValue(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
            if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
                //如果上层就是null了,那就结束,返回null
                return null;
            }
            else {
                //否则继续看下一层,递归调用getValue
                return metaValue.getValue(prop.getChildren());
            }
        }
        else {
            return objectWrapper.get(prop);
        }
    }
    
    //为某个属性生成元对象
    public MetaObject metaObjectForProperty(String name) {
        //实际是递归调用
    	Object value = getValue(name);
        return MetaObject.forObject(value, objectFactory, objectWrapperFactory);
    }
    

    ​ 首先通过PropertyTokenizer来分解这个bean这种引用字段,然后这个类实现迭代器的接口,所以可以直接作为迭代器使用,知道children为空。

    ​ 然后通过metaObjectForProperty来获取这次的indexName的对象。返回后再进行下一次的解析。所以这边代码很精妙,两个函数相互调用,极大减少了代码量。

    session

    session基本上是应用程序访问最长用的一个接口了。一个session的产生顺序如下:

    1. 根据配置的xml文件或者其他流信息,通过SqlSessionFactoryBuilder来创建一个SqlSessionFactory
    2. 通过SqlSessionFactory来开启一个Session

    DefaultSqlSessionFactory

    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);
            //然后产生一个DefaultSqlSession
            return new DefaultSqlSession(configuration, executor, autoCommit);
        }
        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();
        }
    }
    

    ​ Mybatis 提供的产生session的方法主要注意的几个参数

    • 环境信息

    • 事务Transaction

    • 执行器Executor

      这边环境信息可以让我们在配置中指定,来切换不同的环境。执行器接下来会详细分析。

    DefaultSqlSession

    ​ 主要的执行方法,都是通过executor来进行执行的。

    //核心selectList
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
            //根据statement id找到对应的MappedStatement
            MappedStatement ms = configuration.getMappedStatement(statement);
            //转而用执行器来查询结果,注意这里传入的ResultHandler是null
            return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        }
        catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        }
        finally {
            ErrorContext.instance().reset();
        }
    }
    

    ​ 这边就要注意一下selectList还有select区别,代码是一直,只是是否传入一个处理结果的handler,如果传入了,结果就是直接保存是在handler依赖的结果上下文content中。

    //核心select,带有ResultHandler,和selectList代码差不多的,区别就一个ResultHandler
    @Override
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        try {
            MappedStatement ms = configuration.getMappedStatement(statement);
            executor.query(ms, wrapCollection(parameter), rowBounds, handler);
        }
        catch (Exception e) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        }
        finally {
            ErrorContext.instance().reset();
        }
    }
    

    Configuration

    ​ 这个包下, 这个类蛮重要的,这个是Mybatis所有的配置信息都是维护在这个类之中的。主要有

    • 环境信息
    • setting信息
    • mybatis内部使用的基础接口实例化:对象工厂、包装器、类型注册机等等。
  • 相关阅读:
    java入门经验分享——记面向对象先导课程学习感想
    HashCode方法整理
    Java中vector用法整理
    Java中Iterator用法整理
    org.springframework.data.redis.RedisConnectionFailureException
    dubbo服务启动正常,但是访问不到服务,在监测中心也找不服务的原因之一
    【转】Elasticsearch Java Rest Client 指南
    【转】mybatis根据mapper执行sql的过程
    转:IDEA异常解决: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)
    ES的常用查询与聚合
  • 原文地址:https://www.cnblogs.com/BlueMountain-HaggenDazs/p/9039968.html
Copyright © 2011-2022 走看看