Merge pull request #7 from huifer/master

mybatis  source code
pull/8/head
AmyliaY 5 years ago committed by GitHub
commit e188cbefd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,432 @@
# mybatis 反射
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis 反射相关类的源码
## addDefaultConstructor
- mybatis 的反射相关内容在`org.apache.ibatis.reflection` 下存放. 本片主要讲解`org.apache.ibatis.reflection.Reflector`类, 先看一下该类的属性
```java
public class Reflector {
/**
* 实体类.class
*/
private final Class<?> type;
/**
* 可读 属性
*/
private final String[] readablePropertyNames;
/**
* 可写 属性值
*/
private final String[] writablePropertyNames;
/**
* set 方法列表
*/
private final Map<String, Invoker> setMethods = new HashMap<>();
/**
* get 方法列表
*/
private final Map<String, Invoker> getMethods = new HashMap<>();
/**
* set 的数据类型
*/
private final Map<String, Class<?>> setTypes = new HashMap<>();
/**
* get 的数据类型
*/
private final Map<String, Class<?>> getTypes = new HashMap<>();
/**
* 构造函数
*/
private Constructor<?> defaultConstructor;
/**
* 缓存数据, 大写KEY
*/
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();
}
```
- 构造方法, 构造方法传入一个类的字节码,在构造方法中设置相关的属性值
```java
public class Reflector {
/**
* @param clazz 待解析类的字节码
*/
public Reflector(Class<?> clazz) {
type = clazz;
// 构造方法
addDefaultConstructor(clazz);
// get 方法
addGetMethods(clazz);
// set 方法
addSetMethods(clazz);
// 字段值
addFields(clazz);
readablePropertyNames = getMethods.keySet().toArray(new String[0]);
writablePropertyNames = setMethods.keySet().toArray(new String[0]);
for (String propName : readablePropertyNames) {
// 循环操作设置到缓存中,
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
}
}
```
- `addDefaultConstructor` 方法 , 下面截图内容为JDK8 mybatis中 的内容
```java
private void addDefaultConstructor(Class<?> clazz) {
// 获取类里面的所有构造方法
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
// 过滤得到空参构造 constructor -> constructor.getParameterTypes().length == 0
Arrays.stream(constructors).filter(constructor -> constructor.getParameterTypes().length == 0)
.findAny().ifPresent(constructor -> {
System.out.println("有空参构造");
this.defaultConstructor = constructor;
});
}
```
- 创建一个测试类
```java
public class People {
private String name;
public People() {
}
public People(String name) {
this.name = name;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
```java
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
class HfReflectorTest {
@Test
void getDefaultConstructorTest() throws Exception {
Reflector reflector = new Reflector(People.class);
// 获取空参构造方法
Constructor<?> defaultConstructor = reflector.getDefaultConstructor();
People o = (People) defaultConstructor.newInstance();
o.setName("hhh");
System.out.println(o);
}
}
```
- 准备工作完成了开始进行 debug , 在`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`这个方法上打上断点
![1575890354400](/images/mybatis/1575890354400.png)
观察`constructors`属性存在两个方法,这两个方法就是我在`People`类中的构造方法.
根据语法内容我们应该对`parameterTypes`属性进行查看
![1575890475839](/images/mybatis/1575890475839.png)
可以发现空参构造的`parameterTypes`长度是0.因此可以确认`org.apache.ibatis.reflection.Reflector#addDefaultConstructor`方法获取了空参构造
- 继续看`org.apache.ibatis.reflection.Reflector#getDefaultConstructor`方法, 该方法是获取构造函数的方法,如果构造函数没有就抛出异常,这也是为什么我们的实体类需要把空参构造写上去的原因。
```java
public Constructor<?> getDefaultConstructor() {
if (defaultConstructor != null) {
return defaultConstructor;
} else {
// 如果没有空参构造抛出的异常
throw new ReflectionException("There is no default constructor for " + type);
}
}
```
## addGetMethods
- 该方法获取了所有`get`和`is`开头的方法
```java
private void addGetMethods(Class<?> clazz) {
// 反射方法
Map<String, List<Method>> conflictingGetters = new HashMap<>();
Method[] methods = getClassMethods(clazz);
// JDK8 filter 过滤get 开头的方法
Arrays.stream(methods).filter(m -> m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName()))
.forEach(m -> addMethodConflict(conflictingGetters, PropertyNamer.methodToProperty(m.getName()), m));
resolveGetterConflicts(conflictingGetters);
}
```
- 该方法中依旧使用了JDK8语法通过`m.getParameterTypes().length == 0 && PropertyNamer.isGetter(m.getName())`来判断是否是`get`或·`is`开头的内容
- 调用`org.apache.ibatis.reflection.property.PropertyNamer`
```java
public static boolean isGetter(String name) {
// 在语义上 is 开头的也是get开头的
return (name.startsWith("get") && name.length() > 3) || (name.startsWith("is") && name.length() > 2);
}
```
- `resolveGetterConflicts`方法后续介绍
## getClassMethods
- `org.apache.ibatis.reflection.Reflector#getClassMethods`,该方法将传入对象的所有可见方法都获取到进行唯一标识处理成一个`Map`对象 添加方法为`org.apache.ibatis.reflection.Reflector#addUniqueMethods`
```java
private Method[] getClassMethods(Class<?> clazz) {
// 方法唯一标识: 方法
Map<String, Method> uniqueMethods = new HashMap<>();
Class<?> currentClass = clazz;
while (currentClass != null && currentClass != Object.class) {
// getDeclaredMethods 获取 public ,private , protcted 方法
addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());
// we also need to look for interface methods -
// because the class may be abstract
// 当前类是否继承别的类(实现接口)如果继承则需要进行操作
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
// getMethods 获取本身和父类的 public 方法
addUniqueMethods(uniqueMethods, anInterface.getMethods());
}
// 循环往上一层一层寻找最后回到 Object 类 的上级为null 结束
currentClass = currentClass.getSuperclass();
}
Collection<Method> methods = uniqueMethods.values();
return methods.toArray(new Method[0]);
}
```
- `org.apache.ibatis.reflection.Reflector#addUniqueMethods`
```java
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
for (Method currentMethod : methods) {
// 桥接, 具体还不知道
// TODO: 2019/12/9 JAVA 桥接方法
if (!currentMethod.isBridge()) {
// 方法的唯一标识
String signature = getSignature(currentMethod);
// check to see if the method is already known
// if it is known, then an extended class must have
// overridden a method
if (!uniqueMethods.containsKey(signature)) {
uniqueMethods.put(signature, currentMethod);
}
}
}
}
```
- 唯一标识方法`org.apache.ibatis.reflection.Reflector#getSignature`
```java
/**
* 方法唯一标识,返回值类型#方法名称:参数列表
*
* @param method
* @return
*/
private String getSignature(Method method) {
StringBuilder sb = new StringBuilder();
Class<?> returnType = method.getReturnType();
if (returnType != null) {
sb.append(returnType.getName()).append('#');
}
sb.append(method.getName());
Class<?>[] parameters = method.getParameterTypes();
for (int i = 0; i < parameters.length; i++) {
sb.append(i == 0 ? ':' : ',').append(parameters[i].getName());
}
return sb.toString();
}
```
- 照旧我们进行 debug 当前方法为`toString`方法
![1575891988804](/images/mybatis//1575891988804.png)
从返回结果可以看到`sb.toString`返回的是: `返回值类型#方法名`
![1575892046692](/images/mybatis//1575892046692.png)
上图返回结果为`void#setName:java.lang.String` 命名规则:`返回值类型#方法名称:参数列表`
回过头看看`uniqueMethods`里面是什么
![1575892167982](/images/mybatis//1575892167982.png)
方法签名:方法
目前完成了一部分还有一个继承问题需要debug看一下, 编写一个`Man`继承`People` 还需要实现接口
```java
public class Man extends People implements TestManInterface {
@Override
public Integer inte() {
return 1;
}
public String hello() {
return "hello";
}
}
```
```java
public interface TestManInterface {
public Integer inte();
}
```
目标明确了就直接在
![1575892414120](/images/mybatis//1575892414120.png)
这里打断点了
![1575892511471](/images/mybatis//1575892511471.png)
在进入循环之前回率先加载本类的所有可见方法
```java
if (!uniqueMethods.containsKey(signature)) {
// 如果存在该方法唯一签名则不添加
uniqueMethods.put(signature, currentMethod);
}
```
接下来断点继续往下走
![1575892645405](/images/mybatis//1575892645405.png)
走到这一步我们来看看`currentClass.getSuperclass()`是不是上一级的类
![1575892687076](/images/mybatis//1575892687076.png)
通过断点可见这个`currentClass`现在是`People`类,根据之前所说的最终`uniqueMethods`应该存在父类的方法
![1575892763661](/images/mybatis//1575892763661.png)
可以看到父类的方法也都存在了
## resolveGetterConflicts
- `org.apache.ibatis.reflection.Reflector#resolveGetterConflicts`
这个方法解决了`get`方法的冲突问题,同名方法不同返回值
```java
private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
Method winner = null;
String propName = entry.getKey();
boolean isAmbiguous = false;
for (Method candidate : entry.getValue()) {
if (winner == null) {
winner = candidate;
continue;
}
Class<?> winnerType = winner.getReturnType();
Class<?> candidateType = candidate.getReturnType();
if (candidateType.equals(winnerType)) {
if (!boolean.class.equals(candidateType)) {
isAmbiguous = true;
break;
} else if (candidate.getName().startsWith("is")) {
winner = candidate;
}
} else if (candidateType.isAssignableFrom(winnerType)) {
// OK getter type is descendant
} else if (winnerType.isAssignableFrom(candidateType)) {
winner = candidate;
} else {
isAmbiguous = true;
break;
}
}
addGetMethod(propName, winner, isAmbiguous);
}
}
```
## addFields
- `org.apache.ibatis.reflection.Reflector#addFields`
获取类的所有字段没什么好说的直接递归就可以获取了.
```jade
private void addFields(Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!setMethods.containsKey(field.getName())) {
// issue #379 - removed the check for final because JDK 1.5 allows
// modification of final fields through reflection (JSR-133). (JGB)
// pr #16 - final static can only be set by the classloader
int modifiers = field.getModifiers();
if (!(Modifier.isFinal(modifiers) && Modifier.isStatic(modifiers))) {
addSetField(field);
}
}
if (!getMethods.containsKey(field.getName())) {
addGetField(field);
}
}
if (clazz.getSuperclass() != null) {
addFields(clazz.getSuperclass());
}
}
```
## 属性查看
- 下图为一个类的解析结果
![1575894218362](/images/mybatis/1575894218362.png)

@ -0,0 +1,250 @@
# mybatis 日志源码
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis 日志相关源码
## 核心类
- `org.apache.ibatis.logging.Log`
- `org.apache.ibatis.logging.LogFactory`
- 多个日志实现
- `org.apache.ibatis.logging.log4j2.Log4j2Impl`
- `org.apache.ibatis.logging.slf4j.Slf4jLocationAwareLoggerImpl`
- ...
## 源码流程
- mybatis 提供了一个日志接口,内容如下.
```java
/**
* mybatis 的日志接口,提供日志级别
* <ol>
* <li>error</li>
* <li>debug</li>
* <li>trace</li>
* <li>warn</li>
* </ol>
* <p>通过自己定义的接口来实现各大日志框架的内容达到高可用</p>
* @author Clinton Begin
*/
public interface Log {
boolean isDebugEnabled();
boolean isTraceEnabled();
void error(String s, Throwable e);
void error(String s);
void debug(String s);
void trace(String s);
void warn(String s);
}
```
- 有了日志接口必然有实现类, mybatis 有`log4j2` 、 `slf4j` 等日志的相关实现 , 下面是`Slf4jImpl`的代码,其他代码也是一样的模式进行初始化就不再重复贴代码了.
```java
public class Slf4jImpl implements Log {
private Log log;
/**
* 创建日志实例
* @param clazz
*/
public Slf4jImpl(String clazz) {
Logger logger = LoggerFactory.getLogger(clazz);
if (logger instanceof LocationAwareLogger) {
try {
// check for slf4j >= 1.6 method signature
logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
return;
} catch (SecurityException | NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
}
// Logger is not LocationAwareLogger or slf4j version < 1.6
log = new Slf4jLoggerImpl(logger);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
log.error(s, e);
}
@Override
public void error(String s) {
log.error(s);
}
@Override
public void debug(String s) {
log.debug(s);
}
@Override
public void trace(String s) {
log.trace(s);
}
@Override
public void warn(String s) {
log.warn(s);
}
}
```
- 通过上述方法来达到统一接口多个实现,这个在开发中也经常使用.多日志的实现方法有了还缺一个创建方法,创建方法由`org.apache.ibatis.logging.LogFactory`提供
```java
/**
* <p>日志工厂,实现内容:</p>
* <ol>
* <li>org.slf4j.Logger 日志框架 slf4j</li>
* <li>org.apache.commons.logging.Log 日志框架 apache</li>
* <li>org.apache.logging.log4j.Logger 日志框架 log4j2</li>
* <li>org.apache.log4j.Logger 日志框架 log4j </li>
* <li>java.util.logging.Logger 日志框架,JDK的logger</li>
*
* </ol>
* @author Clinton Begin
* @author Eduardo Macarron
*/
public final class LogFactory {
/**
* Marker to be used by logging implementations that support markers.
*/
public static final String MARKER = "MYBATIS";
private static Constructor<? extends Log> logConstructor;
/**
* 日志的实现类的具体选择
*/
static {
// slf4j 日志
tryImplementation(LogFactory::useSlf4jLogging);
// apache 日志
tryImplementation(LogFactory::useCommonsLogging);
// log4j2 日志
tryImplementation(LogFactory::useLog4J2Logging);
// log4 日志
tryImplementation(LogFactory::useLog4JLogging);
// JDK 日志
tryImplementation(LogFactory::useJdkLogging);
// 空 日志
tryImplementation(LogFactory::useNoLogging);
}
/**
* 私有化构造方法,这是一个单例
*/
private LogFactory() {
// disable construction
}
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
public static synchronized void useCommonsLogging() {
setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
}
public static synchronized void useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
public static synchronized void useJdkLogging() {
setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}
public static synchronized void useStdOutLogging() {
setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
}
public static synchronized void useNoLogging() {
setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
}
/**
* 选择具体的日志实现
*/
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
// run()? 似乎违背了代码的语义, 看静态方法.静态方法多行同类型的操作我认为是一个多线程
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
/**
* 选择具体的日志实现
*/
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
}
```
- `LogFactory`是一个单例对象,对外公开`getLog`方法在使用时直接`private static final Log log = LogFactory.getLog(CglibProxyFactory.class);`即可
- 在 `org.apache.ibatis.session.Configuration` 中可以看到下面这些注册方法
```java
// 日志实现类
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
```

@ -0,0 +1,202 @@
# MapperMethod
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis MapperMethod 源码
- 源码地址: `org.apache.ibatis.binding.MapperMethod`,核心方法是`execute`
```java
/**
* CRUD 不同的执行处理
*
* @param sqlSession
* @param args
* @return
*/
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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
```
```java
/**
* 根据 resultHandler 进行处理
*
* @param sqlSession
* @param args
*/
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
// 判断是否有 RowBounds
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
```
- 返回值为多个的情况
```java
/**
* 针对多个查询结果进行 ,转换成不同的 list 或者数组
*
* @param sqlSession
* @param args
* @param <E>
* @return
*/
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
// 直接 list
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
// 转换成 array
return convertToArray(result);
} else {
// 转换成 collection
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
```
### convertToArray
```java
/**
* 转换为数组
*
* @param list 数据库查询结果
* @param <E>
* @return
*/
@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
// 获取返回类型
Class<?> arrayComponentType = method.getReturnType().getComponentType();
// new 一个 array
Object array = Array.newInstance(arrayComponentType, list.size());
if (arrayComponentType.isPrimitive()) {
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
}
return array;
} else {
// 通过 toArray方法转换
return list.toArray((E[]) array);
}
}
```
### convertToDeclaredCollection
```java
/**
* 转换为不同的list对象
*
* @param config
* @param list 数据库查询结果
* @param <E>
* @return
*/
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
// mybatis ObjectFactory 创建mapper 的返回结果对象
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
// metaObject.objectWrapper => CollectionWrapper
// MetaObject 对象的 objectWrapper 现在是 CollectionWrapper 它是 Collection 的包装
metaObject.addAll(list);
return collection;
}
```
- 上述两个为转换的过程,其实质还是在 `org.apache.ibatis.session.SqlSession` 中做执行操作
## debug
- 修改mapper返回数组对`org.apache.ibatis.binding.MapperMethod#convertToArray`方法进行测试
```java
HsSell[] list(@Param("ID") Integer id);
```
![image-20191219092442456](/images/mybatis/image-20191219092442456.png)
- 修改mapper,对`org.apache.ibatis.binding.MapperMethod#convertToDeclaredCollection`进行测试
```java
LinkedList<HsSell> list(@Param("ID") Integer id);
```
![image-20191219093043035](/images/mybatis/image-20191219093043035.png)

@ -0,0 +1,178 @@
# MethodSignature
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis MethodSignature 类
- `org.apache.ibatis.binding.MapperMethod.MethodSignature`
```java
/**
* 方法签名
*/
public static class MethodSignature {
/**
* 返回值是否多个
*/
private final boolean returnsMany;
/**
* 返回值是不是map
*/
private final boolean returnsMap;
/**
* 返回值是否 void
*/
private final boolean returnsVoid;
/**
* 返回的是否是一个游标
*/
private final boolean returnsCursor;
/**
* 返回值是否是 optional
*/
private final boolean returnsOptional;
/**
* 返回类型
*/
private final Class<?> returnType;
/**
* map key
*/
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
/**
* 参数解析
*/
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
/**
* 方法主要是把方法参数转换为SQL命令参数。
*
* @param args
* @return
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
/**
* 是否有 {@link RowBounds}
*
* @return
*/
public boolean hasRowBounds() {
return rowBoundsIndex != null;
}
public RowBounds extractRowBounds(Object[] args) {
return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
}
/**
* 是否uresultHandler
*
* @return
*/
public boolean hasResultHandler() {
return resultHandlerIndex != null;
}
public ResultHandler extractResultHandler(Object[] args) {
return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
}
public String getMapKey() {
return mapKey;
}
public Class<?> getReturnType() {
return returnType;
}
public boolean returnsMany() {
return returnsMany;
}
public boolean returnsMap() {
return returnsMap;
}
public boolean returnsVoid() {
return returnsVoid;
}
public boolean returnsCursor() {
return returnsCursor;
}
/**
* return whether return type is {@code java.util.Optional}.
*
* @return return {@code true}, if return type is {@code java.util.Optional}
* @since 3.5.0
*/
public boolean returnsOptional() {
return returnsOptional;
}
/**
* 获取参数名
* {@link RowBounds}
*
* @param method mapper 方法
* @param paramType
* @return
*/
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
// 获取参数类型
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) {
index = i;
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
/**
* 获取 {@link MapKey} 注解数据
*
* @param method
* @return
*/
private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
}
```

@ -0,0 +1,207 @@
# ParamNameResolver 源码解析
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis `@Param` 注解和`ParamNameResolver`
## 源码
- `org.apache.ibatis.reflection.ParamNameResolver`
```java
/**
* {@link Param} 注解的扫描工具和处理工具
*/
public class ParamNameResolver {
public static final String GENERIC_NAME_PREFIX = "param";
/**
* <p>
* The key is the index and the value is the name of the parameter.<br />
* The name is obtained from {@link Param} if specified. When {@link Param} is not specified,
* the parameter index is used. Note that this index could be different from the actual index
* when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}).
* </p>
* <ul>
* <li>aMethod(@Param("M") int a, @Param("N") int b) -&gt; {{0, "M"}, {1, "N"}}</li>
* <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
* <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
* </ul>
*
* {@link ParamNameResolver#ParamNameResolver(org.apache.ibatis.session.Configuration, java.lang.reflect.Method)} 中的map 变量值转换而得
* {参数索引: 参数名称(arg0,Param注解的value)}
*
*/
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
public ParamNameResolver(Configuration config, Method method) {
// 方法参数类型
final Class<?>[] paramTypes = method.getParameterTypes();
// 参数上的注解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
// 参数索引和参数名称
// {参数索引:参数名称}
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
// 如果是特殊类型跳过
continue;
}
String name = null;
// 注解扫描@Param
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 是否为 Param 注解的下级
if (annotation instanceof Param) {
hasParamAnnotation = true;
// 获取 value 属性值
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// 如果没有写 @param 处理方式如下
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
// 循环参数列表 放入map 对象
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
/**
* 是否为特殊参数 , 依据 是否是 {@link RowBounds} 或者 {@link ResultHandler}
* @param clazz
* @return
*/
private static boolean isSpecialParameter(Class<?> clazz) {
return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
}
/**
* 返回方法名 参数索引
* @param method
* @param paramIndex
* @return
*/
private String getActualParamName(Method method, int paramIndex) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}
/**
* Returns parameter names referenced by SQL providers.
*/
public String[] getNames() {
return names.values().toArray(new String[0]);
}
/**
* <p>
* A single non-special parameter is returned without a name.
* Multiple parameters are named using the naming rule.
* In addition to the default names, this method also adds the generic names (param1, param2,
* ...).
* </p>
* <p>
* 通常参数异常在这个地方抛出 param ... 异常
* 获取参数名称,和参数传递的真实数据
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
// 是否有参数
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 没有使用 @param 注解 参数只有一个
return args[names.firstKey()];
} else {
// 根据索引创建
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
// param + 当前索引位置
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
```
## debug 阶段
- 测试用例为同一个 每次修改mapper方法参数来进行debug
```java
@Test
void testXmlConfigurationLoad() throws IOException {
Reader reader = Resources.getResourceAsReader("mybatis-config-demo.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
Configuration configuration = factory.getConfiguration();
SqlSession sqlSession = factory.openSession();
HsSellMapper mapper = sqlSession.getMapper(HsSellMapper.class);
List<HsSell> list = mapper.list(2);
List<Object> objects = sqlSession.selectList("com.huifer.mybatis.mapper.HsSellMapper.list");
assertEquals(list.size(), objects.size());
}
```
```java
List<HsSell> list( Integer id);
```
如果不写`@Param`称则返回
![image-20191219083223084](assets/image-20191219083223084.png)
```java
List<HsSell> list(@Param("ID") Integer id);
```
- 写`@Param`返回
![image-20191219083344439](/images/mybatis/image-20191219083344439.png)
![image-20191219083354873](/images/mybatis/image-20191219083354873.png)
- `org.apache.ibatis.reflection.ParamNameResolver#getNamedParams`
```java
List<HsSell> list( Integer id);
```
![image-20191219084455292](/images/mybatis/image-20191219084455292.png)
```java
List<HsSell> list(@Param("ID") Integer id);
```
写上`@Param`
![image-20191219084943102](/images/mybatis/image-20191219084943102.png)
![image-20191219085131167](/images/mybatis/image-20191219085131167.png)

@ -0,0 +1,104 @@
# sqlCommand
- Author: [HuiFer](https://github.com/huifer)
- Description: 该文介绍 mybatis sqlCommand类的源码
- `org.apache.ibatis.binding.MapperMethod.SqlCommand`
```java
/**
* 核心内容: sql id , Sql 类型
*/
public static class SqlCommand {
/**
* sql id
*/
private final String name;
/**
* sql 类型select|update|delete|insert|...
*/
private final SqlCommandType type;
/**
* 根据传递的参数 设置sql的一些属性 , sql id , type .
*
* @param configuration
* @param mapperInterface
* @param method
*/
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 方法名
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// Statement 实质是sql
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
/**
* @param mapperInterface mapper.class
* @param methodName 方法名
* @param declaringClass 可能是 mapper.class
* @param configuration
* @return
*/
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// 接口名称+方法名
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
// 从 configuration 获取
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// new 一个新的实例
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
```
![image-20191218191512184](/images/mybatis/image-20191218191512184.png)
![image-20191218191550550](/images/mybatis/image-20191218191550550.png)

@ -0,0 +1,175 @@
# GenericTokenParser
- Author: [HuiFer](https://github.com/huifer)
-
```java
/**
* Copyright 2009-2019 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.parsing;
/**
* @author Clinton Begin
*/
public class GenericTokenParser {
/**
* 开始标记
*/
private final String openToken;
/**
* 结束标记
*/
private final String closeToken;
/**
* 标记处理器
*/
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/**
* 核心处理方法 , 看测试类{@link org.apache.ibatis.parsing.GenericTokenParserTest}
* @param text
* @return
*/
public String parse(String text) {
// 判断是否空
if (text == null || text.isEmpty()) {
return "";
}
// search open token
// 判断 openToken 所在的位置-1不存在
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
// 循环处理 assertEquals("James T Kirk reporting.", parser.parse("${first_name} ${initial} ${last_name} reporting."));
// 将${} 转换成正常文本
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// `\` 忽略这个参数
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
// offset 重新计算进行下一步循环
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// 遇到`\`该参数不需要处理
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
// 计算offset重新推算替换的字符串
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// end == -1 closeToken 不存在,获取后面的所有字符串, openToken - closeToken 之间的内容
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// closeToken存在 继续执行
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
// 返回的是一个替换后的sql脚本
return builder.toString();
}
}
```
- 一个具体的例子`org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler`
- 具体类`org.apache.ibatis.builder.SqlSourceBuilder`
```java
/**
* ? 的来源
*
* @param content
* @return
*/
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
```
```java
/**
* sql 参数类型 返回值
*
* <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
* <!--@mbg.generated-->
* select
* <include refid="Base_Column_List" />
* from hs_sell
* where ID = #{id,jdbcType=INTEGER}
* </select>
* => 替换成问号
* select
* <p>
* <p>
* ID, USER_ID, GOOD_ID, PRICE, `SIZE`, COMPANY_ID, GROUP_ID, VERSION, DELETED, CREATE_USER,
* CREATE_TIME, UPDATE_USER, UPDATE_TIME, WORK_ORDER_ID
* <p>
* from hs_sell
* where ID = ?
*
* @param originalSql sql文本
* @param parameterType 默认 object
* @param additionalParameters
* @return
*/
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// org.apache.ibatis.builder.SqlSourceBuilder.ParameterMappingTokenHandler.handleToken
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
```
![image-20191219100446796](/images/mybatis/image-20191219100446796.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Loading…
Cancel
Save