StatementHandler-Mybatis源码系列
內(nèi)容更新github地址:我飛
StatementHandler接口
StatementHandler封裝了Mybatis連接數(shù)據(jù)庫操作最基礎(chǔ)的部分。因?yàn)椋瑹o論怎么封裝,最終我們都是要使用JDBC和數(shù)據(jù)庫打交道的。
最早我們學(xué)習(xí)java連接數(shù)據(jù)庫時(shí)的代碼就像下面寫的那樣::
import java.sql.*;
public class FirstExample {
// JDBC driver name and database URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/emp";
// Database credentials
static final String USER = "root";
static final String PASS = "123456";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
//STEP 2: Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
//STEP 3: Open a connection
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//STEP 4: Execute a query
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);
//STEP 5: Extract data from result set
while(rs.next()){
//Retrieve by column name
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");
//Display values
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", First: " + first);
System.out.println(", Last: " + last);
}
//STEP 6: Clean-up environment
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}//end finally try
}//end try
System.out.println("There are so thing wrong!");
}//end main
}//end FirstExample
而對于StatementHandler來說就是將下面的代碼進(jìn)行了封裝和抽象,將和數(shù)據(jù)庫交互的能力提供給Mybatis上層應(yīng)用
StatementHandler接口:
public interface StatementHandler {
// 從connection中獲取statement
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
// 對sql進(jìn)行設(shè)置參數(shù)
void parameterize(Statement statement)
throws SQLException;
// 批量執(zhí)行
void batch(Statement statement)
throws SQLException;
// 執(zhí)行預(yù)編譯后的sql語句(update,delete,insert)
int update(Statement statement)
throws SQLException;
// 執(zhí)行查詢sql
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
// 使用游標(biāo)執(zhí)行查詢sql
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
// 獲取執(zhí)行SQL語句的封裝類BoundSql
BoundSql getBoundSql();
// 參數(shù)處理器
ParameterHandler getParameterHandler();
}
先看一下接口下面的實(shí)現(xiàn)類關(guān)系:
BaseStatementHandler
BaseStatementHandler作為繼承StatementHandler接口的抽象類存在。
public abstract class BaseStatementHandler implements StatementHandler {
protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds;
protected BoundSql boundSql;
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
@Override
public BoundSql getBoundSql() {
return boundSql;
}
@Override
public ParameterHandler getParameterHandler() {
return parameterHandler;
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
Integer queryTimeout = null;
if (mappedStatement.getTimeout() != null) {
queryTimeout = mappedStatement.getTimeout();
} else if (configuration.getDefaultStatementTimeout() != null) {
queryTimeout = configuration.getDefaultStatementTimeout();
}
if (queryTimeout != null) {
stmt.setQueryTimeout(queryTimeout);
}
StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
}
protected void setFetchSize(Statement stmt) throws SQLException {
Integer fetchSize = mappedStatement.getFetchSize();
if (fetchSize != null) {
stmt.setFetchSize(fetchSize);
return;
}
Integer defaultFetchSize = configuration.getDefaultFetchSize();
if (defaultFetchSize != null) {
stmt.setFetchSize(defaultFetchSize);
}
}
protected void closeStatement(Statement statement) {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
//ignore
}
}
protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
}
}
它內(nèi)部維護(hù)了核心字段:
- parameterHandler 作用就是將參數(shù)替換sql中的占位符的功能
- resultSetHandler 將sql結(jié)果集映射成結(jié)果對象
- executor 執(zhí)行sql的抽象,后續(xù)詳細(xì)看
只實(shí)現(xiàn)了三個(gè)接口方法:
- getBoundSql
- getParameterHandler
- prepare 其中prepare的實(shí)現(xiàn)使用了模版模式,子類必須實(shí)現(xiàn)
instantiateStatement方法來完成父類prepare方法的模版。
在BaseStatementHandler的構(gòu)造函數(shù)中我們可以看到一段調(diào)用generateKeys的代碼,它和生成主鍵key有關(guān)。
關(guān)于獲得主鍵key和插入數(shù)據(jù)時(shí)放入主鍵key牽涉到以下幾個(gè)配置:
- selectKey
- useGeneratedKeys 設(shè)置true使用自動生成的主鍵
- keyProperty 指定主鍵是(javaBean的)哪個(gè)屬性。
對于支持自動生成記錄主鍵的數(shù)據(jù)庫,如:MySQL,SQL Server,此時(shí)設(shè)置useGeneratedKeys參數(shù)值為true,在執(zhí)行添加記錄之后可以獲取到數(shù)據(jù)庫自動生成的主鍵ID,合keyProperty指定主鍵。
例子:
<insert id="saveMsg" parameterType="cn.com.tt.e.nano.Notice"
useGeneratedKeys="true" keyProperty="msgId">
insert into notice(msg_type,title,content,rec_time,send_time,user_id,deleted,viewed)
values(#{msgType,jdbcType=INTEGER},#{title,jdbcType=VARCHAR},#{content,jdbcType=VARCHAR},
#{recTime,jdbcType=BIGINT},#{sendTime,jdbcType=BIGINT},#{userId,jdbcType=VARCHAR},
#{deleted,jdbcType=TINYINT},#{viewed,jdbcType=INTEGER})
</insert>
如果使用selectKey,可以設(shè)置order屬性為AFTER。
例子:
<insert id="insertAndgetkey" parameterType="com.soft.mybatis.model.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into t_user (username,password,create_date) values(#{username},#{password},#{createDate})
</insert>
對于Oracle數(shù)據(jù)庫,當(dāng)要用到自增字段時(shí),需要用到Sequence或者使用外界傳入的唯一值比如uuid。則也使用selectKey,設(shè)置order為before。
例子:
<insert id="insert" parameterType="com.lzumetal.mybatis.entity.Employee">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
SELECT SEQ_ADMIN.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO tbl_employee(id, name, age, create_time)
VALUES(#{id}, #{name}, #{age}, #{createTime})
</insert>
KeyGenerator
KeyGenerator接口的實(shí)現(xiàn)有:Jdbc3KeyGenerator,SelectKeyGenerator,NoKeyGenerator
useGeneratedKeys設(shè)置在settings配置文件中的相關(guān)源碼在MappedStatement的org.apache.ibatis.mapping.MappedStatement.Builder#Builder中:
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
默認(rèn)是NoKeyGenerator,如果配置了useGeneratedKeys=true并且是insert操作則使用Jdbc3KeyGenerator。
useGeneratedKeys設(shè)置在mapper文件中的相關(guān)源碼:
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
如果設(shè)置了selectKey則使用SelectKeyGenerator(這部分可以更下去看到),useGeneratedKeys邏輯和前面一樣。
selectKey標(biāo)簽中的order分別對應(yīng)著KeyGenerator中的processBefore方法和processAfter方法。
而前面提到的BaseStatementHandler中g(shù)enerateKeys方法就是觸發(fā)processBefore的地方,也是唯一一個(gè)地方。
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.statement.StatementHandler#update中的各個(gè)實(shí)現(xiàn)中都可以看到執(zhí)行processAfter的代碼,比如SimpleStatementHandler的代碼:
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
ParameterHandler
再來看一下前面提到的ParameterHandler,功能就是將動態(tài)的sql中的占位符替換成實(shí)參。
它的實(shí)現(xiàn)是DefaultParameterHandler:
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@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 e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
SimpleStatementHandler類
SimpleStatementHandler是BaseStatementHandler的子類,使用Statement來完成數(shù)據(jù)庫的操作,所以Sql中不會有占位符,parameterize就是空實(shí)現(xiàn)。
query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
最后一步將數(shù)據(jù)庫數(shù)據(jù)轉(zhuǎn)換成java對象,這個(gè)后續(xù)展開。
update方法:
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
其中對主鍵處理的代碼前面已經(jīng)作了解釋,從上面兩個(gè)代碼片段來看已經(jīng)挖到最深了,最終都是調(diào)用java.sql.*的api。
PreparedStatementHandler
PreparedStatementHandler使用PreparedStatement實(shí)現(xiàn)。
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleCursorResultSets(ps);
}
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
RoutingStatementHandler
看名字就可以猜到了這個(gè)是路由用的,看它的構(gòu)造函數(shù):
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
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());
}
}
所以具體使用哪一個(gè)StatementHandler是由MappedStatement.getStatementType()決定的
CallableStatementHandler
依賴CallableStatement,用于調(diào)用存儲過程。
總結(jié)
以上是生活随笔為你收集整理的StatementHandler-Mybatis源码系列的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM学习(一)什么是JVM
- 下一篇: 小图标外链API