Mybatis中@Param注解详细使⽤和原理分析
对于⽬前市场上⽕爆的持久层框架MyBatis相信⼤家在⼯作中肯定是⽤得很多,但是你对其mapper接⼝代理对象和其⽅法上的@Param注解⼜了解多少呢?
废话不多说,接来下就给⼤家来分析下
MapperRegistry
MapperRegistry是⽤于注册和缓存当前框架中所有的mapper接⼝
public class MapperRegistry {
//框架的配置对象
private final Configuration config;
//存放已经注册的mapper接⼝和其代码对象的⼯⼚
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();;
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//从map中取出已经注册的mapper接⼝代理对象的⼯⼚
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) (type);
//创建并返回mapper接⼝的代码对象
wInstance(sqlSession);
}
...其他⽅法省略...
}
上述代码是SqlSession中调⽤getMapper(mapper接⼝)⽅法的底层,也就是说我们调⽤getMapper⽅法其底层是调⽤了MapperRegistry 对象的getMapper⽅法,那么我们继续往下研究那就要去看MapperProxyFactory中的newInstance⽅法啦
MapperProxyFactory
MapperProxyFactory是mapper的代理⼯⼚专门⽤于创建mapper接⼝的代理对象
public class MapperProxyFactory<T> {
//代理的接⼝
private final Class<T> mapperInterface;
//缓存代理的⽅法
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public T newInstance(SqlSession sqlSession) {
//创建⼀个MapperProxy对象,其内部封装了⽅法拿到接⼝的代码对象
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy); //调⽤⽅法返回接⼝的代理对象
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//底层使⽤JDK动态创建接⼝的代理对象
return (T) ClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
...其他⽅法省略...
}
通过上述代码我们最终看到了mapper接⼝的代理对象其底层使⽤的是JDK的动态代理技术创建并返回代理对象,最终接⼝中所有的⽅法都会由mapperProxy对象中的invoke⽅法来实现,当前类的字段MapperMethod对象⾄关总要,后期代理对象的invoke所执⾏的⽅法,最终是会调⽤到其对象的execute⽅法
MapperProxy
MapperProxy类实现了JDK动态代理的InvocationHandler接⼝,最后将由该对象的invoke⽅法来完成真正的⽅法执⾏
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断是否是Object类中的⽅法,如equals/hashCode/toString等⽅法
if (Object.class.DeclaringClass())) {
try { //原封不动调⽤Object类中⽅法作为代理对象⽅法的默认实现
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//接⼝中其他的⽅法则调⽤之前⽅法缓冲器的处理策略
final MapperMethod mapperMethod = cachedMapperMethod(method);
ute(sqlSession, args);
}
...其他⽅法省略...
}
MapperMethod
MapperMethod类是处理mapper接⼝中⽅法的真正处理器,该类内部的execute明确了代理的⽅法参数要怎么处理,查询得到的结果怎么封装然后返回
public class MapperMethod {
//对执⾏的SQL标签的封装,包含SQL类型和任务的完整ID等信息
private final SqlCommand command;
//代理⽅法的签名,其内部封装了⼀系列操作,如⽅法多参数时对@Param注解的处理
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (Type()) { //针对DML/DQL操作执⾏
case INSERT: {
Object param = vertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.Name(), param));
break;
}
case UPDATE: {
Object param = vertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.Name(), param));
break;
}
case DELETE: {
Object param = vertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.Name(), param));
break;
}
case SELECT:
if (urnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (urnsMany()) {
result = executeForMany(sqlSession, args);
} else if (urnsMap()) {
result = executeForMap(sqlSession, args);
} else if (urnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = vertArgsToSqlCommandParam(args);
result = sqlSession.Name(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + Name());
}
if (result == null && ReturnType().isPrimitive() && !urnsVoid()) {
throw new BindingException("Mapper method '" + Name()
+ " attempted to return null from a method with a primitive return type (" + ReturnType() + ").");
}
return result;
}
}
MethodSignature类中的convertArgsToSqlCommandParam⽅法处理接⼝中的参数怎么转换成能⽤于执⾏SQL任务的参数,以下是底层执⾏的getNamedParams⽅法
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
//接⼝⽅法没有参数,返回null
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
//接⼝⽅法上只有1个参数则返回唯⼀的那个对象
return args[names.firstKey()];
} else {
//接⼝⽅法上不⽌⼀个参数,就会把所有的参数封装到Map中然后返回
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : Set()) {
//把@Param注解中的value作为key,对应变量的值作为value
param.Value(), Key()]);
//⾃动⽣成key (param1, param2, ...)
jdk怎么使用final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
//为了不覆盖@Param注解设置的key
if (!ainsValue(genericParamName)) {
param.put(genericParamName, Key()]);
}
i++;
}
return param;
}
}
结合⼀个实际的例⼦来说明下:
假如调⽤⽅法时传⼊的实际参数是username=逍遥,password=123,那么在调⽤时我们可以发现接⼝中的⽅法超过1个的所以⽅法会执⾏最后的else代码
作者:梁开权,叩丁狼教育讲师。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。