很多时候,需要使用jdbcTemplate,既有出于性能考虑的因素,也有出于个人偏好。

关于jdbcTemplate的几个关键性的问题:

一、简介

JdbcTemplate位于org.springframework包,组件标识为spring-jdbc。

处于spring家族的核心区域。spring专注于应用开发,应用开发据大部分和数据库有关,数据库的操作主要由jdbc负责。

用spring.io自己的话说,spring-jdbc就是默默地干了大家不愿意干,但又不得不干的事情。

具体哪些是我们不愿意干的,看spring自己提供的图:

x表示需要做的。

本文不讨论jdbcTemplate是如何做了大家不想做的事情,而是讨论能用jdbcTemplate做什么。

要研究透JdbcTemplate,其实光JdbcTemplate自身是不够,还需要了解jdbc的其它一些内容,如果要彻底研究,请阅读spring.io有关的内容。

限于篇幅,本文只讨论jdbcTempalte等几个template。

关键字列表:

  • DataSource
  • DataSourceUtils
  • Connection
  • RowMapper
  • SqlParameterSource
  • ListMap
  • InitializingBean

二、传递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;
}

四、插入并返回自增主键值

 @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其它组件关系

影响比较多,其中主要是事务。其余略。

如何保证事务?

从原生jdbc可以看出,要完成一个事务,它的代码大概是这样的:

https://www.cnblogs.com/azhqiang/p/4044127.html
------------------------------------------------------------
private Connection conn = null;
private PreparedStatement ps = null;
try {
conn.setAutoCommit(false); //将自动提交设置为false
ps.executeUpdate("修改SQL"); //执行修改操作
ps.executeQuery("查询SQL"); //执行查询操作
conn.commit(); //当两个操作成功后手动提交
} catch (Exception e) {
conn.rollback(); //一旦其中一个操作出错都将回滚,使两个操作都不成功
e.printStackTrace();
}

保持事务的关键在于:使用同一个连接。

以oracle为例子,不同的连接就是不同的会话,它们之间的事务是无关的。这个原则在绝大部分rdbms上是一样的成立的,这也即使rdbms存在的主要理由之一。

如果spirng要完成事务的关键就是保证在事务传递的情况下,能够使用同样的一个连接(大部分情况下)。

这里就涉及到spring的事务组件spring-tx和spring-jdbc中的DataSourceUtils。

spring-tx如何如何保证连接的一致性,是一个有点小小复杂的事情,本文略,总之道路就是这个道理。

九、有关工具类

  • DataSourceUtils
  • JdbcUtils

在只有一个数据源的环境中获取当前数据源(仅限于特定环境):

package study.spring;

import javax.sql.DataSource;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component; @Component
public class SpringbootAwareHelper implements BeanFactoryAware, ApplicationContextAware { private static BeanFactory beanFactory;
private static ApplicationContext appContext; @Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
SpringbootAwareHelper.beanFactory = beanFactory;
} public static <T> T getBean(String id, Class<T> type) {
return beanFactory.getBean(id, type);
} public static <T> T getBean(Class<T> type) {
return beanFactory.getBean(type); } @SuppressWarnings("unchecked")
public static <T> T getBean(String beanName) {
return (T) beanFactory.getBean(beanName);
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
Object ds = applicationContext.getBean("dataSource");
if (ds != null) {
System.out.println("当前的连接池是:" + ds.getClass().getName());
} else {
System.out.println("没有连接池!");
}
// 打印加载的bean信息 /*String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}*/
} public static DataSource getCurrentDatasource() {
return appContext.getBean(DataSource.class);
} }

十、适用场景

  1. 对性能要求较高的。
  2. 个性化要求高的。有些sql在mapper中写有点麻烦,尤其一些复杂的sql。
  3. 不想多一个依赖的,例如一些核心的东西。能够少依赖就尽量少依赖。这种情况下,可能直接上原生jdbc了,连jdbcTemplate都不用了

因为这个缘故,所以spring提供了spring-jdbc组件。

例如公用组件,核心工具,应该尽量少依赖外部的框架。当然实际做的时候,更可能取决于公司的规模和实例,项目和产品的性质。

如果我们想的再长远一些,那么java是否真有存在的必要性?除了强大的生态,java并没有什么傲人的优势,而计算机最大优势就是人对于速度的最求。所以java要存活下去,则必须

修改jvm和编译器,虽然现在已经不断在优化,但是还不够。最好的办法是提供一个编译器直接在运行主机上进行编译打包,不要耗费时间每次去询问。

一处编译处处执行,并没有那么大的必要性,尤其是做项目。

Spring-jdbcTempalate研究的更多相关文章

  1. spring jpetstore研究入门(zz)

    spring jpetstore研究入门 分类: java2008-12-21 23:25 561人阅读 评论(2) 收藏 举报 springstrutsibatissearchweb框架servle ...

  2. Spring Security研究(2)-高级web特性

    1, 添加 HTTP/HTTPS 信道安全 <http> <intercept-url pattern="/secure/**" access="ROL ...

  3. Spring Security研究(1)

      1, 获取Spring Security的Jar包 :从Spring网站下载页下载或者从Maven中央仓库下载.一个好办法是参考实例应用中包含的依赖库. 2,项目模块: Core - spring ...

  4. (满满的是硬货)Spring深入研究一IOC实现

    IOC基于Java底层的反射机制实现 反射机制: 核心: Class cls = Class.forName(类名); Class ptypes[] = new Class[2]; ptypes[0] ...

  5. Spring启动研究2.AbstractApplicationContext.obtainFreshBeanFactory()研究

    据说这个方法里面调用了loadBeanDefinitions,据说很重要,并且跟我感兴趣的标签解析有关,所以仔细看看 obtainFreshBeanFactory()这个方法在AbstractAppl ...

  6. spring 第一篇(1-3):鸟瞰spring蓝图

    如你所见,spring框架的核心是关注于如何使用DI.AOP和模板来让企业级java开发变得更简单.spring确实也是这样做的,所以很值得你去使用它.不过spring内容可能比你所能看到的要多很多. ...

  7. JAVA平台AOP技术研究

    3.1 Java平台AOP技术概览 3.1.1 AOP技术在Java平台中的应用 AOP在实验室应用和商业应用上,Java平台始终走在前面.从最初也是目前最成熟的AOP工具--AspectJ,到目前已 ...

  8. 我的J2EE学习历程

    由于最近手头没有JSP项目,所以暂停Hibernate和Spring的研究.个人觉得只有发现某个东西的不足之后再去学习新的东西来弥补这个不足比较好.就好比,最开始在JSP页面里面写Java代码,每次操 ...

  9. Druid Monitor小记

    继上篇DruidDataSource源码分析之后 , 公司又要求做一个Druid的数据库监控 , 以及spring监控 , 研究一小时 , 总结出了一点经验 , 特此贴出来分享一下 一 . 利用Dru ...

  10. Spring中WebApplicationContext的研究

    Spring中WebApplicationContext的研究 ApplicationContext是Spring的核 心,Context我们通常解释为上下文环境,我想用“容器”来表述它更容易理解一些 ...

随机推荐

  1. MSIL 静态类在 IL 定义上和非静态类的差别

    本文来聊聊 MSIL 的基础知识,给一个 C# 的类标记了 static 之后和标记 static 之前,生成这个类的 IL 代码有什么不同 如以下的代码是一个默认的控制台程序 class Progr ...

  2. 兼容ie8问题

    <!-- 让IE8/9支持媒体查询,从而兼容栅格 --><!--[if lt IE 9]><script src="https://cdn.staticfile ...

  3. GIS中XYZ瓦片的加载流程解析与实现

    1. 什么是XYZ瓦片 XYZ瓦片是一种在线地图数据格式,常见的地图底图如Google.OpenStreetMap 等互联网的瓦片地图服务,都是XYZ瓦片,严格来说是ZXY规范的地图瓦片 ZXY规范的 ...

  4. Educational Codeforces Round 162 (Rated for Div. 2) E

    E:Link 枚举路径两端的颜色 \(k\). 令 \(g[x]\) 表示满足以下条件的点 \(y\) 数量. $ y \in subtree[x]$ \(col[y] = k\) \(y\) 到 \ ...

  5. 【简说Python WEB】Bootstrap

    目录 [简说Python WEB]Bootstrap Bootstrap的导航组件应用 404,500错误页面定制化 系统环境:Ubuntu 18.04.1 LTS Python使用的是虚拟环境:vi ...

  6. kube-proxy 流量流转方式

    简介 kube-proxy 是 Kubernetes 集群中负责服务发现和负载均衡的组件之一.它是一个网络代理,运行在每个节点上, 用于 service 资源的负载均衡.它有两种模式:iptables ...

  7. ssl协议存在弱加密算法修复,禁用低版本的TLS

    验证用网站:https://www.ssleye.com/ssltool/cipher_suites.html https://www.site24x7.com/zhcn/tools/tls-chec ...

  8. mongodb的备份与恢复详解

    简单 Mongodb导出与导入 1: 导入/导出可以操作的是本地的mongodb服务器,也可以是远程的.所以,都有如下通用选项:-h host 主机--port port 端口-u username ...

  9. ansible功能实现

    模糊匹配远程主机文件并拉取到本地服务器 又熬夜加班了.花很长时间研究出来.如何实现模糊匹配到的远程文件批量拉取到本地的剧本.使用copy模块的*,shll模块的* ls|grep XX都没有实现,貌似 ...

  10. lodash已死?radash库方法介绍及源码解析 —— 对象方法篇

    写在前面 主页有更多其他篇章的方法,欢迎访问查看. 本篇我们介绍radash中对象相关方法的使用和源码解析. assign:递归合并两个对象 使用说明 功能说明:类似于 JavaScript 的 Ob ...