內容更新github地址:我飛java
StatementHandler封裝了Mybatis鏈接數據庫操做最基礎的部分。由於,不管怎麼封裝,最終咱們都是要使用JDBC和數據庫打交道的。
最先咱們學習java鏈接數據庫時的代碼就像下面寫的那樣::mysql
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來講就是將下面的代碼進行了封裝和抽象,將和數據庫交互的能力提供給Mybatis上層應用git
StatementHandler接口:github
public interface StatementHandler { // 從connection中獲取statement Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; // 對sql進行設置參數 void parameterize(Statement statement) throws SQLException; // 批量執行 void batch(Statement statement) throws SQLException; // 執行預編譯後的sql語句(update,delete,insert) int update(Statement statement) throws SQLException; // 執行查詢sql <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException; // 使用遊標執行查詢sql <E> Cursor<E> queryCursor(Statement statement) throws SQLException; // 獲取執行SQL語句的封裝類BoundSql BoundSql getBoundSql(); // 參數處理器 ParameterHandler getParameterHandler(); }
先看一下接口下面的實現類關係:
sql
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(); } }
它內部維護了核心字段:apache
只實現了三個接口方法:api
instantiateStatement
方法來完成父類prepare
方法的模版。在BaseStatementHandler的構造函數中咱們能夠看到一段調用generateKeys的代碼,它和生成主鍵key有關。mybatis
關於得到主鍵key和插入數據時放入主鍵key牽涉到如下幾個配置:app
對於支持自動生成記錄主鍵的數據庫,如:MySQL,SQL Server,此時設置useGeneratedKeys參數值爲true,在執行添加記錄以後能夠獲取到數據庫自動生成的主鍵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
,能夠設置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數據庫,當要用到自增字段時,須要用到Sequence或者使用外界傳入的惟一值好比uuid。則也使用selectKey,設置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接口的實現有:Jdbc3KeyGenerator
,SelectKeyGenerator
,NoKeyGenerator
useGeneratedKeys
設置在settings配置文件中的相關源碼在MappedStatement
的org.apache.ibatis.mapping.MappedStatement.Builder#Builder
中:
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
默認是NoKeyGenerator,若是配置了useGeneratedKeys=true
而且是insert操做則使用Jdbc3KeyGenerator
。
useGeneratedKeys
設置在mapper文件中的相關源碼:
if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; }
若是設置了selectKey
則使用SelectKeyGenerator
(這部分能夠更下去看到),useGeneratedKeys
邏輯和前面同樣。
selectKey
標籤中的order
分別對應着KeyGenerator
中的processBefore
方法和processAfter
方法。
而前面提到的BaseStatementHandler中generateKeys方法就是觸發processBefore的地方,也是惟一一個地方。
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
中的各個實現中均可以看到執行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
,功能就是將動態的sql中的佔位符替換成實參。
它的實現是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是BaseStatementHandler的子類,使用Statement來完成數據庫的操做,因此Sql中不會有佔位符,parameterize
就是空實現。
query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); statement.execute(sql); return resultSetHandler.<E>handleResultSets(statement); }
最後一步將數據庫數據轉換成java對象,這個後續展開。
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; }
其中對主鍵處理的代碼前面已經做了解釋,從上面兩個代碼片斷來看已經挖到最深了,最終都是調用java.sql.*
的api
。
PreparedStatementHandler使用PreparedStatement實現。
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); }
看名字就能夠猜到了這個是路由用的,看它的構造函數:
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()); } }
因此具體使用哪個StatementHandler
是由MappedStatement.getStatementType()決定的
依賴CallableStatement
,用於調用存儲過程。