Spring源碼學習(九):Spring JDBC

目錄

1.使用

2.JdbcTemplate的使用

2.1 數據查詢

2.2 數據持久化


Java程序員對JDBC應該都不陌生,使用起來也很簡單,只要用DriverManager開啓一個Connection,就可以創建Statement或PreparedStatement,然後調用SQL語句,獲得ResultSet或將數據修改持久化。但是JDBC使用起來也有很多不便,例如:SQL語句編寫在程序代碼中,不方便維護;參數傳入Statement以及從ResultSet取出數據很麻煩,冗餘代碼多。

1.使用

假設有一個用戶表User,結構如下:

字段 數據類型 說明
id INTEGER 序號,自增主鍵
name VARCHAR 用戶名
password VARCHAR 密碼

首先可以創建一個如下的實體類:

public class User {
    private int id;
    private String name;
    private String password;

    // 省略getter/setter
}

然後基於Spring JDBC提供的RowMapper接口,創建表結構同實體類的映射:

public class UserMapper implements RowMapper {
    @Override
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
        return new User(resultSet.getInt("id"), 
                        resultSet.getString("name"), 
                        resultSet.getString("password"));
    }
}

然後創建數據操作接口及其實現類,這裏只貼實現類代碼:

public class UserServiceImpl implements UserService{
    private JdbcTemplate jdbcTemplate;
    
    private void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public User queryUserById(String id) {
        return jdbcTemplate.queryForObject("select * from User where id = ?", new Object[] {id}, new UserMapper());
    }
    
    @Override
    public User queryUserByName(String name) {
        // 略
    }

    @Override
    public List<User> queryAllUsers() {
        return jdbcTemplate.query("select * from User", new UserMapper());
    }

    @Override
    public void insertUser(User user) {
        jdbcTemplate.update("insert into User(name, password) values (?,?)", user.getName(), user.getPassword());
    }

    @Override
    public void updateUser(User user) {
        // 和insert類似,略
    }

    @Override
    public void deleteUser(User user) {
        // 和insert類似,略
    }
}

接下來需要將UserService配置爲一個Bean:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/test"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <!-- 省略一些連接池配置 -->
</bean>

<bean id="userService" class="hnu.yhc.jdbc.UserServiceImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

使用時,只需要從ApplicationContext#getBean方法,獲取userService即可。

可見Spring JDBC的核心就是JdbcTemplate。

2.JdbcTemplate的使用

從代碼中可以看到,它是通過傳入一個DataSource來實現初始化的。DataSource顧名思義,是數據源,定義了數據驅動、URL、帳號密碼等信息。

JdbcTemplate構造方法做了兩件事:1.賦值,將傳入的dataSource引用賦給成員變量;2.主動調用afterPropertiesSet方法(儘管JdbcTemplate本身也是一個InitializingBean)

afterPropertiesSet方法會做兩個檢查:1.dataSource是否爲空,是則拋出異常;2.如果dataSource不爲空,則檢查是否懶加載,如果不是(默認會懶加載),則初始化SQLExceptionTranslator爲SQLErrorCodeSQLExceptionTranslator,用來解析SQL錯誤信息。

2.1 數據查詢

在示例代碼中,使用了queryForObject和query兩種,其實都是使用的query方法,並且會將傳入的RowMapper封裝爲RowMapperResultSetExtractor,最終調用的query方法簽名爲:query(final String sql, final ResultSetExtractor<T> rse)。

內部主要做了兩件事,一是創建了一個回調,內部會執行SQL語句,獲得結果集,然後使用ResultSetExtractor解析出實體對象,二是調用execute方法運行這個回調。execute方法有很多版本,這裏以execute(StatementCallback<T> action)爲例。

首先從數據源獲取一個連接:

Connection con = DataSourceUtils.getConnection(this.obtainDataSource());

實際調用DataSourceUtils#doGetConnection,根據事務處理需求做了不同處理。如果開啓了事務同步,那麼會用一個ConnectionHolder存儲當前連接,同一事務內再次獲取連接時,就可以從Holder直接獲取引用了。

接下來就是熟悉的con.createStatement()創建SQL語句對象,並且會設置一些屬性,比如超時時間、最大行數等。

接下來就是調用回調的doInStatement方法實際執行語句,例如:

public T doInStatement(Statement stmt) throws SQLException {
    ResultSet rs = null;
    Object var3;
    try {
        rs = stmt.executeQuery(sql);
        var3 = rse.extractData(rs);
    } finally {
        JdbcUtils.closeResultSet(rs);
    }
    return var3;
}

executeQuery都很熟悉了,主要來看extractData方法,RowMapperResultSetExtractor的實現如下:

    public List<T> extractData(ResultSet rs) throws SQLException {
        List<T> results = this.rowsExpected > 0 ? new ArrayList(this.rowsExpected) : new ArrayList();
        int var3 = 0;

        while(rs.next()) {
            results.add(this.rowMapper.mapRow(rs, var3++));
        }

        return results;
    }

其實就是遍歷結果集,對每一行都傳入RowMapper的mapRow方法,解析爲實體對象,然後塞進List。對於queryForObject這樣返回單個對象的方法,只要返回列表中第一個對象即可。

如果過程中出現異常,就會用ExceptionTranslator將SQLException翻譯爲DataAccessException再拋出。

最終會調用DataSourceUtils.releaseConnection方法將連接釋放,其實就是將connectionHolder中的連接引用數減一,直到引用數爲0,才嘗試真正釋放連接。

2.2 數據持久化

JdbcTemplate中,數據插入、更新和刪除,都是用update方法處理的。示例代碼中使用的是update(String sql, @Nullable Object... args)版本。最終調用execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)方法。

在傳遞過程中,首先是將sql語句封裝爲PreparedStatement,將傳入的可變長度參數封裝爲ArgumentPreparedStatementSetter,它的作用就是按順序把參數傳給PreparedStatement,所以就不用一個個setInt、setString...了,如果沒有傳入參數類型,會根據參數的TypeName做推斷。

然後類似於查詢,會將語句和結果處理邏輯都封裝進PreparedStatementCallback#doInPreparedStatement方法,execute方法的核心邏輯也差不多,最終也是靠PreparedStatement的executeUpdate方法完成數據發送。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章