mybatis核心组件介绍

  • SqlSessionFactoryBuilder(构造器):它可以通过xml,注解或者手动配置来创建SqlSessionFacotry
  • SqlSessionFactory:用来创建SqlSession(会话)的工厂
  • SqlSession:SqlSession是mybatis最核心的类,可以用来执行语句,提交或者回滚事务以及获取映射器Mapper的接口
  • SQL Mapper:它是由一个接口,一个xml配置文件或者注解构成,需要给出对应的SQL和映射规则,它负责发送SQL去执行,并发挥结果
  • 组件使用案例:

    public class MybatisTest {    private static SqlSessionFactory sqlSessionFactory;    static {        try {            sqlSessionFactory = new SqlSessionFactoryBuilder()                    .build(Resources.getResourceAsStream("mybatis-config.xml"));        } catch (IOException e) {            e.printStackTrace();        }    }    public static void main(String[] args) {        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);            User user = userMapper.selectById(1);            System.out.println("User : " + user);        }    }}// 结果:User : User{id=1, age=21, name='pjmike'}

mybatis动态代理实现

public static void main(String[] args) {    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// <1>        User user = userMapper.selectById(1);        System.out.println("User : " + user);    }}

在前面的例子中,我们使用sqlSession的getMapper方法获取了UserMapper对象,实际上我们获取的是UserMapper接口的代理类,然后由代理类来执行方法。在探索动态代理类实现之前,我们需要先明确sqlSessionFactory工厂的创建过程做了哪些准备工作。

mybatis全局配置文件解析

private static SqlSessionFactory sqlSessionFactory;static {    try {        sqlSessionFactory = new SqlSessionFactoryBuilder()                .build(Resources.getResourceAsStream("mybatis-config.xml"));    } catch (IOException e) {        e.printStackTrace();    }}

我们使用new SqlSessionFactoryBuilder().build()方法创建SqlSessionFactory工厂

public SqlSessionFactory build(InputStream inputStream, Properties properties) {    return build(inputStream, null, properties);  }  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {    try {      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);      return build(parser.parse());    } catch (Exception e) {      throw ExceptionFactory.wrapException("Error building SqlSession.", e);    } finally {      ErrorContext.instance().reset();      try {        inputStream.close();      } catch (IOException e) {        // Intentionally ignore. Prefer previous error.      }    }  }

对于mybatis的全局配置文件解析,相关的解析代码主要在XMLConfigBuilderparse()方法中

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.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);        }    }

parseConfiguration方法的源码中可以看出,XmlConfigBuilder读取mybatis-config.xml中的配置信息,然后将信息保存到configuration类中

映射器Mapper文件解析

//解析映射器配置文件this.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);                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());                            mapperParser.parse();                        } else if (resource == null && url != null && mapperClass == null) {                            ErrorContext.instance().resource(url);                            inputStream = Resources.getUrlAsStream(url);                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());                            mapperParser.parse();                        } 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()方法就是XmlMapperBuilder对映射器文件的解析

public void parse() {        if (!this.configuration.isResourceLoaded(this.resource)) {            //解析映射文件的mapper节点,该方法主要用于将mapper文件中的元素信息解析到MappedStatement对象,并保存到configuration类的mappedStatements属性中            this.configurationElement(this.parser.evalNode("/mapper"));                this.configuration.addLoadedResource(this.resource);            //重点方法,这个方法内部会根据namespace属性值,生成动态代理类            this.bindMapperForNamespace();        }        this.parsePendingResultMaps();        this.parsePendingCacheRefs();        this.parsePendingStatements();    }

核心方法:bindMapperForNamespace()方法,该方法会根据mapper文件中的namespace属性值,为接口类生成动态代理类

动态代理类的生成

private void bindMapperForNamespace() {        //获取mapper元素的namespace的元素值        String namespace = this.builderAssistant.getCurrentNamespace();        if (namespace != null) {            Class boundType = null;            try {                boundType = Resources.classForName(namespace);            } catch (ClassNotFoundException var4) {                //如果没有这个类,可以直接忽略,这是因为namespace属性值只需要保持唯一就可以了,并不一定对应一个XXXMapper接口,没有XXXMapper接口时,我们可以直接使用SqlSession来进行增删改查            }            if (boundType != null && !this.configuration.hasMapper(boundType)) {                this.configuration.addLoadedResource("namespace:" + namespace);                //如果namespace属性值有对应的java类,调用configuration中的addMapper方法,将其添加到MapperRegistry中                this.configuration.addMapper(boundType);            }        }    }
public <T> void addMapper(Class<T> type) {    //这个类必须是一个class接口,因为使用的是JDK动态代理,所以需要接口,否则不会针对其生成动态代理        if (type.isInterface()) {            if (this.hasMapper(type)) {                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");            }            boolean loadCompleted = false;            try {                //用于生成一个MapperProxyFacotry,用于后面生成动态代理类                this.knownMappers.put(type, new MapperProxyFactory(type));                //以下代码块主要用于解析我们定义的XXXMapper接口里面使用的注解,这里主要处理不使用xml映射文件的情况                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);                parser.parse();                loadCompleted = true;            } finally {                if (!loadCompleted) {                    this.knownMappers.remove(type);                }            }        }    }

MapperRegistry内部维护了一个映射关系,每个接口对应一个MapperProxyFactory(生成动态代理工厂类)

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

这样便于在后面调用MapperRegistrygetMapper()时,直接从Map中获取某一个接口对应的动态代理工厂类,然后再利用工厂类对其接口生成真正的动态代理类

Configuration的getMapper()方法

开始我们通过sqlsession的getMapper()方法调用获取到动态代理类

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);public class DefaultSqlSession implements SqlSession {  private final Configuration configuration;  private final Executor executor;  @Override  public <T> T getMapper(Class<T> type) {    return configuration.getMapper(type, this);  }  ...}

Configuration中的getMapper()方法内部其实是使用的MapperRegistrygetMapper()方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {        MapperProxyFactory<T> 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);            }        }    }
protected T newInstance(MapperProxy<T> mapperProxy) {    //这里使用JDK动态代理,通过Proxy.newProxyInstance生成动态代理类    //newProxyInstance的参数:类加载器,接口类,InvocationHandler接口实现类    //动态代理可以将所有的接口的调用重定向到调用处理器InvocationHandler,调用它的invoke方法        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);    }    public T newInstance(SqlSession sqlSession) {        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);        return this.newInstance(mapperProxy);    }

这里就是InvocationHandler接口的实现类MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {    private static final long serialVersionUID = -6424540398559729838L;    private final SqlSession sqlSession;    private final Class<T> mapperInterface;    private final Map<Method, MapperMethod> methodCache;    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {        this.sqlSession = sqlSession;        this.mapperInterface = mapperInterface;        this.methodCache = methodCache;    }    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        try {            //如果调用的是Object类中定义的方法,直接通过反射调用即可            if (Object.class.equals(method.getDeclaringClass())) {                return method.invoke(this, args);            }            if (this.isDefaultMethod(method)) {                return this.invokeDefaultMethod(proxy, method, args);            }        } catch (Throwable var5) {            throw ExceptionUtil.unwrapThrowable(var5);        }        //调用XXXMapper接口自定义的方法,进行代理        //首先将当前被调用的方法构造成一个MapperMethod对象,然后调用其excute方法真正开始执行        MapperMethod mapperMethod = this.cachedMapperMethod(method);        return mapperMethod.execute(this.sqlSession, args);    }       ...}
public Object execute(SqlSession sqlSession, Object[] args) {        Object param;        Object result;        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);            }            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类,在excute方法中首先使用swithc case语句根据SqlCommandgetType()方法,判断要执行的sql类型,然后调用SqlSession的增删改查方法

getMapper()方法流程图

参考资料 & 鸣谢


  • https://pjmike.github.io/2018...