JdbcTemplate(NamedParameterJdbcTemplate)详解
Spring对数据库的操作在jdbc上面做了深层次的封装,提供了JdbcTemplate模板。
传统Jdbc API与Spring jdbcTemplate比较:
//JDBC API Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery("select count(*) from COUNT student") if(resultSet.next()){ Integer count = resultSet.getInt("COUNT"); } //JDBC Template Integer count = jdbcTemplate.queryForObject("select count(*) from student",Integer.class);
一、JdbcTemplate主要几类方法
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句(了解即可)。
二、JdbcTemplate常用API
//update操作单个增删改 int update(String sql,Object[] args) int update(String sql,Objcet... args) //batchUpdate批量增删改 int[] batchUpdate(String[] sql) int[] batchUpdate(String sql,List<Object[]>) //单个简单查询 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) //获取多个 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):
//获取单个 Map queryForMap(String sql) Map queryForMap(String sql,Objcet[] args) Map queryForMap(String sql,Object... arg) //获取多个 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)
查询复杂对象(封装为实体对象):
Spring JdbcTemplate是通过实现org.springframework.jdbc.core.RowMapper这个接口来完成对entity对象映射。
//获取单个 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) //获取多个 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)
pring JDBC中目前有两个主要的RowMapper实现,使用它们应该能解决大部分的场景了:SingleColumnRowMapper和BeanPropertyRowMapper。
SingleColumnRowMapper:返回单列数据
BeanPropertyRowMapper:当查询数据库返回的是多列数据,且需要将这些多列数据映射到某个具体的实体类上。
//示例: String sql = "select name from test_student where id = ?"; jdbcTemplate.queryForObject(sql, new Object[]{id}, new SingleColumnRowMapper<>(String.class)); String sql = "select name, gender from test_student where name = ?"; jdbcTemplate.queryForObject(sql, new Object[]{name},new BeanPropertyRowMapper<>(Student.class));
定义自己的RowMapper
如果你SQL查询出来的数据列名就是和实体类的属性名不一样,或者想按照自己的规则来装配实体类,那么就可以定义并使用自己的Row Mapper。
//自定义 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支持的回调类
1. 预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句
PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement;
CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement;
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCallback; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) // 关联Spring与Junit @ContextConfiguration(locations = { "classpath:applicationContext.xml" }) // 加载配置spring配置文件 public class AppTest { @Autowired private JdbcTemplate jdbcTemplate; @Test 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); } }
首先使用PreparedStatementCreator创建一个预编译语句,其次由JdbcTemplate通过PreparedStatementCallback回调传回,由用户决定如何执行该PreparedStatement。此处我们使用的是execute方法。以下是我们mysql中常用的插入时返回自增的主键:
public long queryBuildingInfo(Map<String, Object> map) { String sql = "INSERT INTO TS_DEVICE_CHANGE(DEVICE_ID,CHANGE_DEVICE_ID)VALUES(?,?) "; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection con) throws SQLException { // 设置返回的主键字段名 PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, "1"); ps.setString(2, "2"); return ps; } }, keyHolder); // 获取到插入数据生成的ID return keyHolder.getKey().longValue(); }
2. 预编译语句设值回调:用于给预编译语句相应参数设值
PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;
BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小;
import java.sql.PreparedStatement; import java.sql.SQLException; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) // 关联Spring与Junit @ContextConfiguration(locations = { "classpath:applicationContext.xml" }) // 加载配置spring配置文件 public class AppTest { @Autowired private JdbcTemplate jdbcTemplate; @Test 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的int update(String sql, PreparedStatementSetter pss)执行预编译sql,其中sql参数为“insert into user(user_name) values (?) ”,该sql有一个占位符需要在执行前设值,PreparedStatementSetter实现就是为了设值,使用setValues(PreparedStatement pstmt)回调方法设值相应的占位符位置的值。JdbcTemplate也提供一种更简单的方式“update(String sql, Object... args)”来实现设值,所以只要当使用该种方式不满足需求时才应使用PreparedStatementSetter。
3. 自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作
ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作;
StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作;
PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作;
CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作;
4. 结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式
RowMapper:用于将结果集每行数据转换为需要的类型,用户需实现方法mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。
RowCallbackHandler:用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。
ResultSetExtractor:用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;
@Test public void testResultSet1() { jdbcTemplate.update("insert into user(user_name) values('name7')"); String listSql = "select * from user where user_name=?"; List result = jdbcTemplate.query(listSql,new Object[]{"name7"}, new RowMapper<Map>() { public Map mapRow(ResultSet rs, int rowNum) throws SQLException { Map row = new HashMap(); row.put(rs.getInt("user_id"), rs.getString("user_name")); return row; } }); Assert.assertEquals(1, result.size());//查询结果数量为1才向下执行 jdbcTemplate.update("delete from user where user_name='name7'"); }
RowMapper接口提供mapRow(ResultSet rs, int rowNum)方法将结果集的每一行转换为一个Map,当然可以转换为其他类。
@Test 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'"); }
RowCallbackHandler接口也提供方法processRow(ResultSet rs),能将结果集的行转换为需要的形式。
@Test public void testResultSet3() { jdbcTemplate.update("insert into test(name) values('name5')"); String listSql = "select * from test"; List result = jdbcTemplate.query(listSql, new ResultSetExtractor<List>() { public List extractData(ResultSet rs) throws SQLException, DataAccessException { List result = new ArrayList(); while (rs.next()) { Map row = new HashMap(); row.put(rs.getInt("id"), rs.getString("name")); result.add(row); } return result; } }); Assert.assertEquals(0, result.size()); jdbcTemplate.update("delete from test where name='name5'"); }
ResultSetExtractor使用回调方法extractData(ResultSet rs)提供给用户整个结果集,让用户决定如何处理该结果集。
当然JdbcTemplate提供更简单的queryForXXX方法,来简化开发:
//1.查询一行数据并返回int型结果 jdbcTemplate.queryForInt("select count(*) from test"); //2. 查询一行数据并将该行数据转换为Map返回 jdbcTemplate.queryForMap("select * from test where name='name5'"); //3.查询一行任何类型的数据,最后一个参数指定返回结果类型 jdbcTemplate.queryForObject("select count(*) from test", Integer.class); //4.查询一批数据,默认将每行数据转换为Map jdbcTemplate.queryForList("select * from test"); //5.只查询一列数据列表,列类型是String类型,列名字是name jdbcTemplate.queryForList(" select name from test where name=?", new Object[]{"name5"}, String.class); //6.查询一批数据,返回为SqlRowSet,类似于ResultSet,但不再绑定到连接上 SqlRowSet rs = jdbcTemplate.queryForRowSet("select * from test");
四、Spring提供的JDBC模板
- JdbcTemplate:Spring里最基本的JDBC模板,利用JDBC和简单的索引参数查询提供对数据库的简单访问。
- NamedParameterJdbcTemplate:能够在执行查询时把值绑定到SQL里的命名参数,而不是使用索引参数。
- SimpleJdbcTemplate:利用Java 5的特性,比如自动装箱、通用(generic)和可变参数列表来简化JDBC模板的使用。
五、分页查询
使用JdbcTemplate都是使用SQL进行查询的,如果需要分页,则需要对SQL进行修改。下面提供了几种常用数据库的分页SQL拼接的工具类及分页对象:
/** * 分页对象 */ public final class PageBean<T> implements Serializable { //当前页码 private Integer currentPage = 1; //每页显示的记录数 private Integer pageSize = 10; //总记录数 private Integer totalSize = 0; //本页的数据列表 private List<T> recordList; //总页数 private Integer totalPageCount = 0; //开始行号 private Integer startIndex = 0; //页码列表的开始索引 private Integer beginPageIndex; //页码列表的结束索引 private Integer endPageIndex; public PageBean(int currentPage, int pageSize, int totalSize, List<T> recordList) { this.currentPage = currentPage; this.pageSize = pageSize; this.totalSize = totalSize; this.recordList = recordList; //计算beginPageIndex 和 endPageIndex //>>总页数不多于10页,则全部显示 if (totalPageCount <= 10) { beginPageIndex = 1; endPageIndex = totalPageCount; } //总页数多于10页,则显示当前页附近的共10个页码 else { //当前页附近的共10个页码(前4个+当前页+后5个) beginPageIndex = currentPage - 4; endPageIndex = currentPage + 5; //当前面的页码不足4个时,则显示前10个页码 if (beginPageIndex < 1) { beginPageIndex = 1; endPageIndex = 10; } //当后面的页码不足5个时,则显示后10个页码 if (endPageIndex > totalPageCount) { endPageIndex = totalPageCount; beginPageIndex = totalPageCount - 10 + 1; } } } public Integer getCurrentPage() { return currentPage; } public void setCurrentPage(Integer currentPage) { this.currentPage = currentPage; } public Integer getPageSize() { return pageSize; } public void setPageSize(Integer pageSize) { this.pageSize = pageSize; } public Integer getTotalSize() { return totalSize; } public void setTotalSize(Integer totalSize) { this.totalSize = totalSize; } public List<T> getRecordList() { return recordList; } public void setRecordList(List<T> recordList) { this.recordList = recordList; } public Integer getTotalPageCount() { //总页数 totalPageCount = totalSize / pageSize; if (totalSize == 0 || totalSize % pageSize != 0) { totalPageCount++; } return totalPageCount; } public void setTotalPageCount(Integer totalPageCount) { this.totalPageCount = totalPageCount; } public void setStartIndex(Integer startIndex) { this.startIndex = startIndex; } public Integer getStartIndex() { //开始行号=(当前页数-1)x每页显示的条数 return (currentPage - 1) * pageSize; } public Integer getBeginPageIndex() { return beginPageIndex; } public void setBeginPageIndex(Integer beginPageIndex) { this.beginPageIndex = beginPageIndex; } public Integer getEndPageIndex() { return endPageIndex; } public void setEndPageIndex(Integer endPageIndex) { this.endPageIndex = endPageIndex; } }
/** * SQL的分页工具类 */ public final class SqlPageUtil { private SqlPageUtil(){} /** * @MethodName populateMySQLPageQuerySQL * @Description mysql的分页拼接 * @param sqlStr * @param startIndex 起始索引 * @param currentSize 每天显示的记录数 * @Return java.lang.String 拼接后的分页语句 */ public static String populateMySQLPageQuerySQL(String sqlStr, int startIndex, int currentSize){ StringBuffer pageBuffer = new StringBuffer(); if (startIndex == 0 && currentSize <= 0) { pageBuffer.append(sqlStr); } else { pageBuffer.append(sqlStr); pageBuffer.append(" limit " + startIndex + "," + currentSize); } return pageBuffer.toString(); } /** * @MethodName populateOraclePageQuerySQL * @Description oracle的分页拼接 * @param sqlStr * @param startIndex 起始索引 * @param currentSize 每天显示的记录数 * @Return java.lang.String 拼接后的分页语句 */ public static String populateOraclePageQuerySQL(String sqlStr, int startIndex, int currentSize) { StringBuffer pageBuffer = new StringBuffer(); if (startIndex == 0 && currentSize <= 0) { pageBuffer.append(sqlStr); } else { pageBuffer.append(" SELECT ROW_.*, ROWNUM ROWNUM_ FROM ( "); pageBuffer.append(sqlStr); pageBuffer.append(" ) ROW_ "); if (startIndex == 0) { pageBuffer.append(" WHERE ROWNUM <= "); pageBuffer.append(startIndex + currentSize); } else if (currentSize <= 0) { pageBuffer.append(" WHERE ROWNUM > "); pageBuffer.append(startIndex); } else { pageBuffer.insert(0, "SELECT * FROM ( "); pageBuffer.append(" WHERE ROWNUM <= "); pageBuffer.append(startIndex + currentSize); pageBuffer.append(") WHERE ROWNUM_ > "); pageBuffer.append(startIndex); } } return pageBuffer.toString(); } /** * @MethodName populateDB2PageQuerySQL * @Description DB2的分页拼接 * @param sqlStr * @param startIndex 起始索引 * @param currentSize 每天显示的记录数 * @Return java.lang.String 拼接后的分页语句 */ public static String populateDB2PageQuerySQL(String sqlStr, int startIndex, int currentSize) { if (startIndex == 0 ) { return currentSize>1?sqlStr + " fetch first " + currentSize + " rows only": " fetch first 1 row only"; } return "select * from ( select inner2_.*, rownumber() over(order by order of inner2_) as rownumber_ from ( " + sqlStr + " fetch first " + String.valueOf(startIndex+currentSize) + " rows only ) as inner2_ ) as inner1_ where rownumber_ > " + startIndex + " order by rownumber_"; } }