mybatis中的土鸡杂鱼

mybatis中的土鸡杂鱼

1、mapper接口为什么要和mapper.xml在同一个路径下?

为什么在单独学习使用mybatis的时候,强烈推荐要放在同一个路径下?

首先看下我的配置文件写法如下:

    <mappers>
        <package name="com.guang.mybatis.mapper"/>
    </mappers>

也就是说,mapper接口都是com.guang.mybatis.mapper路径下,那么我们的配置文件也要在这个路径下。

那么按照这个思路来,解析过程在XMLMapperBuilder中org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement中

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        }
        // ....省略代码

然后来到

  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 加载xml
      loadXmlResource();
      //...省略代码

那么仔细看下下面这段代码:

  private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      
      // 根据类的全限定路径找对应的xml文件
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      // #1347
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {
        // Search XML mapper that is not in the module but in the classpath.
        try {
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e2) {
          // ignore, resource is not required
        }
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

看到这里,就知道了为什么说要mapper接口和mapper.xml的路径保持一致的原因了。

2、主键生成为什么配置一个字段就可以?

若数据库支持自动生成主键(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys=“true”,表明使用自增主键获取主键值策略,然后再利用 keyProperty 属性指定对应的主键属性,也就是 Mybatis 获取到主键值后,将这个值封装给 JavaBean 的哪个属性。

<insert id="addEmp" databaseId="mysql" parameterType="employee" useGeneratedKeys="true" keyProperty="id">
    insert into tbl_employee (id, last_name, email, gender)
    values (#{id}, #{lastName}, #{email}, #{gender});
</insert>

获取自增主键值:

@Test
public void test04() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Employee employee = new Employee();
        employee.setLastName("lig");
        employee.setEmail("lidaye@123.com");
        employee.setGender("1");
        boolean addEmp = mapper.addEmp(employee);
        System.out.println(addEmp);
        // 直接通过 getId() 方法来获取自增主键值
        System.out.println(employee.getId());
        sqlSession.commit();
    } finally {
        sqlSession.close();
    }
}
[main][com.example.mapper.EmployeeMapper.addEmp]-[DEBUG] ==>  Preparing: insert into tbl_employee (id, last_name, email, gender) values (?, ?, ?, ?); 
[main][com.example.mapper.EmployeeMapper.addEmp]-[DEBUG] ==> Parameters: null, wangwu(String), wangwu@123.com(String), 1(String)
[main][com.example.mapper.EmployeeMapper.addEmp]-[DEBUG] <==    Updates: 1
true
9

其中“9”为获取到的自增主键值。

其他数据库厂商参考文章

原理

下面看在解析过程中org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

	// ...省略代码
	// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
	// ...省略代码

如果在对应的增伤改查语句中写了useGeneratedKeys=true,那么这里就会开始使用Jdbc3KeyGenerator.INSTANCE

那么既然会使用到Jdbc3KeyGenerator,那么看一下在哪里执行的。

首先追踪到org.apache.ibatis.executor.statement.BaseStatementHandler#BaseStatementHandler

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }
  protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }

但是发现org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBefore中什么都没有做

然后在org.apache.ibatis.executor.statement.PreparedStatementHandler#instantiateStatement

  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {      
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

关于这里的KeyColumns参考文章:https://www.cnblogs.com/woyujiezhen/p/11643944.html

但是在MySQL中,我们并没有配置这个东西。所以这里仅仅只是创建了一个预编译的PreparedStatement,并且设置是有返回生成的主键的。

然后找到了org.apache.ibatis.executor.statement.PreparedStatementHandler#update

  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

具体的就可以看下org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator#processBatch

  public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
    // 首先获取得到配置的keyProperty的值,最终会在assignKeys中将方法进行赋值进去
    final String[] keyProperties = ms.getKeyProperties();
    if (keyProperties == null || keyProperties.length == 0) {
      return;
    }
    try (ResultSet rs = stmt.getGeneratedKeys()) {
      final ResultSetMetaData rsmd = rs.getMetaData();
      final Configuration configuration = ms.getConfiguration();
      if (rsmd.getColumnCount() < keyProperties.length) {
        // Error?
      } else {
        assignKeys(configuration, rs, rsmd, keyProperties, parameter);
      }
    } catch (Exception e) {
      throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
    }
  }

3、为什么默认使用的是预编译PreparedStatement?

这里就和上面一样的位置:

StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

4、分页对象RowBounds和ResultContext

在org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleRowValuesForSimpleResultMap方法中有一个细节的地方

  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    // 默认结果集处理
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    // 跳过指定行
    skipRows(resultSet, rowBounds);
    // 利用ResultContext来进行选择是否停止
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }

其实本质上的原因,我们不会用到这里的东西。因为都是默认的,但是如果使用了Sqlsession提供的API中包含了有RowBounds和ResultHandler,那么我们可以来实现自定义处理。

根据官方描述是为了防止数据量太大的情况,但是我们一般来说,只要SQL写的好,就能够避免掉这种情况。

<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

// 因为做了自定义处理,所以结果集应该另外选择了一个集合保存。故返回值为void
void select(String statement, Object parameter, ResultHandler handler);
posted @ 2022-12-26 16:13  写的代码很烂  阅读(24)  评论(0编辑  收藏  举报