Today this is the fifth article of Mybatis. In this article, we will learn the binding module. This module is used to bind a Mapper to its mapping file.
This module is located in the org. Apache. Ibatis. Binding package, class has the following four core
MapperRegistry
The mapper registryMapperProxyFactory
The mapper proxy class factoryMapperProxy
The mapper proxy classMapperMethod
Mapper method class
In today’s article, we will introduce the above four classes.
1 MapperRegistry
MapperRegistry is the Mapper registry. This class records the mapping between Mapper classes and MapperProxyFactory. The property fields of this class are as follows:
// Configuration object The Configuration information of Mybatis will be parsed and stored in this object
private final Configuration config;
// Record the mapping between Mapper interfaces and MapperProxyFactory
private finalMap<Class<? >, MapperProxyFactory<? >> knownMappers =new HashMap<>();
Copy the code
The Configuration object here is a core class in Mybatis. Relevant configurations in Mybatis will be recorded in this object after parsing. We will introduce this class in detail in the following articles. I’m not going to expand it here.
In this class there are two core methods, addMapper and getMapper, which are used to register and get the mapper, respectively. Let’s take a look at the logic of these two methods.
1.1 addMapper()
methods
The addMapper method is used to register the mapper and add it to knownMappers. The content of the execution process and impression, we mentioned this method in the article Mapper mapping file parsing, forget it also doesn’t matter, we learn in detail today, the source code of this method is as follows:
public <T> void addMapper(Class<T> type) {
// Type is an interface
if (type.isInterface()) {
// Check whether it has been loaded
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
// Successfully loaded the tag
boolean loadCompleted = false;
try {
// Add to knowMappers
knownMappers.put(type, new MapperProxyFactory<>(type));
// Save this for the builder module later
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
// If an exception occurs, the mapper needs to be removed
if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
In addition to a single Mapper registration method, the class also provides a method to register by package, the logic is as follows:
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class
superType) { ResolverUtil<Class<? >> resolverUtil =new ResolverUtil<>();
// Use reflection to find the superType class
resolverUtil.find(newResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<? >>> mapperSet = resolverUtil.getClasses();// Walk through to register
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
Copy the code
1.2 getMapper()
methods
In our previous example code, we will call sqlSession. getMapper method to get the Mapper object when performing data operations. The logic of this method is to call MapperRegistry.
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// Find the MapperProxyFactory object corresponding to the specified type
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// No exception was thrown
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
MapperProxy is a proxy object created by the JDK
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code
2 MapperProxyFactory
MapperProxyFactory is the object factory of the mapperProxy class. The name should remind you of the factory pattern. The main function of this class is to create mapperproxy objects. The attributes of this class are as follows:
// The Mapper interface Class object to which the proxy object belongs
private final Class<T> mapperInterface;
Mapper method Invoker // store the relationship between methods in Mapper and MapperMethodInvoker
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
Copy the code
This class creates a mapper proxy object as follows:
public T newInstance(SqlSession sqlSession) {
// Create a MapperProxy object
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// Create a proxy object implementing the mapperInterface interface
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
Copy the code
GetMapper gets a proxy object from the Mapper interface generated by the JDK dynamic proxy. This is why we only need to define the Mapper interface and not its implementation when using Mybatis.
3 MapperProxy
MapperProxy is a MapperProxy class that implements the InvocationHandler interface and implements the Invoke method in the JDK dynamic proxy.
This class has the following property fields:
/ / SqlSession objects
private final SqlSession sqlSession;
// Mapper interface Class object
private final Class<T> mapperInterface;
// Mapper interface methods and MapperMethodInvoker mapping
private final Map<Method, MapperMethodInvoker> methodCache;
Copy the code
MapperMethodInvoker is an internal interface in MapperProxy, which has two implementation classes, source code is as follows:
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super(a);this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
Execute the execute method in MapperMethod
returnmapperMethod.execute(sqlSession, args); }}private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
super(a);this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
returnmethodHandle.bindTo(proxy).invokeWithArguments(args); }}Copy the code
When we call a method in Mapper, we call the mapPerProxy. invoke method, which has the following logic:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// Execute directly if the target method inherits from object
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
returncachedInvoker(method).invoke(proxy, method, args, sqlSession); }}catch (Throwable t) {
throwExceptionUtil.unwrapThrowable(t); }}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
// Determine whether this is the default method of the interface
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return newDefaultMethodInvoker(getMethodHandleJava9(method)); }}catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw newRuntimeException(e); }}else {
// Return the PlainMethodInvoker object where the MapperMethod object is maintained
return new PlainMethodInvoker(newMapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }}); }catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null? re : cause; }}Copy the code
By analyzing this code, our own definition to the Mapper method, finally is called PlainMethodInvoker. Invoke method, and this method will be called MapperMethod. Excute method. So let’s look at the class MapperMethod.
4 MapperMethod
MapperMethod records the method information in Mapper and the SQL statement information in the mapping file. This class has the following attributes:
The SqlCommand object records the name and type of the SQL statement
private final SqlCommand command;
// Method information in the Mapper interface
private final MethodSignature method;
Copy the code
The construction method is as follows:
public MapperMethod(Class
mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
Copy the code
In the constructor, we assign values to two properties, so let’s look at the SqlCommand and MethodSignature classes.
4.1 SqlCommand
SqlCommand records the Mapper method name and type, its properties are as follows:
Mapper Indicates the Class name of the interface. The method name
private final String name;
// The SQL type can be UNKNOWN, INSERT, UPDATE, DELETE, SELECT, or FLUSH
private final SqlCommandType type;
Copy the code
This class is constructed as follows:
public SqlCommand(Configuration configuration, Class
mapperInterface, Method method) {
// Get the method name
final String methodName = method.getName();
finalClass<? > declaringClass = method.getDeclaringClass();// Get the MappedStatement object
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
// Handle the @flush annotation
if(method.getAnnotation(Flush.class) ! =null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "."+ methodName); }}else {
// Set name and type
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: "+ name); }}}private MappedStatement resolveMappedStatement(Class
mapperInterface, String methodName, Class
declaringClass, Configuration configuration) {
// statementId consists of the interface name + "." + the method name in the interface
String statementId = mapperInterface.getName() + "." + methodName;
// Check whether the statement is recorded in the Configuration
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
// Return null if the interface is defined in the Mapper
return null;
}
// Handle the parent class recursively
for(Class<? > superInterface : mapperInterface.getInterfaces()) {if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if(ms ! =null) {
returnms; }}}return null;
}
Copy the code
4.2 MethodSignature
The MethodSignature method records information about methods in the mapper, with the following properties:
// Whether the return type is a list
private final boolean returnsMany;
// Whether the return type is map
private final boolean returnsMap;
// Whether the return value is null
private final boolean returnsVoid;
// Whether the return value is of type CURSOR
private final boolean returnsCursor;
// Whether the return type is Optional
private final boolean returnsOptional;
// Return value type
private finalClass<? > returnType;//
private final String mapKey;
// The location of the ResultHandler type parameter in the parameter list
private final Integer resultHandlerIndex;
// The position of the RowBounds type parameter in the argument list
private final Integer rowBoundsIndex;
/ / paramNameResolver object
private final ParamNameResolver paramNameResolver;
Copy the code
MethodSignature is constructed as follows:
public MethodSignature(Configuration configuration, Class
mapperInterface, Method method) {
// Parse the return value type of the method
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceofClass<? >) {this.returnType = (Class<? >) resolvedReturnType; }else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<? >) ((ParameterizedType) resolvedReturnType).getRawType(); }else {
this.returnType = method.getReturnType();
}
// Sets the corresponding type attribute
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);
/ / get mapKey
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);
}
private String getMapKey(Method method) {
String mapKey = null;
// Whether the return type is Map
if (Map.class.isAssignableFrom(method.getReturnType())) {
/ / @ MapKey annotation
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if(mapKeyAnnotation ! =null) {
// Use the @mapkey annotation to specify the valuemapKey = mapKeyAnnotation.value(); }}return mapKey;
}
Copy the code
Let’s start by looking at the class corresponding to the paramNameResolver property.
2ParamNameResolver
ParamNameResolver is the parameter list used to process methods in Mapper. This class has the following attributes:
// Record the relationship between method parameter positions and values
private final SortedMap<Integer, String> names;
// Record whether the @param annotation is used
private boolean hasParamAnnotation;
Copy the code
SortedMap<Integer, String> names
Used to store parameter locationsboolean hasParamAnnotation
Marks whether it is used in the parameter@Param
annotations
The class is constructed as follows:
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
// Get parameters
finalClass<? >[] paramTypes = method.getParameterTypes();// Get a list of annotations for the parameters
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
// Number of arguments
int paramCount = paramAnnotations.length;
// get names from @Param annotations
// Iterate over the parameters
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// If it is a special type, skip it
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
// Iterate over the annotations on the parameters
for (Annotation annotation : paramAnnotations[paramIndex]) {
// If the annotation is @param
if (annotation instanceof Param) {
hasParamAnnotation = true;
// The name takes the value specified in the annotation
name = ((Param) annotation).value();
break; }}if (name == null) {
// @Param was not specified.
if (useActualParamName) {
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.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
Copy the code
The processing logic in its constructor is as follows:
- Gets the argument list for the method
- Check whether the parameter type is special (
RowBounds
orResultHandler
For special types, skip this step - or
@Param
annotationsvalue
Property, as the parameter name, or the parameter name defined in the method if not specified - Put the parameter index and name into the map
The most important method in this class is getNamedParams. This method is used to associate the parameters received by the method with the parameter name. The source code is as follows:
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
// Whether the argument list is empty If empty returns NULL
if (args == null || paramCount == 0) {
return null;
} else if(! hasParamAnnotation && paramCount ==1) {
// The length of the argument is 1
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// Iterate over the argument list
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// Put the parameter name as key and the received value as value into the map
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
// Param (I +1) is used if names does not contain a parameter name
if(! names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; }returnparam; }}Copy the code
4.3 excute()
methods
The excute() method is the core method in MapperMethod. This method calls the corresponding method in SqlSession based on the SQL type. The logic is as follows:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) { Call the SqlSession method based on the type of the SQL statement
case INSERT: {
// ParamNameResolver processes the args[] array to associate the argument passed in by the user with the specified parameter name
Object param = method.convertArgsToSqlCommandParam(args);
// sqlsession.insert (command-getname (), param) calls the INSERT method of sqlSession
The rowCountResult method converts the result based on the return value type of the method recorded in the Method field
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()) {
// A method whose return value is empty and whose ResultSet is processed by a ResultHandler
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 {
// A method that returns a single object
Object param = method.convertArgsToSqlCommandParam(args);
// Normal select statement execution entry >>
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;
}
Copy the code
RowCountResult (); rowCountResult (); rowCountResult ();
// There is no logic in this method to convert knowledge down based on the return type
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
Copy the code
The query method also does not have much logic, SqlSession logic we will be introduced in detail in the later article, here will not be explained.
3 summary
That’s the end of our article for today. Let’s briefly review today’s content.
-
The Binding module is used to associate a Mapper with its mapping file
-
This module has four core classes, as follows
MapperRegistry
Mapper
The registry, inMybatis
The startup willMapper
theClass
Class andMapperProxyFactory
To associateMapperProxyFactory
MapperProxy
The create factory class will passJDK
Dynamic proxy creationMapper
The proxy classMapperProxy
objectMapperProxy
Mapper
The proxy class of the interface we implementMapper
To which the method is calledinvoke()
methodsMapperMethod
Used to describe theMapper
The method in the interface, which takes care of the processing of arguments and actually calls toSqlSession
Related methods
-
Mybatis will create a proxy class object for the Mapper interface we created through dynamic proxies, we do not need to define its implementation
Thank you for reading, welcome to pay attention to my public number: Bug handling small expert
Java knowledge will be updated regularly