2019独角兽企业重金招聘Python工程师标准>>>
本文主要介绍Mybatis通过动态代理避免对sqlSession直接调用,而是通过MapperProxy代理技术生成了具体dao接口的Mapper实例,里面封装了对sqlSession的调用;Mybatis预留了Interceptor接口,用户可以扩展该接口,实现自定义插件;Mybatis与Spring结合主要通过Spring 的FactoryBean技术实现;
MapperProxy
把Mybatis源码概览(一)中的helloWorld例子的注释代码打开 如下
BlogDao mapper = session.getMapper(BlogDao.class);
List 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文件的方式
//
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 void addMapper(Class 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(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 implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
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;
}
@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 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);
}
}
至此通过动态代理技术返回代理对象,实现通过mapper接口直接调用就完成了。
Plugin
Mybatis预留了Interceptor接口来实现Plugin技术,这里只简单介绍一下
首先实现一个Interceptor接口类,然后在配置xml中配置plugin。这样就可以根据Interceptor拦截我们需要的执行过程,比如可以动态修改MappedStatement,实现对SQL,SQL参数的修改。
这里介绍一个比较好用的分页插件 就是利用该原理实现:Mybatis分页插件 github https://github.com/pagehelper/Mybatis-PageHelper
Mybatis-Spring
一般扩展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