MyBatis技术原理(笔记三)

MyBatis 的解析和运行原理

MyBatis 的运行过程分为两大步:
第1步,读取配置文件缓存到Configuration 对象,用以创建SqlSessionFactory 。
第2步, SqlSession 的执行过程。

涉及的技术难点简介

MyBatis 的底层主要使用了动态代理来实现的,一般应用的是JDK反射机制提供的代理,另一种是CGLIB代理。而在MyBatis中两种代理模式都有使用。

反射技术

Java 反射技术应用广泛,它能够配置:类的全限定名、方法和参数,完成对象的初始化,甚至是反射某些方法。反射的优点是只要配置就可以生成对象,可以解除程序的耦合度,比较灵活。反射的缺点是运行比较慢。

JDK 动态代理

首先JDK动态代理必须有实现接口,建立代理对象和真实对象的关系,实现代理逻辑方法。

public interface HelloWorld {
	public void sayHelloWorld () ;
}
public class HelloWorldimpl implements HelloWorld {
	@Override
	public void sayHelloWorld() {
		System.out.println (" Hello World");
	}
}

public class JdkProxyExample implements InvocationHandler {
	//真实对象
	private Object target = null;
	/**
	*建立代理对象和真实对象的代理关系,并返回代理对象
	* @param target 真实对象
	* @return 代理对象
	*/
	public Object bind(Object target) {
		this.target = target;
		return Proxy.newProxyInstance(target.getClass().getClassLoader(),
	target.getClass().getinterfaces() , this);
	}

	/**
	*代理方法逻辑
	* @param proxy 代理对象
	* @param method 当前调度方法
	* @param args 当前方法参数
	* @return 代理结果返回
	* @throws Throwable 异常
	*/
	@Override
	public Object invoke (Object proxy , Method method , Object [] args ) throws Throwable {
		System.out.println(" 进入代理逻辑方法");
		System.out.println(" 在调度真实对象之前的服务");
		Object obj= method.invoke(target , args ) ; //相当于调用sayHelloWorld方法
		System.out.println (" 在调度真实对象之后的服务");
		return obj ;
	}
}

public void testJdkProxy() {
	JdkProxyExample jdk =new JdkProxyExample();
	//绑定关系,因为挂在接口HelloWorld 下,所以声明代理对象HelloWorld proxy
	HelloWorld proxy= (HelloWorld)jdk.bind(new HelloWorldimpl());
	//注意,此时HelloWorld 对象己经是一个代理对象,它会进入代理的逻辑方法invoke里
	proxy.sayHelloWorld();

}

CGLIB 动态代理

CGLIB 动态代理,它的优势在于不需要提供接口,只要一个非抽象类就能实现动态代理。

public class CglibProxyExample implements MethodInterceptor {
	/**
	 * 生成CGLIB代理对象
	 * @param cls 真实对象
	 * @return 代理对象
	 */
	@SuppressWarnings("rawtypes")
	public Object getPorxy(Class cls) {
		//增强类对象
		Enhancer enhancer = new Enhancer();
		//设置增强类型
		enhancer.setSuperclass(cls);
		//定义代理逻辑对象为当前对象,要求当前对象实现Methodinterceptor 方法
		enhancer.setCallback(this);
		//生成并返回代理对象
		return enhancer.create();		
	}

	/**
	 * 代理逻辑方法
	 * @param proxy 真实对象
	 * @param method 当前调度方法
	 * @param args 当前方法参数
	 * @param methodProxy 方法代理
	 * @return 代理逻辑返回
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {	
		Object result = methodProxy.invokeSuper(proxy, args);
		return result;
	}

}
//目标类
public class RefletServiceImpl {
	private String name;
	public void sayHello(String name){
		System.out.println("Hello,I am "+ name);
	}
	
	public RefletServiceImpl(String name ){
		this.name = name;
	}
	public RefletServiceImpl(){}
}
//实现方法
public class ProxyMain {
	public static void main(String[] args) {
		CglibProxyExample cpe =new CglibProxyExample();
		RefletServiceImpl obj = (RefletServiceImpl) cpe.getPorxy(RefletServiceImpl.class);
		obj.sayHello ("张三");
	}
}

构建SqlSessionFactory 过程

第1 步: 通过org.apache.ibatis.builder.xml.XMLConfigBuilder 解析配置的XML 文件,读出所配置的参数,并将读取的内容存入org.apache.ibatis.session.Configuration 类对象中。其中Configuration 采用单例模式。
第2 步:使用Confinguration 对象去创建SqlSessionFactory 。

注:这种创建的方式就是一种Builder 模式(建造者模式),对于复杂的对象而言,使用构造参数很难实现。这时使用一个类(比如Configuration )作为统领, 一步步地构建所需的内容, 然后通过它去创建最终的对象(比如SqlSessionFacotry)。

构建Configuration

在SqlSessionFactory 构建中, Configuration 是最重要的,它的作用是:
1.读入配置文件,包括基础配置的XML 和映射器XML (或注解)。
2.初始化一些基础配置,比如MyBatis 的别名等, 一些重要的类对象(比如插件、映射器、Object 工厂、typeHandlers 对象等)。
3.提供单例,为后续创建SessionFactory 服务,提供配置的参数。
4.执行一些重要对象的初始化方法。
Configuration 会做如下初始化:

  • properties 全局参数。
  • typeAliases 别名。
  • Plugins 插件。
  • objectFactory 对象工厂。
  • objectWrapperFactory 对象包装工厂。
  • reflectionFactory 反射工厂。
  • settings 环境设置。
  • environments 数据库环境。
  • databaseldProvider 数据库标识。
  • typeHandlers 类型转换器。
  • Mappers 映射器。

构建映射器的内部组成

在MyBatis 中一条SQL 和它相关的配置信息是由3 个部分组成的,它们分别是MappedStatement 、SqlSource 和BoundSql 。

1.MappedStatement的作用是保存一个映射器节点( select|insert|delete|update )的内容。它是一个类,包括许多我们配置的SQL、SQL的id、缓存信息、resultMap 、parameterType 、resultType 、resultMap 、languageDriver 等重要配置内容。它还有一个重要的属性sqlSource。MyBatis 通过读取它来获得某条SQL 配置的所有信息。

2.SqlSource 是提供BoundSql 对象的地方,它是MappedStatement 的一个属性。注意,它是一个接口,而不是一个实现类。对它而言有这么重要的 几个实现类:DynamicSqlSource、ProviderSqlSource 、RawSqlSource 、StaticSqlSource。它的作用是根据上下文和参数解析生成需要的SQL。

3.BoundSql 是一个结果对象,也就是SqlSource 通过对SQL 和参数的联合解析得到的SQL 和参数, 它是建立SQL 和参数的地方,它有3 个常用的 属性: sql、parameterObject、parameterMappings。
(1) parameterObject 为参数本身, 可以传递简单对象、POJO或者Map、@Param 注解的参数,由于它在插件中相当常用,我们有必要讨论它的一些规则。
传递简单对象,基本类型的参数,MyBatis会把参数转为包装类型。
传递POJO或者Map,就是其本身。
传递多个参数,使用@Param 注解, MyBatis就会把parameterObject 也变为一个Map<String, Object>对象,类似于没有@Param注解,只是把其数字的键值置换成@Param 注解键值。
(2) parameterMappings 是一个List,它的每一个元素都是ParameterMapping 对象。对象会描述参数,参数包括属性名称、表达式,javaType 、jdbcType 、typeHandler 等重要信息,一般不需要去改变它。
(3) sql 属性就是书写在映射器里面的一条被SqlSource 解析后的SQL 。

构建SqlSessionFactory

有了Configuration 对象,构建SqlSessionFactory 是很简单的。

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

SqlSession 运行过程

有了SqlSessionFactory 对象就可以轻易拿到SqlSession:

SqlSession sqlSession = sqlSessionFactory.openSession();

映射器( Mapper )的动态代理

映射器内的动态代理,只需要编写相应的Mapper接口,那么Mybatis框架根据代理工厂MapperProxyFactory创建接口的动态代理对象MapperProxy。

MapperRegistry该类是用来注册Mapper接口与获取生成代理类实例的工具类。执行getMapper方法先初始化代理工厂MapperProxyFactory再来调用newInstance方法获取MapperProxy代理对象。当执行代理对象的invoke方法时首先判断是否是一个类,这里Mapper 是一个接口不是类,所以判定失败。然后会生成MapperMethod 对象,它是通过cachedMapperMethod 方法对其初始化的,实际上它最后就是通过SqlSession 对象去运行对象的SQL 而己,其他的增、删、查、改也是类似这样处理的。

SqlSession 下的四大对象

1)Execute:调度执行StatementHandler、ParmmeterHandler、ResultHandler执行相应的SQL语句;

2)StatementHandler:使用数据库中Statement(PrepareStatement)执行操作,即底层是封装好了的prepareStatement;

3)ParammeterHandler:处理SQL参数;

4)ResultHandler:结果集ResultSet封装处理返回。

Executor 执行器

它是真正执行Java与数据库交互的东西,参与了整个SQL查询执行过程中,主要有三种执行器:
1)SIMPLE-简易执行器, 它没有什么特别的, 不配置它就使用默认执行器。
2)REUSE-它是一种能够执行重用预处理语旬的执行器。
3)BATCH-一执行器重用语句和批量更新, 批量专用的执行器。

实现过程: 它调用了StatementHandler 的prepare()进行了预编译和基础的设置, 然后通过StatementHandler 的parameterize()来设置参数,最后使用StatementHandler 的query方法,把ResultHandler 传递进去,使用它组织结果返回给调用者来完成一次查询,这样焦点又转移到了StatementHandler 对象上。

StatementHandler一一数据库会话器

简单来说就是专门处理数据库会话。详细来说就是进行预编译并且调用ParameterHandler的setParameters()方法设置参数。

数据库会话器主要有三种:SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler,分别对应Executor的三种执行器(SIMPLE、REUSE、BATCH),它所对应的是JDBC 的Statement 、PreparedStatement (预编译处理)和CallableStatement (存储过程处理)。

一条查询SQL的执行过程: Executor 先调用StatementHandler 的prepare()方法预编译SQL ,同时设置一些基本运行的参数。然后用parameterize()方法启用ParameterHandler 设置参数,完成预编译,执行查询, update()也是这样的。如果是查询, MyBatis 会使用ResultSetHandler 封装结果返回给调用者。

ParameterHandler参数处理器

对预编译中参数进行设置,如果有配置typeHandler,自然会对注册的typeHandler对参数进行处理。

执行过程:它从parameterObject 对象中取到参数,然后使用typeHandler 转换参数,如果有设置,那么它会根据签名注册的typeHandler 对参数进行处理。而typeHandler 也是在MyBatis初始化时,注册在Configuration 里面的,需要时就可以直接拿来用了, MyBatis 就是通过这样完成参数设置的。

ResultSetHandler一一结果处理器

ResultSetHandler 是组装结果集返回的。

ResultSetHandler接口方法handlerResultSets()是包装并返回结果集的,handleOutputParameters()是处理存储过程输出参数的。

SqlSession 运行总结

SqlSession 运行过程
注:图片来自网络,侵权必删
SqlSession的运行主要是依靠Executor执行器调用(调度)StatementHandler、parameterHanlder、ResultSetHandler,Executor首先通过创建StamentHandler执行预编译并设置参数来运行。而StatementHandler经过3 步:
1) prepared 预编译SQL 。
2) parameterize 设置参数。
3) query/update 执行SQL 。
其中, parameterize 是调用parameterHandler 的方法设置的,而参数是根据类型处理器typeHandler 处理的。query/update 方法通过ResultSetHandler 进行处理结果的封装,如果是update 语旬,就返回整数,否则就通过typeHandler 处理结果类型,然后用ObjectFactory
提供的规则组装对象,返回给调用者。

参考文章

Mybatis的SqlSession运行原理.
Mybatis中Mapper动态代理的实现原理.

猜你喜欢

转载自blog.csdn.net/Dullon_jiang/article/details/88559226