主键
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
接口,其实现类有JdbcTransaction
和 ManagedTransaction
JdbcTransaction
直接使用JDBC提交和回滚工具的事务。 他依赖于从数据源中获取的连接取管理事务的范围。如果设置了autocommit
,则会自动提交和回滚,换而言之,没有设置autocommit
则需要手动提交。
ManagedTransaction
不处理提交和回滚,而是让容器管理事务的完整生命周期。
使用事务只要在Mybatis配置文件中每个environment
标签内添加子标签
即可
连接池
Mybatis 连接池有3种:PooledConnection
、UnpooledDataSource
以及JNDI datasource,由对应的工厂类产生:PooledDataSourceFactory
、UnpooledDataSourceFactory
、JndiDataSourceFactory
。
连接池的使用在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)
,这个handler
是RoutingStatementHandler
的对象,其内部也是一个事件委派,默认为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的事务管理有两种JdbcTransaction
、ManagedTransaction
,分别是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()
。