超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?

文章目录

  • 前言
  • Mybatis dao层两种实现方式的对比
    • 原始Dao开发
      • 原始Dao开发的弊端
    • 基于Mapper动态代理的开发方式
  • Mybatis动态代理实现方式的原理解析
    • 动态代理调用链路解析
      • 先给出链路调用结果
      • 1、调用方法的开始:session.getMapper
      • 2、DeaultSqlSession的getMapper
      • 3、Configuration的getMapper
      • 4、MapperRegistry的getMapper
      • 5、MapperProxyFactory的newIntance
      • 6、MapperProxy的invoke
      • 7、MapperMethod的execute
    • 动态代理类的接口注册/生成
      • 先给出链路调用结果
      • 1、SqlSessionFactoryBuilder().build全局配置文件解析
      • 2、XMLConfigBuilder#parse--parseConfiguration
      • 4、映射器Mapper文件的解析:XMLConfigBuilder#mapperElement
      • 5、XMLMapperBuilder#parse
      • 6、XMLMapperBuilder#configurationElement(XNode context)
      • 7、XMLMapperBuilder#bindMapperForNamespace()核心方法
      • 8、MapperRegistry#addMappper()
  • 总结

前言

在这里插入图片描述

  • 提到MyBatis,很多人可能已经使用过,MyBatis中的mapper接口实际上并没有对应的实现类,它的功能通过一个对应的xml配置文件来实现。这意味着当我们调用一个mapper接口时,我们实际上是在执行xml文件中定义的SQL语句来操作数据。
  • 那么Mybatis的mapper为啥只有接口没有实现类,它却能工作?答案很简单,动态代理,但是要真正理解这个动态代理的整个过程,还是有点费劲的,没事,接下来我们一步步解析。

Mybatis dao层两种实现方式的对比

我们先把刚开始学习 MyBatis 的两种开发方式都回顾一下,虽然我们说回头都是用 Mapper 接口动态代理开发,但原始 Dao 开发的方式也不要忘记,这种方式在以后的开发中可能还是用得上的。另外,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出mybatis动态代理替我们所做的工作,有利于我们理解动态代理的过程。

原始Dao开发

DepartmentDao接口:

public interface DepartmentDao {List<Department> findAll();Department findById(String id);
}

DepartmentDaoImpl:

  • 注意这里的关键代码 sqlSession.selectList("com.linkedbear.mybatis.mapper.DepartmentMapper.findAll"),需要我们自己手动调用SqlSession里面的方法,基于动态代理的方式最后的目标也是成功的调用到这里。

  • 注意:如果是添加,更新或者删除操作的话需要在方法中增加事务的提交。

public class DepartmentDaoImpl implements DepartmentDao {private SqlSessionFactory sqlSessionFactory;public DepartmentDaoImpl(SqlSessionFactory sqlSessionFactory) {this.sqlSessionFactory = sqlSessionFactory;}@Overridepublic List<Department> findAll() {//使用了 try-with-resource 的方式,可以省略 sqlSession.close();的代码。try (SqlSession sqlSession = sqlSessionFactory.openSession()) {return sqlSession.selectList("com.linkedbear.mybatis.mapper.DepartmentMapper.findAll");}}@Overridepublic Department findById(String id) {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {return sqlSession.selectOne("com.linkedbear.mybatis.mapper.DepartmentMapper.findById", id);}}
}

departmentMapper.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="departmentMapper"><select id="findAll" resultType="com.linkedbear.mybatis.entity.Department">select * from tbl_department</select><select id="findById" parameterType="string" resultType="com.linkedbear.mybatis.entity.Department">select * from tbl_department where id = #{id}</select>
</mapper>

MyBatisApplication 测试运行

public class MyBatisApplication {public static void main(String[] args) throws Exception {InputStream xml = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);DepartmentDao departmentDao = new DepartmentDaoImpl(sqlSessionFactory);List<Department> departmentList = departmentDao.findAll();departmentList.forEach(System.out::println);}
}

原始Dao开发的弊端

@Override
public List<Department> findAll() {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {return sqlSession.selectList("com.linkedbear.mybatis.mapper.DepartmentMapper.findAll");}
}@Override
public Department findById(String id) {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {return sqlSession.selectOne("com.linkedbear.mybatis.mapper.DepartmentMapper.findById", id);}
}

从上面的编码中,我们会发现,接口中存在好多重复代码:

可以发现,两个方法的方法名不同、参数列表不同,调用的 mapper 不同,返回值不同,其余的几乎完全相同!我们也知道,更好地优化方案是使用 Mapper 动态代理的方式。所以下面我们再接下来重点讲解使用 Mapper 动态代理的方式开发 Dao 层及其原理。

基于Mapper动态代理的开发方式

使用 Mapper 动态代理的方式开发,需要满足以下几个规范:

  • mapper.xml 中的 namespace 与 Mapper 接口的全限定名完全相同
  • mapper.xml 中定义的 statement ,其 id 与 Mapper 接口的方法名一致
  • Mapper 接口方法的方法参数类型,与 mapper.xml 中定义的 statement 的 parameterType 类型一致
  • Mapper 接口方法的返回值类型,与 mapper.xml 中定义的 statement 的 resultType 类型相同

使用动态代理的话Dao层的接口声明完成以后只需要在使用的时候通过SqlSession对象的getMapper方法获取对应Dao接口的代理对象,关键代码如下:

获取到dao层的代理对象以后通过代理对象调用查询方法就可以实现查询所有部门列表的功能。

public class MyBatisApplication {public static void main(String[] args) throws Exception {InputStream xml = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);SqlSession sqlSession = sqlSessionFactory.openSession();// 获取Mapper接口的代理DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);Department department = departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");System.out.println(department);}
}

Mybatis动态代理实现方式的原理解析

Mybatis的动态代理工作原理概括步骤如下:

  1. Mapper接口与XML的关联
    • Mybatis初始化时,会解析XML配置文件,将里面定义的SQL语句与Mapper接口的方法建立映射关系,并保存在配置对象中。
  2. 动态代理的创建
    • 当我们调用SqlSession.getMapper()方法时,Mybatis使用Java动态代理机制,为Mapper接口创建代理对象。
    • 代理对象的创建,主要通过MapperProxyFactory类来完成。
  3. 调用代理对象的方法时的内部处理
    • 当调用Mapper接口中的方法时,实际上调用的是代理对象的invoke方法。
    • invoke方法中,Mybatis使用之前的映射关系,找到与方法对应的SQL语句。
  4. SQL语句的执行
    • 找到SQL语句后,Mybatis会调用底层的执行器(Executor)来执行SQL语句,并完成参数的绑定,查询,以及结果返回。

动态代理调用链路解析

注意,这里先从我们的使用开始,讲解它是如何被调用的,后面再解析动态代理类的接口的注册

动态代理中最重要的类:Configuration、SqlSession、MapperRegistry、MapperProxyFactory、MapperProxy、MapperMethod,下面开始从入口方法到调用结束的过程分析。

先给出链路调用结果

getMapper方法的大致调用逻辑链是:

SqlSession#getMapper()-->DeaultSqlSession#getMapper——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> Proxy#newProxyInstance()-->MapperProxy#invoke-->MapperMethod#execute

1、调用方法的开始:session.getMapper

UserDao mapper = session.getMapper(UserDao.class); //因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。

2、DeaultSqlSession的getMapper

找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中

@Overridepublic <T> T getMapper(Class<T> type) {return configuration.<T>getMapper(type, this);}

3、Configuration的getMapper

找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。

MapperRegistry还有一个方法是public <T> void addMapper(Class<T> type) 后面再进行解析

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}

4、MapperRegistry的getMapper

4、找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了,通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的代理类,然后我们通过mapperProxyFactory.newInstance(sqlSession);进入MapperProxyFactory的newInstance方法中

knownMappers注意这个:后面会解析这个knownMappers 是怎么来的、如何使用的。

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {//根据Class对象获取创建动态代理的工厂对象MapperProxyFactoryfinal MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {//这里可以看到每次调用都会创建一个新的代理对象返回return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}

5、MapperProxyFactory的newIntance

找到MapperProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建代理类并返回。这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。

protected T newInstance(MapperProxy<T> mapperProxy) {//这里使用JDK动态代理,通过Proxy.newProxyInstance生成动态代理类// newProxyInstance的参数:类加载器、接口类、InvocationHandler接口实现类// 动态代理可以将所有接口的调用重定向到调用处理器InvocationHandler,调用它的invoke方法return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}

6、MapperProxy的invoke

找到MapperProxy类,发现其确实实现了JDK动态代理必须实现的接口InvocationHandler,所以我们重点关注invoke()方法,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute(),所以我们继续查看MapperMethod类的execute方法。

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;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//如果调用的是Object类中定义的方法,直接通过反射调用即可if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}//调用XxxMapper接口自定义的方法,进行代理//首先将当前被调用的方法Method构造成一个MapperMethod对象,然后掉用其execute方法真正的开始执行。final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}}

7、MapperMethod的execute

找到类MapperMethod类的execute方法,发现execute中通过调用本类中的其他方法获取并封装返回结果,我们来看一下MapperMethod整个类。

public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;}

MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。

sqlSession.selectList("com.linkedbear.mybatis.mapper.DepartmentMapper.findAll");

回忆一下上面的解析过程是不是就是一开始给出的链路调用流程

getMapper方法的大致调用逻辑链是: SqlSession#getMapper() ——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> Proxy#newProxyInstance()–>MapperProxy#invoke–>MapperMethod#execute

还有一点我们需要注意:我们通过SqlSession的getMapper方法获得接口代理来进行CRUD操作,其底层还是依靠的是SqlSession的使用方法

动态代理类的接口注册/生成

刚刚我先讲解了动态代理调用链路是怎么样的,但是刚刚上面步骤3、4中涉及的两个点,我这里再进行全面讲解:

Configuration中两个重要方法getMapper()和addMapper()–>实际实现是MapperRegistry,刚刚讲解了getMapper()的链路流程,接下来讲解addMapper()

  • getMapper(): 用于创建接口的动态类
  • addMapper(): mybatis在解析配置文件时,会将需要生成动态代理类的接口注册到其中
public class MyBatisApplication {public static void main(String[] args) throws Exception {InputStream xml = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);SqlSession sqlSession = sqlSessionFactory.openSession();// 获取Mapper接口的代理DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);Department department = departmentMapper.findById("18ec781fbefd727923b0d35740b177ab");System.out.println(department);}
}

在之前的实例中,我们通过调用sqlSession.getMapper()方法获得了DepartmentMapper接口的一个实例。实际上,通过这个方法我们得到的是DepartmentMapper接口的一个动态代理实现,然后我们可以借助这个动态代理实现来调用方法。在揭秘这些动态代理是如何创建出来的之前, 让我们先来审视一下SqlSessionFactory工厂的建立过程,以及它是如何处理相关的配置如mybatis-config文件,以及它是如何加载映射文件的。

先给出链路调用结果

new SqlSessionFactoryBuilder().build(xml)-->XMLConfigBuilder#parse-->>XMLConfigBuilder#parseConfiguration--->XMLConfigBuilder#mapperElement-->XMLMapperBuilder#mapperParser.parse()-->XMLMapperBuilder#configurationElement-->XMLMapperBuilder#bindMapperForNamespace-->Configuration#MapperRegistry#addMappper()

1、SqlSessionFactoryBuilder().build全局配置文件解析

private static SqlSessionFactory sqlSessionFactory;
static {try {sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));} catch (IOException e) {e.printStackTrace();}
}

我们使用new SqlSessionFactoryBuilder().build()的方式创建SqlSessionFactory工厂,走进build方法

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

2、XMLConfigBuilder#parse–parseConfiguration

对于mybatis的全局配置文件的解析,相关解析代码位于XMLConfigBuilder的parse()方法中:

 public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;//解析全局配置文件parseConfiguration(parser.evalNode("/configuration"));return configuration;}private void parseConfiguration(XNode root) {try {//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));//解析mapper映射器文件mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

从parseConfiguration方法的源代码中很容易就可以看出它对mybatis全局配置文件中各个元素属性的解析。当然最终解析后返回一个Configuration对象,Configuration是一个很重要的类,它包含了Mybatis的所有配置信息,它是通过XMLConfigBuilder取钱构建的,Mybatis通过XMLConfigBuilder读取mybatis-config.xml中配置的信息,然后将这些信息保存到Configuration中

4、映射器Mapper文件的解析:XMLConfigBuilder#mapperElement

动态代理类的接口注册/生成,就是由这部分实现的

//解析mapper映射器文件
mapperElement(root.evalNode("mappers"));

该方法是对全局配置文件中mappers属性的解析,走进去:

private void mapperElement(XNode parent) throws Exception {String resource;.....resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");XMLMapperBuilder mapperParser;InputStream inputStream;if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);inputStream = Resources.getResourceAsStream(resource);mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());mapperParser.parse();

XMLMapperBuilder和刚刚的XMLConfigBuilder是不是看起来很像,一个是解析configuration,一个是解析mapper

5、XMLMapperBuilder#parse

这里重点关注两个方法:configurationElement和bindMapperForNamespace

mapperParser.parse()方法就是XMLMapperBuilder对Mapper映射器文件进行解析,可与XMLConfigBuilder进行类比

  public void parse() {if (!configuration.isResourceLoaded(resource)) {configurationElement(parser.evalNode("/mapper")); //解析映射文件的根节点mapper元素configuration.addLoadedResource(resource);  bindMapperForNamespace(); //重点方法,这个方法内部会根据namespace属性值,生成动态代理类}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}

6、XMLMapperBuilder#configurationElement(XNode context)

该方法主要用于将mapper文件中的元素信息,比如insertselect这等信息解析到MappedStatement对象,并保存到Configuration类中的mappedStatements属性中,以便于后续动态代理类执行CRUD操作时能够获取真正的Sql语句信息

  private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace != null && !namespace.isEmpty()) {this.builderAssistant.setCurrentNamespace(namespace);this.cacheRefElement(context.evalNode("cache-ref"));this.cacheElement(context.evalNode("cache"));this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));this.resultMapElements(context.evalNodes("/mapper/resultMap"));this.sqlElement(context.evalNodes("/mapper/sql"));this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} else {throw new BuilderException("Mapper's namespace cannot be empty");}} catch (Exception var3) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);}}

XMLMapperBuilder#buildStatementFromContext方法就用于解析insert、select这类元素信息,并将其封装成MappedStatement对象,具体的实现细节这里就不细说了。

7、XMLMapperBuilder#bindMapperForNamespace()核心方法

该方法是核心方法,它会根据mapper文件中的namespace属性值,为接口生成动态代理类,这就来到了我们的主题内容——动态代理类是如何生成的。

bindMapperForNamespace方法源码如下所示:

 private void bindMapperForNamespace() {//获取mapper元素的namespace属性值String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {// 获取namespace属性值对应的Class对象boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//如果没有这个类,则直接忽略,这是因为namespace属性值只需要唯一即可,并不一定对应一个XXXMapper接口//没有XXXMapper接口的时候,我们可以直接使用SqlSession来进行增删改查}if (boundType != null) {if (!configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);//如果namespace属性值有对应的Java类,调用Configuration的addMapper方法,将其添加到MapperRegistry中configuration.addMapper(boundType);}}}}

这里提到了Configuration的addMapper方法,实际上Configuration类里面通过MapperRegistry对象维护了所有要生成动态代理类的XxxMapper接口信息,可见Configuration类确实是相当重要一类

public class Configuration {...protected MapperRegistry mapperRegistry = new MapperRegistry(this);...public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}...
}

8、MapperRegistry#addMappper()

Configuration将addMapper方法委托给MapperRegistry的addMapper进行的,源码如下:

  public <T> void addMapper(Class<T> type) {// 这个class必须是一个接口,因为是使用JDK动态代理,所以需要是接口,否则不会针对其生成动态代理if (type.isInterface()) {if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// 生成一个MapperProxyFactory,用于之后生成动态代理类knownMappers.put(type, new MapperProxyFactory<>(type));//以下代码片段用于解析我们定义的XxxMapper接口里面使用的注解,这主要是处理不使用xml映射文件的情况MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}

MapperRegistry内部维护一个映射关系,每个接口对应一个MapperProxyFactory(生成动态代理工厂类)

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

这样便于在后面调用MapperRegistry的getMapper()时,直接从Map中获取某个接口对应的动态代理工厂类,然后再利用工厂类针对其接口生成真正的动态代理类。

总结

Mybatis的动态代理工作原理概括步骤如下:

  1. Mapper接口与XML的关联也就是我们刚刚上面解析的动态代理类的接口注册/生成
    • Mybatis初始化时,会解析XML配置文件,将里面定义的SQL语句与Mapper接口的方法建立映射关系,并保存在配置对象中。
  2. 动态代理的创建2、3、4也就是我们刚刚上面解析的动态代理实际调用的链路流程
    • 当我们调用SqlSession.getMapper()方法时,Mybatis使用Java动态代理机制,为Mapper接口创建代理对象。
    • 代理对象的创建,主要通过MapperProxyFactory类来完成。
  3. 调用代理对象的方法时的内部处理
    • 当调用Mapper接口中的方法时,实际上调用的是代理对象的invoke方法。
    • invoke方法中,Mybatis使用之前的映射关系,找到与方法对应的SQL语句。
  4. SQL语句的执行
    • 找到SQL语句后,Mybatis会调用底层的执行器(Executor)来执行SQL语句,并完成参数的绑定,查询,以及结果返回。

参考文章:
https://www.cnblogs.com/hopeofthevillage/p/11384848.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/194376.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Selenium自动化测试:通过cookie绕过验证码的操作

验证码的处理 对于web应用&#xff0c;很多地方比如登录、发帖都需要输入验证码&#xff0c;类型也多种多样&#xff1b;登录/核心操作过程中&#xff0c;系统会产生随机的验证码图片&#xff0c;进行验证才能进行后续操作 ​解决验证码的方法如下&#xff1a; 1、开发做个万…

西瓜书-主要符号表

主要符号表 LaTeX符号说明How to read letter?\mathit{x}标量\boldsymbol{x}向量\mathrm{x}变量集\mathbf{A}矩阵\mathbf{I}单位阵\mathcal{X}样本空间或状态空间calligraphic X\mathcal{D}概率分布Ɗ calligraphic D\mathit{H}数据样本&#xff08;数据集)\mathcal{H}假设空…

基于OpenCV的手势1~5识别系统(源码&环境部署)

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义&#xff1a; 随着计算机视觉技术的快速发展&#xff0c;手势识别系统在人机交互、虚拟现实、智能监控等领域得到了广泛应用。手势识别系统可以通过分析人体的手势…

LTO编译器优化介绍以及开启方法

文章目录 LTO介绍LTO 开启方法 LTO介绍 LTO&#xff08;Link Time Optimization&#xff0c;链接时优化&#xff09;是一种在链接阶段进行优化的技术。传统的编译过程中&#xff0c;编译器仅能对单个编译单元进行优化。LTO 允许编译器看到跨编译单元的代码&#xff0c;从而进行…

jquery 判断是手机端还是电脑端

判断为手机端&#xff1a; var sUserAgent navigator.userAgent.toLowerCase(); var bIsIpad sUserAgent.match(/ipad/i) "ipad"; var bIsIphoneOs sUserAgent.match(/iphone os/i) "iphone os"; var bIsMidp sUserAgent.match(/midp/i) "mid…

【开源】基于Vue和SpringBoot的快递管理系统

项目编号&#xff1a; S 007 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S007&#xff0c;文末获取源码。} 项目编号&#xff1a;S007&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 数据中心模块2.2 快递类型模块2.3 快…

【读书笔记】微习惯

周日晚上尝试速读一本书《微习惯》&#xff0c;共七章看了下目录结构并不复杂&#xff0c;计划每章7-8分钟读完&#xff0c; 从20:15-21:00。读的时候&#xff0c;订下闹钟&#xff0c;催促着自己的进度。边读边记了一些要点和微信读书里面的划线。 第六章实践内容最为丰富&…

“此应用专为旧版android打造,因此可能无法运行”,问题解决方案

当用户在Android P系统上打开某些应用程序时&#xff0c;可能会弹出一个对话框&#xff0c;提示内容为&#xff1a;“此应用专为旧版Android打造&#xff0c;可能无法正常运行。请尝试检查更新或与开发者联系”。 随着Android平台的发展&#xff0c;每个新版本通常都会引入新的…

wvp gb28181 pro 平台国标级连功能说明

国标28181不同平台之间支持两种连接方式&#xff0c;平级和上下级&#xff0c;WVP目前支持向上级级联。 测试环境 测试平台上级&#xff1a;192.168.10.209&#xff08;Alam centos8&#xff09; 测试平台下级&#xff1a;192.168.10.206&#xff08;ky10_x86&#xff09; 下级…

KDE环境文件夹user-dirs为英文

KDE环境文件夹user-dirs 修改KDE主页文件夹为英文 该文件路径 ~/.config/user-dirs.dirs打开后会发现里面的内容如下 # This file is written by xdg-user-dirs-update # If you want to change or add directories, just edit the line youre # interested in. All local …

openGauss学习笔记-140 openGauss 数据库运维-例行维护-例行维护表

文章目录 openGauss学习笔记-140 openGauss 数据库运维-例行维护-例行维护表140.1 相关概念140.2 操作步骤140.3 维护建议 openGauss学习笔记-140 openGauss 数据库运维-例行维护-例行维护表 为了保证数据库的有效运行&#xff0c;数据库必须在插入/删除操作后&#xff0c;基于…

Ant Design Pro 框架设置API Token拦截器的功能

分享记录一个解决方法&#xff0c;希望对大家有帮助。 找到文件&#xff0c;然后定义一个方法。最后调用一下即可。 代码我也给你贴上了。 // 获取token 拦截方法 const setTokenRequest (config: any) > {const token 30|eh5GNXWRe5rO4XLjbbnqy132RABfiKqI338EoIhqc790a…

思维模型 反馈效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。反馈促进改进。 1 反馈效应的应用 1.1 反馈效应在营销中的应用 1 “可口可乐与百事可乐之战” 在 20 世纪 80 年代&#xff0c;可口可乐公司是全球最大的饮料公司之一&#xff0c;其市场…

利用reddit的api进行爬虫

1 介绍 Reddit是一个社交新闻聚合网站&#xff0c;用户可以发布、评价和讨论各种话题。Reddit的内容涵盖了广泛的主题&#xff0c;可以从中获取大量的文本数据进行情绪分析。 2 注册 2.1 注册reddit 你需要先注册一个reddit的账号。 2.2 注册api https://www.reddit.com/…

文科专业和编程基础薄弱的女孩子做软件的神器——aardio学习资源入门

相关资源 aardio 开发桌面应用&#xff0c;这几点必须要掌握&#xff01; - 星安果的文章 - 知乎 https://zhuanlan.zhihu.com/p/430970376 从使用者角度来为aardio编程软件说句话 - popdes的文章 - 知乎 https://zhuanlan.zhihu.com/p/461290014 故事和情怀 优点 1 快捷 2 …

HarmonyOS开发(九):数据管理

1、概述 1.1、功能简介 数据管理为开发者提供数据存储、数据管理能力。 它分为两个部分&#xff1a; 数据存储&#xff1a;提供通用数据持久化能力&#xff0c;根据数据特点&#xff0c;分为用户首选项、键值型数据库和关系型数据库。数据管理&#xff1a;提供高效的数据管…

Bean的加载控制

Bean的加载控制 文章目录 Bean的加载控制编程式注解式ConditionalOn*** 编程式 public class MyImportSelector implements ImportSelector {Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {try {Class<?> clazz Class.forName("…

UCore-OS实验Lab0

实验内容&#xff1a;搭建ucore-os的实验环境 实验准备内容&#xff1a;vmware虚拟机&#xff0c;ubuntu22.04镜像&#xff0c;qemu7.0.0源码 ucore代码地址 GitHub - chyyuu/os_kernel_lab at x86-32 实验步骤&#xff1a; 在vmware中安装ubuntu&#xff0c;因为我个人喜欢…

openEuler学习04-ssl升级到openssl-1.1.1w

当前环境ssl的版本是 1.1.1f &#xff0c;计划升级到openssl-1.1.1w [roottest ~]# more /etc/os-release NAME"openEuler" VERSION"20.03 (LTS-SP3)" ID"openEuler" VERSION_ID"20.03" PRETTY_NAME"openEuler 20.03 (LTS-SP3)&q…

ES6 Promise的用法,async/await异步处理同步化

文章目录 一、什么是promise &#xff1f;二、await / async ES7的新规范&#xff0c;异步处理同步化 一、什么是promise &#xff1f; promise是解决异步的方法&#xff0c;本质上是一个构造函数&#xff0c;可以用它实例化一个对象。对象身上有resolve、reject、all&#xff…