很多时候,需要使用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. [Gin] gin-jwt 中间件的请求流程与使用思路

    gin-jwt 中间件是对 jwt-go 的封装以适应 gin 框架.gin-jwt 对不同的请求流程有不同的 handler: 登录请求流程 是用 LoginHandler. 需要 jwt 令牌的后 ...

  2. WPF 使用 ManipulationDemo 工具辅助调试设备触摸失效问题

    本文将和大家介绍我所在的团队开源的 ManipulationDemo 工具.通过 ManipulationDemo 工具可以提升调试设备触摸失效的效率 此工具在 GitHub 上完全开源,请看 htt ...

  3. Unlink原理和一些手法

    Unlink原理和一些手法 简单介绍一下unlink相关的知识 unlink是利用glibc malloc 的内存回收机制造成攻击的,核心就在于当两个free的堆块在物理上相邻时,会将他们合并,并将原 ...

  4. mosquitto的安装与使用

      一款实现了消息推送协议 MQTT v3.1 的开源消息代理软件,提供轻量级的,支持可发布/可订阅的的消息推送模式,使设备对设备之间的短消息通信变得简单,比如现在应用广泛的低功耗传感器,手机.嵌入式 ...

  5. Web Audio API 第6章 高级主题

    高级主题 这一章涵盖了非常重要的主题,但比本书的其他部分稍微复杂一些. 我们会深入对声音添加音效,完全不通过任何音频缓冲来计算合成音效, 模拟不同声音环境的效果,还有关于空 3D 空间音频. 重要理论 ...

  6. pde复习笔记 第一章 波动方程 第六节 能量不等式、波动方程解的唯一性和稳定性

    能量不等式 这一部分需要知道的是能量的表达式 \[E(t)=\int_{0}^{l}u_{t}^{2}+a^{2}u_{x}^{2} dx \] 一般而言题目常见的问法是证明能量是减少的,也就是我们需 ...

  7. Python——基本输入和输出

    Python提供了基本的输入和输出功能,这些功能通常是通过内置的input()函数(用于输入)和print()函数(用于输出)来实现的.以下是这两个函数的详细描述和示例: 1. print() 函数( ...

  8. Chrome 浏览器插件 V3 版本 Manifest.json 文件中 Action 的类型(Types)、方法(Methods)和事件(Events)的属性和参数解析

    一.类型(Types) 一.OpenPopupOptions 1. 属性 windowId: number 可选 打开操作弹出式窗口的窗口 ID.如果未指定,则默认为当前活动窗口. 二.TabDeta ...

  9. 密码学—仿射密码Python程序

    文章目录 仿射密码 加密算法 解密算法 仿射密码 古典密码,且属于单表加密. 加密算法 仿射密码公式 c = m×k + b mod 26 c是密文,m是明文,m作为26字母中的明文,因此计算出来的密 ...

  10. 守护安全|AIRIOT城市天然气综合管理解决方案

      城市使用天然气存在安全风险和隐患,天然气管理的复杂性也比较高,依靠传统人工难以发现安全漏洞,特别是在燃气场站.管网的安全监管等方面,场站面临作业管理.区域管控等问题,管线存在第三方施工发现问题不及 ...