mybatis自定义类型处理器的一次源码探寻

目录

利用类型解析器自动给数据加密

源码探寻

 sql执行前的类型处理点

sql执行后的类型处理点



数据因为设计加密,所以保存的用数据都是加密后放到数据库中的,闲来无事,探寻下大佬的加密方式和逻辑。

利用类型解析器自动给数据加密

首先展示如何加密的:

定义另一个类 AesTypeHandler 继承抽象类mybatis BaseTypeHandler

public class AesTypeHandler extends BaseTypeHandler<Object> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        try {
            if (StringUtils.isEmpty(parameter)) {
                return;
            }
            //这里对参数做了加密处理
            ps.setString(i,  AESUtil.encrypt((String) parameter));
        } catch (Exception e) {
            throw new RuntimeException("mybatis数据加密异常!");
        }
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String colResult = rs.getString(columnName);
        try {
            if (StringUtils.isEmpty(colResult)) {
                return colResult;
            }
            if (colResult.startsWith(EncryptConstant.PERSISTENCE_DATA_ENCRYPT_HEAD)) {
                //这里对参数做了解密处理
                return AESUtil.decrypt(。。。));
            }
            return colResult;
        } catch (Exception e) {
            throw new RuntimeException("mybatis数据解密异常!");
        }
    }

    @Override
    public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String colResult = rs.getString(columnIndex);
        try {
            if (StringUtils.isEmpty(colResult)) {
                return colResult;
            }
            if (colResult.startsWith(EncryptConstant.PERSISTENCE_DATA_ENCRYPT_HEAD)) {
                //这里对参数做了解密处理
                return AESUtil.decrypt(。。。));
            }
            return colResult;
        } catch (Exception e) {
            throw new RuntimeException("mybatis数据解密异常!");
        }
    }

    @Override
    public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String colResult = cs.getString(columnIndex);
       AESUtil.decrypt(。。。));
            return colResult;
   
    }
}

然后再*mapper.xml中使用的话,如下:

对于加密字段需要手动在ResultMap中指定需要加急的字段的类型处理器

 <result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.XXX.mapper.type.AesTypeHandler" />
    <result column="address" property="address" jdbcType="VARCHAR" typeHandler="com.XXX.mapper.type.AesTypeHandler"  />
    <result column="flag" property="flag" jdbcType="VARCHAR" />
....

这里查询出来的数据就是经过解密的,如果是插入,那也是需要在插入的时候增加的

jdbcType="VARCHAR" typeHandler="com.XXX.mapper.type.AesTypeHandler"

源码探寻

首先看下这个自定义的类型处理器关系

 

我们知道(网上搜索一下):

类型处理器 TypeHandler

MyBatis 中的 TypeHandler 类型处理器用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值。MyBatis 内置了大部分基本类型的类型处理器,所以对于基本类型可以直接处理,当我们需要处理其他类型的时候就需要自定义类型处理器。

MyBatis 内置的 TypeHandler

在 MyBatis 的 TypeHandlerRegistry 类型中,可以看到内置的类型处理器。内置处理器比较多,这里整理常见的一些。
BooleanTypeHandler:用于 java 类型 boolean,jdbc 类型 bit、boolean
ByteTypeHandler:用于 java 类型 byte,jdbc 类型 TINYINT
ShortTypeHandler:用于 java 类型 short,jdbc 类型 SMALLINT
IntegerTypeHandler:用于 INTEGER 类型
LongTypeHandler:用于 long 类型
FloatTypeHandler:用于 FLOAT 类型
DoubleTypeHandler:用于 double 类型
StringTypeHandler:用于 java 类型 string,jdbc 类型 CHAR、VARCHAR
ArrayTypeHandler:用于 jdbc 类型 ARRAY
BigDecimalTypeHandler:用于 java 类型 BigDecimal,jdbc 类型 REAL、DECIMAL、NUMERIC
DateTypeHandler:用于 java 类型 Date,jdbc 类型 TIMESTAMP
DateOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 DATE
TimeOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 TIME
对于常见的 Enum 类型,内置了 EnumTypeHandler 进行 Enum 名称的转换和 EnumOrdinalTypeHandler 进行 Enum 序数的转换。这两个类型处理器没有在 TypeHandlerRegistry 中注册,如果需要使用必须手动配置。
————————————————

> 版权声明:本文为CSDN博主「May的博客」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
> 原文链接:https://blog.csdn.net/lmb55/article/details/90380309

然后看下基础类型处理器 BaseTypeHandler 的方法:

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  /**
   * @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This field will remove future.
   */
  @Deprecated
  protected Configuration configuration;

  /**
   * Sets the configuration.
   *
   * @param c
   *          the new configuration
   * @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This property will remove future.
   */
  @Deprecated
  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

    //setParameter:这个很明显就是在调用sql的对参数的处理,setNonNullParameter是一个抽象方法, 很明显给后续实现类自定以处理的,所以加密的逻辑是写在这个实现类中的
  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
              + "Cause: " + e, e);
      }
    } else {
      try {
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
              + "Try setting a different JdbcType for this parameter or a different configuration property. "
              + "Cause: " + e, e);
      }
    }
  }

    //下面这三个是获取三种不同的实现结果,里面分别调用了这个类的三个抽象方法:getNullableResult getNullableResult getNullableResult 
    //和上面的类似,也是在sql执行之后,再返回结果到调用者那里之前,对类型做一个转换。
    
  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    try {
      return getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(rs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set.  Cause: " + e, e);
    }
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(cs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement.  Cause: " + e, e);
    }
  }

    //抽象方法,自定义实现
  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the nullable result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @return the nullable result
   * @throws SQLException
   *           the SQL exception
   */
    //抽象方法,自定义实现
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

    //抽象方法,自定义实现
  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

    //抽象方法,自定义实现
  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

我们现在知道了 mybatis在这里提供一个接口,可以给用户自定义类型处理器,然后如何使用,我们也知道了,但是还有一个问题,那就是这里 setParameter 和 getResult 具体是在上面时候调用的呢?这里我们一个一个去探寻一下:

 sql执行前的类型处理点

所以我们直接进入TypeHandler<T> 最顶层的父接口:

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @return the result
   * @throws SQLException
   *           the SQL exception
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

点击方法 setParameter 发现进去了类SqlRunner的私有方法

  private void setParameters(PreparedStatement ps, Object... args) throws SQLException {
    for (int i = 0, n = args.length; i < n; i++) {
      if (args[i] == null) {
        throw new SQLException("SqlRunner requires an instance of Null to represent typed null values for JDBC compatibility");
      } else if (args[i] instanceof Null) {
        ((Null) args[i]).getTypeHandler().setParameter(ps, i + 1, null, ((Null) args[i]).getJdbcType());
      } else {
        TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(args[i].getClass());
        if (typeHandler == null) {
          throw new SQLException("SqlRunner could not find a TypeHandler instance for " + args[i].getClass());
        } else {
          typeHandler.setParameter(ps, i + 1, args[i], null);
        }
      }
    }
  }

继续点击方法 setParameters 进入了SqlRunner的 selectAll 方法

public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
  try (PreparedStatement ps = connection.prepareStatement(sql)) {
    setParameters(ps, args);
    try (ResultSet rs = ps.executeQuery()) {
      return getResults(rs);
    }
  }
}

继续往上:找到 SqlRunner的 selectOne方法

public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {
  List<Map<String, Object>> results = selectAll(sql, args);
  if (results.size() != 1) {
    throw new SQLException("Statement returned " + results.size() + " results where exactly one (1) was expected.");
  }
  return results.get(0);
}

好像有点熟悉了,再往上,没有了,难道这个是最上面的调用吗,网上找一下关于SqlRunner的介绍:

SqlRunner sqlRunner = new SqlRunner(connection);
String sql = new SQL()
        .SELECT("*")
        .FROM("person")
        .WHERE("person_id = ?")
        .toString();
​
Map<String, Object> resultMap = sqlRunner.selectOne(sql, 1);
System.out.println(resultMap);

原来这里就可以直接使用了,SqlRunner是一个数据库连接工具类,

[具体看这里]  https://www.cnblogs.com/javaDeveloper/p/13141415.html 

但是好像不是我们需要找到的 SqlSession 中的啊,

好吧,我们从头开始一次:在 TypeHandler 的方法 setParameter 引用中 ,之前我们走的时候SqlRunner的路子,现在我们走 DefaultParameterHandler

 于是找到了:

@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

 再往上:找setParameters在哪里调用的

很明显,看名称,我们进入 PreparedStatementHandler 进入了方法:

 

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

继续往上,

 

如果我们对mybatis多一些了解的话,看到这几个Executor结尾的,就知道了,这几个都是实现接口Executor

我们进入简单的 SimpleExecutor

的方法 prepareStatement 一看名字就是做查询前准备的。。。。。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);
  return stmt;
}

 

再往上:

还是在这个SimpleExecutor中有三个方法都调用了这个方法

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
}

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
  Configuration configuration = ms.getConfiguration();
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
  Statement stmt = prepareStatement(handler, ms.getStatementLog());
  Cursor<E> cursor = handler.queryCursor(stmt);
  stmt.closeOnCompletion();
  return cursor;
}

随便选一个doUpdate进入:

进入了BaseExecutor类(也是实现 接口Executor)方法 update(MappedStatement ms, Object parameter)

@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  clearLocalCache();
  return doUpdate(ms, parameter);
}

再往上,如下图:

 

我们发现了一个叫做 DefaultSqlSession 的东西,DefaultSqlSession 就是SqlSession接口的默认实现类,DefaultSelsession 方法

@Override
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

我们知道,使用基础的访问数据库是这样的:

String source = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(source);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
Integer count = userDao.getCount();

后台使用动态代理实现了,其中SqlSession的默认实现就是DefaultSqlSession,相关mapper的数据库操作就是DefaultSqlSession的操作了,如何看出是动态代理的, 请点击

sqlSession.getMapper(UserDao.class);

进去看看,最终会得到:

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

相信看到这里: return mapperProxyFactory.newInstance(sqlSession); 应该就明白了

sql执行后的类型处理点

这里就不具体说了,最后的上溯点是: CallableStatementHandler 类,其中这两个方法里面的 resultSetHandler.handleOutputParameters(cs); 包含了执行结果后的参数类型转换

 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    CallableStatement cs = (CallableStatement) statement;
    cs.execute();
    List<E> resultList = resultSetHandler.handleResultSets(cs);
    resultSetHandler.handleOutputParameters(cs);
    return resultList;
  }
​
  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    CallableStatement cs = (CallableStatement) statement;
    cs.execute();
    Cursor<E> resultList = resultSetHandler.handleCursorResultSets(cs);
    resultSetHandler.handleOutputParameters(cs);
    return resultList;
  }

总结:多搞搞,多深入,有好处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis中,你可以使用自定义类型处理器(TypeHandler)来处理自定义的Map类型类型处理器用于在Java对象和数据库字段之间进行转换。 以下是一种实现自定义类型处理器处理自定义的Map的示例: 1. 定义一个自定义的Map类型,例如`CustomMap`: ```java public class CustomMap extends HashMap<String, Object> { // 添加自定义的方法或属性 } ``` 2. 实现一个自定义类型处理器,继承自`org.apache.ibatis.type.BaseTypeHandler`类,并实现对应的方法。 ```java import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class CustomMapTypeHandler extends BaseTypeHandler<CustomMap> { @Override public void setNonNullParameter(PreparedStatement ps, int i, CustomMap parameter, JdbcType jdbcType) throws SQLException { // 将CustomMap转换为需要的数据类型,并设置到PreparedStatement中 // ps.setXXX(i, convertedValue); } @Override public CustomMap getNullableResult(ResultSet rs, String columnName) throws SQLException { // 从ResultSet中获取指定列名的值,并将其转换为CustomMap类型 // Object columnValue = rs.getXXX(columnName); // CustomMap map = convertToCustomMap(columnValue); // return map; return null; } @Override public CustomMap getNullableResult(ResultSet rs, int columnIndex) throws SQLException { // 与上面类似,只是根据列索引获取值 return null; } @Override public CustomMap getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { // 与上面类似,只是在CallableStatement中获取值 return null; } } ``` 在上述示例中,我们继承了`BaseTypeHandler`类,并重写了父类的方法,在这些方法中进行了自定义类型的转换逻辑。 3. 在MyBatis的配置文件中,注册自定义类型处理器。 ```xml <typeHandlers> <typeHandler handler="com.example.CustomMapTypeHandler"/> </typeHandlers> ``` 通过以上步骤,你就可以使用自定义的Map类型,并通过自定义类型处理器来处理该类型的转换逻辑。在数据库操作时,MyBatis会自动调用类型处理器来进行转换。你可以根据实际需求,在类型处理器中编写相应的转换逻辑,将自定义的Map类型与数据库字段进行转换。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值