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命令中去。
目录
相关文章
|
8天前
|
XML Java 数据库连接
Mybatis 模块拆份带来的 Mapper 扫描问题
Mybatis 模块拆份带来的 Mapper 扫描问题
14 0
|
1月前
|
SQL
自定义SQL,可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,如何自定义SQL呢?利用MyBatisPlus的Wrapper来构建Wh,在mapper方法参数中用Param注
自定义SQL,可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,如何自定义SQL呢?利用MyBatisPlus的Wrapper来构建Wh,在mapper方法参数中用Param注
|
1月前
|
Java 数据库连接 mybatis
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
SpringBoot配置Mybatis注意事项,mappers层下的name命名空间,要落实到Dao的video类,resultType要落到bean,配置好mybatis的对应依赖。
|
1月前
|
Java 数据库连接 Maven
Private method ‘getVideoList()‘ is never used,mybatis必须指定Mapper文件和实体目录,在参考其他人写的代码,要认真分析别人的代码,不要丢失
Private method ‘getVideoList()‘ is never used,mybatis必须指定Mapper文件和实体目录,在参考其他人写的代码,要认真分析别人的代码,不要丢失
|
2月前
|
SQL Java 数据库连接
Mybatis如何使用mapper代理开发
Mybatis如何使用mapper代理开发
|
2月前
|
XML 关系型数据库 数据库
使用mybatis-generator插件生成postgresql数据库model、mapper、xml
使用mybatis-generator插件生成postgresql数据库model、mapper、xml
142 0
|
2月前
|
SQL Java 数据库连接
Mybatis中一对多mapper配置
Mybatis中一对多mapper配置
23 0
|
3月前
|
XML Java 数据库连接
Mybatis逆向工程的2种方法,一键高效快速生成Pojo、Mapper、XML,摆脱大量重复开发
【5月更文挑战第10天】Mybatis逆向工程的2种方法,一键高效快速生成Pojo、Mapper、XML,摆脱大量重复开发
44 6
|
2月前
|
Java 数据库连接 mybatis
Mybatis mapper动态代理解决方案
该文介绍了Mybatis中使用Mapper接口的方式代替XML配置执行SQL。Mapper接口规范包括:namespace与接口类路径相同,select ID与接口方法名一致,parameterType和方法参数类型匹配,resultType与返回值类型一致。实现过程中,需配置Mapper.xml,编写Mapper.java接口,并在Mybatis-config.xml中设置。测试类中,通过SqlSession的getMapper方法获取接口的动态代理对象,调用方法执行SQL。
|
2月前
|
Java 数据库连接 mybatis
为什么Mybatis Mapper不需要实现类?
在学习Java动态代理之前,我想让大家先思考这样几个问题。 • JDK动态代理为什么不能对类进行代理? • Mybatis Mapper接口为什么不需要实现类? 如果你还不知道上述问题的答案,那么这篇文章一定能消除你心中的疑惑。