Mybatis源码分析
项目介绍
xml文件
<!--GirlMapper.xml中-->
<?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="com.sz.mapper.GirlMapper">
<select id="queryById" resultType="com.sz.pojo.Girl">
select * from girl
<where>
<if test="id>0">
and id = #{id}
</if>
</where>
</select>
</mapper>
<!--mybatis.cfg.xml-->
<?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>
<properties resource="jdbc.properties">
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com.sz.mapper/GirlMapper.xml"/>
</mappers>
</configuration>
java文件
//GirlMapper接口中
public interface GirlMapper {
Girl queryById(@Param("id") long id);
}
//Girl类
public class Girl implements Serializable {
private Long id;
private String name;
private String flower;
private Date birthday;
//省略set,get,tostring方法
}
//工具类MybatisUtil
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis.cfg.xml";
try (InputStream in = Resources.getResourceAsStream(resource)) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSession() {
return sqlSessionFactory.openSession();
}
}
//测试方法
@Test
public void m2() {
SqlSession sqlSession = MybatisUtil.getSession();
GirlMapper mapper = sqlSession.getMapper(GirlMapper.class );
Girl g = mapper.queryById(1l);
System.out.println(g.getId() + " " + g.getName());
sqlSession.commit();
sqlSession.close();
}
源码分析
-
读取配置文件
//Resources的getResourceAsStream方法作用如下: //1.从类路径加载配置文件(如 mybatis.cfg.xml)。 String resource = "mybatis.cfg.xml"; InputStream in = Resources.getResourceAsStream(resource);
这一步主要是加载5个类加载器去查找资源,返回包含mybatis.cfg.xml信息的InputStream
ClassLoaderWrapper中 //一共5个类加载器 ClassLoader[] getClassLoaders(ClassLoader classLoader) { return new ClassLoader[]{ classLoader, defaultClassLoader, Thread.currentThread().getContextClassLoader(), getClass().getClassLoader(), systemClassLoader}; }
-
创建SqlSessionFactory
private static SqlSessionFactory sqlSessionFactory; sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
进入build方法里面查看源代码
SqlSessionFactoryBuilder中 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { SqlSessionFactory var5; try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //parser.parse()返回configuration对象 var5 = this.build(parser.parse());
进入parse方法
XMLConfigBuilder中 public Configuration parse() { if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration")); //对这些node进行解析,塞到configuration中。this.configuration对象就是解析mybatis.cfg.xml得到的对象 return this.configuration; } } private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); Properties settings = this.settingsAsProperties(root.evalNode("settings")); this.loadCustomVfs(settings); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
解析完后,进行build,返回DefaultSqlSessionFactory。
SqlSessionFactoryBuilder中 public SqlSessionFactory build(Configuration config) { //DefaultSqlSessionFactory是SqlSessionFactory的子类 return new DefaultSqlSessionFactory(config); }
-
生成SqlSession
public static SqlSession getSession() { return sqlSessionFactory.openSession(); }
进入openSession方法
DefaultSqlSessionFactory中 public SqlSession openSession() { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; try { Environment environment = this.configuration.getEnvironment(); //事务工厂 TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); //通过事务工厂来产生一个事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //生成一个执行器(事务包含在执行器里) Executor executor = this.configuration.newExecutor(tx, execType); //然后产生一个DefaultSqlSession var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8; }
-
JDK动态代理UserMapper接口
在Test中 GirlMapper mapper = sqlSession.getMapper( GirlMapper.class );
进入getMapper方法
DefaultSqlSession中 public <T> T getMapper(Class<T> type) { return this.configuration.getMapper(type, this); }
进入configuration.getMapper(type, this);方法
Configuration类中 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }
进入mapperRegistry.getMapper(type, sqlSession);
MapperRegistry中 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
进入mapperProxyFactory.newInstance(sqlSession);
MapperProxyFactory中 protected T newInstance(MapperProxy<T> mapperProxy) { //JDK动态代理返回一个实现了指定接口的代理对象 return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); } public T newInstance(SqlSession sqlSession) { MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); //返回的mapperProxy传入this.newInstance(MapperProxy<T> mapperProxy); return this.newInstance(mapperProxy); }
进入MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
MapperProxy中 public class MapperProxy<T> implements InvocationHandler, Serializable { public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } //当UserMapper接口调用方法时就会进入invoke方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } if (this.isDefaultMethod(method)) { return this.invokeDefaultMethod(proxy, method, args); } } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); }
-
当调用Girl g = mapper.queryById(1l);时,进入invoke方法,再进入 mapperMethod.execute(this.sqlSession, args);
在MapperMethod中 public Object execute(SqlSession sqlSession, Object[] args) { Object param; Object result; switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; //mapper.queryById(1l);是查询方法所以进入这里面 case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { //queryById返回值是null,进入这里 this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { //queryById返回值是集合,进入这里 result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { //queryById返回值是Map,进入这里 result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { //queryById返回值是游标类型,进入这里 result = this.executeForCursor(sqlSession, args); } else { //由于我们的queryById方法返回是user对象,所以进入这里 param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }
进入sqlSession.selectOne(this.command.getName(), param);
DefaultSqlSession中 public <T> T selectOne(String statement, Object parameter) { 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; } } public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { List var5; try { MappedStatement ms = this.configuration.getMappedStatement(statement); //执行器executor执行query方法 var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception var9) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9); } finally { ErrorContext.instance().reset(); } return var5; }
执行器executor里面封装了JDBC代码,通过JDBC对数据库进行操作
仿写Mybatis源码
Mybatis源码基本流程
1.png
详细步骤
新建项目,导入mysql的依赖
MyBatisXml类
package com.yy.entity;
import java.util.HashMap;
import java.util.Map;
public class MyBatisXml {
public static String namespace="com.yy.mapper.UserMapper";
public static Map<String,String> map=new HashMap<>();
static {
map.put("selectById","select * from users where id = ?");
}
}
User类
package com.yy.entity;
public class User {
private int id;
private String username;
private String password;
//get set tostring 已经省略
}
Executor接口
package com.yy.executor;
public interface Executor {
<T> T query(String sql,Object o);
}
SimpleExecutor接口
package com.yy.executor;
import com.yy.entity.User;
import java.sql.*;
public class SimpleExecutor implements Executor {
@Override
public <T> T query(String sql, Object o) {
Connection c = getConnection();
try {
PreparedStatement statement=c.prepareStatement(sql);
statement.setInt(1,(Integer) o);
ResultSet resultSet = statement.executeQuery();
User u=null;
while(resultSet.next()){
u=new User();
u.setId(resultSet.getInt("id"));
u.setUsername(resultSet.getString("username"));
u.setPassword(resultSet.getString("password"));
}
return (T)u;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public Connection getConnection(){
String driver="com.mysql.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/user?useSSL=false&serverTimezone=UTC";
String username="root";
String password="root";
try {
Class.forName(driver);
Connection c= DriverManager.getConnection(url,username,password);
return c;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}
}
SqlSession类
package com.yy.executor;
import com.yy.mapper.MapperProxy;
import java.lang.reflect.Proxy;
public class SqlSession {
private Executor e=new SimpleExecutor();
public <T> T selectOne(String sql,Object o){
return e.query(sql,o);
}
public <T> T getMapper(Class<T> tClass){
//第一个参数:类加载器,在类类型上调用getClassLoader()方法是得到当前类型的类加载器,我们知道在Java中所有的类都是通过加载器加载到虚拟机中的
//第二个参数:代理类需要实现的接口
//第三个参数:InvocationHandler动态代理
//因为被代理的接口是UserMapper,所以需要UserMapper的类加载器
return (T) Proxy.newProxyInstance(tClass.getClassLoader(),new Class[]{tClass},new MapperProxy(this));
}
}
MapperProxy类
package com.yy.mapper;
import com.yy.entity.MyBatisXml;
import com.yy.entity.User;
import com.yy.executor.SqlSession;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MapperProxy implements InvocationHandler {
private SqlSession sqlSession;
public MapperProxy(SqlSession sqlSession){
this.sqlSession=sqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("-------"+method);
if(method.getDeclaringClass().getName().equals(MyBatisXml.namespace)){
String sql = MyBatisXml.map.get(method.getName());
return sqlSession.selectOne(sql,args[0]);
}
return null;
}
}
测试类
@Test
public void test01(){
SqlSession session=new SqlSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(1);
System.out.println(user);
}
调试遇坑
调试仿写MYBATIS底层的代码的时候,出现mapper对象为null,MapperProxy类中invoke方法的 System.out.println("-------"+method);一直重复输出?
2.png
单步调试时IDEA会调用被代理类的toString()方法,调用一次被代理类的toString()方法就会进入一次invoke方法,因此会重复输出。invoke方法中method参数是代理类的toString,invoke的返回值就是对应的代理类toString方法的返回值,因为invoke方法返回的null所以mapper的toString方法返回的是null,即上图中的(mapper: "null")
深度解析
我们通过指定虚拟机启动参数,让它保存下来生成的代理类的 Class 文件。
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
通过IDEA反编译这个Class
package com.sun.proxy;
import com.yy.entity.User;
import com.yy.mapper.UserMapper;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements UserMapper {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final User selectById(int var1) throws {
try {
return (User)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.yy.mapper.UserMapper").getMethod("selectById", Integer.TYPE);
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
代码较多,我们拆开来看
public final class $Proxy0 extends Proxy implements UserMapper {
//首先,这个代理类的名字是很随意的,一个程序中如果有多个代理类要生成,「$Proxy + 数字」就是它们的类名。
//接着,你会注意到这个代理类继承 Proxy 类和我们指定的接口 UserMapper(之前如果指定多个接口,这里就会继承多个接口)。
//然后你会发现,这个构造器需要一个 InvocationHandler 类型的参数,并且构造器的主体就是将这个 InvocationHandler 实例传递到父类 Proxy 的对应字段进行保存,这也是为什么所有的代理类都必须使用 Proxy 作为父类的一个原因,就是为了公用父类中的 InvocationHandler 字段。
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
//这一块内容也算是代理类中较为重要的部分了,它将于虚拟机静态初始化这个代理类的时候执行。这一大段代码就是完成反射出所有接口中方法的功能,所有被反射出来的方法都对应一个 Method 类型的字段进行存储。
//除此之外,虚拟机还反射了 Object 中的三个常用方法,也就是说,代理类还会代理真实对象从 Object 那继承来的这三个方法。
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.yy.mapper.UserMapper").getMethod("selectById", Integer.TYPE);
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
//从父类 Proxy 中取出构造实例化时存入的处理器类,并调用它的 invoke 方法。
//方法的参数基本一样,第一个参数是当前代理类实例(事实证明这个参数传过去并没什么用),第二个参数是 Method 方法实例,第三个参数是方法的形式参数集合,如果没有就是 null。
public final User selectById(int var1) throws {
try {
return (User)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
结束语
总之,只有熟悉JDBC,反射,JDK动态代理以及类加载器才能对Mybatis的源码有深刻的理解,Mybatis避免了 JDBC 代码和手动设置参数以及获取结果集。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs映射成数据库中的记录。
单独使用mybatis是有很多限制的,而且很多业务系统本来就是使用spring来管理的事务,因此mybatis最好与spring集成起来使用。
网友评论