初见MyBatis的理解与认知 五

MyBatis

上期我们讲了Java的动态代理是需要有具体的实现类的,所以MyBatis的接口并没有具体的实现类啊,那是怎么进行动态代理的呢?


mapper动态代理

数据库表

create table brand
(
    id           int auto_increment
        primary key,
    brand_name   varchar(20)  null,
    company_name varchar(20)  null,
    ordered      int          null,
    description  varchar(100) null,
    status       int          null
);

声明一个POJO类

public class Brand {

    private Integer id          ;

    private String brandName  ;

    private String companyName;

    private Integer ordered     ;

    private String description ;

    private Integer status      ;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getBrandName() {
        return brandName;
    }

    public void setBrandName(String brandName) {
        this.brandName = brandName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public Integer getOrdered() {
        return ordered;
    }

    public void setOrdered(Integer ordered) {
        this.ordered = ordered;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Brand{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", companyName='" + companyName + '\'' +
                ", ordered=" + ordered +
                ", description='" + description + '\'' +
                ", status=" + status +
                '}';
    }
}

定义一个配置文件mybatis-config.xml




    
        
    
    
    
        
            
            
                
                
                
                
                
            
        
    
    
        
        
        
        
    



BrandMapper(接口)

public interface BrandMapper {
    List selectAll();
}

SQL映射文件





    

测试用例

public class MyBatisDemo {


    public static void main(String[] args) throws IOException {
        //1. 加载mybatis的核心配置文件,获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2.获取SqlSession对象,用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //3.执行sql语句
        //List brands = sqlSession.selectList("com.happyfan.mapper.BrandMapper.selectAll");
        BrandMapper brandmapper = sqlSession.getMapper(BrandMapper.class);
        List brands = brandmapper.selectAll();

        System.out.println(brands);
        //4.释放资源
        sqlSession.close();
    }
}

我们使用sqlSession.getMapper()方法获取BrandMapper对象,实际上这里我们是获取了BrandMapper接口的代理类,然后再由代理类执行方法。那么这个代理类是如何生成的呢?我们利用

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

走进build方法,来看看SqlSessionFactory干了些什么事情。

public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment) {
        return this.build((InputStream)inputStream, environment, (Properties)null);
    }

    public SqlSessionFactory build(InputStream inputStream, Properties properties) {
        return this.build((InputStream)inputStream, (String)null, properties);
    }

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException var13) {
            }

        }

        return var5;
    }

我们可以看到在 build 方法中有一个 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties) (全局配置的解析在 XMLConfigBuilder 的 parser 方法中)
继续往 XMLConfigBuilder 里走。

 public Configuration parse() {
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

    private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

MyBatis 通过 XML ConfigBuileder读取 mybatis-config 中的配置信息,然后将这些信息保存到 Configuration 中(输入流、环境、属性)。mapperElement(root.evalNode(“mappers”)) 该方法是对全局配置文件中mappers属性的解析,点进去。

private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(true) {
                while(var2.hasNext()) {
                    XNode child = (XNode)var2.next();
                    String resource;
                    if ("package".equals(child.getName())) {
                        resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {
                        resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);

                            try {
                                mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                                mapperParser.parse();
                            } catch (Throwable var13) {
                                if (inputStream != null) {
                                    try {
                                        inputStream.close();
                                    } catch (Throwable var11) {
                                        var13.addSuppressed(var11);
                                    }
                                }

                                throw var13;
                            }

                            if (inputStream != null) {
                                inputStream.close();
                            }
                        } else if (resource == null && url != null && mapperClass == null) {
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);

                            try {
                                mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                                mapperParser.parse();
                            } catch (Throwable var12) {
                                if (inputStream != null) {
                                    try {
                                        inputStream.close();
                                    } catch (Throwable var10) {
                                        var12.addSuppressed(var10);
                                    }
                                }

                                throw var12;
                            }

                            if (inputStream != null) {
                                inputStream.close();
                            }
                        } else {
                            if (resource != null || url != null || mapperClass == null) {
                                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

找到 mapperParser.parse() 方法点进去。

 public void parse() {
        if (!this.configuration.isResourceLoaded(this.resource)) {
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }

        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements();
    }

buildMapperForNamespeace() 会根据 mapper文件中的 namespeace 属性值(全限定名,就是类的全称,带包的要用.分隔开来)为接口生成动态代理对象。点击bindMapperForNamespeace。

private void bindMapperForNamespace() {
        String namespace = this.builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class boundType = null;

            try {
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {
            }

            if (boundType != null && !this.configuration.hasMapper(boundType)) {
                this.configuration.addLoadedResource("namespace:" + namespace);
                this.configuration.addMapper(boundType);
            }
        }

    }
}

然后我们就能看到 Configuration 的 addMapper 方法和 getMapper方法(在解析配置文件时,将需要生成动态代理类的接口注册到其中)。然后 Configuration 将addMapper方法委托给MapperRegistry。

 public void addMappers(String packageName, Class superType) {
        this.mapperRegistry.addMappers(packageName, superType);
    }

    public void addMappers(String packageName) {
        this.mapperRegistry.addMappers(packageName);
    }

    public  void addMapper(Class type) {
        this.mapperRegistry.addMapper(type);
    }

    public  T getMapper(Class type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

点击addMapper方法

 public  void addMapper(Class type) {
        if (type.isInterface()) {
            if (this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }

我们可以看到addMapper方法内部会判断会判断当前接口是否已经注册,已近注册就会生成对应的 MapperProxyFactory。
再用Configuration 中 getMapper 方法获取对应的MapperProxyFactory,再利用 MapperProxyFactory 生成动态代理类。

public  T getMapper(Class type, SqlSession sqlSession) {
        MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

getMapper 中有一个 mapperProxyFactory.newInstance 方法(将所有的接口重新定向到InvocationHandle ,调用它的 Invoke 方法,InvocationHandle 接口的实现类是 MapperProxy )。

protected T newInstance(MapperProxy mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

点击MapperProxy(我们能看到在Java动态代理里熟悉的 invoke 方法了)

public class MapperProxy implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -4724728412955527868L;
    private static final int ALLOWED_MODES = 15;
    private static final Constructor lookupConstructor;
    private static final Method privateLookupInMethod;
    private final SqlSession sqlSession;
    private final Class mapperInterface;
    private final Map methodCache;

    public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

    private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
        try {
            return (MapperMethodInvoker)MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
                if (m.isDefault()) {
                    try {
                        return privateLookupInMethod == null ? new DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new DefaultMethodInvoker(this.getMethodHandleJava9(method));
                    } catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
                        throw new RuntimeException(var4);
                    }
                } else {
                    return new PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
                }
            });
        } catch (RuntimeException var4) {
            Throwable cause = var4.getCause();
            throw (Throwable)(cause == null ? var4 : cause);
        }
    }

MapperProxy 调用用接口自定义方法进行代理,根据当前的 method 生成了一个 MapperMethod 对象,点击 MapperMethod 。具体的实现方法就在 MapperMethod 的 execute 方法里了。

public MapperMethod(Class mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch (this.command.getType()) {
            case INSERT:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
                break;
            case UPDATE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
                break;
            case DELETE:
                param = this.method.convertArgsToSqlCommandParam(args);
                result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
                break;
            case SELECT:
                if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                    this.executeWithResultHandler(sqlSession, args);
                    result = null;
                } else if (this.method.returnsMany()) {
                    result = this.executeForMany(sqlSession, args);
                } else if (this.method.returnsMap()) {
                    result = this.executeForMap(sqlSession, args);
                } else if (this.method.returnsCursor()) {
                    result = this.executeForCursor(sqlSession, args);
                } else {
                    param = this.method.convertArgsToSqlCommandParam(args);
                    result = sqlSession.selectOne(this.command.getName(), param);
                    if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                        result = Optional.ofNullable(result);
                    }
                }
                break;
            case FLUSH:
                result = sqlSession.flushStatements();
                break;
            default:
                throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + "' attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

MapperMethod 类中有两个内部类,SqlCommand 和 MethodSignature 类。在 execute 方法中,用switch case 语句,用 SqlCommand 类的 getType 方法(获取 calss 对象的声明类型),判断要执行的 sql 类型,再调用 sqlSession 的增删改查方法。

MyBatis 动态代理的内部处理过程
1、找到SQL配置 ->解析 + 参数(从 invoke 方法中获取参数列表Object ->放参数的容器)
2、拼接SQL
3、执行
4、解析结果并组装值返回值
类似于object -> SQL -> result->object,那问题又来了,result结果是如何变成object并返回的呢?其实执行结果是依靠 xml 中配置的每一个 sql 的返回值类型结合 resultType 或者 resultMap 的映射机制来定义如何组装返回结果的。
以上就是 mapper 动态代理的原理过程了。

你可能感兴趣的:(mybatis)