濮阳杆衣贸易有限公司

主頁(yè) > 知識(shí)庫(kù) > Mybatis mapper動(dòng)態(tài)代理的原理解析

Mybatis mapper動(dòng)態(tài)代理的原理解析

熱門標(biāo)簽:銅陵防封電銷卡 美國(guó)反騷擾電話機(jī)器人 真人語(yǔ)音電話機(jī)器人 騰訊地圖標(biāo)注提升 400電話可以免費(fèi)申請(qǐng)嗎 悟空科技電話機(jī)器人 電銷卡外呼系統(tǒng)供應(yīng)商 怎么在地圖標(biāo)注位置生成圖片 福建外呼系統(tǒng)定制化

前言

在開始動(dòng)態(tài)代理的原理講解以前,我們先看一下集成mybatis以后dao層不使用動(dòng)態(tài)代理以及使用動(dòng)態(tài)代理的兩種實(shí)現(xiàn)方式,通過(guò)對(duì)比我們自己實(shí)現(xiàn)dao層接口以及mybatis動(dòng)態(tài)代理可以更加直觀的展現(xiàn)出mybatis動(dòng)態(tài)代理替我們所做的工作,有利于我們理解動(dòng)態(tài)代理的過(guò)程,講解完以后我們?cè)龠M(jìn)行動(dòng)態(tài)代理的原理解析,此講解基于mybatis的環(huán)境已經(jīng)搭建完成,并且已經(jīng)實(shí)現(xiàn)了基本的用戶類編寫以及用戶類的Dao接口的聲明,下面是Dao層的接口代碼

public interface UserDao {
 /*
 查詢所有用戶信息
  */
 ListUser> findAll();
 /**
  * 保存用戶
  * @param user
  */
 void save(User user);

 /**
  * 更新用戶
  * @return
  */
 void update(User user);
 /**
  * 刪除用戶
  */
 void delete(Integer userId);
 /**
  * 查找一個(gè)用戶
  * @param userId
  * @return
  */
 User findOne(Integer userId);
 /**
  * 根據(jù)名字模糊查詢
  * @param name
  * @return
  */
 ListUser> findByName(String name);
 /**
  * 根據(jù)組合對(duì)象進(jìn)行模糊查詢
  * @param vo
  * @return
  */
 ListUser> findByQueryVo(QueryVo vo);
}

一、Mybatis dao層兩種實(shí)現(xiàn)方式的對(duì)比

1.dao層不使用動(dòng)態(tài)代理

dao層不使用動(dòng)態(tài)代理的話,就需要我們自己實(shí)現(xiàn)dao層的接口,為了簡(jiǎn)便起見,我只是實(shí)現(xiàn)了Dao接口中的findAll方法,以此方法為例子來(lái)展現(xiàn)我們自己實(shí)現(xiàn)Dao的方式的情況,讓我們來(lái)看代碼:

public class UserDaoImpl implements UserDao{
 private SqlSessionFactory factory;
 public UserDaoImpl(SqlSessionFactory factory){
  this.factory = factory;
 }
 public ListUser> findAll() {
  //1.獲取sqlSession對(duì)象
  SqlSession sqlSession = factory.openSession();
  //2.調(diào)用selectList方法
  ListUser> list = sqlSession.selectList("com.example.dao.UserDao.findAll");
  //3.關(guān)閉流
  sqlSession.close();
  return list;
 }
 public void save(User user) {
 }
 public void update(User user) {
 }
 public void delete(Integer userId) {
 }
 public User findOne(Integer userId) {
  return null;
 }
 public ListUser> findByName(String name) {
  return null;
 }
 public ListUser> findByQueryVo(QueryVo vo) {
  return null;
 }

這里的關(guān)鍵代碼 ListUser> list = sqlSession.selectList("com.example.dao.UserDao.findAll"),需要我們自己手動(dòng)調(diào)用SqlSession里面的方法,基于動(dòng)態(tài)代理的方式最后的目標(biāo)也是成功的調(diào)用到這里。

注意:如果是添加,更新或者刪除操作的話需要在方法中增加事務(wù)的提交。

2.dao層使用Mybatis的動(dòng)態(tài)代理

使用動(dòng)態(tài)代理的話Dao層的接口聲明完成以后只需要在使用的時(shí)候通過(guò)SqlSession對(duì)象的getMapper方法獲取對(duì)應(yīng)Dao接口的代理對(duì)象,關(guān)鍵代碼如下:

//3.獲取SqlSession對(duì)象
SqlSession session = factory.openSession();
//4.獲取dao的代理對(duì)象
UserDao mapper = session.getMapper(UserDao.class);
//5.執(zhí)行查詢所有的方法
ListUser> list = mapper.findAll();

獲取到dao層的代理對(duì)象以后通過(guò)代理對(duì)象調(diào)用查詢方法就可以實(shí)現(xiàn)查詢所有用戶列表的功能。

二、Mybatis動(dòng)態(tài)代理實(shí)現(xiàn)方式的原理解析

動(dòng)態(tài)代理中最重要的類:SqlSession、MapperProxy、MapperMethod,下面開始從入口方法到調(diào)用結(jié)束的過(guò)程分析。

1.調(diào)用方法的開始:

//4.獲取dao的代理對(duì)象

UserDao mapper = session.getMapper(UserDao.class); 因?yàn)镾qlSesseion為接口,所以我們通過(guò)Debug方式發(fā)現(xiàn)這里使用的實(shí)現(xiàn)類為DefaultSqlSession。

2.找到DeaultSqlSession中的getMapper方法,發(fā)現(xiàn)這里沒有做其他的動(dòng)作,只是將工作繼續(xù)拋到了Configuration類中,Configuration為類不是接口,可以直接進(jìn)入該類的getMapper方法中

@Override
 public T> T getMapper(ClassT> type) {
 return configuration.T>getMapper(type, this);
 }

3. 找到Configuration類的getMapper方法,這里也是將工作繼續(xù)交到MapperRegistry的getMapper的方法中,所以我們繼續(xù)向下進(jìn)行。

 public T> T getMapper(ClassT> type, SqlSession sqlSession) {
 return mapperRegistry.getMapper(type, sqlSession);
 }

4. 找到MapperRegistry的getMapper的方法,看到這里發(fā)現(xiàn)和以前不一樣了,通過(guò)MapperProxyFactory的命名方式我們知道這里將通過(guò)這個(gè)工廠生成我們所關(guān)注的MapperProxy的代理類,然后我們通過(guò)mapperProxyFactory.newInstance(sqlSession);進(jìn)入MapperProxyFactory的newInstance方法中

public T> T getMapper(ClassT> type, SqlSession sqlSession) {
 final MapperProxyFactoryT> mapperProxyFactory = (MapperProxyFactoryT>) 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);
 }
 }

5. 找到MapperProxyFactory的newIntance方法,通過(guò)參數(shù)類型SqlSession可以得知,上面的調(diào)用先進(jìn)入第二個(gè)newInstance方法中并創(chuàng)建我們所需要重點(diǎn)關(guān)注的MapperProxy對(duì)象,第二個(gè)方法中再調(diào)用第一個(gè)newInstance方法并將MapperProxy對(duì)象傳入進(jìn)去,根據(jù)該對(duì)象創(chuàng)建代理類并返回。這里已經(jīng)得到需要的代理類了,但是我們的代理類所做的工作還得繼續(xù)向下看MapperProxy類。

protected T newInstance(MapperProxyT> mapperProxy) {
 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
 }
 public T newInstance(SqlSession sqlSession) {
 final MapperProxyT> mapperProxy = new MapperProxyT>(sqlSession, mapperInterface, methodCache);
 return newInstance(mapperProxy);
 }

6. 找到MapperProxy類,發(fā)現(xiàn)其確實(shí)實(shí)現(xiàn)了JDK動(dòng)態(tài)代理必須實(shí)現(xiàn)的接口InvocationHandler,所以我們重點(diǎn)關(guān)注invoke()方法,這里看到在invoke方法里先獲取MapperMethod類,然后調(diào)用mapperMethod.execute(),所以我們繼續(xù)查看MapperMethod類的execute方法。

public class MapperProxyT> implements InvocationHandler, Serializable {
 private static final long serialVersionUID = -6424540398559729838L;
 private final SqlSession sqlSession;
 private final ClassT> mapperInterface;
 private final MapMethod, MapperMethod> methodCache;
 public MapperProxy(SqlSession sqlSession, ClassT> mapperInterface, MapMethod, MapperMethod> methodCache) {
 this.sqlSession = sqlSession;
 this.mapperInterface = mapperInterface;
 this.methodCache = methodCache;
 }

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 try {
  if (Object.class.equals(method.getDeclaringClass())) {
  return method.invoke(this, args);
  } else if (isDefaultMethod(method)) {
  return invokeDefaultMethod(proxy, method, args);
  }
 } catch (Throwable t) {
  throw ExceptionUtil.unwrapThrowable(t);
 }
 final MapperMethod mapperMethod = cachedMapperMethod(method);
 return mapperMethod.execute(sqlSession, args);
 }

 private MapperMethod cachedMapperMethod(Method method) {
 MapperMethod mapperMethod = methodCache.get(method);
 if (mapperMethod == null) {
  mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
  methodCache.put(method, mapperMethod);
 }
 return mapperMethod;
 }

 @UsesJava7
 private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
  throws Throwable {
 final ConstructorMethodHandles.Lookup> constructor = MethodHandles.Lookup.class
  .getDeclaredConstructor(Class.class, int.class);
 if (!constructor.isAccessible()) {
  constructor.setAccessible(true);
 }
 final Class?> declaringClass = method.getDeclaringClass();
 return constructor
  .newInstance(declaringClass,
   MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
    | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
  .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
 }
 /**
 * Backport of java.lang.reflect.Method#isDefault()
 */
 private boolean isDefaultMethod(Method method) {
 return ((method.getModifiers()
   (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
   method.getDeclaringClass().isInterface();
 }
}

7. 找到類MapperMethod類的execute方法,發(fā)現(xiàn)execute中通過(guò)調(diào)用本類中的其他方法獲取并封裝返回結(jié)果,我們來(lái)看一下MapperMethod整個(gè)類。

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);
  }
  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;
 }

8. MapperMethod類是整個(gè)代理機(jī)制的核心類,對(duì)SqlSession中的操作進(jìn)行了封裝使用。

該類里有兩個(gè)內(nèi)部類SqlCommand和MethodSignature。 SqlCommand用來(lái)封裝CRUD操作,也就是我們?cè)趚ml中配置的操作的節(jié)點(diǎn)。每個(gè)節(jié)點(diǎn)都會(huì)生成一個(gè)MappedStatement類。

MethodSignature用來(lái)封裝方法的參數(shù)以及返回類型,在execute的方法中我們發(fā)現(xiàn)在這里又回到了SqlSession中的接口調(diào)用,和我們自己實(shí)現(xiàn)UerDao接口的方式中直接用SqlSession對(duì)象調(diào)用DefaultSqlSession的實(shí)現(xiàn)類的方法是一樣的,經(jīng)過(guò)一大圈的代理又回到了原地,這就是整個(gè)動(dòng)態(tài)代理的實(shí)現(xiàn)過(guò)程了。

public class MapperMethod {
 private final SqlCommand command;
 private final MethodSignature method;
 public MapperMethod(Class?> mapperInterface, Method method, Configuration config) {
 this.command = new SqlCommand(config, mapperInterface, method);
 this.method = new MethodSignature(config, mapperInterface, method);
 }
 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);
  }
  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;
 }

 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;
 }

 private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
 MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
 if (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);
 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));
 }
 }

 private E> Object executeForMany(SqlSession sqlSession, Object[] args) {
 ListE> result;
 Object param = method.convertArgsToSqlCommandParam(args);
 if (method.hasRowBounds()) {
  RowBounds rowBounds = method.extractRowBounds(args);
  result = sqlSession.E>selectList(command.getName(), param, rowBounds);
 } else {
  result = sqlSession.E>selectList(command.getName(), param);
 }
 // issue #510 Collections  arrays support
 if (!method.getReturnType().isAssignableFrom(result.getClass())) {
  if (method.getReturnType().isArray()) {
  return convertToArray(result);
  } else {
  return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
  }
 }
 return result;
 }

 private T> CursorT> executeForCursor(SqlSession sqlSession, Object[] args) {
 CursorT> result;
 Object param = method.convertArgsToSqlCommandParam(args);
 if (method.hasRowBounds()) {
  RowBounds rowBounds = method.extractRowBounds(args);
  result = sqlSession.T>selectCursor(command.getName(), param, rowBounds);
 } else {
  result = sqlSession.T>selectCursor(command.getName(), param);
 }
 return result;
 }

 private E> Object convertToDeclaredCollection(Configuration config, ListE> list) {
 Object collection = config.getObjectFactory().create(method.getReturnType());
 MetaObject metaObject = config.newMetaObject(collection);
 metaObject.addAll(list);
 return collection;
 }
 @SuppressWarnings("unchecked")
 private E> Object convertToArray(ListE> list) {
 Class?> arrayComponentType = method.getReturnType().getComponentType();
 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 {
  return list.toArray((E[])array);
 }
 }
 private K, V> MapK, V> executeForMap(SqlSession sqlSession, Object[] args) {
 MapK, V> result;
 Object param = method.convertArgsToSqlCommandParam(args);
 if (method.hasRowBounds()) {
  RowBounds rowBounds = method.extractRowBounds(args);
  result = sqlSession.K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
 } else {
  result = sqlSession.K, V>selectMap(command.getName(), param, method.getMapKey());
 }
 return result;
 }
 public static class ParamMapV> extends HashMapString, V> {
 private static final long serialVersionUID = -2212268410512043556L;
 @Override
 public V get(Object key) {
  if (!super.containsKey(key)) {
  throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
  }
  return super.get(key);
 }
 }
 public static class SqlCommand {
 private final String name;
 private final SqlCommandType type;

 public SqlCommand(Configuration configuration, Class?> mapperInterface, Method method) {
  final String methodName = method.getName();
  final Class?> declaringClass = method.getDeclaringClass();
  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;
 }
 private MappedStatement resolveMappedStatement(Class?> mapperInterface, String methodName,
  Class?> declaringClass, Configuration configuration) {
  String statementId = mapperInterface.getName() + "." + methodName;
  if (configuration.hasStatement(statementId)) {
  return configuration.getMappedStatement(statementId);
  } else if (mapperInterface.equals(declaringClass)) {
  return null;
  }
  for (Class?> superInterface : mapperInterface.getInterfaces()) {
  if (declaringClass.isAssignableFrom(superInterface)) {
   MappedStatement ms = resolveMappedStatement(superInterface, methodName,
    declaringClass, configuration);
   if (ms != null) {
   return ms;
   }
  }
  }
  return null;
 }
 }

 public static class MethodSignature {
 private final boolean returnsMany;
 private final boolean returnsMap;
 private final boolean returnsVoid;
 private final boolean returnsCursor;
 private final Class?> returnType;
 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.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);
 }

 public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
 }

 public boolean hasRowBounds() {
  return rowBoundsIndex != null;
 }

 public RowBounds extractRowBounds(Object[] args) {
  return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
 }

 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;
 }
 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;
 }
 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;
 }
 }

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

您可能感興趣的文章:
  • Idea如何去除Mapper警告方法解析
  • Mybatis Mapper接口工作原理實(shí)例解析
  • MyBatis Mapper接受參數(shù)的四種方式代碼解析
  • mapper接口注入兩種方式詳解
  • Springboot整合通用mapper過(guò)程解析
  • 詳解MyBatis Mapper 代理實(shí)現(xiàn)數(shù)據(jù)庫(kù)調(diào)用原理
  • 如何自動(dòng)生成Mybatis的Mapper文件詳解
  • 如何使用IDEA創(chuàng)建MAPPER模板過(guò)程圖解

標(biāo)簽:云浮 武威 烏海 聊城 白銀 湖南 湖北 臨汾

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Mybatis mapper動(dòng)態(tài)代理的原理解析》,本文關(guān)鍵詞  Mybatis,mapper,動(dòng)態(tài),代理,的,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 下面列出與本文章《Mybatis mapper動(dòng)態(tài)代理的原理解析》相關(guān)的同類信息!
  • 本頁(yè)收集關(guān)于Mybatis mapper動(dòng)態(tài)代理的原理解析的相關(guān)信息資訊供網(wǎng)民參考!
  • 推薦文章
    三门县| 锦屏县| 永济市| 吉安市| 苏州市| 通道| 分宜县| 长春市| 安义县| 五莲县| 台北市| 长泰县| 扶沟县| 炎陵县| 盘锦市| 名山县| 阳泉市| 武宣县| 津南区| 临夏县| 巴马| 博乐市| 温宿县| 谷城县| 庄河市| 惠州市| 黄山市| 定州市| 文成县| 普格县| 阳曲县| 榆社县| 周口市| 旅游| 汽车| 重庆市| 论坛| 固始县| 汝阳县| 濮阳市| 阿尔山市|