DBUtils source code analysis, dbutils source code
In fact, in this article, I only analyzed the operation process of dbutis query.
As for why the class should be designed in this way, the design patterns and other advanced knowledge points should be discussed in the next section.
First, let's take a look at how the simplest DBUtils works.
There is a table in the database, student, which contains three attribute names, student ID, birth date (xm, xh, birth), the first two of which are vchar and birth are 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 is a tool that provides data sources.
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); } //....}
Let's look at the BeanHandler's interior.
//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. We can see that the convert in BeanHandler is the BeanProcessor type.
Next we will be playing a major role.
s = qr.query(sql,rsh,params);
Let's look at the sequence chart.
1.1 prepareStatement (conn, SQL );
protected PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException { return conn.prepareStatement(sql); }
Is to generate prepareStatement
1.2 fillStatement (stmt, params)
You should know the name. fillStatement is a parameter.
The core code is as follows:
for (int i = 0; i < params.length; i++) { if (params[i] != null) { stmt.setObject(i + 1, params[i]); } //... }
Of course, there is another piece of fillStatement on the core code, that is, check whether the number of question marks in SQL is equal to the length of params.
1.3 handle (rs)
BeanHandler.java public T handle(ResultSet rs) throws SQLException { return rs.next() ? this.convert.toBean(rs, this.type) : null; }
When we first analyzed the BeanHandler constructor, we knew that convert was 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)
This is the property of the type (Student. class in the Code) obtained using Introspector)
1.3.1.2 mapColumnsToProperties (rsmd, props)
This step is troublesome. Why bother.
In the database, a table contains fields, which are now stored in ResultSetMetaData.
In our bean, there is a property, which is now stored in the props array.
ColumnToProperty contains the correspondence between fields and attributes.
For example, columnToProperty [3] = 4 means the Third Field in ResultSetMetaData corresponds to the fourth attribute in the bean's PropertyDescriptor.
1.3.1.3 createBean (rs, type, props, columnToProperty)
// Slightly modify the source code. // It does not affect the core idea. 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 ;}
In createBean
This. processColumn (rs, I, propType)
Is to obtain the value, so we already know the resultset, the serial number of this value in the resultset, and the value type. How can we get it?
I will not post the code. Let's look at the source code by yourself. We will be able to see the internal logic in five seconds.
1.3.1.3.1 callSetter (bean, prop, value)
The core here is the following code.
// 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 }
If the value is a String and params is a double, an exception is thrown.