ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# 【第七章】 对JDBC的支持 之 7.4 Spring提供的其它帮助 ——跟我学spring3【私塾在线原创】 7.4  Spring提供的其它帮助 ### 7.4.1  **SimpleJdbc方式** Spring JDBC抽象框架提供SimpleJdbcInsert和SimpleJdbcCall类,这两个类通过利用JDBC驱动提供的数据库元数据来简化JDBC操作。 1、SimpleJdbcInsert: 用于插入数据,根据数据库元数据进行插入数据,本类用于简化插入操作,提供三种类型方法:execute方法用于普通插入、executeAndReturnKey及executeAndReturnKeyHolder方法用于插入时获取主键值、executeBatch方法用于批处理。 ``` @Test public void testSimpleJdbcInsert() { SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTemplate); insert.withTableName("test"); Map<String, Object> args = new HashMap<String, Object>(); args.put("name", "name5"); insert.compile(); //1.普通插入 insert.execute(args); Assert.assertEquals(1, jdbcTemplate.queryForInt("select count(*) from test")); //2.插入时获取主键值 insert = new SimpleJdbcInsert(jdbcTemplate); insert.withTableName("test"); insert.setGeneratedKeyName("id"); Number id = insert.executeAndReturnKey(args); Assert.assertEquals(1, id); //3.批处理 insert = new SimpleJdbcInsert(jdbcTemplate); insert.withTableName("test"); insert.setGeneratedKeyName("id"); int[] updateCount = insert.executeBatch(new Map[] {args, args, args}); Assert.assertEquals(1, updateCount[0]); Assert.assertEquals(5, jdbcTemplate.queryForInt("select count(*) from test")); } ``` * **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: 用于调用存储过程及自定义函数,本类用于简化存储过程及自定义函数调用。 ``` @Test public void testSimpleJdbcCall1() { //此处用mysql,因为hsqldb调用自定义函数和存储过程一样 SimpleJdbcCall call = new SimpleJdbcCall(getMysqlDataSource()); call.withFunctionName("FUNCTION_TEST"); call.declareParameters(new SqlOutParameter("result", Types.INTEGER)); call.declareParameters(new SqlParameter("str", Types.VARCHAR)); Map<String, Object> outVlaues = call.execute("test"); Assert.assertEquals(4, outVlaues.get("result")); } ``` * **new SimpleJdbcCall(getMysqlDataSource())** :通过DataSource对象或JdbcTemplate对象初始化SimpleJdbcCall; * **withFunctionName("FUNCTION_TEST")** **:** 定义自定义函数名;自定义函数sql语句将被编译为类似于{?= call …}形式; * **declareParameters** **:** 描述参数类型,使用方式与StoredProcedure对象一样; * **执行:** 调用execute方法执行自定义函数; ``` @Test public void testSimpleJdbcCall2() { //调用hsqldb自定义函数得使用如下方式 SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate); call.withProcedureName("FUNCTION_TEST"); call.declareParameters(new SqlReturnResultSet("result", new ResultSetExtractor<Integer>() { @Override public Integer extractData(ResultSet rs) throws SQLException, DataAccessException { while(rs.next()) { return rs.getInt(1); } return 0; }})); call.declareParameters(new SqlParameter("str", Types.VARCHAR)); Map<String, Object> outVlaues = call.execute("test"); Assert.assertEquals(4, outVlaues.get("result")); } ``` 调用hsqldb数据库自定义函数与调用mysql自定义函数完全不同,详见StoredProcedure中的解释。 ``` @Test public void testSimpleJdbcCall3() { SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate); call.withProcedureName("PROCEDURE_TEST"); call.declareParameters(new SqlInOutParameter("inOutName", Types.VARCHAR)); call.declareParameters(new SqlOutParameter("outId", Types.INTEGER)); SqlParameterSource params = new MapSqlParameterSource().addValue("inOutName", "test"); Map<String, Object> outVlaues = call.execute(params); Assert.assertEquals("Hello,test", outVlaues.get("inOutName")); Assert.assertEquals(0, outVlaues.get("outId")); } ``` 与自定义函数调用不同的是使用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** **获取自动生成主键方式:** ``` @Test public void testFetchKey1() throws SQLException { final String insertSql = "insert into test(name) values('name5')"; KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection conn) throws SQLException { return conn.prepareStatement(insertSql, new String[]{"ID"}); }}, generatedKeyHolder); Assert.assertEquals(0, generatedKeyHolder.getKey()); } ``` 使用JdbcTemplate的update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)方法执行需要返回自动生成主键的插入语句,其中psc用于创建PreparedStatement并指定自动生成键,如“prepareStatement(insertSql, new String[]{"ID"})”;generatedKeyHolder是KeyHolder类型,用于获取自动生成的主键或复合主键;如使用getKey方法获取自动生成的主键。 **2** **)SqlUpdate** **获取自动生成主键方式:** ``` @Test public void testFetchKey2() { final String insertSql = "insert into test(name) values('name5')"; KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); SqlUpdate update = new SqlUpdate(); update.setJdbcTemplate(jdbcTemplate); update.setReturnGeneratedKeys(true); //update.setGeneratedKeysColumnNames(new String[]{"ID"}); update.setSql(insertSql); update.update(null, generatedKeyHolder); Assert.assertEquals(0, generatedKeyHolder.getKey()); } ``` SqlUpdate获取自动生成主键方式和JdbcTemplate完全一样,可以使用setReturnGeneratedKeys(true)表示要获取自动生成键;也可以使用setGeneratedKeysColumnNames指定自动生成键列名。 **3** **)SimpleJdbcInsert** **:** 前边示例已介绍,此处就不演示了。 ### 7.4.4  JDBC批量操作 JDBC批处理用于减少与数据库交互的次数来提升性能,Spring JDBC抽象框架通过封装批处理操作来简化批处理操作 **1** **)JdbcTemplate** **批处理:** 支持普通的批处理及占位符批处理; ``` @Test public void testBatchUpdate1() { String insertSql = "insert into test(name) values('name5')"; String[] batchSql = new String[] {insertSql, insertSql}; jdbcTemplate.batchUpdate(batchSql); Assert.assertEquals(2, jdbcTemplate.queryForInt("select count(*) from test")); } ``` 直接调用batchUpdate方法执行需要批处理的语句即可。 ``` @Test public void testBatchUpdate2() { String insertSql = "insert into test(name) values(?)"; final String[] batchValues = new String[] {"name5", "name6"}; jdbcTemplate.batchUpdate(insertSql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, batchValues[i]); } @Override public int getBatchSize() { return batchValues.length; } }); Assert.assertEquals(2, jdbcTemplate.queryForInt("select count(*) from test")); } ``` JdbcTemplate还可以通过batchUpdate(String sql, final BatchPreparedStatementSetter pss)方法进行批处理,该方式使用预编译语句,然后通过BatchPreparedStatementSetter实现进行设值(setValues)及指定批处理大小(getBatchSize)。 **2** **)NamedParameterJdbcTemplate** **批处理:** 支持命名参数批处理; ``` @Test public void testBatchUpdate3() { NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); String insertSql = "insert into test(name) values(:myName)"; UserModel model = new UserModel(); model.setMyName("name5"); SqlParameterSource[] params = SqlParameterSourceUtils.createBatch(new Object[] {model, model}); namedParameterJdbcTemplate.batchUpdate(insertSql, params); Assert.assertEquals(2, jdbcTemplate.queryForInt("select count(*) from test")); } ``` 通过batchUpdate(String sql, SqlParameterSource[] batchArgs)方法进行命名参数批处理,batchArgs指定批处理数据集。SqlParameterSourceUtils.createBatch用于根据JavaBean对象或者Map创建相应的BeanPropertySqlParameterSource或MapSqlParameterSource。 **3) SimpleJdbcTemplate** **批处理:** 已更简单的方式进行批处理; ``` @Test public void testBatchUpdate4() { SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(jdbcTemplate); String insertSql = "insert into test(name) values(?)"; List<Object[]> params = new ArrayList<Object[]>(); params.add(new Object[]{"name5"}); params.add(new Object[]{"name5"}); simpleJdbcTemplate.batchUpdate(insertSql, params); Assert.assertEquals(2, jdbcTemplate.queryForInt("select count(*) from test")); } ``` 本示例使用batchUpdate(String sql, List&lt;Object[]&gt; batchArgs)方法完成占位符批处理,当然也支持命名参数批处理等。 **4** **)SimpleJdbcInsert** **批处理:** ``` @Test public void testBatchUpdate5() { SimpleJdbcInsert insert = new SimpleJdbcInsert(jdbcTemplate); insert.withTableName("test"); Map<String, Object> valueMap = new HashMap<String, Object>(); valueMap.put("name", "name5"); insert.executeBatch(new Map[] {valueMap, valueMap}); Assert.assertEquals(2, jdbcTemplate.queryForInt("select count(*) from test")); } ``` 如代码所示,使用executeBatch(Map&lt;String, Object&gt;[] batch)方法执行批处理。