Java Database Connectivity,簡稱JDBC。是Java語言中用來規範客戶端程序如何來訪問數據庫的應用程序接口,提供了諸如查詢和更新數據庫中數據的方法。 隨着Java ORM框架的發展,已經不多有機會再在生產系統中寫JDBC的代碼來訪問數據庫了,可是基本流程咱們仍是要熟悉。下面以一個簡單的查詢爲例,溫故一下JDBC。java
public static void main(String[] args) throws Exception {
Connection conn = getConnection();
String sql = "select * from user where 1=1 and id = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, "501440165655347200");
ResultSet rs = stmt.executeQuery();
while(rs.next()){
String username = rs.getString("username");
System.out.print("姓名: " + username);
}
}
複製代碼
從上面的代碼來看,一次簡單的數據庫查詢操做,能夠分爲幾個步驟。sql
建立Connection鏈接數據庫
傳入參數化查詢SQL語句構建預編譯對象PreparedStatementexpress
設置參數數組
執行SQL緩存
從結果集中獲取數據bash
那麼,我們的主角Mybatis是怎樣完成這一過程的呢?不着急,我們一個一個來看。app
在上一章節的內容中,咱們已經看到了在Service層經過@Autowired注入的userMapper是個代理類,在執行方法的時候實際上調用的是代理類的invoke通知方法。框架
public class MapperProxy<T> implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
複製代碼
MapperMethod對象裏面就兩個屬性,SqlCommand和MethodSignature。源碼分析
SqlCommand包含了執行方法的名稱和方法的類型,好比UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
。 MethodSignature能夠簡單理解爲方法的簽名信息。裏面包含:返回值類型、是否void、是否爲集合類型、是否爲Cursor等,主要還獲取到了方法參數上的@Param註解的名稱,方便下一步獲取參數值。 好比,若是方法上加了@Param的參數: User getUserById(@Param(value="id")String id,@Param(value="password")String password);
,參數會被解析成{0=id, 1=password}
。
判斷方法的SQL類型和返回值類型 ,調用相應的方法。以方法User getUserById(String id,String password)
爲例,會調用到selectOne()方法。
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {}
case UPDATE: {}
case DELETE: {}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
//無返回值
} else if (method.returnsMany()) {
//返回集合類型
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回Map類型
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
//返回Cursor
result = executeForCursor(sqlSession, args);
} else {
//將參數args轉換爲SQL命令的參數
//默認會添加一個《param+參數索引》的參數名
//{password=123456, id=501441819331002368, param1=501441819331002368, param2=123456}
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
...
return result;
}
}
複製代碼
能夠看到,sqlSession.selectOne就能夠獲取到數據庫中的值並完成轉換工做。這裏的sqlSession就是SqlSessionTemplate
實例的對象,因此它會調用到
public class SqlSessionTemplate{
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}
}
複製代碼
sqlSessionProxy也是個代理對象。關於它的建立我們上節課也很認真的分析了,總之它實際會調用到SqlSessionInterceptor.invoke()
。
sqlSession咱們熟悉呀,它做爲MyBatis工做的主要頂層API,表示和數據庫交互的會話,完成必要數據庫增刪改查功能。關於它的建立、執行、提交和資源清理都是在SqlSessionInterceptor的通知方法中完成的。
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//建立SqlSession對象
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//調用sqlSession實際方法
Object result = method.invoke(sqlSession, args);
return result;
} catch (Throwable t) {
....
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
複製代碼
上面的重點就是建立了SqlSession並執行它的方法,它是一個DefaultSqlSession實例的對象,裏面主要有一個經過configuration建立的執行器,在這裏它是SimpleExecutor。
那麼,invoke方法實際調用的就是DefaultSqlSession.selectOne()
。
DefaultSqlSession中的selectOne()
方法最終也會調用到selectList()
方法。它先從數據大管家configuration中根據請求方法的全名稱拿到對應的MappedStatement對象,而後調用執行器的查詢方法。
//statement是調用方法的全名稱,parameter爲參數的Map
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
//在mapper.xml中每個SQL節點都會封裝爲MappedStatement對象
//在configuration中就能夠經過請求方法的全名稱獲取對應的MappedStatement對象
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
複製代碼
其中有個方法wrapCollection(parameter)
咱們能夠了解下,若是參數爲集合類型或者數組類型,它會將參數名稱設置爲相應類型的名稱。
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
複製代碼
在configuration這個大管家對象中,保存着mapper.xml裏面全部的SQL節點。每個節點對應一個MappedStatement對象,而動態生成的各類sqlNode保存在SqlSource對象,SqlSource對象有一個方法就是getBoundSql()。 咱們先來看一下BoundSql類哪有哪些屬性。
public class BoundSql {
//動態生成的SQL,解析完畢帶有佔位性的SQL
private final String sql;
//每一個參數的信息。好比參數名稱、輸入/輸出類型、對應的JDBC類型等
private final List<ParameterMapping> parameterMappings;
//參數
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
}
複製代碼
看到這幾個屬性,也就解釋了BoundSql 的含義。即表示動態生成的SQL語句和相應的參數信息。
不知你們是否還有印象,不一樣類型的SQL會生成不一樣類型的SqlSource對象。好比靜態SQL會生成StaticSqlSource對象,動態SQL會生成DynamicSqlSource對象。
靜態SQL比較簡單,直接就建立了BoundSql對象並返回。
public class StaticSqlSource implements SqlSource {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
複製代碼
動態SQL要根據不一樣的sqlNode節點,調用對應的apply方法,有的還要經過Ognl表達式來判斷是否須要添加當前節點,好比IfSqlNode。
public class DynamicSqlSource implements SqlSource {
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
//rootSqlNode爲sqlNode節點的最外層封裝,即MixedSqlNode。
//解析完全部的sqlNode,將sql內容設置到context
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//設置參數信息 將SQL#{}替換爲佔位符
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
//建立BoundSql對象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
}
複製代碼
rootSqlNode.apply(context)是一個迭代調用的過程。最後生成的內容保存在DynamicContext對象,好比select * from user WHERE uid=#{uid}
。
而後調用SqlSourceBuilder.parse()方法。它主要作了兩件事:
一、將SQL語句中的#{}替換爲佔位符 二、將#{}裏面的字段封裝成ParameterMapping對象,添加到parameterMappings。
ParameterMapping對象保存的就是參數的類型信息,若是沒有配置則爲null。 ParameterMapping{property='uid', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
最後返回的BoundSql對象就包含一個帶有佔位符的SQL和參數的具體信息。
建立完BoundSql對象,調用query方法,來到CachingExecutor.query()。這個方法的前面是二級緩存的判斷,若是開啓了二級緩存且緩存中有數據,就返回。
public class CachingExecutor implements Executor {
public <E> List<E> query(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql)throws SQLException {
//二級緩存的應用
//若是配置</cache>則走入這個流程
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
//從緩存中獲取數據
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
複製代碼
接着看query方法,建立PreparedStatement預編譯對象,執行SQL並獲取返回集合。
public class SimpleExecutor extends BaseExecutor {
public <E> List<E> doQuery(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//獲取Statement的類型,即默認的PreparedStatementHandler
//須要注意,在這裏若是配置了插件,則StatementHandler可能返回的是一個代理
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//建立PreparedStatement對象,並設置參數值
stmt = prepareStatement(handler, ms.getStatementLog());
//執行execute 並返回結果集
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
}
複製代碼
prepareStatement方法獲取數據庫鏈接並構建Statement對象設置SQL參數。
一、建立PreparedStatement
public class SimpleExecutor extends BaseExecutor {
private Statement prepareStatement(StatementHandler handler, Log statementLog) {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
}
複製代碼
咱們看到getConnection方法就是獲取Connection鏈接的地方。但這個Connection也是一個代理對象,它的調用程序處理器爲ConnectionLogger
。顯然,它是爲了更方便的打印日誌。
public abstract class BaseExecutor implements Executor {
protected Connection getConnection(Log statementLog) throws SQLException {
//從c3p0鏈接池中獲取一個鏈接
Connection connection = transaction.getConnection();
//若是日誌級別爲Debug,則爲這個鏈接生成代理對象返回
//它的處理類爲ConnectionLogger
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
}
複製代碼
這個跟咱們的JDBC代碼是同樣的,拿到SQL,調用Connection鏈接的prepareStatement(sql)。但因爲connection是一個代理對象,彷佛又沒那麼簡單。
public class PreparedStatementHandler
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
return connection.prepareStatement(sql);
}
}
複製代碼
因此,在執行的onnection.prepareStatement(sql)
的時候,實際調用的是ConnectionLogger類的invoke()。
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] params)throws Throwable {
try {
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
//調用connection.prepareStatement
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
//又爲stmt建立了代理對象,通知類爲PreparedStatementLogger
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
}
}
}
}
public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = PreparedStatement.class.getClassLoader();
return (PreparedStatement) Proxy.newProxyInstance(cl,
new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}
複製代碼
果真沒那麼簡單,最後返回的PreparedStatement又是個代理對象。
咱們知道,在設置參數的時候,你有不少可選項,好比stmt.setString()、stmt.setInt()、stmt.setFloat()
等,或者粗暴一點就stmt.setObject()
。
固然了,Mybatis做爲一個優秀的ORM框架,不可能這麼粗暴。它先是根據參數的Java類型獲取全部JDBC類型的處理器,再根據JDBC的類型獲取對應的處理器。在這裏咱們沒有配置JDBC類型,因此就是它的類型爲NULL,最後返回的就是StringTypeHandler。
關於類型處理器的匹配和查詢規則,我們在Mybatis源碼分析(三)經過實例來看typeHandlers已經詳細分析過,就再也不細看。
public class StringTypeHandler extends BaseTypeHandler<String> {
public void setNonNullParameter(PreparedStatement ps, int i,
String parameter, JdbcType jdbcType)throws SQLException {
ps.setString(i, parameter);
}
}
複製代碼
二、執行
在SQL預編譯完成以後,調用execute()執行。
public class PreparedStatementHandler{
public <E> List<E> query(Statement statement, ResultHandler resultHandler) {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
}
複製代碼
這裏的PreparedStatement對象也是個代理類,在調用通知類PreparedStatementLogger,執行execute的時候,只是打印了參數的值。即Parameters: 501868995461251072(String)
。
上面的方法咱們看到SQL已經提交給數據庫執行,那麼最後一步就是獲取返回值。
public class DefaultResultSetHandler implements ResultSetHandler {
public List<Object> handleResultSets(Statement stmt) throws SQLException {
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
//將ResultSet封裝成ResultSetWrapper對象
ResultSetWrapper rsw = getFirstResultSet(stmt);
//返回mapper.xml中配置的rsultMap 實際上咱們沒有配置,但會有默認的一個
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
//處理數據庫的返回值,最後加入到multipleResults
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
resultSetCount++;
}
//返回
return collapseSingleResultList(multipleResults);
}
}
複製代碼
上面的代碼咱們看到,第一步就把ResultSet對象封裝成了ResultSetWrapper對象,關於它還須要具體來看。
public class ResultSetWrapper {
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
//全部已註冊的類型處理器
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
//ResultSet對象
this.resultSet = rs;
//元數據 列名、列類型等信息
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
//循環列,將列名、列對應的JDBC類型和列對應的Java類型都獲取到
for (int i = 1; i <= columnCount; i++) {
columnNames.add(metaData.getColumnName(i));
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
classNames.add(metaData.getColumnClassName(i));
}
}
}
複製代碼
上面的重點是拿到數據庫列上的信息,在解析的時候會用到。
handleResultSet方法最後調用到DefaultResultSetHandler.handleRowValuesForSimpleResultMap()
。
public class DefaultResultSetHandler implements ResultSetHandler {
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw,
ResultMap resultMap, ResultHandler<?> resultHandler,
RowBounds rowBounds, ResultMapping parentMapping)throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
//跳過行 Mybatis的RowBounds分頁功能
skipRows(rsw.getResultSet(), rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
}
複製代碼
在這個地方涉及到Mybatis中分頁的一個對象RowBounds。但實際上,咱們基本不會用到它。由於它是一個邏輯分頁,而非物理分頁。
RowBounds對象中有兩個屬性控制着分頁:offset、limit
。offset是說分頁從第幾條數據開始,limit是說一共取多少條數據。由於咱們沒有配置它,因此它默認是offset從0開始,limit取Int的最大值。
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
public RowBounds() {
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
}
複製代碼
skipRows方法就是來跳過offset,它的實現也比較簡單。
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
for (int i = 0; i < rowBounds.getOffset(); i++) {
rs.next();
}
}
複製代碼
offset跳過以後,怎麼控制Limit的呢?這就要看上面的while循環了。
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
//處理數據
}
複製代碼
關鍵在於shouldProcessMoreRows()方法,它實際上是個簡單的判斷。
private boolean shouldProcessMoreRows(ResultContext<?> context,
RowBounds rowBounds) throws SQLException {
//就是看已經取到的數據是否小與Limit
return context.getResultCount() < rowBounds.getLimit();
}
複製代碼
while循環獲取ResultSet的每一行數據,而後經過rs.getxxx()獲取數據。
public class DefaultResultSetHandler implements ResultSetHandler {
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//建立返回值類型,好比咱們返回的是User實體類
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
//自動映射
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
//這個處理配置的ResultMap,就是手動配置數據庫列名與Java實體類字段的映射
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
}
複製代碼
一、獲取返回值類型
第一步是獲取返回值類型,過程就是拿到Class對象,而後獲取構造器,設置可訪問並返回實例。
private <T> T instantiateClass(Class<T> type,
List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
Constructor<T> constructor;
if (constructorArgTypes == null || constructorArgs == null) {
//獲取構造器
constructor = type.getDeclaredConstructor();
if (!constructor.isAccessible()) {
//設置可訪問
constructor.setAccessible(true);
}
//返回實例
return constructor.newInstance();
}
}
複製代碼
返回後,又把它包裝成了MetaObject對象。Mybatis會根據返回值類型的不一樣,包裝成不一樣的Wrapper對象。本例中,因爲是一個實體類,會返回BeanWrapper。
private MetaObject(Object object, ObjectFactory objectFactory,
ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
複製代碼
二、applyAutomaticMappings
在mapper.xml中咱們能夠聲明一個resultMap節點,將數據庫中列的名稱和Java中字段名稱對應起來,應用到SQL節點的resultMap中。也能夠不配置它,直接利用resultType返回一個Bean便可。可是這兩種方式會對應兩種解析方法。
private boolean applyAutomaticMappings(ResultSetWrapper rsw,
ResultMap resultMap, MetaObject metaObject,
String columnPrefix) throws SQLException {
//獲取相應字段的類型處理器
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
//由於返回值類型是一個BeanWapper,經過反射把值設置到JavaBean中。
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
複製代碼
上面代碼的重點是獲取對應字段的類型處理器,調用對應類型處理器的getResult方法從ResultSet中拿到數據的值。
//type是Java字段的類型 jdbcType是數據庫列的JDBC類型
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
//先從全部的處理器中獲取Java類型的處理器
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
//再根據JDBC的類型獲取實際的處理器
handler = jdbcHandlerMap.get(jdbcType);
}
return (TypeHandler<T>) handler;
}
複製代碼
以ID爲例,在Java中是String類型,在數據庫中是VARCHAR,最後返回的類型處理器是StringTypeHandler。調用的時候,就很簡單了。
return rs.getString(columnName);
複製代碼
經過rs.getString()拿到值以後,而後向返回值類型中設置。由於咱們返回的是一個JavaBean,對應的是BeanWapper對象,方法中其實就是反射調用。
public class BeanWrapper extends BaseWrapper {
private void setBeanProperty(PropertyTokenizer prop, Object object, Object value) {
try {
Invoker method = metaClass.getSetInvoker(prop.getName());
Object[] params = {value};
try {
method.invoke(object, params);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} catch (Throwable t) {
throw new ReflectionException("Could not set property '" + prop.getName() + "' of '" + object.getClass() + "' with value '" + value + "' Cause: " + t.toString(), t);
}
}
}
複製代碼
把全部的列都解析完,返回指定的Bean。最後加入到list,整個方法返回。在selectOne方法中,取List的第一條數據。若是數據記錄大於1,就是出錯了。
public class DefaultSqlSession implements SqlSession {
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
}
複製代碼
關於Mybatis執行方法的整個過程,咱們簡單概括一下。
獲取SqlSession,根據方法的返回值類型調用不一樣的方法。好比selectOne
。
獲取BoundSql對象,根據傳遞的參數生成SQL語句
從數據庫鏈接池獲取Connection對象,併爲它建立代理,以便打印日誌
從Connection中獲取PreparedStatement預編譯對象,併爲它建立代理
預編譯SQL,並設置參數
執行、返回數據集合
將數據集轉換爲Java對象
看到這裏,再回憶下咱們開頭的JDBC實例的步驟,能夠看到它們二者之間的主流程都是同樣的。Mybatis只是在此基礎上作了一些封裝,更好的服務於咱們的應用。