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);
评论区