很多时候,需要使用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. SemanticFunction 融合 LLM 和传统编程

    本文将继续和大家介绍 SemanticKernel 神奇的魔法,将使用 LLM 大语言模型编写的自然语言函数和传统的编程语言编写的函数融合到一起的例子.通过本文的例子,大家可以看到 SemanticK ...

  2. WPF 下拉框选项做鼠标 Hover 预览效果

    本文来告诉大家如何在 WPF 中,在 下拉框 ComboBox 里面,鼠标移动到 ComboBoxItem 上时,自动触发对应的事件,用来预览此选项值.例如我在实现一个颜色下拉框,此时我可以通过点击下 ...

  3. kubeadm安装多master节点的k8s集群(1)

    一.环境准备 k8s集群角色 IP 主机名 安装的相关组件 控制节点 192.168.1.10 master apiserver.controller-manager.scheduler.kubele ...

  4. Docker部署Scrapy-redis分布式爬虫框架(整合Selenium+Headless Chrome网页渲染)

    前言 我的京东价格监控网站需要不间断爬取京东商品页面,爬虫模块我采用了Scrapy+selenium+Headless Chrome的方式进行商品信息的采集. 由于最近爬虫用的服务器到期,需要换到新服 ...

  5. XTuner 微调 LLM实操-书生浦语大模型实战营第二期第4节作业

    这一作业中提及的解释比较少,更多的只是一些步骤截图.这是因为教程中已经提及了几乎所有的细节信息,没有什么需要补充的.这个页面相较于官方教程的部分解释得过于详细的内容甚至是有所删减的.比如关于文件路径可 ...

  6. 纯JavaScript制作动态增加的网页数字

    看到别的网页上打开,会显示一个动态的数字,感觉这个效果增加了网页的灵动感.就尝试着写代码,最终实现的方法: 会从0增加到一个数值,实现的代码: <!-- html 部分 --> <d ...

  7. Linux安装Logstash

    Logstash安装 一.上传解压重命名 将Logstash压缩包上传到/home/下解压压缩包并重命名 [root@localhost home] tar -zxf logstash-7.15.0- ...

  8. 【u8 login debug】u8 16.0 没有调试 login的解决办法

    16.0 没有调试 login,改一下注册表 就行[HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Ufsoft\WF\V8.700]"Enable.Debu ...

  9. 通过钩子函数+Traceid实现Flask链路追踪

    背景 在flask web中我们通常需要一个traceid作为调用参数传递给全链路各个调用函数 需要针对一次请求创建一个唯一的traceid:这里用uuid去简化代替 我们需要保证traceid不被污 ...

  10. C++笔记(3)引用

    引用是变量的别名.也就是说,它是某个已存在变量的另一个名字.一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量. 1.创建引用 int i = 0; int& r = i;/ ...