Spring JDBC-使用Spring JDBC访问数据库
2017-09-28 06:02
477 查看
概述
使用Spring JDBC
基本的数据操作
更改数据
返回数据库表的自增主键值
批量更改数据
查询数据
使用RowCallbackHandler处理结果集
使用RowMapperT处理结果集
RowCallbackHandler和RowMapperT的比较
查询单值数据
调用存储过程3种方式
示例源码
Spring JDBC中,仅仅需要做那些和业务相关的DML操作的事儿而将获取资源、Statement创建、释放资源以及异常处理等繁杂乏味的工作交给Spring JDBC.
一般情况下,都是在DAO类中使用JdbcTemplate,JdbcTemplate在XML配置文件中后,在DAO中直接注入引用JdbcTemplate即可.
我们看一个配置文件的例子
在Spring配配置那文件中配置DAO一般分为4个步骤
定义DataSource
定义JdbcTemplate
声明一个抽象的Bean,以便所有的DAO复用配置JdbcTemplate属性的配置(使用注解的方式更加方便)
配置具体的DAO(使用注解的方式更加方便)
其中JdbCTemplate有几个属性可以控制底层JDBC API的属性。
queryTimeout 查询数据的最大超时时间,默认为0 ,表示使用底层JDBC驱动程序的默认设置
fetchSize:设置底层的ResultSet每次从数据库返回的行数,该属性对程序的性能影响较大,如果设置过大,因为一次性载入的数据都会放到内存中,所以内存消耗会很大,反之设置的过小,从数据库读取的次数将增大,也会影响性能。 默认为0 ,表示使用底层JDCB驱动程序的默认设置。 Oracle驱动程序的fetchsize的默认值为10
maxRows:设置底层的ResutlSet从数据库返回的最大行数,默认为0 ,表示使用底层JDBC驱动程序默认的设置
ignoreWarnings :是否忽略SQL的告警信息。默认为true,即所有的告警信息都记录到日志中,如果设置为false,则JdbcTemplate将抛出SQLWarningException
下面我们以示例来实际演示下这些操作
首先我们定义一个抽象的DAO基类, BaseDao。 其中暂时我们只封装了注入JDBC的操作(扩展的话可以将分页等通用的功能抽取到BaseDao中等)
然后我们编写DAO层的代码,简单旗舰,直接将DAO定义成了类。一般来讲将DAO层编写成接口更合适。
由于JdbcTemplate在内部通过PreparedStatement执行SQL语句,所以可以绑定参数的SQL语句,每个“?”占位符可以接受一个参数。
尽量使用可绑定参数的SQL语句,以便数据库可以复用SQL的执行计划,提高数据库的执行效率。 此外,应该在DAO使用类级别的静态常量(final static)定义SQL字符串,不应该在方法内部声明SQL字符串变量,以提高JVM的内存使用效率。
在通过
以下代码仅为演示
配置文件如下(以下的几个操作都加载这个配置文件)
测试类
除了上述两个update方法外,JdbcTemplate还提供了以下几个功能相似的重载方法
public int update(final String sql) 为不带占位符的SQL语句提供的便利方法
public int update(String sql, PreparedStatementSetter pss) PreparedStatementSetter 是一个回调接口,它定义了一个setValues(PreparedStatement ps)接口方法 ,如下所示
PreparedStatement绑定参数时,参数索引从1开始,而非0开始。 第一个参数索引为1,第二个参数索引为2,依次类推。
当然了,还有其他方法 ,需要指出的是,在实际用用中,应该优先考虑不带回调接口的JdbcTemplate方法。没有必要使用那些带有回调接口的方法,因为Spring会在内部自动创建这些回调实例。
com.xgj.dao.transaction.annotationTrans.dao.impl.StudentDaoImpl.java
在实际开发中,我们并不太建议使用表自增键,因为这种方式会让开发变得更加复杂且降低程序的移植性,在应用层中创建主键才是主流的方式,可以使用UUID或者通过一个编码引擎获取主键值。
我们解读下下面两个方法:
多条SQL语句组成一个数组,注意此处的sql语句不能带参数,该方法以批量方式执行这些SQL语句。Spring在内部使用JDBC提供的批量更新API完成操作,如果底层的JDBC Driver不支持批量更新操作,Spring将采用逐条更新的方式模拟批量更新。
BatchPreparedStatementSetter定义了两个方法:
int getBatchSize():指定本批次的大小
void setValues(PreparedStatement ps,int i):为给定的PreparedStatement设置参数
需要注意的是BatchPreparedStatementSetter是一次性地批量提交数据,而不会分批提交,getBatchSize()是整批的大小。所以,如果希望将一个List中的数据通过BatchPreparedStatementSetter批量更新到数据库中,getBatchSize()就应该设置为List的大小。
如果List非常大,希望分多次批量提交,则可分段读取这个大List并暂存到一个小的List中,再将这个小的List通过BatchPreparedStatemetSetter批量保存到数据库中。
Spring会遍历结果集, 对结果集中的每一行调用RowCallbackHandler回调接口处理数据。所以用户无 须 调用ResultSet的next()方法,而只需要定义好如何获取结果行数据的逻辑就可以了。
我们来看个示例
如果需要获取多条记录,依旧可以使用RowCallbackHandler完成任务,只需要稍微调整一下结果集的处理逻辑就可以了。 代码如下
当结果集中没有数据时,并不会抛出异常。只是此时RowCallbackHandle:回调接口 中定义的处理逻辑没有得到调用罢了。
使用
Spring还提供了一个和RowCallbackHandler功能类似的
看下示例
RowCallbackHandler和
从功能上讲,RowCallbackHandler和RowMapper没有太大的区别,它们都是用于定义结果集行的读取逻辑,将ResultSet中的数据映射到对象或者List中 。
RowCallbackHandler接口实现类可以是有状态的,而RowMapper的实现类应该是无状态的。如果RowCallbackHandler实现类是有状态的,用户就不能在多个地方复用,只有无状态的实例都能在不同的地方复用。
比如,Spring有一个RowCallbackHandler的实现类是RowCountCallbackHandler,可以计算结果集行数:
可见RowCountCallbackHandler包含了一个记录结果集行数的状态,在多线程的环境中,如果没有进行特殊的处理,就不能在多个地方复用countCallback实例。
Spring也提供了几个RowMapper实现类,如ColumnMapRowMapper和SingleColumnRowMapper。
ColumnMapRowMapper将结果集中的每一行映射为一个
MapSingleColumnRowMapper将结果集中的某一列映射为一个Object。它们都只是定义了映射逻辑,而没有保持状态。
我们知道,通过JDBC查询返回一个ResultSet结果集时,JDBC并不会一次性将所有匹配的数据都加载到JVM中,而是只返回同一批次的数据(由JDBC驱动程序决定,如Oracle的JDBC驱动程序默认返回10行数据),当通过ResultSet#next()游标流动结果集超过数据范围时,JDBC再获取一批数据。这样以一种“批量化+串行化”的处理方式避免大结果集处理时JVM内存的过大开销。
当处理大结果集数据时,如果使用RowMapper,则虽然获取数据的过程是串行化的,但是结果集中的所有数据最终都会映射并汇总成一个List对象,占用大量的JVM内存,甚至可以直接引发OutOfMemoryException异常。这里应该使用RowCallbackHandler接口,在processRow接口方法内部处理结果集数据。
当使用RowCallbackHandler接口时,如果结果集中没有数据,并不会抛出异常,只是此时RowCallbackHandler回调接口中定义的处理逻辑没有得到调用罢了。
CallProcDemo
CallProcDemoWithSimpleJdbcCall
CallProcDemoWithCallableStatementCreator
使用Spring JDBC
基本的数据操作
更改数据
返回数据库表的自增主键值
批量更改数据
查询数据
使用RowCallbackHandler处理结果集
使用RowMapperT处理结果集
RowCallbackHandler和RowMapperT的比较
查询单值数据
调用存储过程3种方式
示例源码
概述
Spring JDBC是Spring所提供的持久层技术,它的主要目的降低JDBC API的使用难度,以一种更直接、更简洁的方式使用JDBC API。Spring JDBC中,仅仅需要做那些和业务相关的DML操作的事儿而将获取资源、Statement创建、释放资源以及异常处理等繁杂乏味的工作交给Spring JDBC.
使用Spring JDBC
Spring JDBC通过模板和回调机制大大降低了使用JDBC的复杂度。一般情况下,都是在DAO类中使用JdbcTemplate,JdbcTemplate在XML配置文件中后,在DAO中直接注入引用JdbcTemplate即可.
我们看一个配置文件的例子
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --> <context:component-scan base-package="com.xgj.dao.demo"/> <!-- 不使用context命名空间,则需要定义Bean <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:spring/jdbc.properties" /> </bean> --> <!-- 使用context命名空间,同上面的Bean等效.在xml文件中配置数据库的properties文件 --> <context:property-placeholder location="classpath:spring/jdbc.properties" /> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" /> <!-- 配置Jdbc模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /> </beans>
在Spring配配置那文件中配置DAO一般分为4个步骤
定义DataSource
定义JdbcTemplate
声明一个抽象的Bean,以便所有的DAO复用配置JdbcTemplate属性的配置(使用注解的方式更加方便)
配置具体的DAO(使用注解的方式更加方便)
其中JdbCTemplate有几个属性可以控制底层JDBC API的属性。
queryTimeout 查询数据的最大超时时间,默认为0 ,表示使用底层JDBC驱动程序的默认设置
fetchSize:设置底层的ResultSet每次从数据库返回的行数,该属性对程序的性能影响较大,如果设置过大,因为一次性载入的数据都会放到内存中,所以内存消耗会很大,反之设置的过小,从数据库读取的次数将增大,也会影响性能。 默认为0 ,表示使用底层JDCB驱动程序的默认设置。 Oracle驱动程序的fetchsize的默认值为10
maxRows:设置底层的ResutlSet从数据库返回的最大行数,默认为0 ,表示使用底层JDBC驱动程序默认的设置
ignoreWarnings :是否忽略SQL的告警信息。默认为true,即所有的告警信息都记录到日志中,如果设置为false,则JdbcTemplate将抛出SQLWarningException
基本的数据操作
数据库的增删改查(CRUD)及存储过程调用是最常见的数据库操作,JdbcTemplate提供了众多的方法,通过JdbcTemplate可以用简单的方法完成这些数据操作。下面我们以示例来实际演示下这些操作
更改数据
JdbcTemplate提供了若干个update方法,允许对数据表记录记录进行更改和删除操作。首先我们定义一个抽象的DAO基类, BaseDao。 其中暂时我们只封装了注入JDBC的操作(扩展的话可以将分页等通用的功能抽取到BaseDao中等)
package com.xgj.dao.basicOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; public abstract class BaseDao { public JdbcTemplate jdbcTemplate; // 注入JdbcTemplate实例 @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
然后我们编写DAO层的代码,简单旗舰,直接将DAO定义成了类。一般来讲将DAO层编写成接口更合适。
package com.xgj.dao.basicOperation.insertUpdateAndDelete; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import com.xgj.dao.demo.BaseDao; /** * * * @ClassName: ArtisanDao * * @Description: @Repository标注的DAO * * @author: Mr.Yang * * @date: 2017年9月18日 下午4:19:06 */ @Repository public class ArtisanDao extends BaseDao { private static final String sql = "insert into artisan_user(user_name,password) values(?,?)"; /** * * * @Title: addSingleArtisan * * @Description: 增加一个Artisan * * @param artisan * * @return: void */ public void addSingleArtisan(Artisan artisan) { jdbcTemplate.update(sql, artisan.getUserName(), artisan.getPassword()); System.out.println("insert successfully"); } }
由于JdbcTemplate在内部通过PreparedStatement执行SQL语句,所以可以绑定参数的SQL语句,每个“?”占位符可以接受一个参数。
尽量使用可绑定参数的SQL语句,以便数据库可以复用SQL的执行计划,提高数据库的执行效率。 此外,应该在DAO使用类级别的静态常量(final static)定义SQL字符串,不应该在方法内部声明SQL字符串变量,以提高JVM的内存使用效率。
在通过
public int update(String sql, Object... args) throws DataAccessException方法为SQL语句的占位符绑定参数时,并没有显示的指定对应字段的数据类型,此时,Spring直接让PreparedStatement根据参数的类型进行“猜测”。 有一种更好的做法是使用
public int update(String sql, Object[] args, int[] argTypes) throws DataAccessException显示指定每个占位符所对英的字段数据类型,这样就可以保证类型安全,当参数值为null时,这种形式提供了更好的支持。
以下代码仅为演示
// 使用该类中的常量属性定义参数类型 import java.sql.Type .... jdbcTemplate,update(sql, new Object[]{..} ,new int[]{Types.VARCHAR2...}); ....
配置文件如下(以下的几个操作都加载这个配置文件)
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --> <context:component-scan base-package="com.xgj.dao.basicOperation" /> <!-- 不使用context命名空间,则需要定义Bean <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:spring/jdbc.properties" /> </bean> --> <!-- 使用context命名空间,同上面的Bean等效.在xml文件中配置数据库的properties文件 --> <context:property-placeholder location="classpath:spring/jdbc.properties" /> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" /> <!-- 配置Jdbc模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /> </beans>
测试类
package com.xgj.dao.basicOperation.insertUpdateAndDelete; import java.util.ArrayList; import java.util.List; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ArtisanDaoTest { public static void main(String[] args) { // 启动Spring 容器 ApplicationContext ctx = new ClassPathXmlApplicationContext( "classpath:com/xgj/dao/basicOperation/basicOperation.xml"); Artisan artisan = ctx.getBean("artisan", Artisan.class); artisan.setUserName("Artisan"); artisan.setPassword("987654"); ArtisanDao artisanDao = ctx.getBean("artisanDao", ArtisanDao.class); // 调用目标方法 artisanDao.addSingleArtisan(artisan); }
除了上述两个update方法外,JdbcTemplate还提供了以下几个功能相似的重载方法
public int update(final String sql) 为不带占位符的SQL语句提供的便利方法
public int update(String sql, PreparedStatementSetter pss) PreparedStatementSetter 是一个回调接口,它定义了一个setValues(PreparedStatement ps)接口方法 ,如下所示
public void addStudent(Student student){ jdbcTemplate.update(sql,new PreparedStatementSetter (){ public void setValues(PreparedStatement ps) throws SQLException{ ps.setString(1,student.getName()); ps.setString(2,student,getSex()); } }); }
PreparedStatement绑定参数时,参数索引从1开始,而非0开始。 第一个参数索引为1,第二个参数索引为2,依次类推。
当然了,还有其他方法 ,需要指出的是,在实际用用中,应该优先考虑不带回调接口的JdbcTemplate方法。没有必要使用那些带有回调接口的方法,因为Spring会在内部自动创建这些回调实例。
返回数据库表的自增主键值
举个例子 ORACLE数据库com.xgj.dao.transaction.annotationTrans.dao.impl.StudentDaoImpl.java
@Override public void addStudent(final Student student) { // 这里采用和addTeacher不同的方式,输出插入数据库的主键ID KeyHolder keyHolder = new GeneratedKeyHolder(); PreparedStatementCreator preparedStatementCreator = new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement ps = con.prepareStatement(addTeacherSQL, new String[] { "id" }); ps.setString(1, student.getName()); ps.setInt(2, student.getAge()); ps.setString(3, student.getSex()); return ps; } }; jdbcTemplate.update(preparedStatementCreator, keyHolder); System.out.println("获取到的插入数据库的ID:" + keyHolder.getKey().longValue()); }
在实际开发中,我们并不太建议使用表自增键,因为这种方式会让开发变得更加复杂且降低程序的移植性,在应用层中创建主键才是主流的方式,可以使用UUID或者通过一个编码引擎获取主键值。
批量更改数据
如果需要一次性插入或者更新多条记录,当然可以简单的通过多次调用update()方法完成任务,但是这不是最好的实现方案。 更好的选择是使用JDBCTemplate批量数据更改的方法。一般情况下,后者拥有更好的性能,因为更新的数据将被批量发送到数据库中,它减少了对数据库访问的次数。我们解读下下面两个方法:
public int[] batchUpdate(String[] sql)
多条SQL语句组成一个数组,注意此处的sql语句不能带参数,该方法以批量方式执行这些SQL语句。Spring在内部使用JDBC提供的批量更新API完成操作,如果底层的JDBC Driver不支持批量更新操作,Spring将采用逐条更新的方式模拟批量更新。
int[] batchUpdate(String sql,BatchPreparedStatementSetter pss)使用本方法对于同一结构的带参SQL语句多次进行数据更新操作。通过BatchPreparedStatementSetter回调接口进行批量参数的绑定工作。
BatchPreparedStatementSetter定义了两个方法:
int getBatchSize():指定本批次的大小
void setValues(PreparedStatement ps,int i):为给定的PreparedStatement设置参数
/** * * * @Title: addBatchArtisan * * @Description: 批量更新 * * @param artisanList * * @return: void */ public void addBatchArtisan(final List<Artisan> artisanList) { jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int index) throws SQLException { Artisan artisan = artisanList.get(index); ps.setString(1, artisan.getUserName()); ps.setString(2, artisan.getPassword()); } // 指定该批的记录数 @Override public int getBatchSize() { return artisanList.size(); } }); System.out.println("batch insert successfully"); }
需要注意的是BatchPreparedStatementSetter是一次性地批量提交数据,而不会分批提交,getBatchSize()是整批的大小。所以,如果希望将一个List中的数据通过BatchPreparedStatementSetter批量更新到数据库中,getBatchSize()就应该设置为List的大小。
如果List非常大,希望分多次批量提交,则可分段读取这个大List并暂存到一个小的List中,再将这个小的List通过BatchPreparedStatemetSetter批量保存到数据库中。
查询数据
在Spring JDBC中,仅需要指定SQL查询语句并定义好如何从结果集中返回数据就可以了。使用RowCallbackHandler处理结果集
Spring提供了org.springframework.jdbc.core.RowCallbackHandler回调接口,通过该接口可以定义如何从结果集中获取数据. RowCaIlbackHandler接口很简单,仅有一 个方法void processRow(ResultSet rs) throws SQLException。
Spring会遍历结果集, 对结果集中的每一行调用RowCallbackHandler回调接口处理数据。所以用户无 须 调用ResultSet的next()方法,而只需要定义好如何获取结果行数据的逻辑就可以了。
我们来看个示例
package com.xgj.dao.basicOperation.retrieve_select; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import com.xgj.dao.demo.BaseDao; /** * * * @ClassName: ArtisanRDao * * @Description: @Repository 标注的DAO * * @author: Mr.Yang * * @date: 2017年9月18日 下午6:14:24 */ @Repository public class ArtisanRDao extends BaseDao { private static final String selectArtisanUserSql = "select user_name ,password from artisan_user where user_id = ? "; private static final String selectArtisanUsersSql = "select user_name ,password from artisan_user where user_id between ? and ? "; /** * * * @Title: selectArtisanById * * @Description: 取一条数据 * * @param artisanId * @return * * @return: Artisan */ public Artisan selectArtisanById(int artisanId) { final Artisan artisan = new Artisan(); // (1)将结果集中的数据抽取到artisan对象中 jdbcTemplate.query(selectArtisanUserSql, new Object[] { artisanId }, new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { artisan.setUserName(rs.getString("user_name")); artisan.setPassword(rs.getString("password")); } }); return artisan; } }
如果需要获取多条记录,依旧可以使用RowCallbackHandler完成任务,只需要稍微调整一下结果集的处理逻辑就可以了。 代码如下
/** * * * @Title: selectArtisansByIds * * @Description: 使用RowCallbackHandler获取多条记录 * * @param beginId * @param toId * @return * * @return: List<Artisan> */ public List<Artisan> selectArtisansByIds(int beginId, int toId) { final List<Artisan> artisanList = new ArrayList<Artisan>(); jdbcTemplate.query(selectArtisanUsersSql, new Object[] { beginId, toId }, new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { Artisan artisan = new Artisan(); artisan.setUserName(rs.getString("user_name")); artisan.setPassword(rs.getString("password")); artisanList.add(artisan); } }); return artisanList; }
当结果集中没有数据时,并不会抛出异常。只是此时RowCallbackHandle:回调接口 中定义的处理逻辑没有得到调用罢了。
使用RowMapper<T>
处理结果集
Spring还提供了一个和RowCallbackHandler功能类似的RowMapper<T>接口,它也可以使用RowMapper定义结果集映射逻辑,在结果集为多行记录时,该接口更 容易使用。
RowMapper<T>也只有一个接口方法:
T mapRow(ResultSet rs, int rowNum)
看下示例
/** * * * @Title: selectArtisansByIdsWithRowMapper * * @Description: 使用RowMapper获取多行结果集 * * @param beginId * @param toId * @return * * @return: List<Artisan> */ public List<Artisan> selectArtisansByIdsWithRowMapper(int beginId, int toId) { return jdbcTemplate.query(selectArtisanUsersSql, new Object[] { beginId, toId }, new RowMapper<Artisan>() { @Override public Artisan mapRow(ResultSet rs, int rowNum) throws SQLException { Artisan artisan = new Artisan(); artisan.setUserName(rs.getString("user_name")); artisan.setPassword(rs.getString("password")); return artisan; } }); }
RowCallbackHandler和RowMapper<T>
的比较
从功能上讲,RowCallbackHandler和RowMapper没有太大的区别,它们都是用于定义结果集行的读取逻辑,将ResultSet中的数据映射到对象或者List中 。RowCallbackHandler接口实现类可以是有状态的,而RowMapper的实现类应该是无状态的。如果RowCallbackHandler实现类是有状态的,用户就不能在多个地方复用,只有无状态的实例都能在不同的地方复用。
比如,Spring有一个RowCallbackHandler的实现类是RowCountCallbackHandler,可以计算结果集行数:
RowCountCallbackHandler countCallback = new RowCountCallbackHandler(); jdbcTemplate.query("select * from user", countCallback); int rowCount = countCallback.getRowCount();
可见RowCountCallbackHandler包含了一个记录结果集行数的状态,在多线程的环境中,如果没有进行特殊的处理,就不能在多个地方复用countCallback实例。
Spring也提供了几个RowMapper实现类,如ColumnMapRowMapper和SingleColumnRowMapper。
ColumnMapRowMapper将结果集中的每一行映射为一个
MapSingleColumnRowMapper将结果集中的某一列映射为一个Object。它们都只是定义了映射逻辑,而没有保持状态。
我们知道,通过JDBC查询返回一个ResultSet结果集时,JDBC并不会一次性将所有匹配的数据都加载到JVM中,而是只返回同一批次的数据(由JDBC驱动程序决定,如Oracle的JDBC驱动程序默认返回10行数据),当通过ResultSet#next()游标流动结果集超过数据范围时,JDBC再获取一批数据。这样以一种“批量化+串行化”的处理方式避免大结果集处理时JVM内存的过大开销。
当处理大结果集数据时,如果使用RowMapper,则虽然获取数据的过程是串行化的,但是结果集中的所有数据最终都会映射并汇总成一个List对象,占用大量的JVM内存,甚至可以直接引发OutOfMemoryException异常。这里应该使用RowCallbackHandler接口,在processRow接口方法内部处理结果集数据。
当使用RowCallbackHandler接口时,如果结果集中没有数据,并不会抛出异常,只是此时RowCallbackHandler回调接口中定义的处理逻辑没有得到调用罢了。
查询单值数据
Both queryForInt() and queryForLong() are deprecated since version 3.2.2 . To fix it, replace the code with queryForObject(String, Class).package com.xgj.dao.basicOperation.getSingleValue; import org.springframework.stereotype.Repository; import com.xgj.dao.basicOperation.BaseDao; /** * * * @ClassName: GetCountOfArtisanDao * * @Description: @Repository 标注的DAO * * @author: Mr.Yang * * @date: 2017年9月19日 下午12:05:08 */ @Repository public class GetCountOfArtisanDao extends BaseDao { private final static String COUNTSQL = "select count(1) from artisan_user where user_name = ? "; /** * * * @Title: getCount * * @Description: Both queryForInt() and queryForLong() are deprecated since * version 3.2.2 (correct me if mistake). To fix it, replace * the code with queryForObject(String, Class). * * https://www.mkyong.com/spring/jdbctemplate-queryforint-is- * deprecated/ * * @return * * @return: int */ public boolean getCount(String userName) { boolean isExist = false; int count = jdbcTemplate.queryForObject(COUNTSQL, new Object[] { userName }, Integer.class); if (count > 0) { isExist = true; } else { isExist = false; } return isExist; } }
调用存储过程(3种方式)
CallProcDemo
package com.xgj.dao.basicOperation.callProc; import java.sql.CallableStatement; import java.sql.SQLException; import java.sql.Types; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.CallableStatementCallback; import org.springframework.stereotype.Repository; import com.xgj.dao.demo.BaseDao; @Repository public class CallProcDemo extends BaseDao { // (1) 调用存过的语句 private static final String PROCSQL_STRING = "call PROC_artisan_oper(?,?)"; public int getUserCount(final String userName) { int num = jdbcTemplate.execute(PROCSQL_STRING, new CallableStatementCallback<Integer>() { @Override public Integer doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { // (2)绑定入参 cs.setString(1, userName); // (3)注册输出参数 cs.registerOutParameter(2, Types.INTEGER); // 执行 cs.execute(); return cs.getInt(2); } }); System.out.println("num:" + num); return num; } }
CallProcDemoWithSimpleJdbcCall
package com.xgj.dao.basicOperation.callProc; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.core.simple.SimpleJdbcCall; import org.springframework.stereotype.Repository; @Repository public class CallProcDemoWithSimpleJdbcCall { private JdbcTemplate jdbcTemplate; @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void getUserCount(int userId) { SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate) .withProcedureName("PROC_artisan_selectInfo_withId"); // 如果调用function 则为withFunctionName(functionName) // 注册入参 必须和存过的入参保持一致 不区分大小写 SqlParameterSource in = new MapSqlParameterSource().addValue( "p_user_id", userId); // 获取返回结果 Map<String, Object> outMap = jdbcCall.execute(in); for (Map.Entry<String, Object> entry : outMap.entrySet()) { System.out.println("key=" + entry.getKey() + ",value=" + entry.getValue()); } String userName = (String) outMap.get("O_USERNAME"); String password = (String) outMap.get("O_PASSWORD"); System.out.println("userName:" + userName + " ,password=" + password); } }
CallProcDemoWithCallableStatementCreator
package com.xgj.dao.basicOperation.callProc; import java.sql.CallableStatement; import java.sql.SQLException; import java.sql.Types; import java.util.HashMap; import java.util.Map; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.CallableStatementCallback; import org.springframework.jdbc.core.CallableStatementCreator; import org.springframework.jdbc.core.CallableStatementCreatorFactory; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.core.SqlParameter; import org.springframework.stereotype.Repository; import com.xgj.dao.demo.BaseDao; @Repository public class CallProcDemoWithCallableStatementCreator extends BaseDao { private static final String PROCSQL_STRING = "call PROC_artisan_selectInfo_withId(?,?,?)"; @SuppressWarnings({ "rawtypes", "unchecked" }) public void printUserInfo(int userId) { // 使用CallableStatementCreatorFactory 创建 CallableStatementCreator CallableStatementCreatorFactory factory = new CallableStatementCreatorFactory( PROCSQL_STRING); // 设置入参 factory.addParameter(new SqlParameter("p_user_id", Types.INTEGER)); // 设置出参 factory.addParameter(new SqlOutParameter("o_username", Types.VARCHAR)); factory.addParameter(new SqlOutParameter("o_password", Types.VARCHAR)); Map<String, Integer> paramMap = new HashMap<String, Integer>(); paramMap.put("p_user_id", userId); CallableStatementCreator csc = factory .newCallableStatementCreator(paramMap); String userInfo = jdbcTemplate.execute(csc, new CallableStatementCallback() { @Override public Object doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException { // 执行 cs.execute(); // 获取返回结果 String userName = cs.getString(2); String password = cs.getString(3); String returnInfo = userName + "|" + password; return returnInfo; } }); System.out.println("UserInfo:" + userInfo); } }
示例源码
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster相关文章推荐
- 学习《spring 3.x企业应用开发实战》之使用Spring JDBC访问数据库
- 【SpringData】轻松愉快之玩转SpringData( 第2章 使用传统方式访问数据库 - JDBC 访问 )
- 第一个 Spring Boot 程序 : 使用 spring jdbc 访问关系型数据库
- [Spring3.x] 第 11 章 使用 Spring JDBC 访问数据库 & 第 12 章 整合其他 ORM 框架
- 11.1 使用Spring JDBC访问数据库
- [Spring3.x] 第 11 章 使用 Spring JDBC 访问数据库 & 第 12 章 整合其他 ORM 框架
- SpringBoot实战(四)之使用JDBC和Spring访问数据库
- Spring 使用JDBC对数据库进行访问
- 使用spring-jdbc访问数据库
- Spring学习(四)——使用Spring JDBC访问数据库
- 使用JDBC驱动程序访问数据库
- spring 事务管理 1(使用spring的JdbcTemplate访问数据库)
- 使用JDBC访问数据库
- 使用Spring JDBC进行数据访问 (JdbcTemplate/NamedParameterJdbcTemplate/SimpleJdbcTemplate/SimpleJdbcCall/Stor)
- 【java工具】使用jdbc访问数据库获取某个存储过程信息及下面参数信息
- 使用JDBC创建数据库访问程序
- 开始学Spring第4章-使用JdbcTemplate访问数据库
- Java程序员从笨鸟到菜鸟之(七十八)细谈Spring(七)spring之JDBC访问数据库及配置详解
- 使用JDBC创建数据库访问程序
- 使用jdbc访问数据库