1. 首页
  2. 技术知识

Mybatis mapper动态X的原理解析

前言

在开始动态X的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态X以及使用动态X的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态X可以更加直观的展现出mybatis动态X替我们所做的工作,有利于我们理解动态X的过程,讲解完以后我们再进行动态X的原理解析,此讲解基于mybatis的环境已经搭建完成,并且已经实现了基本的用户类编写以及用户类的Dao接口的声明,下面是Dao层的接口代码

public interface UserDao {

/*

查询所有用户信息

  */

List<User> findAll();

/**

  * 保存用户

  * @param user

  */

void save(User user);

/**

  * 更新用户

  * @return

  */

void update(User user);

/**

  * 删除用户

  */

void delete(Integer userId);

/**

  * 查找一个用户

  * @param userId

  * @return

  */

User findOne(Integer userId);

/**

  * 根据名字模糊查询

  * @param name

  * @return

  */

List<User> findByName(String name);

/**

  * 根据组合对象进行模糊查询

  * @param vo

  * @return

  */

List<User> findByQueryVo(QueryVo vo);

}
一、Mybatis dao层两种实现方式的对比



1.dao层不使用动态X

dao层不使用动态X的话,就需要我们自己实现dao层的接口,为了简便起见,我只是实现了Dao接口中的findAll方法,以此方法为例子来展现我们自己实现Dao的方式的情况,让我们来看代码:

public class UserDaoImpl implements UserDao{

private SqlSessionFactory factory;

public UserDaoImpl(SqlSessionFactory factory){

  this.factory = factory;

}

public List<User> findAll() {

  //1.获取sqlSession对象

  SqlSession sqlSession = factory.openSession();

  //2.调用selectList方法

  List<User> list = sqlSession.selectList(“com.example.dao.UserDao.findAll”);

  //3.关闭流

  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 List<User> findByName(String name) {

  return null;

}

public List<User> findByQueryVo(QueryVo vo) {

  return null;

}这里的关键代码 List<User> list = sqlSession.selectList(“com.example.dao.UserDao.findAll”),需要我们自己手动调用SqlSession里面的方法,基于动态X的方式最后的目标也是成功的调用到这里。

注意:如果是添加,更新或者删除操作的话需要在方法中增加事务的提交。


2.dao层使用Mybatis的动态X


使用动态X的话Dao层的接口声明完成以后只需要在使用的时候通过SqlSession对象的getMAPPer方法获取对应Dao接口的X对象,关键代码如下:

//3.获取SqlSession对象

SqlSession session = factory.openSession();

//4.获取dao的X对象

UserDao mapper = session.getMapper(UserDao.class);

//5.执行查询所有的方法

List<User> list = mapper.findAll();获取到dao层的X对象以后通过X对象调用查询方法就可以实现查询所有用户列表的功能。


二、Mybatis动态X实现方式的原理解析

动态X中最重要的类:SqlSession、MapperProxy、MapperMethod,下面开始从入口方法到调用结束的过程分析。


1.调用方法的开始:

//4.获取dao的X对象

UserDao mapper = session.getMapper(UserDao.class); 因为SqlSesseion为接口,所以我们通过Debug方式发现这里使用的实现类为DefaultSqlSession。

2.找到DeaultSqlSession中的getMapper方法,发现这里没有做其他的动作,只是将工作继续抛到了Configuration类中,Configuration为类不是接口,可以直接进入该类的getMapper方法中

@Override

public <T> T getMapper(Class<T> type) {

return configuration.<T>getMapper(type, this);

}
3. 找到Configuration类的getMapper方法,这里也是将工作继续交到MapperRegistry的getMapper的方法中,所以我们继续向下进行。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {

return mapperRegistry.getMapper(type, sqlSession);

}
4. 找到MapperRegistry的getMapper的方法,看到这里发现和以前不一样了,通过MapperProxyFactory的命名方式我们知道这里将通过这个工厂生成我们所关注的MapperProxy的X类,然后我们通过mapperProxyFactory.newInstance(sqlSession);进入MapperProxyFactory的newInstance方法中

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

}

}
5. 找到MapperProxyFactory的newIntance方法,通过参数类型SqlSession可以得知,上面的调用先进入第二个newInstance方法中并创建我们所需要重点关注的MapperProxy对象,第二个方法中再调用第一个newInstance方法并将MapperProxy对象传入进去,根据该对象创建X类并返回。这里已经得到需要的X类了,但是我们的X类所做的工作还得继续向下看MapperProxy类。

protected T newInstance(MapperProxy<T> mapperProxy) {

return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

}

public T newInstance(SqlSession sqlSession) {

final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);

return newInstance(mapperProxy);

}
6. 找到MapperProxy类,发现其确实实现了JDK动态X必须实现的接口InvocationHandler,所以我们重点关注invoke()方法,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute(),所以我们继续查看MapperMethod类的execute方法。

public class MapperProxy<T> implements InvocationHandler, Serializable {

private static final long serialVersionUID = -6424540398559729838L;

private final SqlSession sqlSession;

private final Class<T> mapperInterface;

private final Map<Method, MapperMethod> methodCache;

public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, 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 Constructor<MethodHandles.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方法,发现execute中通过调用本类中的其他方法获取并封装返回结果,我们来看一下MapperMethod整个类。

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类是整个X机制的核心类,对SqlSession中的操作进行了封装使用。

该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。

MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的X又回到了原地,这就是整个动态X的实现过程了。

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) {

List<E> 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> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {

Cursor<T> 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, List<E> list) {

Object collection = config.getObjectFactory().create(method.getReturnType());

MetaObject metaObject = config.newMetaObject(collection);

metaObject.addAll(list);

return collection;

}

@SuppressWarnings(“unchecked”)

private <E> Object convertToArray(List<E> 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> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {

Map<K, 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 ParamMap<V> extends HashMap<String, 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
)) {
   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;
}
}以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持共生网络。

原创文章,作者:starterknow,如若转载,请注明出处:https://www.starterknow.com/115849.html

联系我们