7.1 概述
7.1.1 JDBC回顾
传统应用程序开发中,进行JDBC编程是相当痛苦的,如下所示:
以上代码片段具有冗长、重复、容易忘记某一步骤从而导致出错、显示控制事务、显示处理受检查异常等等。
有朋友可能重构出自己的一套JDBC模板,从而能简化日常开发,但自己开发的JDBC模板不够通用,而且对于每一套JDBC模板实现都差不多,从而导致开发人员必须掌握每一套模板。
Spring JDBC提供了一套JDBC抽象框架,用于简化JDBC开发,而且如果各个公司都使用该抽象框架,开发人员首先减少了学习成本,直接上手开发,如图7-1所示。
图7-1 Spring JDBC与传统JDBC编程对比
7.1.2 Spring对JDBC的支持
Spring通过抽象JDBC访问并提供一致的API来简化JDBC编程的工作量。我们只需要声明SQL、调用合适的Spring JDBC框架API、处理结果集即可。事务由Spring管理,并将JDBC受查异常转换为Spring一致的非受查异常,从而简化开发。
Spring主要提供JDBC模板方式、关系数据库对象化方式和SimpleJdbc方式三种方式来简化JDBC编程,这三种方式就是Spring JDBC的工作模式:
- JDBC模板方式:Spring JDBC框架提供以下几种模板类来简化JDBC编程,实现GoF模板设计模式,将可变部分和非可变部分分离,可变部分采用回调接口方式由用户来实现:如JdbcTemplate、NamedParameterJdbcTemplate、SimpleJdbcTemplate。
- 关系数据库操作对象化方式:Spring JDBC框架提供了将关系数据库操作对象化的表示形式,从而使用户可以采用面向对象编程来完成对数据库的访问;如MappingSqlQuery、SqlUpdate、SqlCall、SqlFunction、StoredProcedure等类。这些类的实现一旦建立即可重用并且是线程安全的。
- SimpleJdbc方式:Spring JDBC框架还提供了SimpleJdbc方式来简化JDBC编程,SimpleJdbcInsert 、 SimpleJdbcCall用来简化数据库表插入、存储过程或函数访问。
Spring JDBC还提供了一些强大的工具类,如DataSourceUtils来在必要的时候手工获取数据库连接等。
7.1.4 Spring的JDBC架构
Spring JDBC抽象框架由四部分组成:datasource、support、core、object。如图7-2所示。
图7-2 Spring JDBC架构图
support包:提供将JDBC异常转换为DAO非检查异常转换类、一些工具类如JdbcUtils等。
datasource包:提供简化访问JDBC 数据源(javax.sql.DataSource实现)工具类,并提供了一些DataSource简单实现类从而能使从这些DataSource获取的连接能自动得到Spring管理事务支持。
core包:提供JDBC模板类实现及可变部分的回调接口,还提供SimpleJdbcInsert等简单辅助类。
object包:提供关系数据库的对象表示形式,如MappingSqlQuery、SqlUpdate、SqlCall、SqlFunction、StoredProcedure等类,该包是基于core包JDBC模板类实现。
7.2 JDBC模板类
7.2.1 概述
Spring JDBC抽象框架core包提供了JDBC模板类,其中JdbcTemplate是core包的核心类,所以其他模板类都是基于它封装完成的,JDBC模板类是第一种工作模式。
JdbcTemplate类通过模板设计模式帮助我们消除了冗长的代码,只做需要做的事情(即可变部分),并且帮我们做哪些固定部分,如连接的创建及关闭。
JdbcTemplate类对可变部分采用回调接口方式实现,如ConnectionCallback通过回调接口返回给用户一个连接,从而可以使用该连接做任何事情、StatementCallback通过回调接口返回给用户一个Statement,从而可以使用该Statement做任何事情等等,还有其他一些回调接口如图7-3所示。
图7-3 JdbcTemplate支持的回调接口
Spring除了提供JdbcTemplate核心类,还提供了基于JdbcTemplate实现的NamedParameterJdbcTemplate类用于支持命名参数绑定、 SimpleJdbcTemplate类用于支持Java5+的可变参数及自动装箱拆箱等特性。
7.2.3 传统JDBC编程替代方案
前边我们已经使用过传统JDBC编程方式,接下来让我们看下Spring JDBC框架提供的更好的解决方案。
1)准备需要的jar包并添加到类路径中:
2)传统JDBC编程替代方案:
在使用JdbcTemplate模板类时必须通过DataSource获取数据库连接,Spring JDBC提供了DriverManagerDataSource实现,它通过包装“DriverManager.getConnection”获取数据库连接,具体DataSource相关请参考【7.5.1控制数据库连接】。
接下来让我们具体分析一下:
1) jdbc:hsqldb:mem:test:表示使用hsqldb内存数据库,数据库名为“test”。
2) public static void setUpClass():使用junit的@BeforeClass注解,表示在所以测试方法之前执行,且只执行一次。在此方法中定义了DataSource并使用DataSource对象创建了JdbcTemplate对象。JdbcTemplate对象是线程安全的。
3) JdbcTemplate执行流程:首先定义SQL,其次调用JdbcTemplate方法执行SQL,最后通过RowCallbackHandler回调处理ResultSet结果集。
Spring JDBC解决方法相比传统JDBC编程方式是不是简单多了,是不是只有可变部分需要我们来做,其他的都由Spring JDBC框架来实现了。
接下来让我们深入JdbcTemplate及其扩展吧。
7.2.4 JdbcTemplate
首先让我们来看下如何使用JdbcTemplate来实现增删改查。
一、首先创建表结构:
1) org.junit包下的@Before和@After分别表示在测试方法之前和之后执行的方法,对于每个测试方法都将执行一次;
2) create memory table test表示创建hsqldb内存表,包含两个字段id和name,其中id是具有自增功能的主键,如果有朋友对此不熟悉hsqldb可以换成熟悉的数据库。
二、定义测试骨架,该测试方法将用于实现增删改查测试:
三、新增测试:
四、删除测试:
五、更新测试:
六、查询测试:
看完以上示例,大家是否觉得JdbcTemplate简化了我们很多劳动力呢?接下来让我们深入学习一下JdbcTemplate提供的方法。
JdbcTemplate主要提供以下五类方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
JdbcTemplate类支持的回调类:
- 预编译语句及存储过程创建回调:用于根据JdbcTemplate提供的连接创建相应的语句;
PreparedStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的PreparedStatement;
CallableStatementCreator:通过回调获取JdbcTemplate提供的Connection,由用户使用该Conncetion创建相关的CallableStatement;
- 预编译语句设值回调:用于给预编译语句相应参数设值;
PreparedStatementSetter:通过回调获取JdbcTemplate提供的PreparedStatement,由用户来对相应的预编译语句相应参数设值;
BatchPreparedStatementSetter:;类似于PreparedStatementSetter,但用于批处理,需要指定批处理大小;
- 自定义功能回调:提供给用户一个扩展点,用户可以在指定类型的扩展点执行任何数量需要的操作;
ConnectionCallback:通过回调获取JdbcTemplate提供的Connection,用户可在该Connection执行任何数量的操作;
StatementCallback:通过回调获取JdbcTemplate提供的Statement,用户可以在该Statement执行任何数量的操作;
PreparedStatementCallback:通过回调获取JdbcTemplate提供的PreparedStatement,用户可以在该PreparedStatement执行任何数量的操作;
CallableStatementCallback:通过回调获取JdbcTemplate提供的CallableStatement,用户可以在该CallableStatement执行任何数量的操作;
- 结果集处理回调:通过回调处理ResultSet或将ResultSet转换为需要的形式;
RowMapper:用于将结果集每行数据转换为需要的类型,用户需实现方法mapRow(ResultSet rs, int rowNum)来完成将每行数据转换为相应的类型。
RowCallbackHandler:用于处理ResultSet的每一行结果,用户需实现方法processRow(ResultSet rs)来完成处理,在该回调方法中无需执行rs.next(),该操作由JdbcTemplate来执行,用户只需按行获取数据然后处理即可。
ResultSetExtractor:用于结果集数据提取,用户需实现方法extractData(ResultSet rs)来处理结果集,用户必须处理整个结果集;
接下来让我们看下具体示例吧,在示例中不可能介绍到JdbcTemplate全部方法及回调类的使用方法,我们只介绍代表性的,其余的使用都是类似的;
1)预编译语句及存储过程创建回调、自定义功能回调使用:
首先使用PreparedStatementCreator创建一个预编译语句,其次由JdbcTemplate通过PreparedStatementCallback回调传回,由用户决定如何执行该PreparedStatement。此处我们使用的是execute方法。
2)预编译语句设值回调使用:
通过JdbcTemplate的int update(String sql, PreparedStatementSetter pss)执行预编译sql,其中sql参数为“insert into test(name) values (?) ”,该sql有一个占位符需要在执行前设值,PreparedStatementSetter实现就是为了设值,使用setValues(PreparedStatement pstmt)回调方法设值相应的占位符位置的值。JdbcTemplate也提供一种更简单的方式“update(String sql, Object... args)”来实现设值,所以只要当使用该种方式不满足需求时才应使用PreparedStatementSetter。
3)结果集处理回调:
RowMapper接口提供mapRow(ResultSet rs, int rowNum)方法将结果集的每一行转换为一个Map,当然可以转换为其他类,如表的对象画形式。
RowCallbackHandler接口也提供方法processRow(ResultSet rs),能将结果集的行转换为需要的形式。
ResultSetExtractor使用回调方法extractData(ResultSet rs)提供给用户整个结果集,让用户决定如何处理该结果集。
当然JdbcTemplate提供更简单的queryForXXX方法,来简化开发:
3) 存储过程及函数回调:
首先修改JdbcTemplateTest的setUp方法,修改后如下所示:
其中CREATE FUNCTION FUNCTION_TEST用于创建自定义函数,CREATE PROCEDURE PROCEDURE_TEST用于创建存储过程,注意这些创建语句是数据库相关的,本示例中的语句只适用于HSQLDB数据库。
其次修改JdbcTemplateTest的tearDown方法,修改后如下所示:
其中drop语句用于删除创建的存储过程、自定义函数及数据库表。
接下来看一下hsqldb如何调用自定义函数:
- {call FUNCTION_TEST(?)}:定义自定义函数的sql语句,注意hsqldb {?= call …}和{call …}含义是一样的,而比如mysql中两种含义是不一样的;
- params:用于描述自定义函数占位符参数或命名参数类型;SqlParameter用于描述IN类型参数、SqlOutParameter用于描述OUT类型参数、SqlInOutParameter用于描述INOUT类型参数、SqlReturnResultSet用于描述调用存储过程或自定义函数返回的ResultSet类型数据,其中SqlReturnResultSet需要提供结果集处理回调用于将结果集转换为相应的形式,hsqldb自定义函数返回值是ResultSet类型。
- CallableStatementCreator:提供Connection对象用于创建CallableStatement对象
- outValues:调用call方法将返回类型为Map<String, Object>对象;
- outValues.get("result"):获取结果,即通过SqlReturnResultSet对象转换过的数据;其中SqlOutParameter、SqlInOutParameter、SqlReturnResultSet指定的name用于从call执行后返回的Map中获取相应的结果,即name是Map的键。
注:因为hsqldb {?= call …}和{call …}含义是一样的,因此调用自定义函数将返回一个包含结果的ResultSet。
最后让我们示例下mysql如何调用自定义函数:
- getMysqlDataSource:首先启动mysql(本书使用5.4.3版本),其次登录mysql创建test数据库(“create database test;”),在进行测试前,请先下载并添加mysql-connector-java-5.1.10.jar到classpath;
- {?= call FUNCTION_TEST(?)}:可以使用{?= call …}形式调用自定义函数;
- params:无需使用SqlReturnResultSet提取结果集数据,而是使用SqlOutParameter来描述自定义函数返回值;
- CallableStatementCreator:同上个例子含义一样;
- cstmt.registerOutParameter(1, Types.INTEGER):将OUT类型参数注册为JDBC类型Types.INTEGER,此处即返回值类型为Types.INTEGER。
- outValues.get("result"):获取结果,直接返回Integer类型,比hsqldb简单多了吧。
最后看一下如何如何调用存储过程:
- {call PROCEDURE_TEST(?, ?)}:调用存储过程sql;
- params:定义存储过程参数;SqlInOutParameter描述INOUT类型参数、SqlOutParameter描述OUT类型参数;
- CallableStatementCreator:用于创建CallableStatement,并设值及注册OUT参数类型;
- outValues:通过SqlInOutParameter及SqlOutParameter参数定义的name来获取存储过程结果。
JdbcTemplate类还提供了很多便利方法,在此就不一一介绍了,但这些方法是由规律可循的,第一种就是提供回调接口让用户决定做什么,第二种可以认为是便利方法(如queryForXXX),用于那些比较简单的操作。
7.2.4 NamedParameterJdbcTemplate
NamedParameterJdbcTemplate类是基于JdbcTemplate类,并对它进行了封装从而支持命名参数特性。
NamedParameterJdbcTemplate主要提供以下三类方法:execute方法、query及queryForXXX方法、update及batchUpdate方法。
首先让我们看个例子吧:
接下来让我们分析一下代码吧:
1)NamedParameterJdbcTemplate初始化:可以使用DataSource或JdbcTemplate 对象作为构造器参数初始化;
2)insert into test(name) values(:name):其中“:name”就是命名参数;
3) update(insertSql, paramMap):其中paramMap是一个Map类型,包含键为“name”,值为“name5”的键值对,也就是为命名参数设值的数据;
4)query(selectSql, paramMap, new RowCallbackHandler()……):类似于JdbcTemplate中介绍的,唯一不同是需要传入paramMap来为命名参数设值;
5)update(deleteSql, paramSource):类似于“update(insertSql, paramMap)”,但使用SqlParameterSource参数来为命名参数设值,此处使用MapSqlParameterSource实现,其就是简单封装java.util.Map。
NamedParameterJdbcTemplate类为命名参数设值有两种方式:java.util.Map和SqlParameterSource:
1)java.util.Map:使用Map键数据来对于命名参数,而Map值数据用于设值;
2)SqlParameterSource:可以使用SqlParameterSource实现作为来实现为命名参数设值,默认有MapSqlParameterSource和BeanPropertySqlParameterSource实现;MapSqlParameterSource实现非常简单,只是封装了java.util.Map;而BeanPropertySqlParameterSource封装了一个JavaBean对象,通过JavaBean对象属性来决定命名参数的值。
可以看出BeanPropertySqlParameterSource使用能减少很多工作量,但命名参数必须和JavaBean属性名称相对应才可以。
7.2.5 SimpleJdbcTemplate
SimpleJdbcTemplate类也是基于JdbcTemplate类,但利用Java5+的可变参数列表和自动装箱和拆箱从而获取更简洁的代码。
SimpleJdbcTemplate主要提供两类方法:query及queryForXXX方法、update及batchUpdate方法。
首先让我们看个例子吧:
1)SimpleJdbcTemplate初始化:可以使用DataSource、JdbcTemplate或NamedParameterJdbcTemplate对象作为构造器参数初始化;
2)update(insertSql, 10, "name5"):采用Java5+可变参数列表从而代替new Object[]{10, "name5"}方式;
3)query(selectSql, mapper, 10, "name5"):使用Java5+可变参数列表及RowMapper回调并利用泛型特性来指定返回值类型(List<UserModel>)。
SimpleJdbcTemplate类还支持命名参数特性,如queryForList(String sql, SqlParameterSource args)和queryForList(String sql, Map<String, ?> args) ,类似于NamedParameterJdbcTemplate中使用,在此就不介绍了。
注:SimpleJdbcTemplate还提供类似于ParameterizedRowMapper 用于泛型特性的支持,ParameterizedRowMapper是RowMapper的子类,但从Spring 3.0由于允许环境需要Java5+,因此不再需要ParameterizedRowMapper ,而可以直接使用RowMapper;
query(String sql, ParameterizedRowMapper<T> rm, SqlParameterSource args)
query(String sql, RowMapper<T> rm, Object... args) //直接使用该语句
SimpleJdbcTemplate还提供如下方法用于获取JdbcTemplate和NamedParameterJdbcTemplate:
1)获取JdbcTemplate对象方法:JdbcOperations是JdbcTemplate的接口
JdbcOperations getJdbcOperations()
2)获取NamedParameterJdbcTemplate对象方法:NamedParameterJdbcOperations是NamedParameterJdbcTemplate的接口
NamedParameterJdbcOperations getNamedParameterJdbcOperations()
对JDBC的支持 之 7.3 关系数据库操作对象化
7.3.1 概述
所谓关系数据库对象化其实就是用面向对象方式表示关系数据库操作,从而可以复用。
Spring JDBC框架将数据库操作封装为一个RdbmsOperation,该对象是线程安全的、可复用的对象,是所有数据库对象的父类。而SqlOperation继承了RdbmsOperation,代表了数据库SQL操作,如select、update、call等,如图7-4所示。
图7-4 关系数据库操作对象化支持类
数据库操作对象化只要有以下几种类型,所以类型是线程安全及可复用的:
- 查询:将数据库操作select封装为对象,查询操作的基类是SqlQuery,所有查询都可以使用该类表示,Spring JDBC还提供了一些更容易使用的MappingSqlQueryWithParameters和MappingSqlQuery用于将结果集映射为Java对象,查询对象类还提供了两个扩展UpdatableSqlQuery和SqlFunction;
- 更新:即增删改操作,将数据库操作insert 、update、delete封装为对象,增删改基类是SqlUpdate,当然还提供了BatchSqlUpdate用于批处理;
- 存储过程及函数:将存储过程及函数调用封装为对象,基类是SqlCall类,提供了StoredProcedure实现。
7.3.2 查询
1)SqlQuery:需要覆盖如下方法来定义一个RowMapper,其中parameters参数表示命名参数或占位符参数值列表,而context是由用户传入的上下文数据。
SqlQuery提供两类方法:
- execute及executeByNamedParam方法:用于查询多行数据,其中executeByNamedParam用于支持命名参数绑定参数;
- findObject及findObjectByNamedParam方法:用于查询单行数据,其中findObjectByNamedParam用于支持命名参数绑定。
演示一下SqlQuery如何使用:
从测试代码可以SqlQuery使用非常简单,创建SqlQuery实现对象,然后调用相应的方法即可,接下来看一下SqlQuery实现:
从测试代码可以看出,具体步骤如下:
一、setJdbcTemplate/ setDataSource:首先设置数据源或JdbcTemplate;
二、setSql("select * from test where name=?"):定义sql语句,所以定义的sql语句都将被编译为PreparedStatement;
三、declareParameter(new SqlParameter(Types.VARCHAR)):对PreparedStatement参数描述,使用SqlParameter来描述参数类型,支持命名参数、占位符描述;
对于命名参数可以使用如new SqlParameter("name", Types.VARCHAR)描述;注意占位符参数描述必须按占位符参数列表的顺序进行描述;
四、编译:可选,当执行相应查询方法时会自动编译,用于将sql编译为PreparedStatement,对于编译的SqlQuery不能再对参数进行描述了。
五、以上步骤是不可变的,必须按顺序执行。
2)MappingSqlQuery:用于简化SqlQuery中RowMapper创建,可以直接在实现mapRow(ResultSet rs, int rowNum)来将行数据映射为需要的形式;
MappingSqlQuery所有查询方法完全继承于SqlQuery。
演示一下MappingSqlQuery如何使用:
MappingSqlQuery使用和SqlQuery完全一样,创建MappingSqlQuery实现对象,然后调用相应的方法即可,接下来看一下MappingSqlQuery实现,findObjectByNamedParam方法用于执行命名参数查询:
和SqlQuery唯一不同的是使用mapRow来讲每行数据转换为需要的形式,其他地方完全一样。
1) UpdatableSqlQuery:提供可更新结果集查询支持,子类实现updateRow(ResultSet rs, int rowNum, Map context)对结果集进行更新。
2) GenericSqlQuery:提供setRowMapperClass(Class rowMapperClass)方法用于指定RowMapper实现,在此就不演示了。具体请参考testGenericSqlQuery()方法。
3) SqlFunction:SQL“函数”包装器,用于支持那些返回单行结果集的查询。该类主要用于返回单行单列结果集。
如代码所示,SqlFunction初始化时需要DataSource和相应的sql语句,如果有参数需要使用declareParameter对参数类型进行描述;run方法默认返回int型,当然也可以使用runGeneric返回其他类型,如String等。
7.3.3 更新
SqlUpdate类用于支持数据库更新操作,即增删改(insert、delete、update)操作,该方法类似于SqlQuery,只是职责不一样。
SqlUpdate提供了update及updateByNamedParam方法用于数据库更新操作,其中updateByNamedParam用于命名参数类型更新。
演示一下SqlUpdate如何使用:
InsertUserModel类实现类似于SqlQuery实现,用于执行数据库插入操作,SqlUpdate还提供一种更简洁的构造器SqlUpdate(DataSource ds, String sql, int[] types),其中types用于指定占位符或命名参数类型;SqlUpdate还支持命名参数,使用updateByNamedParam方法来进行命名参数操作。
7.3.4 存储过程及函数
StoredProcedure用于支持存储过程及函数,该类的使用同样类似于SqlQuery。
StoredProcedure提供execute方法用于执行存储过程及函数。
一、StoredProcedure如何调用自定义函数:
StoredProcedure使用非常简单,定义StoredProcedure实现HsqldbLengthFunction,并调用execute方法执行即可,接下来看一下HsqldbLengthFunction实现:
StoredProcedure自定义函数使用类似于SqlQuery,首先设置数据源或JdbcTemplate对象,其次定义自定义函数,然后使用declareParameter进行参数描述,最后调用compile(可选)编译自定义函数。
接下来看一下mysql自定义函数如何使用:
MysqlLengthFunction自定义函数使用与HsqldbLengthFunction使用完全一样,只是内部实现稍有差别:
MysqlLengthFunction与HsqldbLengthFunction实现不同的地方有两点:
- setFunction(true):表示是自定义函数调用,即编译后的sql为{?= call …}形式;如果使用hsqldb不能设置为true,因为在hsqldb中{?= call …}和{call …}含义一样;
- declareParameter(new SqlOutParameter("result", Types.INTEGER)):将自定义函数返回值类型直接描述为Types.INTEGER;SqlOutParameter必须指定name,而不用使用SqlReturnResultSet首先获取结果集,然后再从结果集获取返回值,这是mysql与hsqldb的区别;
一、StoredProcedure如何调用存储过程:
StoredProcedure存储过程实现HsqldbTestProcedure调用与HsqldbLengthFunction调用完全一样,不同的是在实现时,参数描述稍有不同:
package cn.javass.spring.chapter7; //省略import public class HsqldbTestProcedure extends StoredProcedure { public HsqldbTestProcedure(JdbcTemplate jdbcTemplate) { super.setJdbcTemplate(jdbcTemplate); super.setSql("PROCEDURE_TEST"); super.declareParameter(new SqlInOutParameter("inOutName", Types.VARCHAR)); super.declareParameter(new SqlOutParameter("outId", Types.INTEGER)); compile(); } }
- declareParameter:使用SqlInOutParameter描述INOUT类型参数,使用SqlOutParameter描述OUT类型参数,必须按顺序定义,不能颠倒。
7.4 Spring提供的其它帮助
7.4.1 SimpleJdbc方式
Spring JDBC抽象框架提供SimpleJdbcInsert和SimpleJdbcCall类,这两个类通过利用JDBC驱动提供的数据库元数据来简化JDBC操作。
1、SimpleJdbcInsert: 用于插入数据,根据数据库元数据进行插入数据,本类用于简化插入操作,提供三种类型方法:execute方法用于普通插入、executeAndReturnKey及executeAndReturnKeyHolder方法用于插入时获取主键值、executeBatch方法用于批处理。
- new SimpleJdbcInsert(jdbcTemplate) : 首次通过DataSource对象或JdbcTemplate对象初始化SimpleJdbcInsert;
- insert.withTableName("test") : 用于设置数据库表名;
- args : 用于指定插入时列名及值,如本例中只有name列名,即编译后的sql类似于“insert into test(name) values(?)”;
- insert.compile() : 可选的编译步骤,在调用执行方法时自动编译,编译后不能再对insert对象修改;
- 执行: execute方法用于执行普通插入;executeAndReturnKey用于执行并获取自动生成主键(注意是Number类型),必须首先通过setGeneratedKeyName设置主键然后才能获取,如果想获取复合主键请使用setGeneratedKeyNames描述主键然后通过executeReturningKeyHolder获取复合主键KeyHolder对象;executeBatch用于批处理;
2、SimpleJdbcCall: 用于调用存储过程及自定义函数,本类用于简化存储过程及自定义函数调用。
- new SimpleJdbcCall(getMysqlDataSource()) :通过DataSource对象或JdbcTemplate对象初始化SimpleJdbcCall;
- withFunctionName("FUNCTION_TEST") : 定义自定义函数名;自定义函数sql语句将被编译为类似于{?= call …}形式;
- declareParameters : 描述参数类型,使用方式与StoredProcedure对象一样;
- 执行: 调用execute方法执行自定义函数;
调用hsqldb数据库自定义函数与调用mysql自定义函数完全不同,详见StoredProcedure中的解释。
与自定义函数调用不同的是使用withProcedureName来指定存储过程名字;其他参数描述等完全一样。
7.4.2 控制数据库连接
Spring JDBC通过DataSource控制数据库连接,即通过DataSource实现获取数据库连接。
Spring JDBC提供了一下DataSource实现:
- DriverManagerDataSource :简单封装了DriverManager获取数据库连接;通过DriverManager的getConnection方法获取数据库连接;
- SingleConnectionDataSource :内部封装了一个连接,该连接使用后不会关闭,且不能在多线程环境中使用,一般用于测试;
- LazyConnectionDataSourceProxy :包装一个DataSource,用于延迟获取数据库连接,只有在真正创建Statement等时才获取连接,因此再说实际项目中最后使用该代理包装原始DataSource从而使得只有在真正需要连接时才去获取。
第三方提供的DataSource实现主要有C3P0、Proxool、DBCP等,这些实现都具有数据库连接池能力。
DataSourceUtils: Spring JDBC抽象框架内部都是通过它的getConnection(DataSource dataSource)方法获取数据库连接,releaseConnection(Connection con, DataSource dataSource) 用于释放数据库连接,DataSourceUtils用于支持Spring管理事务,只有使用DataSourceUtils获取的连接才具有Spring管理事务。
7.4.3 获取自动生成的主键
有许多数据库提供自动生成主键的能力,因此我们可能需要获取这些自动生成的主键,JDBC 3.0标准支持获取自动生成的主键,且必须数据库支持自动生成键获取。
1 )JdbcTemplate 获取自动生成主键方式:
使用JdbcTemplate的update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)方法执行需要返回自动生成主键的插入语句,其中psc用于创建PreparedStatement并指定自动生成键,如“prepareStatement(insertSql, new String[]{"ID"})”;generatedKeyHolder是KeyHolder类型,用于获取自动生成的主键或复合主键;如使用getKey方法获取自动生成的主键。
2 )SqlUpdate 获取自动生成主键方式:
SqlUpdate获取自动生成主键方式和JdbcTemplate完全一样,可以使用setReturnGeneratedKeys(true)表示要获取自动生成键;也可以使用setGeneratedKeysColumnNames指定自动生成键列名。
3 )SimpleJdbcInsert : 前边示例已介绍,此处就不演示了。
7.4.4 JDBC批量操作
JDBC批处理用于减少与数据库交互的次数来提升性能,Spring JDBC抽象框架通过封装批处理操作来简化批处理操作
1 )JdbcTemplate 批处理: 支持普通的批处理及占位符批处理;
直接调用batchUpdate方法执行需要批处理的语句即可。
JdbcTemplate还可以通过batchUpdate(String sql, final BatchPreparedStatementSetter pss)方法进行批处理,该方式使用预编译语句,然后通过BatchPreparedStatementSetter实现进行设值(setValues)及指定批处理大小(getBatchSize)。
2 )NamedParameterJdbcTemplate 批处理: 支持命名参数批处理;
通过batchUpdate(String sql, SqlParameterSource[] batchArgs)方法进行命名参数批处理,batchArgs指定批处理数据集。SqlParameterSourceUtils.createBatch用于根据JavaBean对象或者Map创建相应的BeanPropertySqlParameterSource或MapSqlParameterSource。
3) SimpleJdbcTemplate 批处理: 已更简单的方式进行批处理;
本示例使用batchUpdate(String sql, List<Object[]> batchArgs)方法完成占位符批处理,当然也支持命名参数批处理等。
4 )SimpleJdbcInsert 批处理:
如代码所示,使用executeBatch(Map<String, Object>[] batch)方法执行批处理。
7.5 集成Spring JDBC及最佳实践
大多数情况下Spring JDBC都是与IOC容器一起使用。通过配置方式使用Spring JDBC。
而且大部分时间都是使用JdbcTemplate类(或SimpleJdbcTemplate和NamedParameterJdbcTemplate)进行开发,即可能80%时间使用JdbcTemplate类,而只有20%时间使用其他类开发,符合80/20法则。
Spring JDBC通过实现DaoSupport来支持一致的数据库访问。
Spring JDBC提供如下DaoSupport实现:
- JdbcDaoSupport:用于支持一致的JdbcTemplate访问;
- NamedParameterJdbcDaoSupport:继承JdbcDaoSupport,同时提供NamedParameterJdbcTemplate访问;
- SimpleJdbcDaoSupport:继承JdbcDaoSupport,同时提供SimpleJdbcTemplate访问。
由于JdbcTemplate、NamedParameterJdbcTemplate、SimpleJdbcTemplate类使用DataSourceUtils获取及释放连接,而且连接是与线程绑定的,因此这些JDBC模板类是线程安全的,即JdbcTemplate对象可以在多线程中重用。
接下来看一下Spring JDBC框架的最佳实践:
1)首先定义Dao接口
2)定义Dao实现,此处是使用Spring JDBC实现:
此处注意首先Spring JDBC实现放在dao.jdbc包里,如果有hibernate实现就放在dao.hibernate包里;其次实现类命名如UserJdbcDaoImpl,即×××JdbcDaoImpl,当然如果自己有更好的命名规范可以遵循自己的,此处只是提个建议。
3)进行资源配置(resources/chapter7/applicationContext-resources.xml):
PropertyPlaceholderConfigurer用于替换配置元数据,如本示例中将对bean定义中的${…}占位符资源用“classpath:chapter7/resources.properties”中相应的元素替换。
dataSource定义数据源,本示例使用proxool数据库连接池,并使用LazyConnectionDataSourceProxy包装它,从而延迟获取数据库连接;${db.driver.class}将被“classpath:chapter7/resources.properties”中的“db.driver.class”元素属性值替换。
proxool数据库连接池:本示例使用proxool-0.9.1版本,请到proxool官网下载并添加proxool-0.9.1.jar和proxool-cglib.jar到类路径。
ProxoolDataSource属性含义如下:
- driver:指定数据库驱动;
- driverUrl:数据库连接;
- username:用户名;
- password:密码;
- maximumConnectionCount:连接池最大连接数量;
- minimumConnectionCount:连接池最小连接数量;
- statistics:连接池使用样本状况统计;如1m,15m,1h,1d表示没1分钟、15分钟、1小时及1天进行一次样本统计;
- simultaneousBuildThrottle:一次可以创建连接的最大数量;
- trace:true表示被执行的每个sql都将被记录(DEBUG级别时被打印到相应的日志文件);
4)定义资源文件(classpath:chapter7/resources.properties):
用于替换配置元数据中相应的占位符数据,如${db.driver.class}将被替换为“org.hsqldb.jdbcDriver”。
5)dao定义配置(chapter7/applicationContext-jdbc.xml):
首先定义抽象的abstractDao,其有一个dataSource属性,从而可以让继承的子类自动继承dataSource属性注入;然后定义userDao,且继承abstractDao,从而继承dataSource注入;我们在此给配置文件命名为applicationContext-jdbc.xml表示Spring JDBC DAO实现;如果使用hibernate实现可以给配置文件命名为applicationContext-hibernate.xml。
6) 最后测试一下吧(cn.javass.spring.chapter7. JdbcTemplateTest):
首先读取配置文件,获取IUserDao接口实现,然后再调用IUserDao接口方法,进行数据库操作,这样对于开发人员使用来说,只面向接口,不关心实现,因此很容易更换实现,比如像更换为hibernate实现非常简单。