Spring笔记6---JDBC的集成
一. 无论采用何种持久化技术,JDBC始终都是它们的支撑件,如果没有底层的JDBC,ORM是没有办法操控RDBMS的。
??Spring 对 JDBC的集成主要包含了:
??1.提供各种模板类,简化了对RDBMS的CRUD操作,这些模板包括 ? ? ? ? ? ? ? ? ? ?
?? ? JdbcTemplate,NamedParameterJdbcTemplate,SimpleJdbcTemplate等等。这些模板类都是线程安全的,即在多线程环境中能始终保持一致的行为,尤其是Web环境。
?
??2.提供了各种DaoSupport辅助类,进一步简化了模板类的使用。?比如
?? ? JdbcDaoSupport,NamedParameterJdbcDaoSupport,SimpleJdbcDaoSupport。
?
??3.提供了SimpleJdbcInsert和SimpleJdbcCall辅助类。
?
??4.提供了各种数据源类,借助DI容器实现了DataSource的灵活配置。
?
??5.提供了更多高级辅助类,比如:
?? ? ?将JDBC CRUD操作建模成JAVA对象、LOB处理、操控存储过程、大批量数据处理、获得和生成主键等。
?
二. JdbcTemplate是JDBC集成的核心类,简化了JDBC API的使用。
?
1. 不用重新写调用java.sql.Connection对象的语句。
?
?
jdbcTemplate.execute(new ConnectionCallback(){ public Object doInConnectin(Connection con) throws SQLException,DataAccessException{ log.info(con.getMetaData().getDriverName); return null; }});
?? ?*Spring 自身会从数据源中获得一Connection对象,并传入到doInConnection回调方法中。
?
2. 有若干方法用于执行静态SQL语句,即使用java.sql.Statement API.
?
?
..... execute(....) throws ......... query(.....) throws .......... queryForObject (.....) throws .......... queryForList (.....) throws .......... queryForMap (.....) throws .......... queryForRowSet (.....) throws .....int update (.....) throws .....int[] batchUpdate (.....) throws .....
?? *结果集处理接口:ResultSetExtractor,RowCallbackHandler,RowMapper(线程安全,用的最多,包含许多子集,例如ColumnMapRowMapper,BeanPropertyRowMapper).
?
3. 有若干方法用于执行动态sql语句,即使用java.sql.PrepareStatement API.
?
?
..... execute(....) throws ......... query(.....) throws .......... queryForObject (.....) throws .......... queryForList (.....) throws .......... queryForMap (.....) throws .......... queryForRowSet (.....) throws .....int update (.....) throws .....int[] batchUpdate (.....) throws .....?
?*大多方法都含有Object[] args参数,即这些方法都会采用预编译语句,执行效率更高。Hibernate就大量采用了预编译语句。
?
4. 有若干方法用于执行RDBMS的存储操作,即使用java.sql.CallableStatement API.
?
?
..... execute(....) throws ....Map call(CallablestatementCreator csc,List declaredParameters) throws DataAccessException;?
三. JdbcDaoSupport是建立在JdbcTemplate基础上的支持类,其辅助类会自动创建相应模板类。
?
?? ?在使用JdbcDaoSupportImpl实现类之前,需要配置示例如下:
?
?
<bean id="jdbcDaoSupport" ref="dataSource"/><bean>
?
?? ?或者直接提供一个JdbcTemplate给JdbcDaoSupportImpl,如下配置:
?
?
<bean id="jdbcDaoSupport" ref="jdbcTemplate"/><bean>?
四. SimpleJdbcDaoSupport及NamedParameterJdbcDaoSupport;NamedParameterJdbcTemplate及 ? ? SimpleJdbcTemplate的相应支持类。
?
?例如:SimpleJdbcInsert和SimpleJdbcCall支持辅助类,前者用于操作表,后者用于操作存储过程或函数,它们的某些功能比SimpleJdbcTemplate更为强大,而且都是线程安全的。
?
*.SimpleJdbcInsert的usingGeneratedKeyColumns()能自动生成主键id,例子如下:
?
?
ownerInsert = new SimpleJdbcInsert(dataSource).withTableName("owner").usingGeneratedKeyColumns("id");param = new HashMap<String,Object>();param.put("fistname","Nedd");param.put("lastname","Want");log.info(ownerInsert.executeAndReturnKey(param));
?
五.内置的DataSource继承。
?
1.基于测试目的DriverManagerDataSource,SimpleDriverDataSource和SingleConnectionDataSource.
?
2.Apache DBCP数据源。(实现了连接池)
?
?
<bean id = "dataSource" destroy-method="close" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" />?
??上面的bean中配置了destroy-method属性,当销毁Spring DI容器时,BasicDataSource对象的close()方法便会被触发,从而优雅地关闭掉底层连接池持有的数据库连接。
?
3. 其他数据源。
?
?? *Java EE容器内置的数据源:能从JNDI树上查找到已经注册的DataSource实现。示例如下:
?
?
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/petclinic">?
?
?
?? *LazyConnectionDataSourceProxy:会代理其他数据源。
?
?? *TransactionAwareDataSourceProxy:能使得当前的CRUD操作能够参与到Spring的受管事务中。如果当前不存在活动事务,则会使用默认行为,通常在开发中很少用到。
?
?? *UserCredentialsDataSourceAdapter: 将连接数据库的用户凭证信息(用户名,密码)传入到getConnection(String username,String password)方法中。一旦配置好后,客户每次获得数据库连接,都会调用getConnection方法。
?
?? *IsolationLevelDataSourceAdapter:启用isolationLevelName属性能指定隔离级别。继承于UserCredentialsDataSourceAdapter。
?
?? *WebSphereDataSourceAdapter:继承于IsolationLevelDataSourceAdapter,会从WebSphere容器获得数据源。并借助WebSphere专有的api达到启用自定义隔离级别的目的。
?
?? *IsolationLevelDataSourceRouter: 会根据当前Spring受管事务启用的隔离级别来选定合适的DataSource数据源。
?
六. 将JDBC操作建模成Java对象。
?
?? 1. SqlUpdate辅助类
?? 2. UpdatableSqlQuery辅助类
?? 3. MappingSqlQuery辅助类 :能将SQL select查询结果集自动转换成领域对象集合,返回给调用者。其子类必须实现mapRow()方法,它内置大量的execute()和findObject()方法。
?? 4. SqlFunction辅助类。
?
七. 与存储过程交互。
?
?? 1. JdbcTemplate针对存储过程提供的支持。
?
?
Object execute(CallableStatementCreator csc, CallableStatmentCallback action) throws DataAccessException;Object execute(String callString, CallableStatmentCallback action) throws DataAccessException;Object execute(CallableStatementCreator csc, List declaredParameters) throws DataAccessException;?
?如果使用了CallableStatmentCallback回调接口,则要实现doInCallableStatement()方法。
?
?? 2. StoredProcedure辅助类。
?
?? 3. SimpleJdbcCall 辅助类。重要辅助类。
?
?
SimpleJdbcCall sjc = new SimpleJdbcCall(ds);sjc.withProcedureName("show_sal");sqlParameterSource sps = new MapSqlParameterSource().addValue("enames","SMITH").addValue("empnos",7369);log.info(sjc.execute(sps));?
以上开发者直接将存储过程名告知withProcedureName()方法,并将参数提供给了execute方法。
?
如果需要制定传入传出参数,可借助SimpleJdbcCall内置的 declareParameters()方法,示例如下,SqlParameters用于指定传入参数,而SqlOutParameters用于指定传出参数:
?
?
SimpleJdbcCall sjc = new SimpleJdbcCall(ds).withProcedureName("show_sal");sjc.declareParameters(new SqlParameter("enames",Types.VARCHAR)).declareParameters(new SqlParameter("empnos",Types.VARCHAR)).declareParameters(new SqlParameter("sals",Types.FLOAT)).withoutProcedureColumnMetaDataAccess();sqlParameterSource sps = new MapSqlParameterSource().addValue("enames","SMITH").addValue("empnos",7369);log.info(sjc.execute(sps));?
一旦启用了declareParameters()方法,withoutProcedureColumnMetaDataAccess()方法也就该被调用,从而避免再去从底层jdcb驱动获得RDBMS相关元数据,比如存储过程的输入输出参数。
?
?
八. 处理大批量数据。
?
1. JdbcTemplate内置的batchUpdate()方法。
?
?
int[] batchUpdate(String[] sql) throws DataAccessException;int[] batchUpdate(String[] sql,BatchPreparedStatementSetter pss) throws DataAccessException;
?
?
例如:
?
?
final int[] no= new int[]{324,543,1234,421,32142,3212};jdbcTemplate.batchUpdate("update emp set sal = ? where empno = ?",new BatchPreparedStatementSetter(){ public void setValues(PreparedStatement ps,int i) throws SQLException{ ps.setInt(1,no[i]); ps.setFloat(2,no[i]);} public int getBatchSize(){ return no.length;}}){}?
?
2. SimpleJdbcTemplate内置的batchUpdate方法。
?
int[] batchUpdate(String sql,Map[] batchValues) ;int[] batchUpdate(String sql,SqlParameterSource[] batchArgs) ;int[] batchUpdate(String sql,List<Object> batchArgs);int[] batchUpdate(String sql,List<Object> batchArgs,int[] argTypes);?
List对象作为参数示例:
?
SimpleJdbcTemplate sjt = xxx.getBean("simpleJdbcTemplate");List<Object[]> paramList = new ArrayList<Object[]>();paramList.add(new Integer[]{2342,23423});paramList.add(new Integer[]{2342,23423});paramList.add(new Integer[]{2342,23423});paramList.add(new Integer[]{2342,23423});paramList.add(new Integer[]{2342,23423});sjt.batchUpdate("update emp set sal = ? where empno = ?",paramList);
?最后一sql语句也可以将sql类型告知给batchUpdate(),如下:
?
sjt.batchUpdate("update emp set sal = ? where empno = ?",paramList,new int[]{Types.INTEGER,Types.FLOAT});?
?
九. 基于JDBC的LOB集成支持。
?
为操控LOB字段,Spring能应用需要启用LobHandler继承链。当前,Spring内置了DefaultLobHandler和OracleLobHandler实现类。通常除了Oracle数据库外,开发者可直接配置DefaultLobHandler实例即可。
如果用OracleLobHandler,还的为它配置NativeJdbcExtractor对象,以获得底层数据库的原生连接。
?
<bean id="imageDatabase" p:dataSource-ref="dataSource" p:lobHandler-ref="defaultLobHandler"/><bean id="defaultLobHandler" p:dataSource-ref="dataSource" lazy-init="true"/>?
1. 读取
?
借用了AbstractLobStreamingResultSetExtractor回调类,这里调用了LobHandler的getBlobAsBinaryStream()方法,即将Blob转换成了二进制流。
?
@Transactional(readOnly=true)public void streamImage(final name,final OutputStream contentstream) throws DataAccessException{ getJdbcTemplate().query{ "select content from imagedb where image_name=?",new Object[]{name}, new AbstractLobStreamingResultSetExtractor(){ protected void handleNoRowFound() throws LobRetrievalFailureException{ throw new EmptyResultDataAccessException( "image with name '"+name+"' not found in database",1); } public void streamData(ResultSet rs) throws SQLException,IOException{ InputStream is = lobHandler.getBlobAsBinaryStream(rs,1); if(is!=null){ FileCopyUtils.copy(is.contentstream); } } } }}?
2.存储
?
要使用LobHandler内置的LobCreator对象。需要启用AbstractLobCreatingPreparedStatementCallback回调类。借助LobCreator的setBlobAsBinaryStream()方法能将输入流存储到Blob字段中。setClobAsString()方法能将大文本块存储到Clob字段中。
?
@Transactionalpublic void storeImage( final String name, final InputStream contentstream, final int contentLength, final String description) throws DataAccessException{ getJdbcTemplate().execute("insert into imagedb(image_name,content,description) values (?,?,?)",new AbstractLobCreatingPreparedStatementCallback(this.lobHandler)){ protected void setValues( PreparedStatement ps, LobCreator lobCreator ) throws SQLException{ ps.setString(1,name); lobCreator.setBlobAsBinaryStream( ps,2,contentstream,contentlength); lobCreator.setClobAsString(ps,3,description); } } );}?
十. 获得和生成主键。
?
KeyHolder kh = new GeneratedKeyHolder();int update(PreparedStatementCreator psc,KeyHolder generatedKeyHolder) throws DataAccessException;?
?
十一. 还需要注意的。
?
1. 要合理设置Statement的fetchSize大小,即JdbcTemplate的fetchSize变量取值。(查询时,权衡时间和空间的优化)。
?
2. 大批量存储时,开发者要控制每批数据的数据量。设置batchSize的大小。
?
3. 尽量采用PreparedStatement操控数据库,而不是Statement。
?
4. 充分挖掘JdbcTemplate,NamedParameterJdbcTemplate,SimpleJdbcTemplate,SimpleJdbcInsert,SimpleJdbcCall等辅助类的功能。不要直接使用Connection对象操控。
?
?
?
?
?
?
?