Mybatis执行dao接口方法的流程梳理及源码分析

    以前一直都是在公司的写好的框架模式中直接使用Mybatis,而且也甚是简单,不需要什么思考,只注重sql语句就好了。但是用着用着就对他的实现流程方式感到奇怪了,
明明看到的只是在Dao层写了一个接口,在配置文件中写好自己的sql,就可以给人感觉,接口被自动实例化,然后在service层调用接口的实例,完成他从数据库取数据的过程。
在这种好奇的驱使下开启了对Mybatis的浅显阅读。其实之前一段时间看了些框架的代码,一直懒得总结,现在写一点内容供自己以后参考。
    下面以一个最简单的实例开启对Mybatis的理解,也是以最白话的形式供自己以后忘记的一干二净的时候参考。
 
 
这是一个本文阅读代码引子的一个project概览,就以此工程为入口,相信有兴趣研究Mybatis的人,对Mybatis都已经有了一些使用经验,下面黏贴一些代码。
mybatis_config.xml
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" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="123456" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="ball.xml"/>
	</mappers>
</configuration>


ball.xml
接口中的方法数据库存取数据时候所调用的sql语句,每个接口方法都封装在一个MappedStatement中
 
 
<?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">
<!-- namespace必须与接口对应,id必须是自己的方法名-->
<mapper namespace="IPlayDao">
	<select id="selectBall" parameterType="string" resultType="Ball">
		select id, name from ball where id = #{id}
	</select>
</mapper>


IPlayDao.java
 
 
public interface IPlayDao {
	public Ball selectBall(String id);
}


Ball.java
省略getter,setter方法
 
 
public class Ball {
	private String id;
	private String name;
}


Demo.java
主函数类,mybatis的启动也是从这里开始分析
 
 
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



public class Demo {
	private static Logger LOG = LoggerFactory.getLogger(Demo.class);
	public static void main(String[] args) {
		SqlSession sqlSession = null;
		try {
			sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
			IPlayDao play = sqlSession.getMapper(IPlayDao.class);
			Ball ball = play.selectBall("1");
			LOG.info("查询结果: {}====>{}", ball.getId(), ball.getName());
		} finally {
			sqlSession.close();
		}
	}
	static class MyBatisUtil {
		private static SqlSessionFactory sqlSessionFactory = null;

		public static SqlSessionFactory getSqlSessionFactory() {
			InputStream inputStream = null;
			if (sqlSessionFactory == null) {
				try {
					String resource = "mybatis_config.xml";
					inputStream = Resources.getResourceAsStream(resource);
					sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
					return sqlSessionFactory;
				} catch (Exception ex) {
					System.err.println(ex.getMessage());
					ex.printStackTrace();
				}
			}
			return sqlSessionFactory;
		}
	}
}

为了方便将获取sqlSessionFactory的过程写为了一个静态内部类,把所有关键的代码都累积在这一个类中了。
大体上分析一下这个流程:
(1)首先获取解析配置文件mybatis_config
 (2) Mybatis用配置文件最终构建出一个sqlSessionFactory对象
(3)从sqlSessionFactory中打开了mybatis的执行流程大门,取得sqlSession对象
 (4)调用getMapper方法,将接口传入框架内部,实现被代理过程
(5)调用接口方法, 实际上在Mybatsis内部走了一圈代理流程,最终取得结果
    下面开启到Mybatis内部游离,MyBatis的整个流程包含两个过程,一个是配置文件读取封装为Configuration的过程,一个是执行一个接口方法实现整个代理的过程。
其实所有框架都是这样一个过程,首先根据配置文件将整个框架初始化,然后再实现整个流程的相互配合完成接口执行流程工作。
    看起来这样一直写下去文章太长了,先把总结的整个流程写下来,下面分两篇分别介绍Configuration,和代理执行过程,主要是四大对象Executor,StatementHandler,
ParmeterHandler,ResultHandler,靠sqlSession将整个配合流程完美组装在了一起。
1.getMapper()的过程中对dao接口实现了动态代理
2.接口调用其内部的方法的时候,转到了MapperProxy类的invoke方法中
3.MapperProxy类中将方法包装成MapperMethod,然后调用其MapperMethod的execute方法(其中将sqlsession作为参数传过去,为命令模式)
4.sqlsession中包含有executor执行器,sqlsession调用的方法对应内部使用executor来调用
5.具体方法中会初始化StatementHandler
6.StatementHandler中又会初始化ParameterHandler和ResultHandler
7.StatementHandler初始化完成以后,作为参数传入prepareStatement方法中,创建真正的JDBC的Statement对象
               JDBC  |-----------------1.DriverManage  ---------------------------->Connection
                            |                                                    |createStatement() ---------Statement
                            |  ----------------2.Connection    |
                            |                                                    |prepareStatement(sql)----Prea
                            |                             | 
                            |                             |                                  
                            |                             | Statement
                            |                             | 
                            |------------------3.  |
                                                          | 
                                                          |   
                                                          |PrepareStatement
                                                          |
                                                          |
8.prepareStatement中会用statementHandler创建Statement/PrepareStatementHandler准备参数,获取结果
9.  prepare()获取Statement==》因为Statement和prepareStatement传入sql语句的时间不同,所以内部具体使用instantiateStatement创建
10.paramerize()设置参数(Statement该函数为空,主要为prepareStatement准备)
11.StatementHandler调用具体的方法(上述mappermethod具体 执行的增删改查)
12.Statement取得sql,然后执行Statement的execute方法,然后将Statement传入到ResultHandler的query()(或者其他方法),   ResultHandler该方法内部再调用Statement         的getResutSet结果集封装。
13.PrepareStatement在9中已经传入sql,10中设置了参数,所以只需要执行execute方法 ,类似封装结构。
详细源码分析:(1)解析配置文件封装configuration
                                  (2)接口执行流程
                          

猜你喜欢

转载自blog.csdn.net/ccityzh/article/details/71517490