美文网首页
Mybatis源码之主键、事务、连接池

Mybatis源码之主键、事务、连接池

作者: Kohler | 来源:发表于2018-08-06 16:54 被阅读22次

    主键

    Mybatis 主键生成策略使用方式为在数据变更语句 insert,update 设置 useGeneratedKeys属性为true(仅对 insert 和 update 有用),这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。

    其实现方式为:Mybatis提供了主键生成器接口KeyGenerator,在sql语句执行完毕后会执行keyGenerator.processAfter(),方法执行stmt.getGeneratedKeys(),从statement中取出生成的主键;而insert语句本身是不返回记录的主键值,而是返回插入的记录条数。

    事务

    Mybatis 事务实现方式为提供了一个 Transaction 接口,其实现类有JdbcTransactionManagedTransaction

    JdbcTransaction直接使用JDBC提交和回滚工具的事务。 他依赖于从数据源中获取的连接取管理事务的范围。如果设置了autocommit,则会自动提交和回滚,换而言之,没有设置autocommit则需要手动提交。

    ManagedTransaction 不处理提交和回滚,而是让容器管理事务的完整生命周期。

    使用事务只要在Mybatis配置文件中每个environment标签内添加子标签

    <!--JDBC|MANAGED-->
    <transactionManager type="JDBC"/>
    

    即可

    连接池

    Mybatis 连接池有3种:PooledConnectionUnpooledDataSource 以及JNDI datasource,由对应的工厂类产生:PooledDataSourceFactoryUnpooledDataSourceFactoryJndiDataSourceFactory

    连接池的使用在Mybatis配置文件中每个environment子标签dataSource增加属性

    <!--UNPOOLED|POOLED|JNDI-->
    <dataSource type="POOLED">
    

    即可

    下面针对源码分别进行分析

    源码分析

    主键

    BlogDao blogDao = sqlSession.getMapper(BlogDao.class);
    Blog blog = new Blog();
    blog.setTitle("test");
    blog.setContent("Hello World !");
    Long count = blogDao.saveBlog(blog);
    System.out.println(count);
    System.out.println(blog.getId());
    

    上面是一个简单的Mybatis保存数据的代码,saveBlog的statement如下

    <insert id="saveBlog" useGeneratedKeys="true" keyProperty="id" parameterType="blog">
        insert into blog (title, content)
        values (#{title}, #{content})
    </insert>
    

    也很简单,跟进代码调试在MapperMethod.execute() 方法可以看到进入INSERT case,最后会以result返回,如下

    result = rowCountResult(sqlSession.insert(command.getName(), param));
    

    根据方法名可以猜测,insert返回的是执行的行,也就是插入的行。

    sqlSession的insert方法会调用其update方法,update方法根据statement名称,取出之前解析mapper文件得到的MappedStatement,然后调用executor的update方法,executor默认是CachingExecutor,具体的执行会委派给其内部的委派对象SimpleExecutor执行

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

    在获取statement后,继续执行handler.update(stmt),这个handlerRoutingStatementHandler的对象,其内部也是一个事件委派,默认为PREPARED

    // RoutingStatementHandler构造函数
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
    

    进入delegate.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();
        // Jdbc3KeyGenerator 会进行主键处理
      keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
      return rows;
    }
    

    根据不同的KeyGenerator策略进行不同的操作Jdbc3KeyGenerator.processAfter,会进行主键的处理。

    连接池

    在上面执行SimpleExecutor.prepareStatement()的时候,会获取连接,根据不同的连接池配置,获取连接的方式也是不一致的。

    // BaseExecutor
    protected Connection getConnection(Log statementLog) throws SQLException {
      Connection connection = transaction.getConnection();
      if (statementLog.isDebugEnabled()) {
        return ConnectionLogger.newInstance(connection, statementLog, queryStack);
      } else {
        return connection;
      }
    }
    

    调用transaction.getConnection,根据配置Mybatis的事务管理有两种JdbcTransactionManagedTransaction,分别是JDBC的方式和交给容器管理,这里我的配置是JDBC

    // JdbcTransaction
    public Connection getConnection() throws SQLException {
      if (connection == null) {
        openConnection();
      }
      return connection;
    }
    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }
        connection = dataSource.getConnection();
        if (level != null) {
            connection.setTransactionIsolation(level.getLevel());
        }
        setDesiredAutoCommit(autoCommmit);
    }
    

    JdbcTransaction 的构造函数传参有数据连接池、事务隔离级别、是否自动提交

    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
      dataSource = ds;
      level = desiredLevel;
      autoCommmit = desiredAutoCommit;
    }
    

    到这已经很清楚了,transaction 通过从连接池中获取连接,并设置相应的隔离级别和是否自动提交,返回给executor。

    因为我配置连接池是POOLED,这里可以进入PooledDataSource.getConnection() 方法

    public Connection getConnection() throws SQLException {
        // 注意返回的是getProxyConnection()方法获取的connection
      return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
    }
    

    主要通过内部对象PoolState对象state来管理,popConnection方法内部synchronized (state)保证线程安全。

    可以看到新建连接的对象为PooledConnection,而返回的是PooledConnection.getProxyConnection()获取的connection。

    conn = new PooledConnection(dataSource.getConnection(), this);
    

    PooledConnection 内部有个UnpooledDataSource dataSource,而dataSource.getConnection()方法真正获取一个数据库连接,使用获取的连接构造一个PooledConnection对象,PooledConnection构造函数如下

    // PooledConnection
    public PooledConnection(Connection connection, PooledDataSource dataSource) {
      this.hashCode = connection.hashCode();
      this.realConnection = connection;
      this.dataSource = dataSource;
      this.createdTimestamp = System.currentTimeMillis();
      this.lastUsedTimestamp = System.currentTimeMillis();
      this.valid = true;
      this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }
    

    有两点:一个realConnection,一个proxyConnection,而上面返回的是proxyConnection,为什么要有一个proxyConnection呢?可以看到InvocationHandler是this,那便看PooledConnection.invoke方法

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String methodName = method.getName();
      if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
        dataSource.pushConnection(this);
        return null;
      } else {
        try {
          if (!Object.class.equals(method.getDeclaringClass())) {
            // issue #579 toString() should never fail
            // throw an SQLException instead of a Runtime
            checkConnection();
          }
          return method.invoke(realConnection, args);
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      }
    }
    

    原来之所以返回proxyConnection,是因为proxyConnection屏蔽了close方法,取而代之使用了dataSource.pushConnection(this),将连接放回连接池,而realConnection在真正需要释放连接的时候,才调用。

    事务

    事务在前面多少提到了,获取连接的时候,根据配置设置连接的隔离级别和是否自动提交。在需要手动提交或者回滚的时候,调用sqlSession.rollback() 或者 sqlSession.commit()

    相关文章

      网友评论

          本文标题:Mybatis源码之主键、事务、连接池

          本文链接:https://www.haomeiwen.com/subject/vbygvftx.html