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

主键

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标签内添加子标签



即可

连接池

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

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



即可

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

源码分析

主键

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 into blog (title, content)
    values (#{title}, #{content})

也很简单,跟进代码调试在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源码之主键、事务、连接池)