MapperScannerConfigurer

113 阅读6分钟

MapperScannerConfigurer

1.首先先介绍一下原生mybatis对于接口的操作

1.1 mybatis如何操作数据库?

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

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

sqlSession相当于一个连接,可以通过其来调用接口的实现类来进行对数据库的操作。

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

在一个项目中,所有的mapper都共享同一个sqlSession、sqlSessionFactory以及Configuration

public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
​
    static {
        try {
            String resource = "mybatis-config.xml";
​
            //读取后生成一个sqlSessionFactory
            InputStream resourceAsStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
​
        } catch (IOException e) {
            e.printStackTrace();
        }
​
    }
    //创建sqlSession
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

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

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

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

深入了解:SqlSessionFactoryBuilder.builder(inputStream)

//该方法.builder中的主要内容: 
 
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
SqlSessionFactory localSqlSessionFactory = build(parser.parse());
 
//build(parser.parse())方法实则为:
 
public SqlSessionFactory build(Configuration config) {
   return new DefaultSqlSessionFactory(config);
 }
1.4 Mapper接口的注册

当我们可以从sqlSession中获得Mapper接口相关的类,那么类是如何被放入的呢

首先先看获得相关动态代理是通过下面sqlSession.getMapper()方法来获得

    @Before
    public void init() {
        sqlSession = MyBatisUtils.getSqlSession();
        //代理对象 执行语句
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);
        System.out.println("monster=" + monsterMapper.getClass());
    }

因此我们可以通过此联想到相关的方法,Configuration中存在addMapper方法

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

因此mapper实际上是在MapperRegistry.addMapper中进行添加的

public class MapperRegistry {
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers 
                                                  = new HashMap<Class<?>, MapperProxyFactory<?>>();
  
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) { // 只添加接口
      if (hasMapper(type)) { // 不允许重复添加
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type)); // 注意这里
 
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
 
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}
​

我们首先看到MapperRegistry类,有一个私有属性knowMappers,它是一个HashMap 。其Key 为当前Class对象,value 为一个MapperProxyFactory实例

在MapperRegistry类的addMapper()方法中,knownMappers.put(type, new MapperProxyFactory(type));相当于把:诸如BlogMapper 之类的Mapper接口被添加到了MapperRegistry 中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory 实例作为value 。MapperProxyFactory则是用来创建Mapper Proxy的工厂。

1.5 流程顺序

从调用Mapper接口方法开始

monsterMapper = sqlSession.getMapper(MonsterMapper.class);

SqlSession是一个接口,实现类分别为DefaultSqlSession和SqlSessionManager

当我们初始化SqlSessionFactory时所调用的SqlSessionFactoryBuilder().build(resourceAsStream)生成的为DefaultSqlSession类型,因此我们需要对其进行追源码

public class DefaultSqlSession implements SqlSession {
  private Configuration configuration;  
  
    @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this); //最后会去调用MapperRegistry.getMapper
  }
}

这里调用了configura.getMapper,结合上面所给代码,这一步操作其实最终是调用了MapperRegistry,而此前我们已经知道,MapperRegistry是存放了一个HashMap的,我们继续跟踪进去看看,那么这里的get,肯定是从这个hashMap中取数据。我们来看看代码:

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

我们调用的session.getMapper(BlogMapper.class);最终会到达上面这个方法,这个方法,根据BlogMapper的class对象,以它为key在knowMappers 中找到了对应的value —— MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的newInstance()方法。根据这个名字,我们就能猜到这个方法是创建了一个对象,代码是这样的:

public class MapperProxyFactory<T> { //映射器代理工厂
 
  private final Class<T> mapperInterface;
  private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
 
  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  // 删除部分代码,便于阅读
 
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //使用了JDK自带的动态代理生成映射器代理类的对象
    return (T) Proxy.newProxyInstance(
             mapperInterface.getClassLoader(),
             new Class[] { mapperInterface }, 
             mapperProxy);
  }
 
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
 
}

看到这里,就清楚了,最终是通过Proxy.newProxyInstance产生了一个BlogMapper的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个Proxy.newProxyInstance方法生成代理类的三个要素是:

  1. ClassLoader —— 指定当前接口的加载器即可
  2. 当前被代理的接口是什么 —— 这里就是 BlogMapper
  3. 代理类是什么 —— 这里就是 MapperProxy

以下的方法就为spring底层的AOP对于动态代理的实现

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

其中invoke是调用代理对象,mapperMethod.execute()则是实现执行操作

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {         //insert  处理,调用SqlSession的insert
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) { // update
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {   // delete
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      // 删除部分代码 
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
     // 删除部分代码
    return result;
  }

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

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

2、在初始化SqlSessionFactory时,Mapper 接口进行注册,注册在了名为 MapperRegistry 类的 HashMap中,key = Mapper class, value = 创建当前Mapper的工厂。

3、SqlSessionFactory创建SqlSession。

4、SqlSession中可以通过getMapper()拿到代理对象,SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。

5、动态代理的 代理类是 MapperProxy ,这里边mapperMethod.execute(sqlSession, args)最终完成了增删改查方法的调用。

[blog.csdn.net/qq_40645822…]

2.spring的整合

在spring-mybatis整合过程中,需要将mybatis注入到spring中,这就需要在配置文件中进行配置,下面是org.mybatis.spring.mapper.MapperScannerConfigure的作用:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.lxp.furn.dao"></property>
</bean>

通过MapperScannerConfigurer来扫描Dao包里面的mapper接口,动态的将mapper接口进行实例化;

ssm中对于mapper.xml和mybatis-config.xml文件,是在application.xml中通过下面进行读入配置

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="dataSource" ref="pooledDataSource"/>
​
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
3.springboot

在springboot中则即可以通过原生的实现,也可以在接口上加@Mapper实现自动注入

对于mapper文件则是可以通过直接在.yml文件中指定位置读入

对于其他配置可以直接在.yml中配置,但当配置繁杂时,也可以创建mybatis-config.xml文件再将其注入到.yml文件中

![www.cnblogs.com/emanlee/p/1…]

概括