DBUtils源碼分析,dbutils源碼
其實,在這篇文章裡,我只是分析了dbutis的query的運作流程。
至於類為什麼要這樣設計,蘊含的設計模式等等進階知識點咱們在下節再探討。
先看看最簡單的DBUtils是如何工作的。
資料庫裡有一張表,student,裡面就三個屬性 姓名,學號,出生日期( xm,xh,birth)其中前兩個是vchar,birth是date;
package dbutils;import model.Student;import org.apache.commons.dbutils.QueryRunner;import org.apache.commons.dbutils.handlers.BeanHandler;import org.apache.commons.dbutils.ResultSetHandler;import org.junit.Test;import java.sql.SQLException;public class BeanExample { @Test public void testBean() { QueryRunner qr = new QueryRunner(new MyDBSource()); String sql = "select * from student where xh=?"; Object params[] = { "02" }; Student s=null; try { ResultSetHandler<Student> rsh=new BeanHandler<>(Student.class); s = qr.query(sql,rsh,params); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(s.getXm()); }}
其中,MyDBSource就是個提供資料來源的工具而已。
public class MyDBSource implements DataSource { private static String driverClassName = "com.mysql.jdbc.Driver"; private static String url = "jdbc:mysql://localhost:3306/webexample"; private static String userName = "root"; private static String passWord = "root"; @Override public Connection getConnection() throws SQLException { try { Class.forName(driverClassName); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return DriverManager.getConnection(url, userName, passWord); } //....}
我們再看看BeanHandler的內部。
//BeanHandler.java public BeanHandler(Class<T> type) { this(type, ArrayHandler.ROW_PROCESSOR); } public BeanHandler(Class<T> type, RowProcessor convert) { this.type = type; this.convert = convert; } //ArrayHandler.java public class ArrayHandler implements ResultSetHandler<Object[]> { static final RowProcessor ROW_PROCESSOR = new BasicRowProcessor(); //.... } //BasicRowProcessor.java public class BasicRowProcessor implements RowProcessor { private static final BeanProcessor defaultConvert = new BeanProcessor(); public BasicRowProcessor() { this(defaultConvert); } //... }
ok,我們可以看到在BeanHandler中的convert,最終是BeanProcessor類型。
再下來就是我們的重頭戲了。
s = qr.query(sql,rsh,params);
我們看時序圖
1.1 prepareStatement(conn, sql);
protected PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException { return conn.prepareStatement(sql); }
就是產生prepareStatement
1.2 fillStatement(stmt, params)
大家就是看名字也該知道,fillStatement就是填參數
其核心代碼如下:
for (int i = 0; i < params.length; i++) { if (params[i] != null) { stmt.setObject(i + 1, params[i]); } //... }
當然fillStatement在核心代碼上面還有一塊,就是檢查sql中的問號數量與params的長度是否相等。
1.3 handle(rs)
BeanHandler.java public T handle(ResultSet rs) throws SQLException { return rs.next() ? this.convert.toBean(rs, this.type) : null; }
在最開始分析BeanHandler的建構函式時,我們就已經知道了convert是BeanProcessor。
1.3.1 toBean(rs, this.type)
public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException { PropertyDescriptor[] props = this.propertyDescriptors(type); ResultSetMetaData rsmd = rs.getMetaData(); int[] columnToProperty = this.mapColumnsToProperties(rsmd, props); return this.createBean(rs, type, props, columnToProperty); }
1.3.1.1 propertyDescriptors(type)
這是運用內省(Introspector)獲得類型(就是代碼裡的Student.class)的屬性(property)
1.3.1.2 mapColumnsToProperties(rsmd, props)
這一步比較麻煩,為什麼說麻煩呢。
資料庫裡,一張表上有欄位,現在這些欄位存放在ResultSetMetaData裡面
在我們的bean裡面,有屬性(property),現在存放在props這個數組裡。
columnToProperty這裡面就放的是欄位與屬性的對應關係。
例如 columnToProperty[3]=4 就是說ResultSetMetaData裡的第三個欄位對應於bean的PropertyDescriptor裡面的第四個屬性。
1.3.1.3 createBean(rs, type, props, columnToProperty)
//對源碼略微有刪改 //但絕對不影響核心思想 private <T> T createBean(ResultSet rs, Class<T> type, PropertyDescriptor[] props, int[] columnToProperty) throws SQLException { T bean = this.newInstance(type); for (int i = 1; i < columnToProperty.length; i++) { PropertyDescriptor prop = props[columnToProperty[i]]; Class<?> propType = prop.getPropertyType(); Object value = null; if(propType != null) value = this.processColumn(rs, i, propType); this.callSetter(bean, prop, value); } return bean; }
在createBean裡面
this.processColumn(rs, i, propType)
就是獲得value,那麼我們已經知道resultset,還有這個value在resultset中的序號還有value類型,那麼該如何擷取呢?
代碼我就不貼了,大家自己看源碼吧,看上5秒鐘就能知道內部邏輯了。
1.3.1.3.1 callSetter(bean, prop, value)
這裡面的核心就是下面的代碼
// Don't call setter if the value object isn't the right type if (this.isCompatibleType(value, params[0])) { setter.invoke(target, new Object[]{value}); } else { throw new SQLException( "Cannot set " + prop.getName() + ": incompatible types, cannot convert " + value.getClass().getName() + " to " + params[0].getName()); // value cannot be null here because isCompatibleType allows null }
如果value是個String,params卻是個double那就得拋出異常了。