Mybatis Mapper Bean 生成源码分析二

简介: Mybatis Mapper Bean 生成源码分析二

Mybatis Mapper Bean 生成源码分析二

问题

在java程序中,我们往往一个@Autowired注解就可以获取到一个Mapper接口实例,一个未经我们实现的接口实例,那么这个Bean实例是怎么在Spring初始化的过程中注入的呢? 让我们来找找答案。

探寻内容偏长,没有DEBUG过且不耐烦可直接跳过去看总结~

MapperFactoryBean创建工厂

public T getObject() throws Exception {
  // getSqlSession() 即 SqlSessionTemplate;
    return this.getSqlSession().getMapper(this.mapperInterface);
}

关键类org.mybatis.spring.SqlSessionTemplate

在org.mybatis.spring.SqlSessionTemplate中有一个getMapper方法,如下:

public <T> T getMapper(Class<T> type) {
        return this.getConfiguration().getMapper(type, this);
    }

这个SqlSessionTemplate会被注入到容器中,而且能够在我们代码中使用,获取到一个Mapper。使用如下:

@Autowired
    public SqlSessionTemplate sqlSession;
  public void demo() {
        sqlSession.getMapper(UserMapper.class);
    }

显然这个SqlSessionTemplate类可以做到根据接口取到实例,那么Spring启动时其实也是根据它来获取的。

spring启动过程注入一个动态代理对象

对于如下一个需要Mapper的服务,启动过程中Mapper必然会被注入进来:

@Component
@RestController
public class UserController {
    @Autowired
    UserMapper userMapper;
}

那么其会走spring的如下方法来获取Bean:

// org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
// 方法很长,将会省略很多
protected <T> T doGetBean(
      String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
      throws BeansException {
    // beanName即userMapper
    String beanName = transformedBeanName(name);
  Object bean;
  // Eagerly check singleton cache for manually registered singletons.
    // 获取的sharedInstance即为工厂
  Object sharedInstance = getSingleton(beanName);
    // 启动时,第一次实际产生工厂代码为下方代码,生成了MapperFactoryBean
    sharedInstance = getSingleton(beanName, () -> {
            try {
                            // 作为懒加载的方法,最终创建工厂对象,值得一看
              return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
              destroySingleton(beanName);
              throw ex;
            }
          });
    // 通过工厂来生成Bean实例
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    return (T) bean;
}
// org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean
  private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
    Object object;
    try {
      if (System.getSecurityManager() != null) {
        // 省略
      }
      else {
                // 重点,通过工厂进行获取Bean
        object = factory.getObject();
      }
    }
    return object;
  }

其堆栈最终如下图:

所以可以看到在下方代码中,返回了一个JDK动态代理的对象,就是我们服务内会注入的名为“userMapper”的Bean了:

// com.baomidou.mybatisplus.core.MybatisMapperRegistry#getMapper
    @SuppressWarnings("unchecked")
    @Override
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // 代理工厂
        final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
        }
        try {
            // 通过代理工厂,来生成对应的代理实例
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }

代理工厂生成一个怎样的实例

来看看MybatisMapperProxyFactory,究竟是怎么样一个工厂

// com.baomidou.mybatisplus.core.override.MybatisMapperProxyFactory
public class MybatisMapperProxyFactory<T> {
    // 接口名称
    @Getter
    private final Class<T> mapperInterface;
    @Getter
    private final Map<Method, MybatisMapperProxy.MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
    public MybatisMapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    @SuppressWarnings("unchecked")
    protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
        // 最终包装成JDK动态代理的对象
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }
    // debug可查看实际走这一个方法来产生
    public T newInstance(SqlSession sqlSession) {
        final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
}
// com.baomidou.mybatisplus.core.override.MybatisMapperProxypublic class MybatisMapperProxy<T> implements InvocationHandler, Serializable {    // bean最后实际会调用到这个代理的invoke方法,由此方法做路由到具体方法    @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 {                return cachedInvoker(method).invoke(proxy, method, args, sqlSession);            }        } catch (Throwable t) {            throw ExceptionUtil.unwrapThrowable(t);        }    }        private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {        try {            return CollectionUtils.computeIfAbsent(methodCache, method, m -> {                if (m.isDefault()) {                    try {                        if (privateLookupInMethod == null) {                            return new DefaultMethodInvoker(getMethodHandleJava8(method));                        } else {                            return new DefaultMethodInvoker(getMethodHandleJava9(method));                        }                    } catch (IllegalAccessException | InstantiationException | InvocationTargetException                        | NoSuchMethodException e) {                        throw new RuntimeException(e);                    }                } else {                    // 缓存一个Mapper方法                    return new PlainMethodInvoker(new MybatisMapperMethod(mapperInterface, method, sqlSession.getConfiguration()));                }            });        } catch (RuntimeException re) {            Throwable cause = re.getCause();            throw cause == null ? re : cause;        }    }    }
  • 最终使用的方法:通过这个方法最终决定走查询、插入、更新等等命令。
// com.baomidou.mybatisplus.core.override.MybatisMapperMethod#execute    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 {                    // TODO 这里下面改了                    if (IPage.class.isAssignableFrom(method.getReturnType())) {                        result = executeForIPage(sqlSession, args);                        // TODO 这里上面改了                    } else {                        Object param = method.convertArgsToSqlCommandParam(args);                        result = sqlSession.selectOne(command.getName(), param);                        if (method.returnsOptional()                            && (result == null || !method.getReturnType().equals(result.getClass()))) {                            result = Optional.ofNullable(result);                        }                    }                }                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;    }

总结

有如下几步,来进行Mapper接口Bean的注入

  1. spring doGetBean 方法获取到bean工厂
  2. bean工厂通过SqlSessionTemplate来进行获取(创建)Mapper实例
  3. MybatisMapperProxyFactory工厂通过newInstance构建了一个通过JDK代理过的MybatisMapperProxy对象。
  4. 该代理对象的invoke方法完成了Mapper接口方法的路由,到具体SQL命令中去。
目录
相关文章
|
1月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
30 10
|
1月前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
125 0
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
|
3月前
|
SQL Java 数据库连接
Mybatis系列之 Error parsing SQL Mapper Configuration. Could not find resource com/zyz/mybatis/mapper/
文章讲述了在使用Mybatis时遇到的资源文件找不到的问题,并提供了通过修改Maven配置来解决资源文件编译到target目录下的方法。
Mybatis系列之 Error parsing SQL Mapper Configuration. Could not find resource com/zyz/mybatis/mapper/
|
2月前
|
SQL XML Java
mybatis :sqlmapconfig.xml配置 ++++Mapper XML 文件(sql/insert/delete/update/select)(增删改查)用法
当然,这些仅是MyBatis功能的初步介绍。MyBatis还提供了高级特性,如动态SQL、类型处理器、插件等,可以进一步提供对数据库交互的强大支持和灵活性。希望上述内容对您理解MyBatis的基本操作有所帮助。在实际使用中,您可能还需要根据具体的业务要求调整和优化SQL语句和配置。
44 1
|
3月前
|
XML Java 数据库连接
Mybatis 模块拆份带来的 Mapper 扫描问题
Mybatis 模块拆份带来的 Mapper 扫描问题
43 0
|
4月前
|
SQL
自定义SQL,可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,如何自定义SQL呢?利用MyBatisPlus的Wrapper来构建Wh,在mapper方法参数中用Param注
自定义SQL,可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,如何自定义SQL呢?利用MyBatisPlus的Wrapper来构建Wh,在mapper方法参数中用Param注
|
4月前
|
Java 数据库连接 mybatis
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
|
4月前
|
Java 数据库连接 Maven
Private method ‘getVideoList()‘ is never used,mybatis必须指定Mapper文件和实体目录,在参考其他人写的代码,要认真分析别人的代码,不要丢失
Private method ‘getVideoList()‘ is never used,mybatis必须指定Mapper文件和实体目录,在参考其他人写的代码,要认真分析别人的代码,不要丢失
|
5月前
|
SQL Java 数据库连接
Mybatis如何使用mapper代理开发
Mybatis如何使用mapper代理开发
|
5月前
|
XML 关系型数据库 数据库
使用mybatis-generator插件生成postgresql数据库model、mapper、xml
使用mybatis-generator插件生成postgresql数据库model、mapper、xml
501 0