目 录CONTENT

文章目录

spring-jdbc

FatFish1
2025-01-18 / 0 评论 / 0 点赞 / 77 阅读 / 0 字 / 正在检测是否收录...

spring提供的jdbc框架包括JdbcTemplate和NamedParameterJdbcTemplate,这两个的区别就是NamedJdbcTemplate支持以参数名的形式作为占位符构建参数列表,防错能力更强,而JdbcTemplate就是与PreparedStatement一样,使用"?"作为占位符

JdbcTemplate

是spring基于JDBC做了深度封装提供的一个框架,只需要把datasource注册到jdbcTemplate中即可

核心方法

  • execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;

  • update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;

  • query方法及queryForXXX方法:用于执行查询相关语句;

  • call方法:用于执行存储过程、函数相关语句

方法按照返回结果可以分为几大类:

单个增删改查

// update操作单个增删改
int update(String sql,Object[] args)
int update(String sql,Objcet... args)

// 单列简单查询
T queryForObjcet(String sql,Class<T> type)
T queryForObjcet(String sql,Object[] args,Class<T> type)
T queryForObjcet(String sql,Class<T> type,Object... arg)

批量增删改

// batchUpdate批量增删改
int[] batchUpdate(String[] sql)
int[] batchUpdate(String sql,List<Object[]>)

批量查

查询单行多列

// 以Map形式返回
Map queryForMap(String sql)
Map queryForMap(String sql,Objcet[] args)
Map queryForMap(String sql,Object... arg)

// 通过RowMapper封装成复杂类型
T queryForObject(String sql,RowMapper<T> mapper)
T queryForObject(String sql,object[] args,RowMapper<T> mapper)
T queryForObject(String sql,RowMapper<T> mapper,Object... arg)

查询有特定类型的特定列

一般我们的sql只查一列,且是一些相对简单的特定类型,例如String、Integer等

// 获取多个
List<T> queryForList(String sql,Class<T> type)
List<T> queryForList(String sql,Object[] args,Class<T> type)
List<T> queryForList(String sql,Class<T> type,Object... arg)

查询多列并以map形式封装

如果sql查询的是多列,一般采用map的形式封装,类型为List<Map<String, Object>>,其中Map::key为列名,Map::value为数据,需要自行转换,List中每一个Map数据为数据库中的一行

//获取多个
List<Map<String,Object>> queryForList(String sql)
List<Map<String,Object>> queryForList(String sql,Obgject[] args)
List<Map<String,Object>> queryForList(String sql,Obgject... arg)

这也是最常用的一种

查询多列并封装成复杂对象

如果想跳过自行封装的过程,也可以通过实现RowMapper接口实现对PO的映射,从而让JdbcTemplate帮我们完成封装操作

//获取多个
List<T> query(String sql,RowMapper<T> mapper)
List<T> query(String sql,Object[] args,RowMapper<T> mapper)
List<T> query(String sql,RowMapper<T> mapper,Object... arg)

定义RowMapper的案例如下:

//自定义
public class StudentRowMapper implements RowMapper<Student> {

    @Override
    public Student mapRow(ResultSet rs, int i) throws SQLException {
        Student student = new Student();
        student.setName(rs.getString("name"));
        student.setGender(rs.getString("gender"));
        student.setEmail(rs.getString("email"));
        return student;
    }
}

//使用
String sql = "select name, gender, email from test_student where name = ?";
jdbcTemplate.queryForObject(sql, new Object[]{name}, new StudentRowMapper());

需要注意的是,这些简单的接口提供的仅仅是普通的Statement执行,因为底层的核心方法JdbcTemplate#execute都是通过Connection#createStatement创建的普通Statement,参考Spring-jdbc源码部分补链接

回调类

如果spring-jdbc的框架这么菜只能提供普通Statement执行那就太小看spring了

实际上底层执行都是通过回调函数完成的结果分析,spring同时还提供了创建Statement创建回调和结果回调,让开发者可以自行实现PreparedStatement和CallabeStatement的执行逻辑

预编译回调

通过预编译回调,可以使用户获取Connection自行创建PrepareStatement或CallableStatement,必须搭配结果处理回调函数使用,因为JdbcTemplate#execute初始的实现只能处理Statement的结果

  public void testPpreparedStatement1() {
        int count = jdbcTemplate.execute(new PreparedStatementCreator() {
            public java.sql.PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                return conn.prepareStatement("select count(*) from user");
            }
        }, new PreparedStatementCallback<Integer>() {
            public Integer doInPreparedStatement(java.sql.PreparedStatement pstmt)
                    throws SQLException, DataAccessException {
                pstmt.execute();
                ResultSet rs = pstmt.getResultSet();
                rs.next();
                return rs.getInt(1);
            }
        });
        System.out.println(count);
    }

预编译语句参数占位符回调

  • PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值

  • BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小

  public void testPreparedStatement2() {
        String insertSql = "insert into user(user_name) values (?)";
        int count = jdbcTemplate.update(insertSql, new PreparedStatementSetter() {
            public void setValues(PreparedStatement pstmt) throws SQLException {
                pstmt.setObject(1, "mmNN");
            }
        });
        Assert.assertEquals(1, count);
        String deleteSql = "delete from user where user_name=?";
        count = jdbcTemplate.update(deleteSql, new Object[] { "mmNN" });
        Assert.assertEquals(1, count);
    }

但是对于JdbcTemplate一个比较不好的点是:必须按顺序传入实际参数,且必须使用Object[]数组形式,用起来比NamendJdbcTemplate复杂一点

自定义功能回调

可以让用户获取JDBC中的一些层级,做一些操作

JdbcTemplate支持使用以下回调类:

  • ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作

  • StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作

  • PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量操作

  • CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作

结果处理回调

除了非常场景的RowMapper接口,还有RowCallbackHandler,写起来直接使用内部类,简单一些

public void testResultSet2() {
    jdbcTemplate.update("insert into user(user_name) values('name5')");
    String listSql = "select * from user";
    final List result = new ArrayList();
    jdbcTemplate.query(listSql, new RowCallbackHandler() {
        public void processRow(ResultSet rs) throws SQLException {
            Map row = new HashMap();
            row.put(rs.getInt("user_id"), rs.getString("user_name"));
            result.add(row);
        }
    });
    Assert.assertEquals(1, result.size());
    jdbcTemplate.update("delete from user where user_name='name5'");
}

NamedParameterJdbcTemplate

看它的名字,有个NamedParameter,叫做已经命名了的参数

实际上它叫具名参数JdbcTemplate

具名参数是相对于传统JDBC中以?作为占位符的形式,改为采用":name"的形式作为占位,这样占位的好处就是,设置参数值是不需要考虑顺序,更易于维护,也具备更高的可读性

因此NamedParameterJdbcTemplate就是在JdbcTemplate的基础上支持具名参数,且支持JdbcTemplate所有能力的一种更高级封装

比JdbcTemplate好写的一点在于,NamedParameterJdbcTemplate的参数位支持map形式,无需入参,也传个空map进去即可,例如:

// JdbcTemplate
T queryForObject(String sql,RowMapper<T> mapper)
T queryForObject(String sql,object[] args,RowMapper<T> mapper)
T queryForObject(String sql,RowMapper<T> mapper,Object... arg)

// NamedParameterJdbcTemplate
T queryForObject(String sql, Map<String, ?> paramMap, Class<T> requiredType)

NamedParameterJdbcTemplate框架提供了一个参数设置接口:SqlParameterSource,或者也可以直接使用Map作为参数的封装

核心方法与JdbcTemplate类似,不额外总结,这里专门对比下传统JDBC和NamedParameterJdbcTemplate在批量数据处理参数上的差异:

传统JDBC需要通过多次循环设置参数,比较复杂:

connection = dataSource.getConnection();
List<PreparedStatement> preparedStatements = new ArrayList<>();
String sql = "insert into t_student (name, age, class) values (?, ?, ?);"
try{
    connection.setAutoCommit(false);
    String sql = String.format(INSERT_UDR, udrInfoTableName);
    PreparedStatement ps = connection.prepareStatement(sql);
    int[] affectRows;
    for (Student student : students) {
        int index = 0;
        ps.setString(++index, student.getName);
        ps.setInt(++index, student.getAge);
        ps.setInt(++index, student.getClass);
    }
    preparedStatements.add(ps);
    affectRows = ps.executeBatch();
    connection.commit(true);
    connection.setAutoCommit(true);
} catch (Exception e) {
    connection.rollback();
    throw e;
} finally {
    // 对connection和所有的preparedStatement批量做close操作
}

这种写法如果参数多了,还是非常可怕的

采用NamendParameterJdbcTemplate的写法如下:

String sql = "insert into t_student (name, age, class) values (:name, :age, :class);"
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dateSource);
MapSqlParameterSource[] params = new MapSqlParameterSource[students.size()];
int index = 0;
for (Student student : students) {
    params[index] = new MapSqlParameterSource().addValue("name", student.getName)
        .addValue("age", student.getAage)
        .addValue("class", student.getClass);
    ++index;
}
params.batchUpdate(sql, params);

0

评论区