共计 18067 个字符,预计需要花费 46 分钟才能阅读完成。
自动写代码机器人,免费开通
Mybatis 中怎么利用 mapper 实现动态代理,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面丸趣 TV 小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。
前言
在开始动态代理的原理讲解以前,我们先看一下集成 mybatis 以后 dao 层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现 dao 层接口以及 mybatis 动态代理可以更加直观的展现出 mybatis 动态代理替我们所做的工作,有利于我们理解动态代理的过程,讲解完以后我们再进行动态代理的原理解析,此讲解基于 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 层不使用动态代理
dao 层不使用动态代理的话,就需要我们自己实现 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 里面的方法,基于动态代理的方式最后的目标也是成功的调用到这里。
注意:如果是添加,更新或者删除操作的话需要在方法中增加事务的提交。
2.dao 层使用 Mybatis 的动态代理
使用动态代理的话 Dao 层的接口声明完成以后只需要在使用的时候通过 SqlSession 对象的 getMapper 方法获取对应 Dao 接口的代理对象,关键代码如下:
//3. 获取 SqlSession 对象
SqlSession session = factory.openSession();
//4. 获取 dao 的代理对象
UserDao mapper = session.getMapper(UserDao.class);
//5. 执行查询所有的方法
List User list = mapper.findAll();
获取到 dao 层的代理对象以后通过代理对象调用查询方法就可以实现查询所有用户列表的功能。
二、Mybatis 动态代理实现方式的原理解析
动态代理中最重要的类:SqlSession、MapperProxy、MapperMethod,下面开始从入口方法到调用结束的过程分析。
1. 调用方法的开始:
//4. 获取 dao 的代理对象
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 的代理类,然后我们通过 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 对象传入进去,根据该对象创建代理类并返回。这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看 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 动态代理必须实现的接口 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 类是整个代理机制的核心类,对 SqlSession 中的操作进行了封装使用。
该类里有两个内部类 SqlCommand 和 MethodSignature。SqlCommand 用来封装 CRUD 操作,也就是我们在 xml 中配置的操作的节点。每个节点都会生成一个 MappedStatement 类。
MethodSignature 用来封装方法的参数以及返回类型,在 execute 的方法中我们发现在这里又回到了 SqlSession 中的接口调用,和我们自己实现 UerDao 接口的方式中直接用 SqlSession 对象调用 DefaultSqlSession 的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
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[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;
}
}
看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注丸趣 TV 行业资讯频道,感谢您对丸趣 TV 的支持。
向 AI 问一下细节