mybatis源码解析六(代理模式再分析)
前面几期大概一起看了下maybatis的源码,这一期,我们通过设计模式来分析下mybatis的,但是在分析之前,我们再来屡一下mybatis的执行流程,做一个整体的讲解,
我们都知道,当我们写了mapper接口后,通过mapper接口myabtis就可以执行相应的查询,那马这里mybatis到底是怎么做到的那,前面已经分析关于一次了,但是比较笼统,
今天我们再一起分析下,我们都知道,在我们写完mapper接口后,我们在每个mapper接口上添加@Mapper注解或者在启动类上添加@MapperScan注解后,基本上剩下的工作就不用我们
在做了,mybatis会根据我们的xml文件中配置的sql语句,为我们返回结果,那到底mybatis是怎么通过接口就去生成实现类,来完成上述操作的那,我们自己也没有写实现类啊,
接下来,我们从源码中寻找答案
我写了个计较简单的查询查询
@Test
@Transactional
public void test5(){
final RyxAccount ryxAccountByPrimaryKey = ryxAccountService.getRyxAccountByPrimaryKey(1);
System.out.println(ryxAccountByPrimaryKey);
final RyxAccount ryxAccountByPrimaryKey1 = ryxAccountService.getRyxAccountByPrimaryKey(1);
System.out.println(ryxAccountByPrimaryKey1);
}
mapper接口
public interface RyxAccountMapper extends BaseMapper{
/**
* 根据primaryKey更新
*/
public Integer updateByPrimaryKey(@Param("bean") T t, @Param("id") Integer id);
/**
* 根据primaryKey删除
*/
public Integer deleteByPrimaryKey(@Param("id") Integer id);
/**
* 根据primaryKey获取对象
*/
public T selectByPrimaryKey(@Param("id") Integer id);
List
接下来,我们通过断点的方式,一探究竟
首先,当我们启动项目的时候,因为配置了@Mapperscan的原因,mybaits回去我们配置的扫描包下扫描接口(加载配置文件,获取连接,这些,就不做具体分析了),
当扫描到接口后,会进入到mybatis的MapperRegistry
看看他的appMapper方法
public
//分析是不是接口
if (type.isInterface()) {
//如果接口已经存在,则抛出异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//不存在,则将扫描到的mapper接口添加到knowerMapper中,knowerMapper是一个map,key为具体的mapper类,value为mapperProxyFactory
knownMappers.put(type, new MapperProxyFactory
//创建mapper注解解析器对象
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//执行paser方法
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//检查资源名称是否被加载过
loadXmlResource();
//设置当前的命名空间
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//解析是否配置了二级缓存注解@CacheNameSpace(这里解释下,和xml文件中配置了缓存是一个意思)
parseCache();
//解析二级缓存引用
parseCacheRef();
//获取接口中的方法
Method[] methods = type.getMethods();
//遍历此方法
for (Method method : methods) {
try {
// issue #237
//检查是否是桥接方法(主要目的是泛型的兼容问题)
if (!method.isBridge()) {
//解析语句
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
void parseStatement(Method method) {
//获取mapper接口方法中的参数类型
Class parameterTypeClass = getParameterType(method);
//获取语言驱动类
LanguageDriver languageDriver = getLanguageDriver(method);
//从注解获取sql源信息类(就是说我们在方法上配置了注解之后,这里就是获取方法上的注解的方法)
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
等这些做完之后那,我们继续往下看,加载sqlSesson我们先不做分析,直接看MappperProxy,还记得我们之前的分析嘛,myBatis将扫描完的接口添加到knowMappers中
其中key是具体得接口类,value为mapperProxyFactory,再获取到sqlSession后,最终会在MapperFactoryBean中调用getMapper
MapperFactoryBean,由于整合spring框架,这里不再调用mybatis的DefaultSqlSession,而是调用了SqlSessionTemplete
@Override
public T getObject() throws Exception {
//调用SqlSessionTemplete的getMapper方法
return getSqlSession().getMapper(this.mapperInterface);
}
public
return mapperRegistry.getMapper(type, sqlSession);
}
//最终回到了MapperRegistry中的getMapper方法
public
//从knownMappers通过key获取value,mapperProxyFactory
final MapperProxyFactory
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//通过jdk动态代理的方式生成代理类
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy
//代用jdk动态代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
最终看到了,我们实现代理的类,mapperProxy,我们看看这个类,实现了InvocationHandler接口,最终通过mapperMethod.execute方法去数据库查询数据并返回结果,
这路下来,是不是感觉真的是踏破铁鞋无觅处,柳暗花明又一村啊,真的是太难了,
public class MapperProxy
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class
private final Map
public MapperProxy(SqlSession sqlSession, Class
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
可能这样不太好看,我们仿照jdk的动态代理自己先写一个代理类,来模仿下.看看大神门的代码精髓
我们先自定义一个接口.
public interface RyxMapper {
String save(String str);
}
自定义一个mapperProxy
public class MapperProxy
private Class proxyInterface;
public MapperProxy(Class proxyInterface) {
this.proxyInterface = proxyInterface;
}
@SuppressWarnings("unchecked")
public T getProxy() {
return (T) Proxy.newProxyInstance(proxyInterface.getClassLoader(), new Class[]{proxyInterface}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我要开始执行了....查询数据库操作了");
System.out.println("查库操作已完成,返回结果啦");
return "数据库结果集:"+"123";
}
}
写一个测试类执行下:
public static void main(String[] args) {
RyxMapper mapper = (RyxMapper)new MapperProxy(RyxMapper.class).getProxy();
final String ok = mapper.save("ok");
System.out.println(ok);
}
最终的执行结果肯定是不言而喻的,没有问题,最终会打印出结果,真的是书上得来终觉浅,实践是检验真理的唯一途径啊.
ok,咱们接着往下分析,现在通过代理,我们拿到了要执行的sql语句和参数
接下来,回去调用
mapperMethod.execute(sqlSession, args);这个方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
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);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
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;
}
这段源码就不在赘述了,咱们直接看一个最简单的例子吧,
sqlSession.selectOne(command.getName(), param);
最终会走到DefautSqlSession中执行查询操作,这里,我们看到了,一个很警醒的注释,DefaultSqlSession是非线程安全的,下一章我们分析下这个问题
/**
- The default implementation for {@link SqlSession}.
- Note that this class is not Thread-Safe.
- @author Clinton Begin
*/
public class DefaultSqlSession implements SqlSession {
以上就是今天的内容,Thanks