MyBatis的SqlSession.getMapper()源码分析

数据库   发布日期:2023年07月21日   浏览次数:526

今天小编给大家分享一下MyBatis的SqlSession.getMapper()源码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

什么是 MyBatis?

1、MyBatis 是一款优秀的持久层框架

2、MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

3、MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

原理解析

1、程序员和Mybatis 和数据的关系:人通过mybatis框架来操作数据库。

2、思考问题并解决

问题1:首先我们必须告诉MyBatis要怎么操作数据库?

我们把可以通过XML配置文件或者注解的方式,MyBatis提供了一个类Configuration, Mybatis 读取XML配置文件后会将内容放在一个Configuration类中,Configuration类会存在整个Mybatis生命周期,以便重复读取。

问题2:想要Mybatis与数据库打交道,就要有一个类似于JDBC的Connection对象,在MyBatis中叫SqlSesion,所以我们要有一个SqlSession。

Mybatis 读取XML配置文件后会将内容放在一个Configuration类中,SqlSessionFactoryBuilder会读取Configuration类中信息创建SqlSessionFactory。SqlSessionFactory创建SqlSession。

  1. String resource = "mybatis-config.xml";
  2. InputStream inputStream = Resources.getResourceAsStream(resource);
  3. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  4. SqlSession sqlSession=null;
  5. try{
  6. sqlSession=sqlSessionFactory.openSession();
  7. //some code
  8. sqlSession.commit();
  9. } catch(Exception ex){
  10. sqlSession.roolback();
  11. } finally{
  12. if(sqlSession!=null){
  13. sqlSession.close();
  14. }
  15. }

关于SqlSessionFactory的创建,Mybatis采用构造模式来完成创建。

第一步:XMLConfigBuilder解析XML配置,读出配置参数,存入Configuration类中。

第二步:Configuration类创建SqlSessionFactory。(DefaultSqlSessionFactory的构造函数传入Configuration类)

深入了解:SqlSessionFactoryBuilder.builder(inputStream)

  1. //该方法.builder中的主要内容:
  2. XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
  3. SqlSessionFactory localSqlSessionFactory = build(parser.parse());
  4. //build(parser.parse())方法实则为:
  5. public SqlSessionFactory build(Configuration config) {
  6. return new DefaultSqlSessionFactory(config);
  7. }

问题3:SqlSession能干什么?

SqlSession用途主要有两种

①. 获取对应的Mapper,让映射器通过命名空间和方法名称找到对应的SQL,发送给数据库执行后返回结果。

  1. RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
  2. Role role = roleMapper.getRole(1L);

②. 直接使用SqlSession,通过命名信息去执行SQL返回结果,该方式是IBatis版本留下的,SqlSession通过Update、Select、Insert、Delete等方法操作。

  1. Role role = (Role)sqlSession.select("com.mybatis.mapper.RoleMapper.getRole",1L);

Mybatis底层利用JDK动态代理技术实现该接口,底层最后还是使用的IBatis中SqlSession通过Update、Select、Insert、Delete等方法操作。

问题4:上面说到Mybatis底层利用JDK动态代理技术实现该接口,但是我们在使用MyBatis的时候,都是只写接口不用写实现类,为什么呢?

为什么要使用动态代理?可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强。

我们先看看传统的JDK动态代理:

1、首先有一个接口

  1. public interface Calculate {
  2. void add(int i, int j);
  3. }

2、然后是接口的实现类

  1. public class CalculateImp implements Calculate {
  2. @Override
  3. public void add(int i, int j) {
  4. System.out.println("result = " + (i + j));
  5. }
  6. }

3、代理类实现InvocationHandler

  1. public class CalculateProxy implements InvocationHandler {
  2. private Object target;
  3. //总要让我知道要代理谁吧:构造方法中把传入一个代理类的实例
  4. public CalculateProxy(Object target) {
  5. this.target = target;
  6. }
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. System.out.println("====== Before() ======");
  10. method.invoke(target, args);
  11. System.out.println("====== After () ======");
  12. return null;
  13. }
  14. }

4、拿到代理对象,操作接口方法

  1. public class test {
  2. public static void main(String[] args) {
  3. InvocationHandler handler = new CalculateProxy(new CalculateImp());
  4. Calculate calculateProxy =
  5. (Calculate) Proxy.newProxyInstance(Calculate.class.getClassLoader(),
  6. new Class[]{Calculate.class},
  7. handler);
  8. calculateProxy.add(10,20);
  9. }
  10. }

Proxy.newProxyInstance()方法有三个参数:

1. 类加载器(Class Loader)

2. 需要实现的接口数组

3. InvocationHandler接口。所有动态代理类的方法调用,都会交由InvocationHandler接口实现类里的invoke()方法去处理。这是动态代理的关键所在。

回到我们之前的问题,我们并没有接口实现类,那没有实现类还为什么还能调用方法操作。其实是这样的:

操作数据库主要是通过SQL语句,那么只要找到SQL语句然后执行不就可以!

通过例子分析:

  1. BlogMapper mapper = session.getMapper(BlogMapper.class);
  2. Blog blog = mapper.selectBlog(1);

这里 mapper 可以调用selectBlog(1) 这个方法,说明 mapper 是个对象,因为对象才具有方法行为实现啊。BlogMapper接口是不能实例化的,更没有具体方法实现。

我们并没有定义一个类,让它实现BlogMapper接口,而在这里它只是通过调用session.getMapper() 所得到的。

由此,我们可以推断:肯定是session.getMapper() 方法内部产生了BlogMapper的实现类。有什么技术可以根据BlogMapper 接口生成了一个实现类呢?想到这里,对于有动态代理 。

Mapper 接口的注册

我们既然能够从SqlSession中得到BlogMapper接口的,那么我们肯定需要先在哪里把它放进去了,然后 SqlSession 才能生成我们想要的代理类啊。

我们可以从getMapper()联系,可能会有一个setMapper()或者addMapper()方法。确实是有!

  1. configuration.addMapper(BlogMapper.class);

跟着这个 addMapper 方法的代码实现是这样的:

  1. public <T> void addMapper(Class<T> type) {
  2. mapperRegistry.addMapper(type);
  3. }

我们看到这里 mapper 实际上被添加到 mapperRegistry (mapper注册器)中。继续跟进代码:

  1. public class MapperRegistry {
  2. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers
  3. = new HashMap<Class<?>, MapperProxyFactory<?>>();
  4. public <T> void addMapper(Class<T> type) {
  5. if (type.isInterface()) { // 只添加接口
  6. if (hasMapper(type)) { // 不允许重复添加
  7. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  8. }
  9. boolean loadCompleted = false;
  10. try {
  11. knownMappers.put(type, new MapperProxyFactory<T>(type)); // 注意这里
  12. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  13. parser.parse();
  14. loadCompleted = true;
  15. } finally {
  16. if (!loadCompleted) {
  17. knownMappers.remove(type);
  18. }
  19. }
  20. }
  21. }
  22. }

我们首先看到MapperRegistry类,有一个私有属性knowMappers,它是一个

  1. HashMap

  1. Key
为当前Class对象,
  1. value
为一个MapperProxyFactory实例

在MapperRegistry类的addMapper()方法中,knownMappers.put(type, new MapperProxyFactory<T>(type));相当于把:诸如

  1. BlogMapper
之类的Mapper接口被添加到了
  1. MapperRegistry
中的一个HashMap中。

并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的

  1. MapperProxyFactory
实例作为value 。

MapperProxyFactory从名字来看,好像是一个工厂,用来创建Mapper Proxy的工厂。

上面我们已经知道,Mapper 接口被到注册到了

  1. MapperRegistry
中&mdash;&mdash;放在其名为knowMappers 的HashMap属性中,我们在调用Mapper接口的方法的时候,是这样的:
  1. BlogMapper mapper = session.getMapper(BlogMapper.class);

这里,我们跟踪一下session.getMapper() 方法的代码实现,这里 SqlSession 是一个接口,他有两个实现类,

一个是

  1. DefaultSqlSession
,另外一个是
  1. SqlSessionManager
,

这里我们用的是

  1. DefaultSqlSession
. 为什么是
  1. DefaultSqlSession
呢?因为我们在初始化SqlSessionFactory的时候所调用的
  1. SqlSessionFactoryBuilder
的build()方法里边配置的就是
  1. DefaultSqlSession
, 所以,我们进入到DefaultSession类中,看看它对
  1. session.getMapper(BlogMapper.class)
是怎么实现的:
  1. public class DefaultSqlSession implements SqlSession {
  2. private Configuration configuration;
  3. @Override
  4. public <T> T getMapper(Class<T> type) {
  5. return configuration.<T>getMapper(type, this); //最后会去调用MapperRegistry.getMapper
  6. }
  7. }

如代码所示,这里的 getMapper 调用了 configuration.getMapper , 这一步操作其实最终是调用了

  1. MapperRegistry
,而此前我们已经知道,
  1. MapperRegistry
是存放了一个HashMap的,我们继续跟踪进去看看,那么这里的get,肯定是从这个hashMap中取数据。

我们来看看代码:

  1. public class MapperRegistry {
  2. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();// Mapper 映射
  3. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  4. final MapperProxyFactory<T> mapperProxyFactory =
  5. (MapperProxyFactory<T>) knownMappers.get(type);
  6. try {
  7. return mapperProxyFactory.newInstance(sqlSession); // 重点看这里
  8. } catch (Exception e) {
  9. }
  10. }
  11. }

我们调用的

  1. session.getMapper(BlogMapper.class);
最终会到达上面这个方法,这个方法,根据
  1. BlogMapper
的class对象,以它为
  1. key
  1. knowMappers
中找到了对应的
  1. value
&mdash;&mdash; MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的
  1. newInstance()
方法。

根据这个名字,我们就能猜到这个方法是创建了一个对象,代码是这样的:

  1. public class MapperProxyFactory<T> { //映射器代理工厂
  2. private final Class<T> mapperInterface;
  3. private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  4. public MapperProxyFactory(Class<T> mapperInterface) {
  5. this.mapperInterface = mapperInterface;
  6. }
  7. // 删除部分代码,便于阅读
  8. @SuppressWarnings("unchecked")
  9. protected T newInstance(MapperProxy<T> mapperProxy) {
  10. //使用了JDK自带的动态代理生成映射器代理类的对象
  11. return (T) Proxy.newProxyInstance(
  12. mapperInterface.getClassLoader(),
  13. new Class[] { mapperInterface },
  14. mapperProxy);
  15. }
  16. public T newInstance(SqlSession sqlSession) {
  17. final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  18. return newInstance(mapperProxy);
  19. }
  20. }

看到这里,就清楚了,最终是通过

  1. Proxy.newProxyInstance
产生了一个BlogMapper的代理对象。

Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。

具体是使用了JDK动态代理,这个

  1. Proxy.newProxyInstance
方法生成代理类的三个要素是:
  • ClassLoader &mdash;&mdash; 指定当前接口的加载器即可

  • 当前被代理的接口是什么 &mdash;&mdash; 这里就是 BlogMapper

  • 代理类是什么 &mdash;&mdash; 这里就是 MapperProxy

代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。

我们贴出MapperProxy的代码,如下:

  1. public class MapperProxy<T> implements InvocationHandler, Serializable {// 实现了InvocationHandler
  2. @Override
  3. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  4. //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
  5. if (Object.class.equals(method.getDeclaringClass())) {
  6. try {
  7. return method.invoke(this, args); // 注意1
  8. } catch (Throwable t) {
  9. throw ExceptionUtil.unwrapThrowable(t);
  10. }
  11. }
  12. final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了缓存
  13. //执行CURD
  14. return mapperMethod.execute(sqlSession, args); // 注意2
  15. }
  16. }

我们调用的

  1. Blog blog = mapper.selectBlog(1);
实际上最后是会调用这个
  1. MapperProxy
  1. invoke
方法。

这段代码中,

  1. if
语句先判断,我们想要调用的方法是否来自Object类,这里的意思就是,如果我们调用
  1. toString()
方法,那么是不需要做代理增强的,直接还调用原来的method.invoke()就行了。

只有调用

  1. selectBlog()
之类的方法的时候,才执行增强的调用&mdash;&mdash;即
  1. mapperMethod.execute(sqlSession, args);
这一句代码逻辑。

  1. mapperMethod.execute(sqlSession, args);
这句最终就会执行增删改查了,代码如下:
  1. public Object execute(SqlSession sqlSession, Object[] args) {
  2. Object result;
  3. if (SqlCommandType.INSERT == command.getType()) { //insert 处理,调用SqlSession的insert
  4. Object param = method.convertArgsToSqlCommandParam(args);
  5. result = rowCountResult(sqlSession.insert(command.getName(), param));
  6. } else if (SqlCommandType.UPDATE == command.getType()) { // update
  7. Object param = method.convertArgsToSqlCommandParam(args);
  8. result = rowCountResult(sqlSession.update(command.getName(), param));
  9. } else if (SqlCommandType.DELETE == command.getType()) { // delete
  10. Object param = method.convertArgsToSqlCommandParam(args);
  11. result = rowCountResult(sqlSession.delete(command.getName(), param));
  12. } else if (SqlCommandType.SELECT == command.getType()) {
  13. // 删除部分代码
  14. } else {
  15. throw new BindingException("Unknown execution method for: " + command.getName());
  16. }
  17. // 删除部分代码
  18. return result;
  19. }

再往下一层,就是执行JDBC那一套了,获取链接,执行,得到ResultSet,解析ResultSet映射成JavaBean。

以上就是MyBatis的SqlSession.getMapper()源码分析的详细内容,更多关于MyBatis的SqlSession.getMapper()源码分析的资料请关注九品源码其它相关文章!