Mybatis 与 代理模式

简介: 我们经常在工作中使用到 AOP 、在设计模式中、它属于代理模式。代理模式属于一种结构性设计模式、让你能够提供对象的替代品或占位符。代理控制着对于远对象的访问、并允许在将请求提交给对象前后进行一些处理。

网络异常,图片无法展示
|


我们经常在工作中使用到 AOP 、在设计模式中、它属于代理模式。

代理模式属于一种结构性设计模式、让你能够提供对象的替代品或占位符。代理控制着对于远对象的访问、并允许在将请求提交给对象前后进行一些处理。


JDK 中的动态代理


核心类就是 InvocationHandler

网络异常,图片无法展示
|


Mybatis 中代理模式


也是在日志模块中、

网络异常,图片无法展示
|

网络异常,图片无法展示
|


这些代理类从其名字中可以猜测出其用来打印日志

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
  private final Connection connection;
  private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
    super(statementLog, queryStack);
    this.connection = conn;
  }
  @Override
  public Object invoke(Object proxy, Method method, Object[] params)
      throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }
      if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
        }
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;
      } else if ("createStatement".equals(method.getName())) {
        Statement stmt = (Statement) method.invoke(connection, params);
        stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;
      } else {
        return method.invoke(connection, params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
  public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
    InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
    ClassLoader cl = Connection.class.getClassLoader();
    return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
  }
  public Connection getConnection() {
    return connection;
  }
}
复制代码

ConnectionLogger 拦截了 createStatement 和  prepareStatement 这两个方法、分别创建对应的 Logger 代理。在 debug 开启的情况下还打印了相关信息

PreparedStatementLogger 的 invoke 方法、这个代理方法主要还是打印相关参数信息

@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, params);
    }
    if (EXECUTE_METHODS.contains(method.getName())) {
      if (isDebugEnabled()) {
        debug("Parameters: " + getParameterValueString(), true);
      }
      clearColumnInfo();
      if ("executeQuery".equals(method.getName())) {
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      } else {
        return method.invoke(statement, params);
      }
    } else if (SET_METHODS.contains(method.getName())) {
      if ("setNull".equals(method.getName())) {
        setColumn(params[0], null);
      } else {
        setColumn(params[0], params[1]);
      }
      return method.invoke(statement, params);
    } else if ("getResultSet".equals(method.getName())) {
      ResultSet rs = (ResultSet) method.invoke(statement, params);
      return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
    } else if ("getUpdateCount".equals(method.getName())) {
      int updateCount = (Integer) method.invoke(statement, params);
      if (updateCount != -1) {
        debug("   Updates: " + updateCount, false);
      }
      return updateCount;
    } else {
      return method.invoke(statement, params);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
}
复制代码


Mybatis 创建 Connection 代理

BaseExecutor 中如果开启了 debug 模式、则会创建 Connection 代理

网络异常,图片无法展示
|


连接池中的代理模式

Mybatis 中的 PooledConnection 也是使用了代理模式

class PooledConnection implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (CLOSE.equals(methodName)) {
      // 将连接归还到连接池
      dataSource.pushConnection(this);
      return null;
    }
    try {
      if (!Object.class.equals(method.getDeclaringClass())) {
        checkConnection();
      }
      return method.invoke(realConnection, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
}  
复制代码

主要做的功能是关闭连接到时候、代理该方法、不是直接关闭连接、而是归还到池中、等待下一次使用。


使用场景

  • AOP、在对象的执行前后执行一些与对象职责无关功能
  • 延迟初始化、一些消耗资源的对象、并不需要系统启动的时候就初始化、而可以等到真正使用的时候才初始化
  • 缓存、例如连接缓存、数据缓存


优点

  • 客户端无感的情况下控制服务对象
  • 开闭原则、可以在不对服务或者客户端作出修改的情况下创建新代理。


缺点

  • 代码变得复杂、中间多了代理类这一层
  • 服务响应可能会变慢


与其他设计模式比较

  • 适配器模式能为被封装对象提供不同的接口、代理模式能为对象提供相同的接口、装饰模式则能为对象提供加强接口。
  • 装饰和代理有着相似的结构、但是其意图却非常不同、这两个模式都是基于组合原则、也就是说一个对象应该将部分工作委派给另一个对象。代理模式通常管理其服务对象的生命周期(比如说连接缓存、是归还池中复用、还是真的关闭。)、而装饰模式的生成则是由客户端自行控制组合的。



目录
相关文章
|
3月前
|
SQL Java 数据库连接
在mybatis中#{}和${}的区别
在MyBatis中,使用#{}可以防止SQL注入,它通过预处理语句来安全地设置参数值,而${}会将传入的数据直接插入SQL语句中,不安全,通常用于传入数据库对象或在确保数据安全的情况下使用。
|
5月前
|
Java 数据库连接 mybatis
Mybatis mapper动态代理解决方案
该文介绍了Mybatis中使用Mapper接口的方式代替XML配置执行SQL。Mapper接口规范包括:namespace与接口类路径相同,select ID与接口方法名一致,parameterType和方法参数类型匹配,resultType与返回值类型一致。实现过程中,需配置Mapper.xml,编写Mapper.java接口,并在Mybatis-config.xml中设置。测试类中,通过SqlSession的getMapper方法获取接口的动态代理对象,调用方法执行SQL。
|
6月前
|
SQL Java 数据库连接
Mybatis中#{}和${}的区别
Mybatis中#{}和${}的区别
44 0
|
6月前
|
设计模式 Java 数据库连接
MyBatis 设计模式解析
MyBatis 设计模式解析
|
6月前
|
SQL XML Java
Mybatis【使用注解开发】
Mybatis【使用注解开发】
|
SQL Java 程序员
24MyBatis - Mapper动态代理方式
24MyBatis - Mapper动态代理方式
48 0
|
XML SQL Java
mybatis注解方式
mybatis注解方式
|
XML SQL Java
【MyBatis学习笔记 五】MyBatis注解开发实现方式
【MyBatis学习笔记 五】MyBatis注解开发实现方式
123 0
|
设计模式 Java 数据库连接
Mybatis源码分析一-Mybatis基础架构以及设计模式
Mybatis源码分析一-Mybatis基础架构以及设计模式
170 0
|
XML SQL Java
Mybatis使用注解开发
Mybatis支持使用注解进行开发。它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。 选择何种方式来配置映射,以及是否应该要统一映射语句定义的形式,完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松地在基于注解和 XML 的语句映射方式间自由移植和切换。 例如,我们可以这样定义UserMapper接口类:
82 1