Package of binding function code
org.apache.ibatis.binding
binding module function
Encapsulating ibatis programming model In the ibatis programming model, SqlSession is the entry of sql execution. The practical method is sqlSession.selectOne(namespace+id, parameter list) (for example, sqlSession.selectOne("com.enjoylearning.mybatis.mapper.TUserMapper.selectByPrimaryKey", 2)), which is not easy to maintain and read.
Problems to be solved
- Find the corresponding method in SqlSession (insert|update|select)
- Find namespace and method name (2D coordinates)
- Transfer parameters
Core class
- Mapperregistry: the registry of the mapper interface and the corresponding proxy factory; is a member variable of Configuration
- MapperProxyFactory: used to generate the strength object of mapper interface dynamic agent; ensure that the strength object of mapper is a local variable
- MapperMethod: encapsulates the corresponding method information in mapper interface. MapperMethod object does not record any state, so it can be shared among multiple proxy objects. sqlCommand: encapsulates sql statement; mtethodsignature: encapsulates the input and return types of mapper interface.
Personal understanding
- MapperRegistry's map < Class <? >, mapperproxyfactory <? > > knownmappers = new HashMap < > (); key: mapper's Class object value: the factory that produces the mapper's dynamic proxy example
- Mapperproxyfactory: Map < method, mappermethod > methodcache key: Reflection class of method information of value method (sql statement, input parameter, return value)
Related code
public class MapperRegistry { private final Configuration config;//config object, mybatis globally unique //The relationship between mapper interface and MapperProxyFactory is recorded. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); public MapperRegistry(Configuration config) { this.config = config; } @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); }
/** * * The instance object used to generate the dynamic agent of mapper interface; * @author Lasse Voss */ public class MapperProxyFactory<T> { //class object of mapper interface private final Class<T> mapperInterface; //key is the method object of a method in the mapper interface, value is the corresponding MapperMethod, MapperMethod object does not record any state information, so it can be shared among multiple proxy objects. private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public class MapperMethod { //Get the namespace, method name and SQL statement type of the method from configuration private final SqlCommand command; //Encapsulates information about the mapper interface method (input parameter, return type); private final MethodSignature method;
Procedure of getMapper
public class MapperProxyFactory<T> { //class object of mapper interface private final Class<T> mapperInterface; //key is the method object of a method in the mapper interface, value is the corresponding MapperMethod, MapperMethod object does not record any state information, so it can be shared among multiple proxy objects. private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { //Create a dynamic proxy object that implements the mapper interface return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { //Each call creates a new MapperProxy object final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
Two newInstance methods generate mapper's dynamic proxy object MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable { ....... @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) {//If the method of Object itself is not enhanced return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //Get mapperMethod object from cache, if not, create one and add it to cache final MapperMethod mapperMethod = cachedMapperMethod(method); //Call execute method to execute sql return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }
As can be seen from the invoke method, mapperMethod calls the execute method.
public class MapperMethod { ...... public Object execute(SqlSession sqlSession, Object[] args) { Object result; //According to the sql statement type and the parameters returned by the interface, different 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()) {//Return value is void executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) {//Return value is set or array result = executeForMany(sqlSession, args); } else if (method.returnsMap()) {//The return value is map result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) {//Return value is cursor result = executeForCursor(sqlSession, args); } else {//Handle the case of returning to a single object //Parsing parameters through parameter parser Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = OptionalUtil.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; }
Focus on result = sqlSession.selectOne(command.getName(), param); the usage of ibatis is used here