mybatis高级操作及源码分析(一)

Mybatis中使用注解 or xml 文件?

注解使用姿势

下面以Select注解为例。
@Select 的本质还是 xml 文件的形式,有两种方式@Select注解和@SelectProvider。

以下使用@Select注解。

<pre>
public interface UserMapper {
  Select("SELECT id, name FROM users WHERE id = #{id}")
  User selectById(int id);
}
</pre>



@Select("<script>SELECT firstName <if test=\"includeLastName != null\">, lastName</if> FROM names WHERE lastName LIKE #{name}</script>")
  List<Name> selectXmlWithMapper(Parameter p);

以下使用@SelectProvider注解。@SelectProvider注解也可以使用:String sql = new SQL().SELECT().FROM().WHERE().toString();

<pre>
public interface UserMapper {
  SelectProvider(type = SqlProvider.class, method = "selectById")
  User selectById(int id);
  public static class SqlProvider {
    public static String selectById() {
      return "SELECT id, name FROM users WHERE id = #{id}";
    }
  }
 }
 </pre>

Xml使用方式

太常见,略。

是注解还是XML

个人觉得一个比较折中的方式是简单 SQL 可以用注解开发,如果是一些有诸如条件判断类的需求的 SQL 还是要写在 xml 文件中。不要为了拥抱注解,而完全摒弃了 xml 的形式。但这里需要特别注意,一个实体类只能使用xml或注解中的一种,不能一个实体类中一个方法使用注解,一个方法使用xml。

关于SelectOne方法

selectOne实质是使用的selectList。

@Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

关于SelectList的源码分析

下面以源码包中的LanguageTest测试类为例。代码如下。

@BeforeAll
  static void setUp() throws Exception {
    try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/language/MapperConfig.xml")) {
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }

    BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
        "org/apache/ibatis/submitted/language/CreateDB.sql");
  }

分为四个步骤:

  1. 读取配置文件。
  2. 新建sqlSessionFactory。
  3. 初始化数据库脚本。
  4. 执行selectList方法。

1. 读取配置文件

在这里插入图片描述

//LanguageTest.java
Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/language/MapperConfig.xml")

//Resources.java
/**
   * 返回类路径上的资源作为Reader对象
   *
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static Reader getResourceAsReader(String resource) throws IOException {
    Reader reader;
    if (charset == null) {
    //因没指定字符集,走这里
      reader = new InputStreamReader(getResourceAsStream(resource));
    } else {
      reader = new InputStreamReader(getResourceAsStream(resource), charset);
    }
    return reader;
  }


public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource);
}


/**
   * 返回类路径上的资源作为Stream对象
   *
   * @param loader   The classloader used to fetch the resource
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
      throw new IOException("Could not find resource " + resource);
    }
    return in;
}

//ClassLoaderWrapper.java
/**
   * 从特定的类加载器开始,从类路径获取资源
   *
   * @param resource    - the resource to find
   * @param classLoader - the first class loader to try
   * @return the stream or null
   */
  public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
    return getResourceAsStream(resource, getClassLoaders(classLoader));
  }

// 将当前ClassLoader加入到ClassLoader集合
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        classLoader,//当前classLoader,这里为空
        defaultClassLoader,//默认的classLoader
        Thread.currentThread().getContextClassLoader(),
        getClass().getClassLoader(),
        systemClassLoader};
  }

/**
   * 尝试从一组类加载器中获取资源
   *
   * @param resource    - the resource to get
   * @param classLoader - the classloaders to examine
   * @return the resource or null
   */
  InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    for (ClassLoader cl : classLoader) {
        if (null != cl) {

        // try to find the resource as passed
        InputStream returnValue = cl.getResourceAsStream(resource);

        // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
        if (null == returnValue) {
          returnValue = cl.getResourceAsStream("/" + resource);
        }

        if (null != returnValue) {
          return returnValue;
        }
      }
    }
    return null;
  }

// ClassLoader
/**
     * Returns an input stream for reading the specified resource.
     *
     * <p> The search order is described in the documentation for {@link
     * #getResource(String)}.  </p>
     *
     * @param  name
     *         The resource name
     *
     * @return  An input stream for reading the resource, or <tt>null</tt>
     *          if the resource could not be found
     *
     * @since  1.1
     */
    public InputStream getResourceAsStream(String name) {
        URL url = getResource(name);
        try {
            return url != null ? url.openStream() : null;
        } catch (IOException e) {
            return null;
        }
    }

/**
     * Finds the resource with the given name.  A resource is some data
     * (images, audio, text, etc) that can be accessed by class code in a way
     * that is independent of the location of the code.
     *
     * <p> The name of a resource is a '<tt>/</tt>'-separated path name that
     * identifies the resource.
     *
     * <p> 该方法将首先在父类加载器中搜索*资源;如果父级为<tt> null </ tt>,
     * 则会搜索虚拟机内置的类加载器的路径。如果失败,此方法将调用{@link #findResource(String)}来查找资源。 </p>
     *
     * @apiNote When overriding this method it is recommended that an
     * implementation ensures that any delegation is consistent with the {@link
     * #getResources(java.lang.String) getResources(String)} method.
     *
     * @param  name
     *         The resource name
     *
     * @return  A <tt>URL</tt> object for reading the resource, or
     *          <tt>null</tt> if the resource could not be found or the invoker
     *          doesn't have adequate  privileges to get the resource.
     *
     * @since  1.1
     */
    public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

/**
     * 从VM的内置类加载器中查找资源。
     */
    private static URL getBootstrapResource(String name) {
        URLClassPath ucp = getBootstrapClassPath();
        Resource res = ucp.getResource(name);
        return res != null ? res.getURL() : null;
    }

先加载JAVA_HOME的路径下的instrument.dll动态链接库。
在这里插入图片描述
加载指令动态链接库到本地动态链接库上下文中。
在这里插入图片描述

在这里插入图片描述
idea运行main方法之前的准备工作。
在这里插入图片描述

//InstrumentationImpl.java
    private void loadClassAndCallPremain(String var1, String var2) throws Throwable {
        this.loadClassAndStartAgent(var1, "premain", var2);
    }

加载lib下的jar包,包括加载rt.jar内的2000多个常用类。
在这里插入图片描述

先尝试从jdk的相关路径进行获取。
在这里插入图片描述
即从URLClassPath获取。
在这里插入图片描述
然后再尝试从URL的路径进行获取。

//URLClassLoader.java
public URL findResource(final String name) {
        /*
         * The same restriction to finding classes applies to resources
         */
        URL url = AccessController.doPrivileged(
            new PrivilegedAction<URL>() {
                public URL run() {
                    return ucp.findResource(name, true);
                }
            }, acc);

        return url != null ? ucp.checkURL(url) : null;
}
//URL.java
* <P>如果用于URL的协议(例如HTTP或JAR),则存在一个公共的,专门的URLConnection子类,该子类属于以下软件包之一或其子软件包之一:
     * java.lang,java.io,java .util,java.net,
     * 返回的连接将属于该子类。例如,对于HTTP,将返回HttpURLConnection,对于JAR,将返回JarURLConnection。</ P>
     *
     * @return     a {@link java.net.URLConnection URLConnection} linking
     *             to the URL.
     * @exception  IOException  if an I/O exception occurs.
     * @see        java.net.URL#URL(java.lang.String, java.lang.String,
     *             int, java.lang.String)
     */
    public URLConnection openConnection() throws java.io.IOException {
        return handler.openConnection(this);
    }
    
//Handler.java
public synchronized URLConnection openConnection(URL var1, Proxy var2) throws IOException {
        String var4 = var1.getFile();
        String var5 = var1.getHost();
        String var3 = ParseUtil.decode(var4);
        var3 = var3.replace('/', '\\');
        var3 = var3.replace('|', ':');
        if (var5 != null && !var5.equals("") && !var5.equalsIgnoreCase("localhost") && !var5.equals("~")) {
            var3 = "\\\\" + var5 + var3;
            File var6 = new File(var3);
            if (var6.exists()) {
                return this.createFileURLConnection(var1, var6);
            } else {
                URLConnection var7;
                try {
                    URL var8 = new URL("ftp", var5, var4 + (var1.getRef() == null ? "" : "#" + var1.getRef()));
                    if (var2 != null) {
                        var7 = var8.openConnection(var2);
                    } else {
                        var7 = var8.openConnection();
                    }
                } catch (IOException var10) {
                    var7 = null;
                }

                if (var7 == null) {
                    throw new IOException("Unable to connect to: " + var1.toExternalForm());
                } else {
                    return var7;
                }
            }
        } else {
            return this.createFileURLConnection(var1, new File(var3));
        }
    }
// URLClassLoader.java
public InputStream getResourceAsStream(String name) {
        URL url = getResource(name);
        try {
            if (url == null) {
                return null;
            }
            URLConnection urlc = url.openConnection();
            InputStream is = urlc.getInputStream();
            if (urlc instanceof JarURLConnection) {
                JarURLConnection juc = (JarURLConnection)urlc;
                JarFile jar = juc.getJarFile();
                synchronized (closeables) {
                    if (!closeables.containsKey(jar)) {
                        closeables.put(jar, null);
                    }
                }
            } else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
                synchronized (closeables) {
                    closeables.put(is, null);
                }
            }
            return is;
        } catch (IOException e) {
            return null;
        }
    }

java启动后的线程有哪些?

从下图可以看到有ReferenceHanlder线程,有main线程组中的main线程,有Attach Listencer线程,有Finalizer线程,有Signal Dispatcher线程。可以通过以下方法得出。

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

public class MultiThread {
    public static void main(String[] args) {
        // 获取java线程管理MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,仅打印线程ID和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
        }
        System.out.println(System.getProperty("java.version"));
    }
}

ReferenceHandler线程:由Reference静态代码块中建立并且运行的线程,它的运行方法中依赖了比较多的本地(native)方法,ReferenceHandler线程的主要功能是处理pending链表中的引用对象。
在这里插入图片描述
在这里插入图片描述

 // ReferenceHandler直接继承于Thread覆盖了run方法
    private static class ReferenceHandler extends Thread {
        
        // 静态工具方法用于确保对应的类型已经初始化
        private static void ensureClassInitialized(Class<?> clazz) {
            try {
                Class.forName(clazz.getName(), true, clazz.getClassLoader());
            } catch (ClassNotFoundException e) {
                throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
            }
        }

        static {
            // 确保Cleaner这个类已经初始化
            // pre-load and initialize Cleaner class so that we don't
            // get into trouble later in the run loop if there's
            // memory shortage while loading/initializing it lazily.
            ensureClassInitialized(Cleaner.class);
        }

        ReferenceHandler(ThreadGroup g, String name) {
            super(g, null, name, 0, false);
        }
        
        // 注意run方法是一个死循环执行processPendingReferences
        public void run() {
            while (true) {
                processPendingReferences();
            }
        }
    }

    /* 原子获取(后)并且清理VM中的pending引用链表
     * Atomically get and clear (set to null) the VM's pending-Reference list.
     */
    private static native Reference<Object> getAndClearReferencePendingList();

    /* 检验VM中的pending引用对象链表是否有剩余元素
     * Test whether the VM's pending-Reference list contains any entries.
     */
    private static native boolean hasReferencePendingList();

    /* 等待直到pending引用对象链表不为null,此方法阻塞的具体实现又VM实现
     * Wait until the VM's pending-Reference list may be non-null.
     */
    private static native void waitForReferencePendingList();

    // 锁对象,用于控制等待pending对象时候的加锁和开始处理这些对象时候的解锁
    private static final Object processPendingLock = new Object();
    // 正在处理pending对象的时候,这个变量会更新为true,处理完毕或者初始化状态为false,用于避免重复处理或者重复等待
    private static boolean processPendingActive = false;

    // 这个是死循环中的核心方法,功能是处理pending链表中的引用元素
    private static void processPendingReferences() {
        // Only the singleton reference processing thread calls
        // waitForReferencePendingList() and getAndClearReferencePendingList().
        // These are separate operations to avoid a race with other threads
        // that are calling waitForReferenceProcessing().
        // (1)等待
        waitForReferencePendingList();
        Reference<Object> pendingList;
        synchronized (processPendingLock) {
            // (2)获取并清理,标记处理中状态
            pendingList = getAndClearReferencePendingList();
            processPendingActive = true;
        }
        // (3)通过discovered(下一个元素)遍历pending链表进行处理
        while (pendingList != null) {
            Reference<Object> ref = pendingList;
            pendingList = ref.discovered;
            ref.discovered = null;
            // 如果是Cleaner类型执行执行clean方法并且对锁对象processPendingLock进行唤醒所有阻塞的线程
            if (ref instanceof Cleaner) {
                ((Cleaner)ref).clean();
                // Notify any waiters that progress has been made.
                // This improves latency for nio.Bits waiters, which
                // are the only important ones.
                synchronized (processPendingLock) {
                    processPendingLock.notifyAll();
                }
            } else {
                // 非Cleaner类型并且引用队列不为ReferenceQueue.NULL则进行入队操作
                ReferenceQueue<? super Object> q = ref.queue;
                if (q != ReferenceQueue.NULL) q.enqueue(ref);
            }
        }
        // (4)当次循环结束之前再次唤醒锁对象processPendingLock上阻塞的所有线程
        // Notify any waiters of completion of current round.
        synchronized (processPendingLock) {
            processPendingActive = false;
            processPendingLock.notifyAll();
        }
    }

ReferenceHandler线程启动的静态代码块如下:

static {
        // ThreadGroup继承当前执行线程(一般是主线程)的线程组
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        // 创建线程实例,命名为Reference Handler,配置最高优先级和后台运行(守护线程),然后启动
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
        // 注意这里覆盖了全局的jdk.internal.misc.JavaLangRefAccess实现
        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean waitForReferenceProcessing()
                throws InterruptedException{
                return Reference.waitForReferenceProcessing();
            }

            @Override
            public void runFinalization() {
                Finalizer.runFinalization();
            }
        });
    }

    // 如果正在处理pending链表中的引用对象或者监测到VM中的pending链表中还有剩余元素则基于锁对象processPendingLock进行等待
    private static boolean waitForReferenceProcessing()
        throws InterruptedException{
        synchronized (processPendingLock) {
            if (processPendingActive || hasReferencePendingList()) {
                // Wait for progress, not necessarily completion.
                processPendingLock.wait();
                return true;
            } else {
                return false;
            }
        }
    }

由于ReferenceHandler线程是Reference的静态代码创建的,所以只要Reference这个父类被初始化,该线程就会创建和运行,由于它是守护线程,除非JVM进程终结,否则它会一直在后台运行(注意它的run()方法里面使用了死循环)。

Attach Listener线程:负责接收到外部的命令,对该命令进行执行然后把结果返回给发送者。
在Linux平台上,attach方法最终是使用了/proc和ptrace来读取目标VM中的数据,ptrace提供了一种使父进程可以监视和控制其它进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪(ptrace会使内核暂停当前进程并将控制权交给跟踪进程,使跟踪进程得以察看或者修改被跟踪进程的寄存器,待收集完跟踪信息以后会把控制权交回给当前进程让其继续运行)。

VirtualMachine.attach(Attach到Attach Listener线程后执行有限命令)
VirtualMachine.attach方法过程分析(linux):

(1)信号机制

JVM启动的时候并不会马上创建Attach Listener线程,而是通过另外一个线程Signal Dispatcher在接收到信号处理请求(如jstack,jmap等)时创建临时socket文件/tmp/.java_pid并创建Attach Listener线程(external process会先发送一个SIGQUIT信号给target VM process,target VM会创建一个Attach Listener线程);

(2)Unix domain socket

Attach Listener线程会通过Unix domain socket与external process建立连接,之后就可以基于这个socket进行通信了。

创建好的Attach Listener线程会负责执行这些命令(从队列里不断取AttachOperation,然后找到请求命令对应的方法进行执行,比如jstack命令,找到 { “threaddump”, thread_dump }的映射关系,然后执行thread_dump方法)并且把结果通过.java_pid文件返回给发送者。

整个过程中,会有两个文件被创建:

.attach_pid,external process会创建这个文件,为的是触发Attach Listener线程的创建,因为SIGQUIT信号不是只有external process才会发的,通过这个文件来告诉target VM,有attach请求过来了(如果.attach_pid创建好了,说明Attach Listener线程已经创建成功)。相关代码在LinuxVirtualMachine.java中;

.java_pid,target VM会创建这个文件,这个是因为Unix domain socket本身的实现机制需要去创建一个文件,通过这个文件来进行IPC。相关代码在attachListener_linux.cpp中。

其中的都是target VM的pid。

Signal Dispatcher线程:signal dispatcher 线程通过sem_wait会在等待,当进程接到信号SIGQUIT的时候,只有vm thread会被中断(见上面分析),而进入UserHandler 函数,通过调用 os::signal_notify 去通告signal dispatcher 线程,让 signal dispatch 线程去处理信号。

在信号设计里,因为信号中断是在内核态调用的,内核调用了线程注入了自己的信号函数,一般只允许在该函数里处理简单的事物,所以在java里面专门设计了处理信号后续的线程(signal dispatcher),接受到信号的线程通过信号函数notify到处理信号的线程(signal dispatcher ),最后由该线程做后续的事情。比如线程dump。

Finalizer线程:用于垃圾回收的线程。
在这里插入图片描述

private static class FinalizerThread extends Thread {
        private volatile boolean running;
        FinalizerThread(ThreadGroup g) {
            super(g, "Finalizer");
        }
        public void run() {
            // in case of recursive call to run()
            if (running)
                return;

            // Finalizer thread starts before System.initializeSystemClass
            // is called.  Wait until JavaLangAccess is available
            while (!VM.isBooted()) {
                // delay until VM completes initialization
                try {
                    VM.awaitBooted();
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
            final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
            running = true;
            for (;;) {
                try {
                    Finalizer f = (Finalizer)queue.remove();
                    f.runFinalizer(jla);
                } catch (InterruptedException x) {
                    // ignore and continue
                }
            }
        }
    }

    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }

2. 新建sqlSessionFactory

在这里插入图片描述

因遇到new 关键字,需要通过classLoader进行加载该类到内存。

sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

// SqlSessionFactoryBuilder.java
public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

//XMLConfigBuilder.java
  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }
  
//XPathParser.java
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
}

private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
          // NOP
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

//DocumentBuilderImpl.java
public Document parse(InputSource is) throws SAXException, IOException {
        if (is == null) {
            throw new IllegalArgumentException(
                DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
                "jaxp-null-input-source", null));
        }
        if (fSchemaValidator != null) {
            if (fSchemaValidationManager != null) {
                fSchemaValidationManager.reset();
                fUnparsedEntityHandler.reset();
            }
            resetSchemaValidator();
        }
        domParser.parse(is);
        Document doc = domParser.getDocument();
        domParser.dropDocumentReferences();
        return doc;
    }

//DOMParser.java
/**
     * parse
     *
     * @param inputSource
     *
     * @exception org.xml.sax.SAXException
     * @exception java.io.IOException
     */
    public void parse(InputSource inputSource)
        throws SAXException, IOException {

        // parse document
        try {
            XMLInputSource xmlInputSource =
                new XMLInputSource(inputSource.getPublicId(),
                                   inputSource.getSystemId(),
                                   null);
            xmlInputSource.setByteStream(inputSource.getByteStream());
            xmlInputSource.setCharacterStream(inputSource.getCharacterStream());
            xmlInputSource.setEncoding(inputSource.getEncoding());
            parse(xmlInputSource);
        }

        // wrap XNI exceptions as SAX exceptions
        catch (XMLParseException e) {
            Exception ex = e.getException();
            if (ex == null) {
                // must be a parser exception; mine it for locator info and throw
                // a SAXParseException
                LocatorImpl locatorImpl = new LocatorImpl();
                locatorImpl.setPublicId(e.getPublicId());
                locatorImpl.setSystemId(e.getExpandedSystemId());
                locatorImpl.setLineNumber(e.getLineNumber());
                locatorImpl.setColumnNumber(e.getColumnNumber());
                throw new SAXParseException(e.getMessage(), locatorImpl);
            }
            if (ex instanceof SAXException) {
                // why did we create an XMLParseException?
                throw (SAXException)ex;
            }
            if (ex instanceof IOException) {
                throw (IOException)ex;
            }
            throw new SAXException(ex);
        }
        catch (XNIException e) {
            Exception ex = e.getException();
            if (ex == null) {
                throw new SAXException(e.getMessage());
            }
            if (ex instanceof SAXException) {
                throw (SAXException)ex;
            }
            if (ex instanceof IOException) {
                throw (IOException)ex;
            }
            throw new SAXException(ex);
        }

    }

//XMLConfigBuilder.java
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));//获取最外层的configuration标签
    return configuration;
  }

private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));//先解析properties属性
      Properties settings = settingsAsProperties(root.evalNode("settings"));//解析settings属性
      loadCustomVfs(settings);//VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序。
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));//保存类和别名的关系
      pluginElement(root.evalNode("plugins"));//插件拦截器,其使用可参考https://mybatis.org/mybatis-3/zh/configuration.html#plugins
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));//这个是我们最关心的mappers配置
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

xml配置文件

<configuration>

  <settings>
    <setting name="defaultScriptingLanguage" value="velocity"/>
  </settings>

  <typeAliases>
    <typeAlias alias="velocity" type="org.apache.ibatis.submitted.language.VelocityLanguageDriver"/>
    <typeAlias alias="name" type="org.apache.ibatis.submitted.language.Name"/>
  </typeAliases>

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:mem:language"/>
        <property name="username" value="sa"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="org/apache/ibatis/submitted/language/Mapper.xml"/>
  </mappers>

</configuration>

3. 初始化数据库脚本

//BaseDataTest.java
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
        "org/apache/ibatis/submitted/language/CreateDB.sql");

  public static void runScript(DataSource ds, String resource) throws IOException, SQLException {
    try (Connection connection = ds.getConnection()) {
      ScriptRunner runner = new ScriptRunner(connection);
      runner.setAutoCommit(true);
      runner.setStopOnError(false);
      runner.setLogWriter(null);
      runner.setErrorLogWriter(null);
      runScript(runner, resource);
    }
}

public static void runScript(ScriptRunner runner, String resource) throws IOException, SQLException {
    try (Reader reader = Resources.getResourceAsReader(resource)) {
      runner.runScript(reader);
    }
}

  /**
   * 返回类路径上的资源作为Reader对象(同之前的读取配置文件)
   *
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static Reader getResourceAsReader(String resource) throws IOException {
    Reader reader;
    if (charset == null) {
      reader = new InputStreamReader(getResourceAsStream(resource));
    } else {
      reader = new InputStreamReader(getResourceAsStream(resource), charset);
    }
    return reader;
}

public void runScript(Reader reader) {
    setAutoCommit();

    try {
      if (sendFullScript) {
        executeFullScript(reader);
      } else {
        executeLineByLine(reader);
      }
    } finally {
      rollbackConnection();
    }
  }

//ScriptRunner.java
private void handleLine(StringBuilder command, String line) throws SQLException {
    String trimmedLine = line.trim();
    if (lineIsComment(trimmedLine)) {
      Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine);
      if (matcher.find()) {
        delimiter = matcher.group(5);
      }
      println(trimmedLine);
    } else if (commandReadyToExecute(trimmedLine)) {
      command.append(line, 0, line.lastIndexOf(delimiter));
      command.append(LINE_SEPARATOR);
      println(command);
      executeStatement(command.toString());
      command.setLength(0);
    } else if (trimmedLine.length() > 0) {
      command.append(line);
      command.append(LINE_SEPARATOR);
    }
}

private void executeStatement(String command) throws SQLException {
    try (Statement statement = connection.createStatement()) {
      statement.setEscapeProcessing(escapeProcessing);
      String sql = command;
      if (removeCRs) {
        sql = sql.replace("\r\n", "\n");
      }
      try {
        boolean hasResults = statement.execute(sql);
        while (!(!hasResults && statement.getUpdateCount() == -1)) {
          checkWarnings(statement);
          printResults(statement, hasResults);
          hasResults = statement.getMoreResults();
        }
      } catch (SQLWarning e) {
        throw e;
      } catch (SQLException e) {
        if (stopOnError) {
          throw e;
        } else {
          String message = "Error executing: " + command + ".  Cause: " + e;
          printlnError(message);
        }
      }
    }
  }

4. 执行selectList方法

我们以testLangRaw测试方法为例。

@Test
  void testLangRaw() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      Parameter p = new Parameter(true, "Fli%");
      List<Name> answer = sqlSession.selectList("selectRaw", p);
      assertEquals(3, answer.size());
      for (Name n : answer) {
        assertEquals("Flintstone", n.getLastName());
      }
    }
  }

//DefaultSqlSessionFactory.java
  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

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);
      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();
    }
}

//JdbcTransactionFactory.java
@Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }

//DefaultSqlSessionFactory.java
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

//DefaultSqlSession.java
  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

//Configuration.java
  public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
    if (validateIncompleteStatements) {
      buildAllStatements();
    }
    return mappedStatements.get(id);
  }
//ParamNameResolver.java
  /**
   * 如果对象是{@link Collection}或数组,则包装到{@link ParamMap}。
   *
   * @param object a parameter object
   * @param actualParamName an actual parameter name
   *                        (If specify a name, set an object to {@link ParamMap} with specified name)
   * @return a {@link ParamMap}
   * @since 3.5.5
   */
  public static Object wrapToMapIfCollection(Object object, String actualParamName) {
    if (object instanceof Collection) {
      ParamMap<Object> map = new ParamMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
      return map;
    } else if (object != null && object.getClass().isArray()) {
      ParamMap<Object> map = new ParamMap<>();
      map.put("array", object);
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
      return map;
    }
    return object;
  }

//CachingExecutor.java
  @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

//MappedStatement.java
public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }
//RawSqlSource.java
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
}

//CachingExecutor.java
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}

//BaseExecutor.java
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

//CachingExecutor.java
 @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

//BaseExecutor.java
@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }


  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);//查询当前缓存
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

//SimpleExecutor.java
  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  
//PreparedStatementHandler.java
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

参考:
关于java启动后的线程有哪些?https://www.cnblogs.com/throwable/p/12271653.html
Attach机制:https://blog.csdn.net/fedorafrog/article/details/104537472

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值