Mybais之MapperProxy

Mybatis中实现了为数据访问层接口动态创建代理,简化了代码书写方式。我们可以不必手动实现接口了,因为Mybatis在运行时帮我们实现了。

MapperProxyFactory为创建动态代理类实例代码:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

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

}

MapperProxy是实现了InvocationHandler接口的handler,在创建代理实例时Mapper接口类和MapperProxy作为参数传入,也就是创建了Mapper接口的代理甙类,当调用Mapper接口的方法时,实际上执行了invoke方法。

class MapperProxy implements InvocationHandler, Serializable {
    ...
@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);
  }

  ...
}

在MapperRegistry中注册动态代理类的工厂实例,mapper接口作为参数新建一个MapperProxyFactory实例

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

获取代理类实例,mapper接口和sqlSession做参数,在Spring中实际上sqlSession是SqlSessionTemplate实例,SqlSessionTemplate一个singleton

  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);
    }
  }
  ```
此处可能有个疑惑,为什么SqlSessionTemplate作为一个singleton传入的?
Mybatis中的SqlSession是非线程安全的,每个线程应该有独立的SqlSession实例。所以SqlSession应该是限制在请求范围。使用完毕需要执行关闭动作。
```java
SqlSession session = sqlSessionFactory.openSession();
try {
  // do work
} finally {
  session.close();
}




<div class="se-preview-section-delimiter"></div>

解开SqlSessionTemplate的秘密

通过查看源码,SqlSessionTemplate实现了SqlSession,并创建了SqlSession的动态代理类。

public class SqlSessionTemplate implements SqlSession, DisposableBean {
  ...
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
  ...




<div class="se-preview-section-delimiter"></div>

SqlSessionTemplate实现SqlSession接口方法,并委托给动态代理类执行,即通过回调SqlSessionInterceptor的invoke方法,

   private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

而在invoke方法中使用了SqlSession的另一个实现DefaultSqlSession来执行真正的数据库查询操作,此处也证明了JDK的动态代理是接口的代理而非类的代理。
SqlSession使用ThreadLocal<Map<Object, Object>> resources保存,最终SqlSession是每线程一个实例。也消除了为什么SqlSessionTemplate是一个Singleton的疑惑。

MyBatis是一个开源的持久化框架,用于将数据库操作和Java代码进行解耦。它提供了许多方便的功能和特性,而MyBatis插件则是用于扩展和定制MyBatis功能的组件。 MyBatis插件允许你在MyBatis的执行过程中拦截并修改SQL语句、参数和结果。你可以使用插件来实现一些自定义的逻辑,比如日志记录、性能监控、动态SQL增强等。 要创建一个MyBatis插件,你需要实现Intercepto接口,并覆盖其中的intercept方法。该方法会在MyBatis执行SQL语句前后被调用,你可以在其中编写自己的逻辑。同时,你还需要在插件类上添加@Intercepts注解,指定要拦截的目标方法。 以下是一个示例代码,展示了如何创建一个简单的MyBatis插件: ```java @Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) public class MyPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 在执行SQL前的逻辑 System.out.println("Before executing SQL"); // 执行原有的SQL语句 Object result = invocation.proceed(); // 在执行SQL后的逻辑 System.out.println("After executing SQL"); return result; } @Override public Object plugin(Object target) { // 将插件应用到目标对象上 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 设置插件属性 } } ``` 在以上示例中,我们使用@Intercepts注解指定了要拦截的目标方法,这里选择了Executor类的query和update方法。然后在intercept方法中,我们添加了执行SQL前后的逻辑。最后,使用Plugin.wrap方法将插件应用到目标对象上。 当你完成了插件的开发后,需要在MyBatis配置文件中进行配置: ```xml <plugins> <plugin interceptor="com.example.MyPlugin"> <!-- 设置插件属性 --> <property name="key" value="value"/> </plugin> </plugins> ``` 以上是一个简单的MyBatis插件的实现示例,你可以根据自己的需求进行定制和扩展。希望对你有所帮助!如果你还有其他问题,可以继续提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值