简述

MyBatis在进行参数处理、结果映射等操作时,会涉及大量的反射操作。Java中的反射虽然功能强大,但是代码编写起来比较复杂且容易出错,为了简化反射操作的相关代码,MyBatis提供了专门的反射模块,该模块位于org.apache.ibatis.reflection包中,它对常见的反射操作做了进一步封装,提供了更加简洁方便的反射API。

Reflector & ReflectorFactory

Reflector

Reflector是MyBatis中反射模块的基础,每个Reflector对象都对应一个类,在Reflector中缓存了反射操作需要使用的类的元信息。Reflector 中各个字段的含义如下:


/**
* 缓存了反射操作需要使用的类的元信息。
* 允许在属性名和getter/setter方法之间轻松映射
*
* @author Clinton Begin
*/
public class Reflector { /**
* 对应Class类型
*/
private final Class<?> type;
/**
* 可读属性的名称集合,可读属性就是存在相应getter方法的属性,初始值为空数组
*/
private final String[] readablePropertyNames;
/**
* 可写属性的名称集合,可写属性就是存在相应setter方法的属性,初始值为空数组
*/
private final String[] writeablePropertyNames;
/**
* 记录了属性相应的setter方法,key是属性名称,value是Invoker对象,它是对setter方法对应Method对象的封装,
*/
private final Map<String, Invoker> setMethods = new HashMap<String, Invoker>();
/**
* 属性相应的getter方法集合,key是属性名称,value也是Invoker对象
* <p>
*/
private final Map<String, Invoker> getMethods = new HashMap<String, Invoker>();
/**
* 记录了属性相应的setter方法的参数值类型,key是属性名称,value是setter方法的参数类型
*/
private final Map<String, Class<?>> setTypes = new HashMap<String, Class<?>>();
/**
* 记录了属性相应的getter方法的返回值类型,key是属性名称,value是getter方法的返回值类型
*/
private final Map<String, Class<?>> getTypes = new HashMap<String, Class<?>>();
/**
* 记录了默认构造方法
*/
private Constructor<?> defaultConstructor;
/**
* 记录了所有属性名称集合
*/
private Map<String, String> caseInsensitivePropertyMap = new HashMap<String, String>(); /**
* 构造函数,对上述字段初始化
*/
public Reflector(Class<?> clazz) {
type = clazz;
addDefaultConstructor(clazz);
addGetMethods(clazz);
addSetMethods(clazz);
addFields(clazz);
readablePropertyNames = getMethods.keySet().toArray(new String[getMethods.keySet().size()]);
writeablePropertyNames = setMethods.keySet().toArray(new String[setMethods.keySet().size()]);
for (String propName : readablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
for (String propName : writeablePropertyNames) {
caseInsensitivePropertyMap.put(propName.toUpperCase(Locale.ENGLISH), propName);
}
} /**
* 获取指定Class对象的默认构造方法(包括无参构造方法)
*/
private void addDefaultConstructor(Class<?> clazz) {
Constructor<?>[] consts = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : consts) {
//默认构造方法没有参数
if (constructor.getParameterTypes().length == 0) {
//允许利用反射检查任意类的私有变量
if (canAccessPrivateMethods()) {
try {
constructor.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
if (constructor.isAccessible()) {
this.defaultConstructor = constructor;
}
}
}
} /**
* 负责解析类中定义的getter方法
*/
private void addGetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingGetters = new HashMap<String, List<Method>>();
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
if (method.getParameterTypes().length > 0) {
continue;
}
String name = method.getName();
if ((name.startsWith("get") && name.length() > 3)
|| (name.startsWith("is") && name.length() > 2)) {
//获取字段名
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingGetters, name, method);
}
}
resolveGetterConflicts(conflictingGetters);
} private void resolveGetterConflicts(Map<String, List<Method>> conflictingGetters) {
//遍历conflictingGetters集合
for (Entry<String, List<Method>> entry : conflictingGetters.entrySet()) {
Method winner = null;
//获取属性名
String propName = entry.getKey();
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)) {
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
} 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 {
//返回值相同,存在二义性,抛出异常
throw new ReflectionException(
"Illegal overloaded getter method with ambiguous type for property "
+ propName + " in class " + winner.getDeclaringClass()
+ ". This breaks the JavaBeans specification and can cause unpredictable results.");
}
}
addGetMethod(propName, winner);
}
} /**
* 收集get方法
*/
private void addGetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
getMethods.put(name, new MethodInvoker(method));
Type returnType = TypeParameterResolver.resolveReturnType(method, type);
getTypes.put(name, typeToClass(returnType));
}
} /**
* 收集set方法
*/
private void addSetMethods(Class<?> cls) {
Map<String, List<Method>> conflictingSetters = new HashMap<String, List<Method>>();
Method[] methods = getClassMethods(cls);
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("set") && name.length() > 3) {
if (method.getParameterTypes().length == 1) {
name = PropertyNamer.methodToProperty(name);
addMethodConflict(conflictingSetters, name, method);
}
}
}
resolveSetterConflicts(conflictingSetters);
} private void addMethodConflict(Map<String, List<Method>> conflictingMethods, String name, Method method) {
List<Method> list = conflictingMethods.get(name);
if (list == null) {
list = new ArrayList<Method>();
conflictingMethods.put(name, list);
}
list.add(method);
} private void resolveSetterConflicts(Map<String, List<Method>> conflictingSetters) {
for (String propName : conflictingSetters.keySet()) {
List<Method> setters = conflictingSetters.get(propName);
Class<?> getterType = getTypes.get(propName);
Method match = null;
ReflectionException exception = null;
for (Method setter : setters) {
Class<?> paramType = setter.getParameterTypes()[0];
if (paramType.equals(getterType)) {
// should be the best match
match = setter;
break;
}
if (exception == null) {
try {
match = pickBetterSetter(match, setter, propName);
} catch (ReflectionException e) {
// there could still be the 'best match'
match = null;
exception = e;
}
}
}
if (match == null) {
throw exception;
} else {
addSetMethod(propName, match);
}
}
} private Method pickBetterSetter(Method setter1, Method setter2, String property) {
if (setter1 == null) {
return setter2;
}
Class<?> paramType1 = setter1.getParameterTypes()[0];
Class<?> paramType2 = setter2.getParameterTypes()[0];
if (paramType1.isAssignableFrom(paramType2)) {
return setter2;
} else if (paramType2.isAssignableFrom(paramType1)) {
return setter1;
}
throw new ReflectionException("Ambiguous setters defined for property '" + property + "' in class '"
+ setter2.getDeclaringClass() + "' with types '" + paramType1.getName() + "' and '"
+ paramType2.getName() + "'.");
} private void addSetMethod(String name, Method method) {
if (isValidPropertyName(name)) {
setMethods.put(name, new MethodInvoker(method));
Type[] paramTypes = TypeParameterResolver.resolveParamTypes(method, type);
setTypes.put(name, typeToClass(paramTypes[0]));
}
} private Class<?> typeToClass(Type src) {
Class<?> result = null;
if (src instanceof Class) {
result = (Class<?>) src;
} else if (src instanceof ParameterizedType) {
result = (Class<?>) ((ParameterizedType) src).getRawType();
} else if (src instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) src).getGenericComponentType();
if (componentType instanceof Class) {
result = Array.newInstance((Class<?>) componentType, 0).getClass();
} else {
Class<?> componentClass = typeToClass(componentType);
result = Array.newInstance((Class<?>) componentClass, 0).getClass();
}
}
if (result == null) {
result = Object.class;
}
return result;
} private void addFields(Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (canAccessPrivateMethods()) {
try {
field.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
if (field.isAccessible()) {
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());
}
} private void addSetField(Field field) {
if (isValidPropertyName(field.getName())) {
setMethods.put(field.getName(), new SetFieldInvoker(field));
Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
setTypes.put(field.getName(), typeToClass(fieldType));
}
} private void addGetField(Field field) {
if (isValidPropertyName(field.getName())) {
getMethods.put(field.getName(), new GetFieldInvoker(field));
Type fieldType = TypeParameterResolver.resolveFieldType(field, type);
getTypes.put(field.getName(), typeToClass(fieldType));
}
} private boolean isValidPropertyName(String name) {
return !(name.startsWith("$") || "serialVersionUID".equals(name) || "class".equals(name));
} /*
* 此方法返回一个数组,该数组包含该类中声明的所有方法和任何超类
* 我们使用此方法不是为了代替 Class.getMethods(),
* 因为我们想访问类中的私有方法.
*
* @param cls Class对象
* @return 包含该类中所有方法的数组
*/
private Method[] getClassMethods(Class<?> cls) {
//用于记录指定类中定义的全部方法的唯一签名以及对应的Method对象
Map<String, Method> uniqueMethods = new HashMap<String, Method>();
Class<?> currentClass = cls;
while (currentClass != null) {
//记录当前类中定义的所有方法
addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods()); // 记录接口中定义的方法
Class<?>[] interfaces = currentClass.getInterfaces();
for (Class<?> anInterface : interfaces) {
addUniqueMethods(uniqueMethods, anInterface.getMethods());
}
//当前类的父类
currentClass = currentClass.getSuperclass();
} Collection<Method> methods = uniqueMethods.values();
//转换成数组返回
return methods.toArray(new Method[methods.size()]);
} /**
* 为每个方法生成唯一签名,并记录到uniqueMethods集合中
*/
private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
for (Method currentMethod : methods) {
if (!currentMethod.isBridge()) {
//得到方法签名
String signature = getSignature(currentMethod);
//根据方法签名排重
if (!uniqueMethods.containsKey(signature)) {
if (canAccessPrivateMethods()) {
try {
currentMethod.setAccessible(true);
} catch (Exception e) {
// Ignored. This is only a final precaution, nothing we can do.
}
}
//记录签名与方法的对应关系
uniqueMethods.put(signature, currentMethod);
}
}
}
} /**
* 获取方法签名,eg:java.lang.String#getSignature:java.lang.reflect.Method
*/
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++) {
if (i == 0) {
sb.append(':');
} else {
sb.append(',');
}
sb.append(parameters[i].getName());
}
return sb.toString();
} /**
* 获取访问私有方法的权限
*/
private static boolean canAccessPrivateMethods() {
try {
SecurityManager securityManager = System.getSecurityManager();
if (null != securityManager) {
//允许利用反射检查任意类的私有变量
securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
}
} catch (SecurityException e) {
return false;
}
return true;
} /*
* Gets the name of the class the instance provides information for
*
* @return The class name
*/
public Class<?> getType() {
return type;
} public Constructor<?> getDefaultConstructor() {
if (defaultConstructor != null) {
return defaultConstructor;
} else {
throw new ReflectionException("There is no default constructor for " + type);
}
} public boolean hasDefaultConstructor() {
return defaultConstructor != null;
} public Invoker getSetInvoker(String propertyName) {
Invoker method = setMethods.get(propertyName);
if (method == null) {
throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
}
return method;
} public Invoker getGetInvoker(String propertyName) {
Invoker method = getMethods.get(propertyName);
if (method == null) {
throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
}
return method;
} /*
* Gets the type for a property setter
*
* @param propertyName - the name of the property
* @return The Class of the propery setter
*/
public Class<?> getSetterType(String propertyName) {
Class<?> clazz = setTypes.get(propertyName);
if (clazz == null) {
throw new ReflectionException("There is no setter for property named '" + propertyName + "' in '" + type + "'");
}
return clazz;
} /*
* Gets the type for a property getter
*
* @param propertyName - the name of the property
* @return The Class of the propery getter
*/
public Class<?> getGetterType(String propertyName) {
Class<?> clazz = getTypes.get(propertyName);
if (clazz == null) {
throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
}
return clazz;
} /*
* Gets an array of the readable properties for an object
*
* @return The array
*/
public String[] getGetablePropertyNames() {
return readablePropertyNames;
} /*
* Gets an array of the writeable properties for an object
*
* @return The array
*/
public String[] getSetablePropertyNames() {
return writeablePropertyNames;
} /*
* Check to see if a class has a writeable property by name
*
* @param propertyName - the name of the property to check
* @return True if the object has a writeable property by the name
*/
public boolean hasSetter(String propertyName) {
return setMethods.keySet().contains(propertyName);
} /*
* Check to see if a class has a readable property by name
*
* @param propertyName - the name of the property to check
* @return True if the object has a readable property by the name
*/
public boolean hasGetter(String propertyName) {
return getMethods.keySet().contains(propertyName);
} public String findPropertyName(String name) {
return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
}
}

Reflector.addGetMethods()方法主要负责解析类中定义的getter方法,Reflector.addSetMethods()方法负责解析类中定义的setter方法,两者的逻辑类似,具体实现见源码。

ReflectorFactory接口

ReflectorFactory接口主要实现了对Reflector对象的创建和缓存,该接口定义如下:

public interface ReflectorFactory {

    /**
* 是否会缓存Reflector对象
*/
boolean isClassCacheEnabled(); /**
* 设置是否缓存Reflector对象
*/
void setClassCacheEnabled(boolean classCacheEnabled); /**
* 创建指定Class的Reflector对象
*/
Reflector findForClass(Class<?> type);
}

MyBatis只为该接口提供了DefaultReflectorFactory这一个实现类,它与Reflector的关系如下:

DefaultReflectorFactory各字段及方法的含义如下:

public class DefaultReflectorFactory implements ReflectorFactory {
/**
* 默认开启对Reflector对象的缓存
*/
private boolean classCacheEnabled = true; /**
* 使用集合ConcurrentHashMap实现对Reflector的缓存
*/
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<Class<?>, Reflector>(); public DefaultReflectorFactory() {
} @Override
public boolean isClassCacheEnabled() {
return classCacheEnabled;
} @Override
public void setClassCacheEnabled(boolean classCacheEnabled) {
this.classCacheEnabled = classCacheEnabled;
} /**
* 为指定的Class创建Reflector对象,并将Reflector对象缓存到reflectorMap中
*/
@Override
public Reflector findForClass(Class<?> type) {
//检查是否开启缓存
if (classCacheEnabled) {
// synchronized (type) removed see issue #461
Reflector cached = reflectorMap.get(type);
if (cached == null) {
//创建Reflector对象
cached = new Reflector(type);
//放入ConcurrentHashMap集合缓存
reflectorMap.put(type, cached);
}
return cached;
} else {
//没有开启缓存,直接创建Reflector对象并返回
return new Reflector(type);
}
} }

除了使用MyBatis提供的DefaultReflectorFactory实现,我们还可以在mybatis-config.xml中配置自定义的ReflectorFactory实现类,从而实现功能上的扩展。

TypeParameterResolver

TypeParameterResolver是一个工具类,提供了一系列静态方法来解析指定类中的字段、方法返回值或方法参数的类型。

在开始介绍TypeParameterResolver之前,先简单介绍一下Type接口的基础知识。Type是所有类型的父接口,它有四个子接口和一个实现类,

1、Class比较常见,它表示的是原始类型。

Class类的对象表示JVM中的一个类或接口,每个Java类在JVM里都表现为一个Class对象。在程序中可以通过“类名.class”、“对象.getClass()”或是“Class.forName(”类名”)”等方式获取Class对象。数组也被映射为Class 对象,所有元素类型相同且维数相同的数组都共享同一个 Class 对象。

2、ParameterizedType表示的是参数化类型

例如List<String>、Map<Integer,String>、Service<User>这种带有泛型的类型。

ParameterizedType接口中常用的方法有三个,分别是:

Type getRawType()—返回参数化类型中的原始类型,例如List<String>的原始类型为List。 

Type[] getActualTypeArguments()—获取参数化类型的类型变量或是实际类型列表,例如Map<Integer, String>的实际泛型列表Integer和String。需要注意的是,该列表的元素类型都是Type,也就是说,可能存在多层嵌套的情况。 

Type getOwnerType()—返回是类型所属的类型,例如存在A<T>类,其中定义了内部类InnerA<I>,则InnerA<I>所属的类型为A<T>,如果是顶层类型则返回null。这种关系比较常见的示例是Map<K,V>接口与Map.Entry<K,V>接口,Map<K,V>接口是Map.Entry<K,V>接口的所有者。 · TypeVariable表示的是类型变量,它用来反映在JVM编译该泛型前的信息。例如List<T>中的T就是类型变量,它在编译时需被转换为一个具体的类型后才能正常使用。

TypeParameterResolver中各个静态方法之间的调用关系大致如图所示

TypeParameterResolver中通过resolveFieldType()方法、resolveReturnType()方法、resolveParamTypes()方法分别解析字段类型、方法返回值类型和方法参数列表中各个参数的类型。具体实现可查看其源码及官方的单元测试方法。

ObjectFactory接口

MyBatis中有很多模块会使用到ObjectFactory接口,该接口提供了多个create()方法的重载,通过这些create()方法可以创建指定类型的对象。ObjectFactory接口的定义如下:

/**
* Mybatis 使用ObjectFactory去创建需要的对象
* @author Clinton Begin
*/
public interface ObjectFactory { /**
* 设置配置信息
* @param properties 配置信息
*/
void setProperties(Properties properties); /**
* 通过默认构造函数创建指定类的对象
* @param type 对象类型
* @return
*/
<T> T create(Class<T> type); /**
* 根据参数列表,从指定类型中选择合适的构造器创建对象
* @param type 对象类型
* @param constructorArgTypes 参数类型
* @param constructorArgs 参数值
* @return
*/
<T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs); /**
* 检测指定类型是否是集合类型
* @param type 对象类型
* @return 集合类型返回true否则返回false
* @since 3.1.0
*/
<T> boolean isCollection(Class<T> type); }

DefaultObjectFactory是MyBatis提供的ObjectFactory接口的唯一实现,它是一个反射工厂,其create()方法通过调用instantiateClass()方法实现。DefaultObjectFactory.instantiateClass()方法会根据传入传入的参数列表选择合适的构造函数实例化对象,具体实现如下:

private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
try {
Constructor<T> constructor;
//通过无参构造函数创建对象
if (constructorArgTypes == null || constructorArgs == null) {
constructor = type.getDeclaredConstructor();
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance();
}
//根据参数列表查找合适的构造函数,并实例化对象
constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));
} catch (Exception e) {
StringBuilder argTypes = new StringBuilder();
if (constructorArgTypes != null && !constructorArgTypes.isEmpty()) {
for (Class<?> argType : constructorArgTypes) {
argTypes.append(argType.getSimpleName());
argTypes.append(",");
}
argTypes.deleteCharAt(argTypes.length() - 1); // remove trailing ,
}
StringBuilder argValues = new StringBuilder();
if (constructorArgs != null && !constructorArgs.isEmpty()) {
for (Object argValue : constructorArgs) {
argValues.append(String.valueOf(argValue));
argValues.append(",");
}
argValues.deleteCharAt(argValues.length() - 1); // remove trailing ,
}
throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
}
}

除了使用MyBatis提供的DefaultObjectFactory实现,我们还可以在mybatis-config.xml配置文件中指定自定义的ObjectFactory接口实现类,从而实现功能上的扩展。

Mybatis源码学习之反射工具(三)的更多相关文章

  1. mybatis源码学习(一) 原生mybatis源码学习

    最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...

  2. mybatis源码学习:一级缓存和二级缓存分析

    目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...

  3. mybatis源码学习:基于动态代理实现查询全过程

    前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...

  4. mybatis源码学习:插件定义+执行流程责任链

    目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...

  5. Mybatis源码学习第六天(核心流程分析)之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

  6. mybatis源码学习(三)-一级缓存二级缓存

    本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点 1.一级缓存配置信息 2.一级缓存源码学习笔记 3.二级缓存配置信息 4.二级缓存源码 5.一级缓存.二级缓存总结 1.一级缓存配 ...

  7. Mybatis源码学习第八天(总结)

    源码学习到这里就要结束了; 来总结一下吧 Mybatis的总体架构 这次源码学习我们,学习了重点的模块,在这里我想说一句,源码的学习不是要所有的都学,一行一行的去学,这是错误的,我们只需要学习核心,专 ...

  8. Mybatis源码学习之整体架构(一)

    简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...

  9. Mybatis源码学习之DataSource(七)_1

    简述 在数据持久层中,数据源是一个非常重要的组件,其性能直接关系到整个数据持久层的性能.在实践中比较常见的第三方数据源组件有Apache Common DBCP.C3P0.Proxool等,MyBat ...

随机推荐

  1. Scala学习十四——模式匹配和样例类

    一.本章要点 match表达式是更好的switch,不会有意外调入下一个分支 如果没有模式能够匹配,会抛出MatchError,可以用case _模式避免 模式可以包含一个随意定义的条件,称做守卫 你 ...

  2. c# internal关键字

    对于一些大型的项目,通常由很多个DLL文件组成,引用了这些DLL,就能访问DLL里面的类和类里面的方法.比如,你写了一个记录日志的DLL,任何项目只要引用此DLL就能实现记录日志的功能,这个DLL文件 ...

  3. Pytorch报错:cuda runtime error (59) : device-side assert triggered at /pytorch/aten/src/THC/generic/THCTensorMath.cu:26

    Pytorch报错:cuda runtime error (59) : device-side assert triggered at /pytorch/aten/src/THC/generic/TH ...

  4. 05 Redis-Sentinel

    一.什么是Redis-Sentinel Redis-Sentinel是redis官方推荐的高可用性解决方案当用redis作master-slave的高可用时,如果master本身宕机,redis本身或 ...

  5. 微信小程序转发事件

    和生命周期是同级,在.js文件里面设置 // 分享按钮 onShareAppMessage: function () { return { title: '前端伪大叔', path: "/p ...

  6. String s=new String("xyz");创建了几个String Object?二者之前的区别是什么?

    两个.第一个对象是字符串常量"xyz",第二个对象是new String("xyz")的时候产生的,在堆中分配内存给这个对象,只不过这个对象的内容是指向字符串常 ...

  7. 第二章、Django以及数据库的配置

    目录 第二章.Django以及数据库的配置 一.小白必会三板斧 二.静态文件配置 三.form表单 action和method参数可以写的形式 四.request对象及方法 五.django连接数据库 ...

  8. Image Processing and Analysis_8_Edge Detection:Multiresolution edge detection techniques ——1995

    此主要讨论图像处理与分析.虽然计算机视觉部分的有些内容比如特 征提取等也可以归结到图像分析中来,但鉴于它们与计算机视觉的紧密联系,以 及它们的出处,没有把它们纳入到图像处理与分析中来.同样,这里面也有 ...

  9. Spring Boot Start 打包方式装B指南

    项目结构如下: test包:实际的代码 spring-boot-start-test包:start 配置包 代码详细配置如下 https://github.com/fqybzhangji/spring ...

  10. PAT Advanced 1020 Tree Traversals (25 分)

    1020 Tree Traversals (25 分)   Suppose that all the keys in a binary tree are distinct positive integ ...