Mybatis八股分析

Mybatis八股分析
mengnankkzhouMybatis
1.UserMappe这个类为啥要是接口呢?
MyBatis的Mapper之所以必须定义为接口,其根本原因在于MyBatis框架在底层使用了JDK动态代理(JDK Dynamic Proxy)技术,来为我们自动地生成这个接口的实现类。
只定义了UserMapper接口,并在XML文件中写了SQL,但我们从来没有手动编写过一个class UserMapperImpl implements UserMapper。然而,在Service层,我们却可以直接@Autowired注入一个UserMapper的实例并调用它的方法。
- 启动时扫描:当Spring容器启动时,MyBatis的
MapperScannerConfigurer会扫描指定的包路径(如com.example.mapper),找到所有被@Mapper注解标记的接口,或者所有继承了特定标记接口的接口。 - 注册Bean定义:对于找到的每一个Mapper接口(比如
UserMapper.class),MyBatis并不会去创建一个真实的实现类,而是在Spring容器中注册一个特殊类型的Bean定义——MapperFactoryBean。 - 创建代理对象:当Service层需要注入
UserMapper时,Spring会向MapperFactoryBean请求获取Bean实例。此时,MapperFactoryBean就会调用JDK动态代理,在内存中动态地生成一个UserMapper接口的代理实现对象。
“这个动态生成的代理对象,它的内部有一个
InvocationHandler。当我们调用代理对象的任何方法时(比如userMapper.selectById(1)),这个调用都会被InvocationHandler拦截。”InvocationHandler的逻辑大致是:”
- 它会获取到我们调用的方法名(
selectById)和参数(1)。 - 它会将方法名与Mapper XML文件中配置的SQL语句的
id进行映射和绑定。 - 它会从连接池获取一个数据库连接,将参数设置到SQL语句中,然后通过JDBC执行这条SQL。
- 最后,它会将查询结果封装成我们方法签名中定义好的返回类型(如
User对象),并返回。
- 它会获取到我们调用的方法名(
正是因为MyBatis依赖于JDK动态代理,而JDK动态代理技术本身就要求被代理的目标必须是一个接口。它无法为一个具体的类或抽象类创建代理。这就是为什么Mapper必须是接口的根本技术原因。
2.mybatis工作原理
将SQL语句的执行从繁琐的JDBC样板代码中解耦出来,通过XML或注解的方式进行配置,并利用Java的反射和动态代理技术,优雅地将接口方法与SQL语句绑定起来。
那我们先来说说他的执行周期:
- 初始化
- 首先,通过
SqlSessionFactoryBuilder,MyBatis会读取全局配置文件mybatis-config.xml。这个文件里定义了数据源(DataSource)、事务管理器(TransactionManager)、别名(typeAliases)、插件(plugins)以及Mapper映射文件的路径等核心信息。 - 接着,根据映射文件路径,MyBatis会逐一加载并解析所有的Mapper XML文件(例如
UserMapper.xml)。
然后解析并构建Configuration对象,解析的所有信息,无论是全局配置还是每个SQL语句的细节,都会被封装到一个极其核心的Configuration对象中。
在解析Mapper XML时,我们所有的标签都会解析成一个MappedStatement,他是一个完整sql语句的封装
所有的MappedStatement都会被存放在Configuration对象的一个Map里,其key就是Mapper接口的全限定名 + 方法名(例如com.example.mapper.UserMapper.selectUserById),value就是对应的MappedStatement实例。
- 当
Configuration对象构建完毕后,SqlSessionFactoryBuilder会用它来创建一个SqlSessionFactory的实例。 SqlSessionFactory是一个重量级、线程安全的对象,它在应用的生命周期中通常只需要一个实例。它的作用就像一个“数据库连接池工厂”,专门用于创建SqlSession。
- 执行
如果我们执行了下面的语句,User user = userMapper.selectUserById(1);
获取Mapper代理对象,
- 我们从
SqlSession中通过sqlSession.getMapper(UserMapper.class)获取到的userMapper实例,并不是UserMapper接口的实现类,而是一个由MyBatis通过JDK动态代理创建的代理对象。这是MyBatis最核心的魔法之一。 当我们调用代理对象的
selectUserById(1)方法时,这个调用会被代理对象拦截。代理对象的
InvocationHandler实现是MapperProxy。它在invoke方法中接收到方法调用后,并不会去执行任何具体的业务逻辑。- 相反,它会根据被调用的接口名和方法名(
com.example.mapper.UserMapper.selectUserById),去第一阶段构建好的Configuration对象中,找到对应的MappedStatement。 MapperProxy会将请求转发给SqlSession,而SqlSession的真正工作是委托给一个Executor(执行器)来完成的。Executor是MyBatis中负责SQL执行、事务管理和缓存维护的核心组件。它有多种实现,如SimpleExecutor(默认)、ReuseExecutor、BatchExecutor。Executor会接收到MappedStatement和传入的参数(1)。Executor会通过一个ParameterHandler,使用JDBC的PreparedStatement,安全地将我们的参数(1)设置到SQL语句的?占位符上,防止SQL注入。Executor执行PreparedStatement,从数据库获取到ResultSet结果集。- 接着,
Executor会通过一个ResultSetHandler来处理这个结果集。 ResultSetHandler会根据MappedStatement中配置的resultType或resultMap,利用Java反射机制,创建出目标对象(如User对象),然后从ResultSet中逐列取出数据,调用User对象的setter方法,将数据填充进去。resultMap是这里一个非常强大的功能,它可以处理数据库列名和Java对象属性名不匹配的情况,以及复杂的嵌套查询和关联查询。ResultSetHandler将封装好的Java对象(User实例)返回给调用方,一次完整的MyBatis查询流程就结束了。
总结一下:
- 加载配置:解析XML和注解,将所有配置信息和SQL语句封装到
Configuration和MappedStatement中。 - 创建会话工厂:基于
Configuration构建SqlSessionFactory。 - 动态代理:当调用Mapper接口方法时,通过JDK动态代理拦截调用,并找到对应的
MappedStatement。 - 委托执行器:将请求交给
Executor,由它负责底层的JDBC操作、事务和缓存。 - 参数与结果映射:通过
ParameterHandler和ResultSetHandler,利用反射机制,完成Java对象与PreparedStatement参数以及ResultSet结果集之间的映射。












