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的全局配置文件解析,相关的解析代码主要在XMLConfigBuilder
的parse()
方法中
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();
这样便于在后面调用MapperRegistry
的getMapper()
时,直接从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()
方法内部其实是使用的MapperRegistry
的getMapper()
方法
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
语句根据SqlCommand
的getType()
方法,判断要执行的sql类型,然后调用SqlSession的增删改查方法
getMapper()方法流程图
参考资料 & 鸣谢
- https://pjmike.github.io/2018...