本文主要介绍Mybatis通过动态代理避免对sqlSession直接调用,而是通过MapperProxy代理技术生成了具体dao接口的Mapper实例,里面封装了对sqlSession的调用;Mybatis预留了Interceptor接口,用户可以扩展该接口,实现自定义插件;Mybatis与Spring结合主要通过Spring 的FactoryBean技术实现;
把Mybatis源码概览(一)中的helloWorld例子的注释代码打开 如下
BlogDao mapper = session.getMapper(BlogDao.class); List<Blog> blogs= mapper.selectAll();
这样通过session.getMapper获取某个Dao接口的Mapper代理实例,这样后面查询就不需要直接对sqlSession来操作,在具体应用中会省略掉很多代码。具体生产Mapper代理原理我们可以Debug一步一步分析。
第一加载解析Mybatis配置文件时 跟进代码,下面这个片段是解析Mybatis几大属性 其中最后有个mappers
//XMLConfigBuilder类中 private void parseConfiguration(XNode root) { try { Properties settings = settingsAsPropertiess(root.evalNode("settings")); //issue #117 read properties first propertiesElement(root.evalNode("properties")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectionFactoryElement(root.evalNode("reflectionFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); //解析具体的mapper映射,转到下断代码 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
//XMLConfigBuilder类中 private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //解析通过xml resource引用其他mapper文件的方式 //<mapper resource="mapper/BlogMapper.xml"/> String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //获取到mapper映射文件 然后进行解析到configuration, //这样以后可以直接通过configuration取到该mapper XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //执行解析,此步过后,可以在configuration中看到MapperRegistry属性里面已经有实例, //而且其knownMappers已经有值,这一步主要调用MapperRegistry类中的addMapper方法 mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
//MapperRegistry类 //解析到mapper对应的dao接口 添加进去,并为该接口实例一个MapperProxyFacotry //(里面就是通过JDK Proxy动态生产一个具体接口实例) 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 { //为每个mapper接口添加一个MapperProxyFacotry knownMappers.put(type, new MapperProxyFactory<T>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
这里详细介绍下MapperMethod,MapperProxy,MapperProxyFactory,MapperRegistry这四个类之间的关系:
MapperRegistry可以理解为一个map ,维护了Mapper接口与具体的MapperProxyFactory的关系,registry一般直接放在congfiguration中,可以直接用来查询;获得到具体MapperProxyFactory之后,MapperProxyFactory主要是在newInstance中调用Proxy.newInstance来生产对应Mapper接口的具体MapperProxy,MapperProxy实现了InvocationHandler接口,这个MapperProxy为了提高效率,里面为该Mapper对应的每一个方法维护了一个对应的MapperMethod,这样实现了对MapperMethod的重复利用;MapperProxy在invoke方法中会根据调用的mapper方法名找一个对应的MapperMethod来执行具体的调用sqlSession发起SQL请求;如果没找到就new一个 ,并放到map缓存起来;
这里主要是通过动态代理技术把MapperMethod对sqlSession的执行操作封装到Mapper代理中
下面主要看下MapperProxy和MapperMethod源码
MapperProxy类
//实现了InvocationHandler,并把每个mapper方法对应的MapperMethod缓存起来 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; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //通过mapperMethod发起具体的请求 final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } //缓存MapperMethod实例,实现对象重复利用 private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
MapperMethod类
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; //---------省略代码-----// //可以看到最后还是转换到对sqlSession的调用 public Object execute(SqlSession sqlSession, Object[] args) { Object result; //判断具体SQL类型 执行增删改查操作 if (SqlCommandType.INSERT == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); } else if (SqlCommandType.SELECT == command.getType()) { if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else if (SqlCommandType.FLUSH == command.getType()) { result = sqlSession.flushStatements(); } else { throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } //-------省略代码--------// }
上面代理生产的准备工作完成后,我们在执行mapper接口具体方法时会到configuration->mapperRegistry中查找MapperProxy,返回具体的mapperProxy实例
//MapperRegistry类 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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); } }
至此通过动态代理技术返回代理对象,实现通过mapper接口直接调用就完成了。
Mybatis预留了Interceptor接口来实现Plugin技术,这里只简单介绍一下
首先实现一个Interceptor接口类,然后在配置xml中配置plugin。这样就可以根据Interceptor拦截我们需要的执行过程,比如可以动态修改MappedStatement,实现对SQL,SQL参数的修改。
这里介绍一个比较好用的分页插件 就是利用该原理实现:Mybatis分页插件 github https://github.com/pagehelper/Mybatis-PageHelper
一般扩展spring 都会用到spring schema技术,并实现相应的handler 解析类,解析相应的扩展配置,这里不多介绍。
Mybatis-Spring jar中最重要的几个类就是MapperFactoryBean,SqlSessionFactoryBean,SqlSessionTemplate。
先看看SqlSessionTemplate实现了SqlSession接口,对sqlSession做了一层包装。这个类构造时候需要SqlSessionFactory参数,主要还是用来生成SqlSession的,但是这里生产的是对SqlSession做了代理的,实现了对方法调用的异常处理,事务,session关闭等处理。
看下面SqlSessionTemplate关键代码
//其构造方法,需要传入sqlSessionFactory来生产sqlSession public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; //生成代理 this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } //内部类 实现invocationHandler接口 private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //utils 类的静态方法 SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { //实现具体调用 Object result = method.invoke(sqlSession, args); //提交事务 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { //异常处理 Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { //关闭session if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
SqlSessionFactoryBean主要是实现了spring FactoryBean,里面属性配置包含了datasource(数据源),configLocation(mybatis配置信息,比如plugin等),mapperLocations(我们写的sql mapper映射xml地址);有了这些我们就可以通过SqlSessionFactoryBean 为我们提供SqlSessionFactory实例。我们就可以把SqlSessionFactory交给SqlSessionTemplate为我们生成session代理。
MapperFactoryBean类主要实现了spring FactoryBean接口,通过getObject为我们提供Mapper代理,避免对sqlSession的手工操作。MapperFactoryBean扩展了抽象类SqlSessionDaoSupport,里面有对SqlSessionTemplate的封装,为我们提供session代理。
MapperFactoryBean中的关键代码
@Override public T getObject() throws Exception { //借助sqlSessiontTemplate生成的session代理,查找出对应我们dao接口对应的mapper实例 //这样我们在spring中只需要定义相应的dao接口方法参数 即可,不用手工一一来操作sqlsession return getSqlSession().getMapper(this.mapperInterface); }
到这mybatis-spring大概分析完毕,里面还有很多细枝末叶,这里就不介绍了。
本文链接:http://my.oschina.net/robinyao/blog/645886