Spring源码学习(九):Spring JDBC

目录

1.使用

2.JdbcTemplate的使用

2.1 数据查询

2.2 数据持久化


Java程序员对JDBC应该都不陌生,使用起来也很简单,只要用DriverManager开启一个Connection,就可以创建Statement或PreparedStatement,然后调用SQL语句,获得ResultSet或将数据修改持久化。但是JDBC使用起来也有很多不便,例如:SQL语句编写在程序代码中,不方便维护;参数传入Statement以及从ResultSet取出数据很麻烦,冗余代码多。

1.使用

假设有一个用户表User,结构如下:

字段数据类型说明
idINTEGER序号,自增主键
nameVARCHAR用户名
passwordVARCHAR密码

首先可以创建一个如下的实体类:

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方法完成数据发送。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值