Spring-jdbcTempalate研究
作者:互联网
很多时候,需要使用jdbcTemplate,既有出于性能考虑的因素,也有出于个人偏好。
关于jdbcTemplate的几个关键性的问题:
一、简介
JdbcTemplate位于org.springframework包,组件标识为spring-jdbc。
处于spring家族的核心区域。spring专注于应用开发,应用开发据大部分和数据库有关,数据库的操作主要由jdbc负责。
用spring.io自己的话说,spring-jdbc就是默默地干了大家不愿意干,但又不得不干的事情。
具体哪些是我们不愿意干的,看spring自己提供的图:
x表示需要做的。
要研究透JdbcTemplate,其实光JdbcTemplate自身是不够,还需要了解jdbc的其它一些内容,如果要彻底研究,请阅读spring.io有关的内容。
限于篇幅,本文只讨论jdbcTempalte等几个template。
二、传递SQL参数
从jdbc底层来说,只有一种传递参数的方式,下面来看参考代码:Lesson: JDBC Basics (The Java™ Tutorials > JDBC Database Access) (oracle.com)
Processing SQL Statements with JDBC (The Java™ Tutorials > JDBC Database Access > JDBC Basics) (oracle.com)
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { String updateString = "update COFFEES set SALES = ? where COF_NAME = ?"; String updateStatement = "update COFFEES set TOTAL = TOTAL + ? where COF_NAME = ?"; try (PreparedStatement updateSales = con.prepareStatement(updateString); PreparedStatement updateTotal = con.prepareStatement(updateStatement)) { con.setAutoCommit(false); for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey()); updateTotal.executeUpdate(); con.commit(); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); if (con != null) { try { System.err.print("Transaction is being rolled back"); con.rollback(); } catch (SQLException excep) { JDBCTutorialUtilities.printSQLException(excep); } } } }
在原生jdbc中,使用?表示一个参数,?起到占位的作用。
Spring jdbcTemplate为了传递参数方便,支持多种表示参数和设置参数的方式。
表示参数的方式:
a.占位,使用?表示
b.命名,使用":参数名“表示
传递参数的几种方式:
a.不定大小的数组,集合。通常对应占位传参
b.Map,Bean。通常对应命名参数
来看看Spring JdbcTemplate的一些源码:
JdbcTemplate org.springframework.jdbc.core.JdbcTemplate.batchUpdate(String, Collection<T>, int, ParameterizedPreparedStatementSetter<T>) org.springframework.jdbc.core.JdbcTemplate.batchUpdate(String, List<Object[]>) org.springframework.jdbc.core.JdbcTemplate.query(String, Object[], int[], ResultSetExtractor<T>) org.springframework.jdbc.core.JdbcTemplate.update(String, Object...) NamedParamterJdbcTempalte org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(String, Map<String, ?>) org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.queryForObject(String, SqlParameterSource, Class<T>)
大部分传参都容易理解。命名参数传递总体比较优雅,比较好维护,除了写sql的时候会有那么一点点麻烦。
但我们感兴趣的是SqlParameterSource
我们来看下SqlParameterSource
* @author Thomas Risberg * @author Juergen Hoeller * @since 2.0 * @see NamedParameterJdbcOperations * @see NamedParameterJdbcTemplate * @see MapSqlParameterSource * @see BeanPropertySqlParameterSource */ public interface SqlParameterSource SqlParameterSource 接口有三个真正的实现: org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource org.springframework.jdbc.core.namedparam.MapSqlParameterSource org.springframework.jdbc.core.namedparam.EmptySqlParameterSource 其中BeanPropertySqlParameterSource特别受一些人喜欢(有些人喜欢把任何东西包装成bean) BeanPropertySqlParameterSource的源码注释: SqlParameterSource implementation that obtains parameter valuesfrom bean properties of a given JavaBean object. The names of the beanproperties have to match the parameter names. Uses a Spring BeanWrapper for bean property access underneath.
下面来看看一个例子:
@Override @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,rollbackFor=Exception.class) public int addFamilyWithNJT2(String name) { String sql="insert into family(name) values(:name)"; //使用bean/pojo传递参数 Family family=new Family(name); KeyHolder keyHolder=new GeneratedKeyHolder(); SqlParameterSource paramSource=new BeanPropertySqlParameterSource(family); int qty=njdbcTp.update(sql, paramSource, keyHolder); JSONObject.toJSONString(paramSource, true); return keyHolder.getKey().intValue(); }
三、批处理执行
批量执行,多用于数据导入,采集的业务场景。
当然,如果是对付高速大量的数据导入,不建议使用目前这种方式,建议直接使用原生的jdbc或者是数据库产生的api来操作。
只不过,只要咱的数据量不是太大,一般也够用。
下面来个例子:
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class) @Override public String batchExecute() { /** * 几种基本的batch操作 */ String sql = "insert into family(name,batch_no) values(?,?)"; //1.0 ParameterizedPreparedStatementSetter List<Object[]> argList = new ArrayList<>(); String batchNo = UUID.randomUUID().toString(); for (int i = 0; i < 2; i++) { Object[] a = new Object[2]; a[0] = UUID.randomUUID().toString(); a[1] = batchNo; argList.add(a); } jdbcTp.batchUpdate(sql, argList, 4, (PreparedStatement ps, Object[] argument) -> { ps.setObject(1, argument[0]); ps.setObject(2, argument[1]); }); //2.0 BatchPreparedStatementSetter BatchPreparedStatementSetter btss = new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setObject(1, argList.get(i)[0]); ps.setObject(2, argList.get(i)[1]); } @Override public int getBatchSize() { //这个大小不能超过参数集合大小,否则会报错。 return argList.size(); } }; int[] qtys = jdbcTp.batchUpdate(sql, btss); int ttlQty = 0; for (int i = 0, len = qtys.length; i < len; i++) { ttlQty += qtys[i]; } System.out.println(ttlQty); return batchNo; }
四、插入并返回key
@Override public int addFamilyWithJT(String name) { KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTp.update((Connection con) -> { String sql = "insert into family(name) values(?)"; PreparedStatement ps =con.prepareStatement(sql, new String[]{"custom_id"}); ps.setInt(1, Integer.valueOf(name)); return ps; }, keyHolder); return keyHolder.getKey().intValue(); }
五、查询并返回bean/pojo
@Override public HcsThirdsrv getByName(String serviceName) { String sql="SELECT\r\n" + " service_name,\r\n" + " service_name_cn,\r\n" + " instance_service_name,\r\n" + " service_desc,\r\n" + " status_flag,\r\n" + " add_time,\r\n" + " last_optime\r\n" + "FROM\r\n" + " hcs_thirdsrv \n" + "where service_name=? or service_name_cn=? \n" + "limit 1"; RowMapper<HcsThirdsrv> rowMapper =new BeanPropertyRowMapper<HcsThirdsrv>(HcsThirdsrv.class); HcsThirdsrv srv=this.jdbcTp.queryForObject(sql, rowMapper,serviceName); return srv; }
spring对于返回bean的支持并不友好,希望以后的版本,能够直接出一个不用rowMapper的(现在我们自己都是对jdbcTemplate再封装一遍)。
六、性能
1.比较-mybatis、原生jdbc
主要是和mybatis比较,网上有专门的测试,例如:
https://blog.csdn.net/liulk20170518/article/details/119358143
但不是很多,也比较老旧。此外考虑到mybatis的不断进化。
但毫无疑问,mybatis总会比jdbcTemplate慢一些,因为它花了额外的一些时间做七七八八的处理。
执行速度上,原生jdbc>jdbcTemplate>mybatis,这是没有异议的。
我们很多项目还大量使用mybatis,主要是出于工程考虑:用cpu的速度来弥补工程师的思维能力欠缺和手动速度,以提高工程效率。
灵活优雅,有时候就是慢的代名词。
简单粗暴,有时候能够更快解决问题。
2.如何优化
java的主要性能消耗在于数据转换、反射和解析,后者是先天不可调整,所以只能尽量减少反射操作和数据转换。
所以,如果可能的话,执行sql的时候,尽量使用ListMap或者Map来返回结果。如果您看不习惯没有关系,只要快就可以了。
七、异常
spring提供了几个常见的异常:
- BadSqlGrammarException --语法错误
- CannotGetJdbcConnectionException -- 获取连接异常
- IncorrectResultSetColumnCountException -- 错误结果集合列数异常,例如本来只要一列的,现在有2列
- InvalidResultSetAccessException -- 不可用的结果集合读取异常,通常发生在列位置或者名称设置错误的情况
- JdbcUpdateAffectedIncorrectNumberOfRowsException -- 实际影响行数超出预计的异常。例如本来应该只影响1行,但现在2行
- LobRetrievalFailureException -- 读取大字段数据失败
- SQLWarningException -- sql警告异常。没有特别说明
- UncategorizedSQLException -- 无分类sql异常
实际执行的时候,更可能抛出的是org.springframework.dao下异常,这个包路径属于spring事务模块。
在这个包里面,有更多更在明确的异常说明,例如下图:
八、和spring其它组件关系
影响比较多,其中主要是事务。具体略。
九、适用场景
- 对性能要求较高的。
- 个性化要求高的。有些sql在mapper中写有点麻烦,尤其一些复杂的sql。
- 不想多一个依赖的,例如一些核心的东西。能够少依赖就尽量少依赖。这种情况下,可能直接上原生jdbc了,连jdbcTemplate都不用了
因为这个缘故,所以spring提供了spring-jdbc组件。
例如公用组件,核心工具,应该尽量少依赖外部的框架。当然实际做的时候,更可能取决于公司的规模和实例,项目和产品的性质。
如果我们想的再长远一些,那么java是否真有存在的必要性?除了强大的生态,java并没有什么傲人的优势,而计算机最大优势就是人对于速度的最求,所以java要存活下去,则必须
修改jvm和编译器,一处编译处处执行,并没有那么大的必要性,尤其是做项目,虽然的确有的时候是不错。
标签:jdbc,String,jdbcTempalate,研究,Spring,spring,sql,org,name 来源: https://www.cnblogs.com/lzfhope/p/16071399.html