Spring - JdbcTemplate

更新时间:2023-10-03 21:07:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Spring JdbcTemplate

JdbcTemplate的API明显地分割为几个部分: 1. Query

用于从数据库查询数据。该部分的API具有queryForXXX形式,或query方法。每个方法均有3-6个重载版本,query()方法甚至有16个重载版本。具体来说,Query部分的API有:queryForInt, queryForList, queryForLong, queryForMap, queryForObject, queryForRowSet以及query方法。 ? queryForInt、queryForLong

queryForInt和queryForLong这两个方法非常相似,只是它们的返回值上存在差异。以queryForLong为例:

long queryForLong(String sql)

long queryForLong(String sql, Object[] args)

long queryForLong(String sql, Object[] args, int[] argTypes)

其中,sql为需要执行的SQL语句,args对应了sql中的参数,argTypes则是对应sql中各个参数的类型。值得注意的是,这些方法要求数据库的返回值只能为一个1行1列的long型数据(超过或者SQL查询语句没有相应返回),否则将抛出IncorrectResultSizeDataAccessException异常。 示例1:查询customer表中的用户数 public long getCustomerCount(){ } return this.jdbcTemplate.queryForLong(\); 示例2:查询customer表中名字含有in的用户数 public long getCustomerCount2(){ } return this.jdbcTemplate.queryForLong( \COUNT(*) FROM customer WHERE name LIKE ?\, new Object[]{\}); 在后台的数据库中,name字段被声明为VARCHAR(10)。因此,这条语句相当于:

return this.jdbcTemplate.queryForLong(

\,

new Object[]{\}, new int[]{Types.VARCHAR});

如果错误地指定了相应字段的类型,将可能导致异常。例如这里将Types.VARCHAR错误地指定为java.sql.Types.INTEGER,将招致如下异常:

org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [SELECT COUNT(*) FROM customer WHERE name LIKE ?]; SQL state [S1000]; error code [0]; Cannot convert class java.lang.String to SQL type requested due to java.lang.NumberFormatException - For input string: \nested exception is java.sql.SQLException: Cannot convert class java.lang.String to SQL type requested due to java.lang.NumberFormatException - For input string: \java.sql.SQLException: Cannot convert class java.lang.String to SQL type requested due to java.lang.NumberFormatException - For input string: \...... 可以看到,由于无法将%in%转换为Types.INTEGER,程序出现了异常。

? queryForObject

queryForObject函数共有6个重载版本: Object queryForObject(String sql, Class requiredType) Object queryForObject(String sql, Object[] args, Class requiredType) Object queryForObject(String sql, Object[] args, int[] argTypes, Class requiredType) Object queryForObject(String sql, Object[] args, int[] argTypes, RowMapper rowMapper) Object queryForObject(String sql, Object[] args, RowMapper rowMapper) Object queryForObject(String sql, RowMapper rowMapper) 这6个API又明显地分为两个部分:前3个为一部分,后3个为一部分。参数sql,args,argsType的含义与前面介绍的一致。参数requiredType表明了相应函数的返回值类型。对于该参数来说是实际上是有限制的,比如它不能被设定为自定义类型,例如这里的Customer类。如果你需要将返回值设定为自定义类型,你需要使用后3个API来完成。RowMapper接口的功能就是将结果集中的某行转换为一个Object。

与queryForInt/queryForLong一样,前3个API都被期望返回一个1行1列的数据,该行数据就是结果数据(直接为结果),否则会抛出异常。因而,参数requiredType的值必须是一个对应数据库中的表字段对应的Java类型。例如:MySQL数据库中的VARCHAR类型对应着java.lang.String, BIGINT对应着java.lang.Long等等。对于后3个API,由于使用的策略是将结果集中的某一行转换为一个Object,这要求返回结果须为单一的一行(不限列的数量),否则会抛出异常。 示例1:获取id为1的用户的姓名 public String getCustomerName(){ } return (String)this.jdbcTemplate.queryForObject( \, new Object[]{ Long.valueOf(1L), }, new int[]{Types.INTEGER, }, String.class); 如果在上面的程序中,将id的值从1(数据库中存在对应记录)该为-1(数据库中不存在相应的记录),那么程序将会抛出异常:

org.springframework.dao.IncorrectResultSizeDataAccessException

如果你不期望如此,可以使用后面要介绍的queryForList来解决这个问题。 示例2:获取id为1的用户的性别

public Gender getCustomerGender(){ return ((Short)this.jdbcTemplate.queryForObject( \, Short.class) == 0) ? Gender.FEMALE : Gender.MALE; } 由于数据库中customer表的gender列被创建为tinyint(1) default '1',因此我们可以使用Short类来处理。当然,这里还可以用Integer来替代Short:

return ((Integer)this.jdbcTemplate.queryForObject(

\,

Integer.class) == 0) ? Gender.FEMALE : Gender.MALE;

这也不会带来任何错误。甚至我们还可以使用queryForInt/queryForLong来实现:

return (this.jdbcTemplate.queryForInt(

\gender FROM customer WHERE id=1\) == 0) ? Gender.FEMALE

: Gender.MALE;

示例3:获取id为1的用户对象 public Customer getCustomer() { return (Customer)this.jdbcTemplate.queryForObject( \, new Object[]{ Long.valueOf(1L), }, new RowMapper(){ public Object mapRow(ResultSet rs, int index) throws SQLException { Customer customer = new Customer();//构造Customer对象 customer.setId(rs.getInt(\)); customer.setName(rs.getString(\)); customer.setGender((rs.getShort(\) == (short)0) ? Gender.FEMALE : customer.setCellPhoneNo(rs.getString(\)); Gender.MALE); } return customer; });

RowMapper很简单,不是吗?

意:使用queryForObject方法的时候,需要特别注意。你可能期望对于你的查询,如果数据库中没有对应的记录返回null就好。但是,JdbcTemplate并非如此,它会抛出异常。 ? queryForList

方法queryForList一共有6个重载版本:

List queryForList(String sql) List queryForList(String sql, Class elementType) List queryForList(String sql, Object[] args) List queryForList(String sql, Object[] args, Class elementType) List queryForList(String sql, Object[] args, int[] argTypes) List queryForList(String sql, Object[] args, int[] argTypes, Class elementType)

参数sql, args, argTypes的含义和前面的含义相同,elementType是返回值的每一个元素的类型。对于elementType的取值与前面的requiredType是相同的。如果不指定的话,返回值将会被设定为一个a list of maps,即返回值中的每个元素都是一个map对象 。该

map中的entry的key为查询中的字段名,value为该字段下的值。 示例1:获取所有所有用户名 @SuppressWarnings(\) public List getCustomerNames(){ } return this.jdbcTemplate.queryForList(\, String.class); 示例2:获取所有男性的ID,姓名和手机号

@SuppressWarnings(\) public List getCustomerInfo(){ return this.jdbcTemplate.queryForList( \); } 可以看到queryForList方法并没有指定elementType参数,因此Spring默认地赋值给它为ListOrderedMap,这就是前面提到的map。如果将这个结果打印出来的话,其输出为: [{id=1, name=Wang Jin, cellphoneno=13540186833}, {id=2, name=Wu Dong, cellphoneno=13540186834}] 根据当前数据库的内容,该List中包含两份数据,该输出证实了这一点。 示例3:获取名字为Lucky的用户的手机号 @SuppressWarnings(\) public List getLuckyCellphoneno(){ } return this.jdbcTemplate.queryForList( \, new Object[]{\}); 该段程序逻辑上并不复杂。但是有两点需要说明: 1). 当前数据库中并不存在名字为Lucky的用户,但是,与queryForObject不同的是,程序并不会抛出异常。相反,程序返回的是一个size为0的List。

2). 尽管在逻辑上,返回的List中的每一个元素都应该是一个String对象,换句话说,返回的List应该是List。但是由于我们没有显示地指定elementType参数,Spring会按照默认的情况进行处理,即视返回值为a list of maps。从而,返回值为List类型。

另一方面,Spring似乎提供了”特别的支持”。对于示例3给定的例子,我们可以直接将返回值的类型标记为List, Spring也不会有丝毫的抱怨,照样给出期望的结果。例如,我们获取名字为Wang Jin的用户的id(从数据库中可以看到其值为1): @SuppressWarnings(\) public List getWangId(){ return this.jdbcTemplate.queryForList(\id FROM customer WHERE name=?\, } new Object[]{\});

这样的程序也不会导致任何的错误,程序还是欢喜地给出了结果:[{id=1}]。但是,问

题是这并不是期望的输出!我们期望的输出是[1]。出现这样问题的原因是,queryForList方法是在泛型出现之前便已经存在了,而它的返回值是一个不带任何元素类型的(似乎该种类型的

List/Map被称为Raw type, 题外话?)。正是因为如此,才会造成前面的困扰。下面再给出一个类似的例子:

#1 List listString = new ArrayList(); #2 listString.add(\); #3 listString.add(\); #4 #5 List list = listString; #6 List listInteger = list; #7 System.out.println(listInteger); 这个程序片断首先构建了一个List,并且在第5行将其转换为了一个Raw type的List,实际上这抹去了listString的信息。在第6行的时候,将无元素类型信息的list对象转换为一个List对象。listString,listInteger和list这三个引用都指向了同一个对象。由于Raw type的List的介入,使得指向的这个对象(ArrayList)丢失了元素类型信息,即ArrayList变得与ArrayList无异。但是,由于语法上的保证,listString还是只能向其中加入String对象,listInteger还是只能向其中加入Integer对象。毋庸置疑的,listString/listInteger对于元素的添加仍然会影响到它们指向的对象:

listInteger.add(1); //这会导致向ArrayList中添加一个Integer对象 listString.add(“c”); //这会导致向ArrayList中添加一个String对象 这两个add方法都是OK的,因为它们指向的那个ArrayList对象已经没有了型别意识,这些对象会被当作Object对象而添加到其中。

PS: 表达式listString==list以及listInteger==list都会返回true,而listString==listInteger则会出现编译期错误。泛型的引入,如同Auto-boxing的引入一样,破坏了==运算符的传递性。

结论 在使用queryForList的时候如果你期望访问的List中的元素为某一个特定的类型,那么请指定明确的elementType,否则将会返回a list of maps。

示例3:获取id为1的用户对象 @SuppressWarnings(\) public Customer getCustomerById(long id) { List customers = this.jdbcTemplate.queryForList( \, ListOrderedMap lom = customers.get(0); Customer customer = new Customer(); customer.setId(id); customer.setName((String)lom.get(\)); customer.setGender(((Boolean)lom.getValue(1)) ? Gender.MALE : Gender.FEMALE); new Object[]{ Long.valueOf(id), }); if(customers.size() == 0) return null; if(customers.size() > 1) throw new MoreThanOneCustomerException(\than one customer with id(\ + id + \);

PreparedStatementCreator接口用于创建PerparedStatement对象,PreparedStatemetSetter接口则用于设置PreparedStatemet对象,它们定义的方法分别如下: PreparedStatementCreator PreparedStatement createPreparedStatement(Connection con) throws SQLException PreparedStatementSetter void setValues(PreparedStatement ps) throws SQLException JdbcTemplate的所有方法只要出现了二者之一,便会使用PreparedStatement来处理。 ? Object query(String sql, ResultSetExtractor rse)

Object query(String sql, Object[] args, int[] argTypes, ResultSetExtractor rse)

Object query(String sql, Object[] args, ResultSetExtractor rse)

很显然,这三个query方法有相当程度上的相似性,主要取决于是否要指定参数以及参数的SQL类型。

示例1 :通过id获得用户对象 public Customer getCustomerById(final long id) { return (Customer)this.jdbcTemplate.query( \, new Object[]{ Long.valueOf(id)}, new ResultSetExtractor(){ public Object extractData(ResultSet rs) throws SQLException, DataAccessException { } }); } if(!rs.next()) //从原始的ResultSet中提取数据 Customer customer = new Customer(); customer.setId(id); customer.setName(rs.getString(\)); customer.setGender((rs.getBoolean(\)) ? Gender.MALE : Gender.FEMALE); return null; customer.setCellPhoneNo(rs.getString(\)); return customer; 从这里可以看到,ResultSetExtractor接口为程序访问ResultSet提供了一条绿色通道,当然这也是最原始的访问方式。事实上,ResultSetExtractor中的ResultSet并不要求只能返回至多一条记录,任意记录数都是可以接受的。这与传统的JDBC编程是相同的。

return (Customer)this.jdbcTemplate.query( \, new Object[]{ false }, //返回性别为女的用户 new ResultSetExtractor(){ public Object extractData(ResultSet rs) throws SQLException, DataAccessException { if(!rs.next()) //这个ResultSet是不只一条记录的,当前数据库中有2条记录 }); } return null; Customer customer = new Customer(); customer.setId(id); customer.setName(rs.getString(\)); customer.setGender((rs.getBoolean(\)) ? Gender.MALE : Gender.FEMALE); customer.setCellPhoneNo(rs.getString(\)); return customer; 这个例子再次证明ResultSetExtractor提供的ResultSet和传统方式下进行查询得到的结果集对象是完全相同的,只是你不要关闭它(不过在当前版本中,即使在程序中调用rs.close()也不会出现任何问题)。

? Object query(PreparedStatementCreator psc, PreparedStatementSetter pss, ResultSetExtractor rse)

Object query(PreparedStatementCreator psc, ResultSetExtractor rse) Object query(String sql, PreparedStatementSetter pss, ResultSetExtractor rse)

这三个方法是以PreparedStatement方式进行工作的。作为一个示例,我们仍然使用前面的获取第一个性别为女的用户为例: getFirstFemaleCustomer public Customer getFirstFemaleCustomer () { return (Customer)this.jdbcTemplate.query( new PreparedStatementCreator(){ public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { return connection.prepareStatement( \); }}, new PreparedStatementSetter(){ }, new ResultSetExtractor(){ public Object extractData(ResultSet rs) throws SQLException, DataAccessException { public void setValues(PreparedStatement ps) throws SQLException { } ps.setBoolean(1, false); if(!rs.next()) Customer customer = new Customer(); customer.setId(id); return null; customer.setName(rs.getString(\)); customer.setGender((rs.getBoolean(\)) ? Gender.MALE : Gender.FEMALE); customer.setCellPhoneNo(rs.getString(\)); } } rs.close(); return customer; }); 这段代码使用了Object query(PreparedStatementCreator psc, PreparedStatementSetter pss, ResultSetExtractor rse)形式,即实现了PreparaedStatementCreator、PreparedStatementSetter和ResultSetExtractor这三个接口。使用其余两种方式是类似的,这里不再赘述。

对于返回值为List的query方法,也可以分为两部分。

? List query(PreparedStatementCreator psc, RowCallbackHandler rch)

List query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch) List query(String sql, Object[] args, RowCallbackHandler rch)

List query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch) List query(String sql, RowCallbackHandler rch)

这些方法都包含一个RowCallbackHandler接口,你可以在该接口中定义你用来处理当前行的方法。

示例1:获取所有男性用户,并在获取的过程中打印用户的名字 public List getAllMaleCustomers(){ } return this.jdbcTemplate.query( \, new Object[]{ true, }, new RowCallbackHandler(){ public void processRow(ResultSet rs) throws SQLException { System.out.println(rs.getString(\)); }); }

当query方法在处理结果集中的每一行的时候都会回调RowCallbackHandler,就在这个时候我们将打印用户名字的代码添加了进来。如果运行该段程序,可以看到用户的名字是正确打印出来了的。然而,返回的List却是一个null。 打开Javadoc,发现该方法的返回值部分的说明如下: Returns: the result List in case of a ResultReader, or null else

很显然,我们自己定义的内部类不是一个ResultReader对象,因此List的值为null。利用Eclipse的Open Type Hierarchy功能,可以看到在Spring中实现了ResultReader的类共有三个,其中包括2个内部类和RowWrapperResultReader:

现在来使用RowMapperResultReader类:

@SuppressWarnings(\) public List getAllMaleCustomers(){ } return (List)this.jdbcTemplate.query( \, new Object[]{ true, }, new RowMapperResultReader(new RowMapper(){ public Object mapRow(ResultSet rs, int index) throws SQLException { } Customer customer = new Customer(); customer.setName(rs.getString(\)); customer.setCellPhoneNo(rs.getString(\)); return customer; customer.setId(rs.getInt(\)); customer.setGender((rs.getShort(\) == (short)0) ? Gender.FEMALE : Gender.MALE); })); 这里的ResultSet也是游标指定在当前行的数据集。因此,在mapRow中完成打算完成的功能也会收到在ResultReader/RowCallbackHandler的相应方法中完成相同功能的效果。也正是因为如此,在RowCallbackHandler接口作参数的地方多使用RowMapperResultReader来实现。

? List query(PreparedStatementCreator psc, RowMapper rowMapper)

List query(String sql, Object[] args, int[] argTypes, RowMapper rowMapper) List query(String sql, Object[] args, RowMapper rowMapper)

List query(String sql, PreparedStatementSetter pss, RowMapper rowMapper) List query(String sql, RowMapper rowMapper)

这几个方法相对简单:RowMapper将ResultSet中的某一行影射为一个对象,该对象最终会被添加到作为结果的List对象中。 @SuppressWarnings(\) public List getAllMaleCustomers(){ return (List)this.jdbcTemplate.query( \, new Object[]{ true, }, new RowMapper(){ } public Object mapRow(ResultSet rs, int index) throws SQLException { } Customer customer = new Customer(); customer.setName(rs.getString(\)); customer.setGender((rs.getShort(\) == (short)0) ? customer.setCellPhoneNo(rs.getString(\)); return customer; customer.setId(rs.getInt(\)); Gender.FEMALE : Gender.MALE); }); 这段程序的输出与使用RowMapperResultReader的那段代码相同。 2. Update

JdbcTemplate使用的update方法可以在其中使用的SQL语句包括UPDATE,CREATE、INSERT等。JdbcTemplate提供的API中与Update相关的有: int update(PreparedStatementCreator psc) int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder) protected int update(PreparedStatementCreator psc, PreparedStatementSetter pss) int update(String sql) int update(String sql, Object[] args) int update(String sql, Object[] args, int[] argTypes) int update(String sql, PreparedStatementSetter pss) int[] batchUpdate(String[] sql) int[] batchUpdate(String sql, BatchPreparedStatementSetter pss) 上表的下半部分的两个方法是与批量更新相关的,上半部分则是通常情况下的更新操作。其中, int update(String sql) ,int update(String sql, Object[] args),int

update(String sql, Object[] args, int[] argTypes) ,int update(String sql, PreparedStatementSetter pss)最容易理解。

示例1: 添加用户对象

public void addCustomer(final Customer customer){ int retVal = this.jdbcTemplate.update( \, new PreparedStatementSetter(){ public void setValues(PreparedStatement ps) throws SQLException { } }); if(retVal > 0) ps.setString(1, customer.getName()); ps.setBoolean(2, (Gender.FEMALE == customer.getGender()) ? false : true); ps.setString(3, customer.getCellPhoneNo());

本文来源:https://www.bwwdw.com/article/a6wd.html

Top