MyBatis 的 DAO 接口跟 XML 文件里面的 SQL 是如何建立关系的?

本文深入解析MyBatis与Spring集成的工作原理,包括如何通过SqlSessionFactory建立配置关系,Mapper接口的代理创建过程,以及如何解决XML文件与DAO接口间可能的冲突问题。

我们在使用 MyBatis 的时候,都用的是 Dao 接口和 XML 文件里的 SQL 一一对应来进行使用的。那你是否思考过二者是如何建立关系的?

在开始正文之前,首先解释 Dao 接口和 XML 文件里的 SQL 是如何一一对应的?

一句话讲完就是:MyBatis 会先解析这些 XML 文件,通过 XML 文件里面的命名空间 (namespace)跟 DAO 建立关系;然后 XML 中的每段 SQL 会有一个id 跟 DAO 中的接口进行关联。

那么问题来了: "如果我有两个这个 XML 文件都跟这个 DAO 建立关系了,那不是就是冲突了?"

带着这个疑问我们就要开始下面的正题了!

一、初始化

首先我们要知道每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。

SqlSessionFactory 是一个接口,它里面其实就两个方法:openSessiongetConfiguration

其中,openSession 方法是为了获取一个 SqlSession 对象,完成必要数据库增删改查功能。但是,SqlSessionFactory 属性太少了,所以需要 getConfiguration 的配合;来配置 mapper 映射文件、SQL 参数、返回值类型、缓存等属性。

/**
 * Creates an {@link SqlSession} out of a connection or a DataSource
 * 
 * @author Clinton Begin
 */
public interface SqlSessionFactory {


  SqlSession openSession();


  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);


  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);


  Configuration getConfiguration();


}

可以看到 getConfiguration 是属于 Configuration 类的一个方法。你可以把它当成一个配置管家。MyBatis 所有的配置信息都维持在 Configuration 对象之中,基本每个对象都会持有它的引用。

但日常开发中我们都是将 MyBatis 与 Spring 一起使用的,所以把实例化交给 Spring 处理。

因此我们可以看下 org.MyBatis.spring.SqlSessionFactoryBean,它实现了 InitializingBean 接口。这说明,在这个类被实例化之后会调用到 afterPropertiesSet()。它只有一个方法

public void afterPropertiesSet() throws Exception {
	this.sqlSessionFactory = buildSqlSessionFactory();
}

而这个 afterPropertiesSet 方法只有一个动作,就是 buildSqlSessionFactory。它可以分为两部分来看:

  • 1、从配置文件的 property 属性中加载各种组件,解析配置到 configuration

  • 2、加载 mapper 文件,解析 SQL 语句,封装成 MappedStatement 对象,配置到 configuration 中。

二、mapper 接口方法是怎样被调用到的?

大致有如下两种方式:

  • MyBatis 提供的 API

使用 MyBatis 提供的 API 进行操作,通过获取 SqlSession 对象,然后根据 Statement Id 和参数来操作数据库。

String statement = "com.mmzsblog.business.DAO.MemberMapper.getMemberList";
List<Member> result = sqlsession.selectList(statement);
  • mapper 接口

定义 Mapper 接口,并在里面定义一系列业务数据操作方法。在 Service 层通过注入 mapper 属性,调用其方法就可以执行数据库操作。就像下面这样

public interface MemberMapper {	
	List<Member> getMemberList();
}


@Service
public class MemberServiceImpl implements MemberService{
	@Resource
	private MemberMapper memberMapper;
	
	@Override
	public List<Member> getMemberList() {
		return memberMapper.getMemberList();
	}
}

那么,MemberMapper 只是个接口,并没有任何实现类。我们在调用它的时候,它是怎样最终执行到我们的 SQL 语句的呢?

三、Mapper 接口的代理创建过程

3.1、首先我们会配置需要扫描的基本包路径

通过注解的方式配置:

@MapperScan({"com.mmzsblog.business.DAO"})

或者xml的方式配置:

<bean class="org.MyBatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.mmzsblog.business.DAO" />
	<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
3.2、开始扫描

我们来到 org.MyBatis.spring.mapper.MapperScannerConfigurer 这个类,可以看到它实现了几个接口。

其中的重点是 BeanDefinitionRegistryPostProcessor。它可以动态的注册 Bean 信息,方法为 postProcessBeanDefinitionRegistry()

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            this.processPropertyPlaceHolders();
        }
        
        // 创建ClassPath扫描器,设置属性,然后调用扫描方法
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        // 创建ClassPath扫描器,设置属性,然后调用扫描方法
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

ClassPathMapperScanner 继承自 Spring 中的类 ClassPathBeanDefinitionScanner,所以它的 scan 方法会调用到父类 ClassPathBeanDefinitionScanner 的 scan 方法,

public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
    ……
    public int scan(String... basePackages) {
        // 
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
        this.doScan(basePackages);
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }


        return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
    }
    ……
}

而在父类的 scan 方法中又调用到子类 ClassPathMapperScanner 重写的 doScan 方法。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    ……
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            this.processBeanDefinitions(beanDefinitions);
        }


        return beanDefinitions;
    }
    ……
}

此处 super.doScan(basePackages) 是 Spring 中的方法,就不贴代码多叙述了,想详细了解的话,可以自己翻一下源码哦。

3.3、bean 注册完成并创建 sqlSession 代理

并且经过上面这些步骤,此时已经扫描到了所有的 Mapper 接口,并将其注册为 BeanDefinition 对象。而注册的时候就是用到了上面 doScan 方法中的 processBeanDefinitions 方法。

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    ……
    // 设置 beanClass
    private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean();
    ……
    
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        Iterator var3 = beanDefinitions.iterator();


        while(var3.hasNext()) {
            BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
            GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
            }
            // 将 mapper 接口的名称添加到构造参数
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            // 设置 BeanDefinition的class
            definition.setBeanClass(this.mapperFactoryBean.getClass());
            // 添加属性 addToConfig
            definition.getPropertyValues().add("addToConfig", this.addToConfig);
            boolean explicitFactoryUsed = false;
            // 添加属性 sqlSessionFactory
            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
                definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
                explicitFactoryUsed = true;
            } else if (this.sqlSessionFactory != null) {
                definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
                explicitFactoryUsed = true;
            }


            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
                if (explicitFactoryUsed) {
                    this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
                }


                definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
                explicitFactoryUsed = true;
            } else if (this.sqlSessionTemplate != null) {
                if (explicitFactoryUsed) {
                    this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
                }


                definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
                explicitFactoryUsed = true;
            }


            if (!explicitFactoryUsed) {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
                }


                definition.setAutowireMode(2);
            }
        }
    }
    ……
}

处理的过程相对比较简单,只是往 BeanDefinition 对象中设置了一些属性。例如:

  • 设置 beanClass

设置 BeanDefinition 对象的 BeanClassMapperFactoryBean<?> 。这就相当于使用 MemberMapper 注册时:当前的 mapper 接口在 Spring 容器中,beanName 是 memberMapper,beanClass 是 MapperFactoryBean.class。故在Spring 的 IOC 初始化的时候,实例化的对象就是 MapperFactoryBean 对象。

  • 设置 sqlSessionFactory 属性

BeanDefinition 对象添加属性 sqlSessionFactory,是为了 BeanDefinition 对象设置 PropertyValue 的时候,方便调用到 setSqlSessionFactory()

3.4、创建 sqlSession 代理类

最终在 setSqlSessionFactory 这个方法里,sqlSession 获取到的是 SqlSessionTemplate 实例。而在 SqlSessionTemplate 对象中,主要包含 sqlSessionFactorysqlSessionProxy,而 sqlSessionProxy 实际上是 SqlSession 接口的代理对象。实际调用的是代理类的 invoke 方法。

public class MapperProxy<T> implements InvocationHandler, Serializable {
  ……
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      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);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  ……
}
3.5、小结

Mapper 接口的代理创建过程大致如下:

  • 1、扫描 mapper 接口基本包路径下的所有对象,将其注册为 BeanDefinition 对象

  • 2、设置 BeanDefinition 的对象的 beanClass 和 sqlSessionFactory 属性(而其中获取 BeanDefinition 对象的时候,调用其工厂方法 getObject,返回 mapper 接口的代理类)

  • 3、设置 sqlSessionFactory 属性的时候,会调用 SqlSessionTemplate 的构造方法,创建 SqlSession 接口的代理类

最后我们在 Service 层,通过

@Resource 
private MemberMapper memberDao;

注入属性的时候,返回的就是代理类。执行 memberDao 的方法的时候,实际调用的也是代理类的 invoke 方法。

四、回答最开始的问题

MyBatis 在初始化 SqlSessionFactoryBean 的时候,找到配置需要扫描的基本包路径去解析里面所有的 XML 文件。重点就在如下两个地方:

1、创建 SqlSource

MyBatis 会把每个 SQL 标签封装成 SqlSource 对象。然后根据 SQL 语句的不同,又分为动态 SQL 和静态 SQL 。其中,静态 SQL 包含一段 String 类型的 SQL 语句;而动态 SQL 则是由一个个 SqlNode 组成

2、创建 MappedStatement

XML 文件中的每一个 SQL 标签就对应一个 MappedStatement 对象,这里面有两个属性很重要。

  • id

全限定类名+方法名组成的 ID。

  • sqlSource

当前 SQL 标签对应的 SqlSource 对象。创建完 MappedStatement 对象,会将它缓存到 Configuration#mappedStatements 中。

前面初始化中提到的 Configuration 对象,我们知道它就是 MyBatis 中的配置大管家,基本所有的配置信息都维护在这里。

例如下面这样一段代码:

<!-- namespace 的值就是全限定类名 -->
<mapper namespace="com.java.mmzsblog.DAO.MemberMapper">
    ……
    <!-- select 标签中 id 的值就是方法名,它和全限定类中的方法名是对应的 -->
    <select id="getMemberById" resultType="com.java.mmzsblog.entity.member">
        select * from member
        <where>
            <if test="memberId!=null">
                and member_id=#{memberId}
            </if>
        </where>
    </select>
    ……
</mapper>

把所有的 XML 都解析完成之后,Configuration 就包含了所有的 SQL 信息。然后解析完成的 XML 大概就是这样了:

看到上面的图示,聪明如你,也许就大概知道了。当我们执行 MyBatis 方法的时候,就通过全限定类名+方法名找到 MappedStatement 对象,然后解析里面的 SQL 内容,执行即可。

精彩回顾:

一文带你了解 Redis 的发布与订阅的底层原理

Gateway:数据报文走出局域网的必经之路

如果有人再问你 Java IO,把这篇文章砸他头上

< END >

欢迎加入我们的知识星球,一起探讨技术、畅享人生。加入方式,长按下方二维码噢

已在知识星球更新的优质内容如下:

如果你喜欢这篇文章,欢迎在看、转发哦。

让我们努力向前奔跑吧!

<think>我们正在讨论MyBatisXML映射文件如何映射到DAO接口的原理。根据之前的引用,特别是引用[1][2][3][4],我们可以总结出以下关键点:1.**Mapper接口XML映射文件的对应关系**:-Mapper接口的全限定名(包括包名和接口名)必须与XML映射文件中`<mapper>`标签的`namespace`属性值完全一致。-Mapper接口中的方法名必须与XML映射文件中定义的SQL语句的`id`属性值相同。-方法的输入参数类型和输出参数类型也需要与XML中定义的`parameterType`和`resultType`(或`resultMap`)相匹配。2.**动态代理机制**:-MyBatis在运行时通过动态代理技术为Mapper接口生成代理对象(ProxyObject)。-当调用Mapper接口的方法时,代理对象会拦截方法调用,并根据接口的全限定名和方法名,拼接成一个字符串(如`com.example.MyMapper.selectById`),然后根据这个字符串去查找对应的MappedStatement(即映射的SQL语句)。3.**MappedStatement的作用**:-每一个在XML映射文件中定义的`<select>`,`<insert>`,`<update>`,`<delete>`标签都会被解析为一个MappedStatement对象,其中包含了SQL语句、参数映射、结果映射等信息。4.**绑定过程**:-在MyBatis初始化时,会解析XML映射文件,将解析得到的MappedStatement注册到全局配置对象(Configuration)中,以`namespace.id`作为key。-同时,当解析到一个XML映射文件时,会检查`namespace`属性是否对应一个存在的接口,如果存在,则为该接口生成动态代理类,并将该接口和代理工厂注册到MapperRegistry中。5.**方法调用流程**:-当我们通过SqlSession的`getMapper`方法获取Mapper接口的实例时,实际上是从MapperRegistry中获取对应的代理工厂,然后创建代理对象。-当我们调用代理对象的方法时,代理对象会调用`MapperProxy`的`invoke`方法,该方法会根据接口类型和方法信息,获取对应的MappedStatement,并执行相应的数据库操作。现在,我们将根据以上原理详细解释MyBatis的底层实现机制,并按照要求进行回答。###MyBatisXML映射文件DAO接口的映射原理MyBatis的核心设计思想是通过XML配置或注解将Java方法与SQL语句进行解耦,实现SQL和Java代码的分离。其中,XML映射文件DAO接口的映射机制建立在**动态代理**和**方法签名解析**的基础上。####🔧1.**映射文件解析过程**在MyBatis启动时,配置文件(如`mybatis-config.xml`)中的每个`<mapper>`配置都会被加载:-如果配置的是XML映射文件路径(如`<mapperresource="com/example/UserMapper.xml"/>`),MyBatis会解析该XML文件。-解析时,会将`<mappernamespace="...">`中的`namespace`属性作为接口的全限定名进行验证(是否与某个Java接口对应)。-随后解析XML中的SQL标签(如`<selectid="methodName">`),每个SQL标签被解析为一个`MappedStatement`对象,该对象包含以下关键信息:-`id`:SQL语句的唯一标识,格式为`${namespace}.${id}`(如`com.example.UserMapper.selectById`)。-`sqlSource`:实际的SQL语句(可能包含动态SQL标签)。-`parameterType`:参数类型。-`resultType`或`resultMap`:结果映射。这些`MappedStatement`对象会被存储在全局`Configuration`对象的`mappedStatements`属性中(一个Map结构)[^4]。####🧬2.**接口XML绑定机制**-**接口合法性验证**:在解析XML过程中,MyBatis会检查`namespace`属性值是否指向一个存在的Java接口。如果存在,则建立映射关系。-**动态代理对象生成**:通过`SqlSession.getMapper(Class<T>type)`获取接口实例时:1.MyBatis从`MapperRegistry`中获取该接口对应的`MapperProxyFactory`(代理工厂)[^1]。2.调用`newInstance(MapperProxy<T>)`生成代理对象:```javapublicTnewInstance(SqlSessionsqlSession){finalMapperProxy<T>mapperProxy=newMapperProxy<>(sqlSession,mapperInterface,methodCache);return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),newClass[]{mapperInterface},mapperProxy);}```3.生成的代理对象实现了目标接口,并将所有方法调用委托给`MapperProxy`[^1]。####🔌3.**方法调用拦截与路由**当调用代理对象的方法时:1.动态代理拦截器`MapperProxy.invoke()`会执行以下操作:-如果方法是`Object`类中的方法(如`toString`,`equals`),直接调用。-否则,将方法签名(接口名+方法名)作为key,查找对应的`MappedStatement`[^2]。2.根据方法参数类型和XML配置,动态执行以下步骤:-将Java参数转换为SQL参数(通过`TypeHandler`)。-执行SQL(调用`Executor.query()`或`Executor.update()`)。-将结果集转换为返回类型(如`List<User>`或`User`)。####📊4.**映射关系匹配示例假设有以下DAO接口:```javapackagecom.example;publicinterfaceUserMapper{UserselectById(intid);}```对应的XML映射文件(UserMapper.xml):```xml<mappernamespace="com.example.UserMapper"><selectid="selectById"parameterType="int"resultType="com.example.User">SELECT*FROMuserWHEREid=#{id}</select></mapper>```当调用`userMapper.selectById(1)`时:1.代理对象解析方法签名为:`com.example.UserMapper.selectById`。2.在`Configuration.mappedStatements`中查找key为`com.example.UserMapper.selectById`的`MappedStatement`。3.执行SQL语句:`SELECT*FROMuserWHEREid=?`,参数`?`替换为1。####🌐5.**动态代理技术的应用**MyBatis通过JDK动态代理机制实现DAO接口的映射:-**依赖接口而非类**:代理对象基于接口实现,因此要求DAO必须是接口(无实现类),这也是为什么MyBatisDAO通常定义为接口[^1]。-**方法签名一致性**:方法名称、参数类型、返回值类型必须与XML配置严格匹配,否则会因找不到`MappedStatement`而报错[^3]。####⚙️6.**注解与XML的共存机制**如果同时使用XML和注解配置同一方法,MyBatis的处理逻辑是:-优先使用XML配置的`MappedStatement`(覆盖注解)。-注解配置通过`@Select`,`@Insert`等声明SQL语句,同样会转化为`MappedStatement`存储在全局配置中。####🚀性能优化策略-**`MapperRegistry`缓存**:每个接口的代理工厂只创建一次,提升多次调用性能。-**方法级缓存**:`MapperMethod`对象(包含SQL执行逻辑)被缓存在`methodCache`中,避免重复解析方法签名[^1]。####§相关问题1.如果XML中的`namespace`写错,MyBatis会如何处理?2.如何在MyBatis中同时使用XML映射和注解配置?3.MyBatis如何支持重载的DAO方法?4.为什么Dao接口没有实现类,MyBatis却能执行方法?5.如何动态替换MyBatis映射文件中的SQL?---以上原理深刻体现了MyBatis通过**接口抽象**和**动态代理**实现了SQL操作的优雅封装,同时提供了灵活的扩展机制。####参考资料[^1]:MyBatis动态代理机制源码分析:`org.apache.ibatis.binding.MapperProxy`[^2]:XML映射与DAO接口绑定原理:`org.apache.ibatis.session.Configuration#addMapper`[^3]:MappedStatement注册过程:`org.apache.ibatis.builder.xml.XMLMapperBuilder#parse`[^4]:MyBatis官方文档关于XML配置说明:[mybatis.org](https://mybatis.org/mybatis-3/configuration.html)</think> ### MyBatis XML映射到DAO接口的实现原理 MyBatis通过**动态代理**和**方法签名映射**实现XML文件DAO接口的无缝连接,以下是核心实现机制: #### 🔍 一、核心映射机制(基于方法签名) 1. **命名空间绑定** XML中的`<mapper namespace="com.example.UserDao">`必须**完全匹配**DAO接口的全限定名: ```xml <!-- XML映射文件 --> <mapper namespace="com.example.UserDao"> <select id="findById" parameterType="int" resultType="User"> SELECT * FROM users WHERE id = #{id} </select> </mapper> ``` 2. **方法签名映射** 接口方法与XML语句的映射关系: ```java // DAO接口声明 public interface UserDao { User findById(int id); // 方法名匹配XML的id } ``` - **映射键值**:`namespace.id` → `com.example.UserDao.findById` - **参数匹配**:方法参数类型对应XML的`parameterType` - **返回类型**:方法返回类型匹配XML的`resultType/resultMap` #### 🧬 二、动态代理实现流程 1. **初始化阶段(加载映射)** MyBatis启动时解析XML映射文件: ```mermaid graph TB A[XML文件解析] --> B[创建MappedStatement对象] B --> C{存储到<br>Configuration.mappedStatements} C -->|Key = namespace+id| D["Map<String, MappedStatement>"] ``` 2. **接口代理生成** ```java // 通过SqlSession获取DAO代理 UserDao userDao = sqlSession.getMapper(UserDao.class); // 底层实现: public class DefaultSqlSession { public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } } ``` - **代理工厂**:`MapperProxyFactory`创建JDK动态代理对象 - **代理处理器**:`MapperProxy`实现`InvocationHandler`接口 3. **方法调用拦截** ```java // MapperProxy核心逻辑 public Object invoke(Object proxy, Method method, Object[] args) { // 拼接方法全路径:com.example.UserDao.findById String commandKey = method.getDeclaringClass().getName() + "." + method.getName(); // 获取MappedStatement MappedStatement ms = configuration.getMappedStatement(commandKey); // 执行SQL(委托给Executor) return executor.query(ms, args); } ``` #### ⚙️ 三、关键数据结构 1. **MappedStatement 成员** | 属性 | 说明 | XML对应标签 | |------|------|-------------| | `id` | 唯一标识 | `<select>/<update>`等 | | `sqlSource` | SQL语句 | SQL文本 | | `parameterType` | 参数类型 | `parameterType` | | `resultType` | 返回类型 | `resultType` | 2. **Configuration 注册中心** ```java public class Configuration { // 存储所有映射关系 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>(); // 注册Mapper接口 public <T> void addMapper(Class<T> type) { // 验证接口名=namespace // 动态生成代理类 } } ``` #### 💡 四、设计优势解析 1. **解耦实现** - **接口无实现类**:开发者只需声明接口方法 - **SQL集中管理**:XML分离业务代码与SQL逻辑 2. **动态扩展能力** 通过`@Select`等注解可实现XML替代方案: ```java @Select("SELECT * FROM users WHERE id = #{id}") User findById(int id); ``` #### ⚠️ 五、常见问题处理 1. **映射失败场景** | 问题类型 | 原因 | |----------|------| | `BindingException` | namespace不匹配或方法名错误 | | `ParameterType`错误 | 接口参数与SQL参数不兼容 | | `ResultType`冲突 | 返回对象字段与SQL列不匹配 | 2. **接口方法重载限制** MyBatis**不支持重载方法**(如`findById(int)`和`findById(String)`),因为: $$ \text{映射键} = \text{namespace} + \text{方法名} $$ 方法名重复会导致映射键冲突 #### 🧪 六、验证实验 可在MyBatis日志开启DEBUG模式观察: ```properties # log4j.properties log4j.logger.org.apache.ibatis=DEBUG ``` 执行时输出: ``` DEBUG - Creating proxy for mapper interface com.example.UserDao DEBUG - Mapped statement: com.example.UserDao.findById ``` ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值