Mybatis:生成Mapper代理

接上文,前面说到使用Mybatis进行数据库操作,要先获取SqlSession,从而Mybatis会为当前的数据库操作分配一个执行器Executor和事务工厂TransactionFactory

之后通过SqlSession就可以获取Mapper接口的代理对象,从而调用接口方法进行数据库操作

public class MybatisDemo {

    public static void main(String[] args) {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 添加Mapper接口,并解析对应的XML文件
            session.getConfiguration().addMapper(BlogMapper.class);
            BlogMapper blogMapper = session.getMapper(BlogMapper.class);
            // 执行数据库查询,ID为101的记录
            blogMapper.selectBlog(101);
        }
    }
    /**
     * 获取数据源
     */
    private static DataSource getDataSource() { // ...    }

    /**
     * 获取Mybatis的SessionFactory
     */
    private static SqlSessionFactory getSqlSessionFactory() {  // ... }
}

既然使用Mybatis时仅仅提供了Mapper接口以及对应的XML文件,那通过SqlSession获取接口代理对象时,它又做了什么?

在上面的Demo中,可以看到如下代码:

// 通过当前session获取到Mybatis全局配置对象configration,
// 为其添加一个Mapper接口
session.getConfiguration().addMapper(BlogMapper.class);

通过Configuration可以往Mybatis注册接口,跟进Configuration#addMapper,来到MapperRegistery,这是Myabtis的Mapper注册中心,Mybatis采用HashMap保存Mapper接口及其代理对象生成工厂的映射关系

public class MapperRegistry {
    // 保存Mapper接口映射关系  
    // key : Mapper接口  value : 代理对象生成工厂
    private final Map, MapperProxyFactory> knownMappers = new HashMap<>();

    /**
     * 注册Mapper接口
     */
    public  void addMapper(Class type) {
        // 必须是接口才会处理,否则直接不理会,相当于这个方法执行
        if (type.isInterface()) {    
            // 这其实就是校验knownMappers是否contains当前key
            if (hasMapper(type)) {  
                // 省略异常抛出 ...
            }
            boolean loadCompleted = false;
            try {
                // 添加Mapper接口和代理对象工厂映射关系
                knownMappers.put(type, new MapperProxyFactory<>(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
                // 解析Mapper的XML配置
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
}

上述代码中,向Mybatis注册Mapper的时候,它使用一个Map集合保存接口以及接口代理工厂的映射关系,同时使用MapperAnnotationBuilder#parse,解析接口对应的XML文件

public class MapperAnnotationBuilder {
    // Mapper接口类型
    private final Class type;

    /**
     * 解析XML配置
     */
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            // 加载XML资源
            loadXmlResource();
            configuration.addLoadedResource(resource);
            // ...
        }
        parsePendingMethods();
    }
    /**
     * 加载XML资源并解析
     */
    private void loadXmlResource() {
        if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
            // 根据Mapper接口的类名.xml作为名称加载XML
            String xmlResource = type.getName().replace('.', '/') + ".xml";
            InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
            if (inputStream == null) {
                try {
                    inputStream = Resources
                            .getResourceAsStream(type.getClassLoader(), xmlResource);
                } catch (IOException e2) {
                }
            }
            if (inputStream != null) {
                // 解析XML生成MappedStatement添加到Configuration,后续执行SQL操作会用到
                XMLMapperBuilder xmlParser 
                        = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), 
                                xmlResource, configuration.getSqlFragments(), type.getName());
                xmlParser.parse();
            }
        }
    }
}

MapperAnnotationBuilder通过Mapper的包路径尝试加载XML资源,加载成功之后,利用XMLMapperBuilder进一步解析XML配置信息,最终会将XML内的信息解析成多个Map保存到全局配置对象Configuration的几个Map集合中,其中mappedStatements会保存配置文件中selectupdatedeleteinsert标签信息,这个属性将会在后续执行数据库操作中起到至关重要的作用

public class Configuration {
    // 缓存XML文件中各个数据库操作标签的信息
    protected final Map mappedStatements 
            = new StrictMap("Mapped Statements collection")
                    .conflictMessageProducer((savedValue, targetValue) 
                            -> ". please check " + savedValue.getResource() 
                                + " and " + targetValue.getResource());
    // 保存XML文件中标签的信息
    protected final Map resultMaps = new StrictMap<>("Result Maps collection");
    // 保存XML文件中标签的信息
    protected final Map parameterMaps = new StrictMap<>("Parameter Maps collection");
}

到此为止,向Mybatis注册Mapper接口的过程结束,接着再看通过SqlSession获取Mapper接口代理对象时Mybatis做了什么?

回到文章开头的代码块,可以看到如下方法:

 BlogMapper blogMapper = session.getMapper(BlogMapper.class);

跟进这个方法,又来到了Mapper注册中心MapperRegistry,它通过之前注册Mapper时保存的MapperProxyFactory为当前的Mapper接口创建代理对象

public class MapperRegistry {

    private final Map, MapperProxyFactory> knownMappers = new HashMap<>();
    /**
     * 从注册的Mapper集合中查找对应的代理对象生成工厂
     */
    public  T getMapper(Class type, SqlSession sqlSession) {
        final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {  
            // 生成代理类
            return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
            throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
    }
}

MapperProxyFactory创建代理对象的逻辑如下,底层采用JDK的动态代理技术生成代理对象

public class MapperProxyFactory {

    // Mapper接口的类型
    private final Class mapperInterface;
    // 接口中的方法信息
    private final Map methodCache = new ConcurrentHashMap<>();

    public MapperProxyFactory(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class getMapperInterface() {
        return mapperInterface;
    }

    public Map getMethodCache() {
        return methodCache;
    }
    /**
     * Mybatis最终通过JDK动态代理技术,为Mapper接口生成代理对象
     */
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), 
                new Class[] { mapperInterface }, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        // MapperProxy实现了InvocationHandler
        final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, 
                mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
}

既然使用JDK动态代理技术,必然有一个实现InvocationHandler接口的方法,也就是上面的MapperProxy,它封装了当前SqlSession、接口类型、接口类方法信息,当通过代理对象调用接口方法的时候,其实就是调用了MapperProxyinvoke方法

public class MapperProxy implements InvocationHandler, Serializable {
    // 当前会话对象
    private final SqlSession sqlSession;
    // 接口方法的缓存信息
    private final Map methodCache;
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // ...
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
    /**
     * 获取接口方法的缓存信息,如果没有那就创建一个缓存起来,将其返回
     */
    private MapperMethod cachedMapperMethod(Method method) {
        return methodCache.computeIfAbsent(method, 
                k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }
}

到此为止,Mybatis生成代理对象过程结束,简单来说,就是注册Mapper接口的同时,会为当前接口生成一个代理对象生成工厂并扫描它的XML配置文件,将解析好的信息放在Configuration中,当通过SqlSession获取接口的实现类时,会根据之前保存好的代理对象工厂生成代理对象,从Configuration获取事先解析好的信息执行数据库操作

你可能感兴趣的:(Mybatis:生成Mapper代理)