Mybatis中Dao接口的工作原理

简介: Mybatis中Dao接口的工作原理

解释:


Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。


下面来看这个问题:

如何通过全限名+方法名来定位一个mapper的?


初始化

首先mybatis会把各种mapper映射进行初始化

对于mybatis与spring结合的项目,最开始是从SqlSessionFactoryBean开始

**SqlSessionFactoryBean类**


```java

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

   //其他省略...

   if (xmlConfigBuilder != null) {

     try {

         //初始化mybatis config配置文件

       xmlConfigBuilder.parse();

       LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");

     } catch (Exception ex) {

       throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);

     } finally {

       ErrorContext.instance().reset();

     }

   }

      //其他省略...

       try {

         XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),

             targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());

         //格式化mapper xml文件

         xmlMapperBuilder.parse();

       } catch (Exception e) {

         throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);

       } finally {

         ErrorContext.instance().reset();

       }

       LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");

   //其他省略...

   return this.sqlSessionFactoryBuilder.build(targetConfiguration);

 }

```




**XMLMapperBuilder类**


```java

/**

  * 解析mapper映射配置文件

  */

 public void parse() {

   //判断是否已经加载过该映射文件

   if (!configuration.isResourceLoaded(resource)) {

     configurationElement(parser.evalNode("/mapper"));

     configuration.addLoadedResource(resource);

     //注册 Mapper 接 口

     bindMapperForNamespace();

   }

   //处理 configurationElement ()方法中解析失败的<resultMap>节点

   parsePendingResultMaps();

   //处理 configurationElement ()方法中 解析失败的< cache-ref>节点

   parsePendingCacheRefs();

   //处理 configurationElement ()方法中 解析失败的 SQL 语句节点

   parsePendingStatements();

 }

```


**bindMapperForNamespace()方法**


```java

//绑定Mapper接口

 private void bindMapperForNamespace() {

   //获取当前namespace名称

   String namespace = builderAssistant.getCurrentNamespace();

   if (namespace != null) {

     Class<?> boundType = null;

     try {

       boundType = Resources.classForName(namespace);

     } catch (ClassNotFoundException e) {

       //ignore, bound type is not required

     }

     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#loadXmlResource

         configuration.addLoadedResource("namespace:" + namespace);

         configuration.addMapper(boundType);

       }

     }

   }

 }

```


你从configuration.addMapper(boundType)进入,到最后你会发现,会以类全限定名为key,mapper代理作为value放入knownMappers 中


**MapperRegistry类**


```java


public <T> void addMapper(Class<T> type) {

     //....

     try {

       //放入knownMappers中

       knownMappers.put(type, new MapperProxyFactory<T>(type));

       // It's important that the type is added before the parser is run

       // otherwise the binding may automatically be attempted by the

       // mapper parser. If the type is already known, it won't try.

       MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);

       parser.parse();

       loadCompleted = true;

     } finally {

       if (!loadCompleted) {

         knownMappers.remove(type);

       }

     }

   }

 }

```


另外还有一个地方也会初始化,在初始化mybatis config配置文件的时候,可以看**XMLConfigBuilder.java**中mapperElement方法


### 定位


测试用例


```java

 @Test

 public void shouldSelectBlogWithPostsUsingSubSelect() throws Exception {

   SqlSession session = sqlSessionFactory.openSession();

   try {

     //getMapper返回一个MapperProxy对象

     BoundBlogMapper mapper = session.getMapper(BoundBlogMapper.class);

     Blog b = mapper.selectBlogWithPostsUsingSubSelect(1);

     assertEquals(1, b.getId());

     session.close();

     assertNotNull(b.getAuthor());

     assertEquals(101, b.getAuthor().getId());

     assertEquals("jim", b.getAuthor().getUsername());

     assertEquals("********", b.getAuthor().getPassword());

     assertEquals(2, b.getPosts().size());

   } finally {

     session.close();

   }

 }

```


MapperProxy类实现了InvocationHandler接口,代理类调用的时候会执行invoke方法

**MapperProxy类**


```java

@Override

 public 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)) {

       //对jdk7以上版本,动态语言的支持

       return invokeDefaultMethod(proxy, method, args);

     }

   } catch (Throwable t) {

     throw ExceptionUtil.unwrapThrowable(t);

   }

   //从缓存中获取 MapperMethod对象,如果缓存中没有,则创建新的 MapperMethod对象并添加到缓存中

   final MapperMethod mapperMethod = cachedMapperMethod(method);

   //调用 MapperMethod.execute ()方法执行 SQL 语 句

   return mapperMethod.execute(sqlSession, args);

 }

```


看 cachedMapperMethod(method)方法


```java

private MapperMethod cachedMapperMethod(Method method) {

   MapperMethod mapperMethod = methodCache.get(method);

   if (mapperMethod == null) {

     创建 MapperMethod 对象 , 并添加到 methodCache 集合 中缓存

     mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());

     methodCache.put(method, mapperMethod);

   }

   return mapperMethod;

 }

```


MapperMethod 中封装了 Mapper接口中对应方法的信息,以及对应 SQL 语句的信息


```java

 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {

   //记录了 SQL语句的名称和类型

   this.command = new SqlCommand(config, mapperInterface, method);

   //Mapper 接 口中对应方法的相关信息

   this.method = new MethodSignature(config, mapperInterface, method);

 }

```


看SqlCommand -->resolveMappedStatement你会发现,sql语句的名称是由:Mapper 接口的名称与对应的方法名称组成的

相关文章
|
6月前
|
SQL 缓存 Java
框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性
本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。
|
6月前
|
人工智能 Java 数据库连接
MyBatis Plus 使用 Service 接口进行增删改查
本文介绍了基于 MyBatis-Plus 的数据库操作流程,包括配置、实体类、Service 层及 Mapper 层的创建。通过在 `application.yml` 中配置 SQL 日志打印,确保调试便利。示例中新建了 `UserTableEntity` 实体类映射 `sys_user` 表,并构建了 `UserService` 和 `UserServiceImpl` 处理业务逻辑,同时定义了 `UserTableMapper` 进行数据交互。测试部分展示了查询、插入、删除和更新的操作方法及输出结果,帮助开发者快速上手 MyBatis-Plus 数据持久化框架。
422 0
|
10月前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
11月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
291 10
|
11月前
|
SQL XML Java
Mybatis的原理和MybaitsPlus
这篇文章对比分析了Mybatis和Mybatis Plus的特点与底层实现机制,探讨了两者之间的差异及各自的优势。
320 0
|
XML Java 数据库连接
MyBatis中的接口代理机制及其使用
【8月更文挑战第5天】MyBatis的接口代理机制是其核心功能之一,允许通过定义接口并在运行时生成代理对象来操作数据库。开发者声明一个带有`@Mapper`注解的接口,MyBatis则依据接口方法、映射配置(XML或注解)及数据库信息动态生成代理类。此机制分为四步:创建接口、配置映射文件或使用注解、最后在业务逻辑中注入并使用代理对象。这种方式简化了数据库操作,提高了代码的可读性和可维护性。例如,在电商系统中可通过`OrderMapper`处理订单数据,在社交应用中利用`MessageMapper`管理消息,实现高效且清晰的数据库交互。
132 5
|
SQL Java 数据库连接
springboot~mybatis-pagehelper原理与使用
【7月更文挑战第15天】MyBatis-PageHelper是用于MyBatis的分页插件,基于MyBatis的拦截器机制实现。它通过在SQL执行前动态修改SQL语句添加LIMIT子句以支持分页。使用时需在`pom.xml`添加依赖并配置方言等参数。示例代码: PageHelper.startPage(2, 10); List&lt;User&gt; users = userMapper.getAllUsers(); PageInfo&lt;User&gt; pageInfo = new PageInfo&lt;&gt;(users); 这使得分页查询变得简单且能获取总记录数等信息。
330 2
|
3月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
497 1
Spring boot 使用mybatis generator 自动生成代码插件
|
6月前
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于注解的整合
本文介绍了Spring Boot集成MyBatis的两种方式:基于XML和注解的形式。重点讲解了注解方式,包括@Select、@Insert、@Update、@Delete等常用注解的使用方法,以及多参数时@Param注解的应用。同时,针对字段映射不一致的问题,提供了@Results和@ResultMap的解决方案。文章还提到实际项目中常结合XML与注解的优点,灵活使用两者以提高开发效率,并附带课程源码供下载学习。
471 0