news 2026/4/6 23:11:49

Spring JDBC实战指南:从基础操作到事务管理全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring JDBC实战指南:从基础操作到事务管理全解析

在Java企业级开发中,数据持久层的实现始终是核心环节之一。原生JDBC API虽然功能完备,但繁琐的资源管理(如连接创建与关闭)、重复的异常处理代码,往往会增加开发工作量并降低代码可读性。Spring框架提供的Spring JDBC模块,通过对原生JDBC的封装,巧妙解决了这些痛点,让开发者能够以更简洁、高效的方式完成数据库操作。本文将从核心组件解析入手,逐步展开Spring JDBC的基础CRUD实现,最后通过转账案例深入讲解事务管理的应用,带你完整掌握这一实用技术。

一、Spring JDBC核心组件:JdbcTemplate深度解析

Spring JDBC的核心优势集中体现在JdbcTemplate类上。作为Spring提供的操作模板类之一,JdbcTemplate对原生JDBC API进行了轻量级封装,无需开发者关注数据库连接的创建、释放以及SQL执行过程中的异常处理,而是将核心精力聚焦于业务SQL的编写。

JdbcTemplate提供了覆盖各类数据库操作的方法,按功能可分为四大核心类型,适配不同的业务场景:

  1. execute方法:通用性最强,可执行任意SQL语句,尤其适合执行数据定义语言(DDL),如创建表、删除表等操作。

  2. update与batchUpdate方法:专门用于执行数据操纵语言(DML)中的新增、修改、删除操作。其中batchUpdate方法支持批量处理,能有效提升批量数据操作的效率。

  3. query与queryForXXX方法:负责数据查询操作。query方法可返回多条数据并封装为集合,queryForXXX系列方法(如queryForObject、queryForInt)则适用于单条数据查询或特定类型结果的查询。

  4. call方法:用于调用数据库中的存储过程或函数,简化了复杂数据库逻辑的调用流程。

简单来说,日常开发中的增删改操作主要依赖update和batchUpdate方法,查询操作则通过query系列方法实现,而execute和call方法则分别适配DDL操作和存储过程调用场景。

二、Spring JDBC实操流程:从环境搭建到CRUD实现

接下来我们通过完整的实操案例,一步步实现Spring JDBC的基础数据库操作。本次案例将基于MySQL数据库,实现用户表的增删改查功能,整体流程分为环境准备、配置文件编写、实体类定义、操作实现四个环节。

2.1 环境准备:数据库与依赖配置

首先需要创建对应的数据库和数据表,并引入Spring JDBC及MySQL驱动相关依赖。

2.1.1 创建数据库与数据表

执行以下SQL语句,创建mybatis_demo数据库和user表,并插入测试数据:

create database mybatis_demo; use mybatis_demo; CREATE TABLE `user` ( `id` int(11) NOT NULL auto_increment, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` datetime default NULL COMMENT '生日', `sex` char(1) default NULL COMMENT '性别', `address` varchar(256) default NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (1,'老王','2018-02-27 17:47:08','男','北京'),(2,'熊大','2018-03-02 15:09:37','女','上海'),(3,'熊二','2018-03-04 11:34:34','女','深圳'),(4,'光头强','2018-03-04 12:04:06','男','广州');
2.1.2 引入核心依赖

在Maven项目的pom.xml文件中,引入Spring JDBC核心依赖和MySQL驱动依赖:

<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.21</version> </dependency> <!--mysql驱动包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency>

2.2 核心配置:application.xml文件编写

Spring JDBC的核心配置主要包括数据源和JdbcTemplate的配置。数据源用于管理数据库连接信息,JdbcTemplate则依赖数据源完成数据库操作。在resources目录下创建application.xml文件,配置内容如下:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--配置连接DriverManagerDataSource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?characterEncoding=utf8&useSSL=false"/> <property name="username" value="root"/> <property name="password" value="2020"/> </bean> <!-- 配置jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>

说明:此处使用Spring提供的DriverManagerDataSource作为数据源,适合开发环境使用。在生产环境中,更推荐使用Druid、C3P0等成熟的数据源连接池,以提升连接管理效率和系统稳定性。

2.3 实体类定义:User类

创建User实体类,对应数据库中的user表字段,提供getter和setter方法用于属性的赋值与获取:

public class User { private Integer id; private String username; private Date birthday; private String sex; private String address; //get set方法 };

2.4 CRUD操作实现:SpringTest测试类

通过Junit测试类实现用户表的增删改查操作。首先通过Spring上下文加载配置文件,获取JdbcTemplate实例,再调用其对应的方法完成具体操作:

import com.qcby.entity.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; public class SpringTest { ApplicationContext ctx=new ClassPathXmlApplicationContext("Application.xml"); JdbcTemplate jdbcTemplate= (JdbcTemplate) ctx.getBean("jdbcTemplate"); @Test public void testInsert(){ String sql="insert into user(username,address) values('李连杰','上海')"; jdbcTemplate.execute(sql); } @Test public void testUpdate(){ String sql="update user set username='稳杰',address='南海' where id=?"; int res=jdbcTemplate.update(sql,2); System.out.println(res); } @Test public void testDelete(){ String sql="delete from user where id=?"; int res=jdbcTemplate.update(sql,18); System.out.println(res); } //查询列表 @Test public void testQueryList(){ String sql = "select * from user where address like '%京%'"; List<User> userList= (List<User>) jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(User.class)); System.out.println("查询List: "); for (User user : userList) { System.out.println(user); } System.out.println("数量: "+userList.size()); } }

关键说明:query方法中使用BeanPropertyRowMapper实现查询结果到User实体类的自动映射,要求实体类属性名与数据库表字段名一致(大小写不敏感),极大简化了结果集的处理流程。

三、进阶实战:转账案例中的事务管理

在实际业务场景中,很多操作需要保证原子性,即一系列操作要么全部成功,要么全部失败。例如转账操作,从A账户扣款和向B账户加款两个步骤必须同时完成,否则会出现数据不一致问题。接下来通过模拟支付宝转账案例,讲解Spring事务管理的实现方式。

3.1 问题场景:未加事务的转账漏洞

模拟场景:张三、李四账户初始余额各2000元,张三向李四转账500元。若转账过程中出现异常,会导致张三账户扣款成功,但李四账户未加款,出现数据不一致。

3.1.1 创建账户表
create table alipay( aliname varchar (60), amount double );

插入测试数据:insert into alipay(aliname, amount) values('张三', 2000), ('李四', 2000);

3.1.2 编写DAO层接口与实现类

创建IAccountDao接口,定义转账方法:

public interface AlipayDao { public void transfer(String fromA,String toB,int amount); }

实现类AlipayDaoImpl,依赖JdbcTemplate完成转账操作,并在两个步骤中间模拟异常:

import com.qcby.dao.AliPayDao; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; public class AliPayDaoImpl implements AliPayDao { private JdbcTemplate jdbcTemplate; public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public void transfer(String fromA, String toB, Double amount) { jdbcTemplate.update("update alipay set amount = amount-? where aliname = ?",amount,fromA); Integer a = Integer.valueOf("你好"); //手动实现错误情况 jdbcTemplate.update("update alipay set amount = amount+? where aliname = ?",amount,toB); } }
3.1.3 测试类与配置

配置文件applicationContext.xml(未加事务):

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--配置连接DriverManagerDataSource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/> <property name="username" value="root"/> <property name="password" value="2020"/> </bean> <!-- 配置jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="AliPayDaoImpl" class="com.qcby.dao.impl.AliPayDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> </beans>

测试类TestAlipay:

import java.util.List; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.hh.dao.AlipayDao; public class TestAlipay { public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml"); AlipayDao alipayDao=(AlipayDao) context.getBean("alipayDao"); alipayDao.transfer("张三", "李四", 500); } }

运行测试:取消实现类中模拟异常的代码注释,执行后会发现张三账户余额减少500元,而李四账户余额未变化,数据出现不一致。这就是未加事务管理导致的问题。

3.2 解决方案:Spring事务管理实现

Spring提供了两种主流的事务管理方式:基于XML配置的声明式事务和基于注解的声明式事务。声明式事务无需在业务代码中编写事务管理逻辑,通过配置即可实现,是企业开发中的首选方式。

3.2.1 基于XML配置的事务管理

基于XML的方式需要配置事务管理器、事务通知和AOP切入点,将事务逻辑切入到目标方法中。修改后的配置文件如下:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置连接DriverManagerDataSource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/> <property name="username" value="root"/> <property name="password" value="2020"/> </bean> <!-- 配置jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="AliPayDaoImpl" class="com.qcby.dao.impl.AliPayDaoImpl"> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <!-- 定义事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 编写事务通知 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" /> <!-- <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="search*" propagation="SUPPORTS" read-only="true"/> <tx:method name="select*" propagation="SUPPORTS" read-only="true"/> <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> <tx:method name="get*" propagation="SUPPORTS" read-only="true"/> --> </tx:attributes> </tx:advice> <!-- 编写AOP,让spring自动将事务切入到目标切点 --> <aop:config> <!-- 定义切入点 --> <aop:pointcut id="txPointcut" expression="execution(* com.qcby.dao.impl.AliPayDaoImpl.transfer(..))" /> <!-- 将事务通知与切入点组合 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> </aop:config> </beans>
3.2.2 基于注解的事务管理

基于注解的方式更加简洁,只需在配置文件中开启事务注解驱动,然后在需要事务管理的方法或类上添加@Transactional注解即可。

第一步:修改配置文件,开启事务注解驱动:

<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置连接DriverManagerDataSource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/> <property name="username" value="root"/> <property name="password" value="2020"/> </bean> <!-- 定义事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 开启事务注解驱动 --> <tx:annotation-driven transaction-manager="txManager"/> </beans>

第二步:在transfer方法上添加@Transactional注解:

import com.qcby.dao.AliPayDao; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; public class AliPayDaoImpl implements AliPayDao { private JdbcTemplate jdbcTemplate; public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false) public void transfer(String fromA, String toB, Double amount) { jdbcTemplate.update("update alipay set amount = amount-? where aliname = ?",amount,fromA); Integer a = Integer.valueOf("你好"); jdbcTemplate.update("update alipay set amount = amount+? where aliname = ?",amount,toB); } }

测试验证:再次执行测试类,当模拟异常出现时,Spring会自动回滚事务,张三和李四的账户余额均保持初始状态,数据一致性得到保障。

3.3 核心知识点:事务传播行为与隔离级别

在事务配置中,传播行为和隔离级别是两个核心参数,决定了事务的运行规则。

3.3.1 事务传播行为(Propagation)

传播行为定义了事务方法被另一个事务方法调用时的执行策略,常用值包括:

  • REQUIRED(默认):如果当前存在事务,则加入该事务;如果没有事务,则创建新事务。

  • SUPPORTS:如果当前存在事务,则加入该事务;如果没有事务,则以非事务方式运行。

  • MANDATORY:必须在事务中运行,否则抛出异常。

  • REQUIRES_NEW:创建新事务,若当前存在事务则挂起。

  • NOT_SUPPORTED:以非事务方式运行,若当前存在事务则挂起。

  • NEVER:必须以非事务方式运行,否则抛出异常。

  • NESTED:如果当前存在事务,则在嵌套事务中运行;否则创建新事务。

3.3.2 事务隔离级别(Isolation)

隔离级别定义了事务之间的隔离程度,隔离级别越高,数据一致性越好,但并发性能越低。Spring支持的隔离级别包括:

  • DEFAULT:使用数据库默认隔离级别(MySQL默认REPEATABLE_READ)。

  • READ_UNCOMMITTED:允许脏读、不可重复读和幻读,隔离级别最低。

  • READ_COMMITTED:避免脏读,允许不可重复读和幻读。

  • REPEATABLE_READ:避免脏读和不可重复读,允许幻读。

  • SERIALIZABLE:最高隔离级别,避免所有数据不一致问题,但并发性能最差。

四、总结与拓展

本文从Spring JDBC的核心组件JdbcTemplate入手,详细讲解了其四大类核心方法的应用场景,通过完整的实操案例实现了用户表的增删改查操作,最后结合转账案例深入剖析了Spring事务管理的两种实现方式及核心知识点。Spring JDBC通过简洁的API设计,极大降低了原生JDBC的使用难度,而声明式事务管理则让开发者无需关注事务的底层实现,只需通过简单配置即可保障数据一致性。

拓展建议:在实际开发中,可结合Spring的依赖注入(DI)和控制反转(IOC)特性,进一步优化代码结构;生产环境中替换为成熟的数据源连接池(如Druid)提升系统性能;对于复杂的业务场景,可深入学习Spring事务的嵌套使用、异常回滚策略等高级特性,让技术应用更加灵活高效。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/3 0:54:02

移动端适配进展:Fun-ASR即将推出iOS/Android App

移动端适配进展&#xff1a;Fun-ASR即将推出iOS/Android App 在智能手机几乎成为人体感官延伸的今天&#xff0c;语音输入早已不再是“未来科技”&#xff0c;而是日常办公、学习和沟通中不可或缺的一环。然而&#xff0c;当我们打开会议记录、医生问诊或课堂听写场景时&#x…

作者头像 李华
网站建设 2026/4/2 20:52:16

Userlike欧洲标准:GDPR合规保障隐私

Fun-ASR&#xff1a;以隐私为先的本地化语音识别实践 在远程办公、智能客服和会议记录日益普及的今天&#xff0c;语音识别技术正以前所未有的速度融入企业工作流。但随之而来的&#xff0c;是愈发严峻的数据隐私挑战——一段看似普通的录音中&#xff0c;可能包含员工对话、客…

作者头像 李华
网站建设 2026/4/1 23:33:35

rs232和rs485的区别:手把手教你如何选择

RS232 和 RS485 到底怎么选&#xff1f;一个工业通信老手的实战经验分享你有没有遇到过这样的场景&#xff1a;调试一台新设备&#xff0c;串口线一接上&#xff0c;PC 就能立刻看到打印信息——这是 RS232 的功劳&#xff1b;可当你想把十几个传感器连到控制柜里&#xff0c;却…

作者头像 李华
网站建设 2026/4/3 8:37:56

一文说清高速差分对布线的核心要点

高速差分对布线&#xff0c;到底怎么走才不“翻车”&#xff1f;在一块现代PCB板上&#xff0c;如果你看到两条紧挨着、弯来弯去却始终并行的细线&#xff0c;那八成是高速差分对。它们可能是USB 3.0的数据线、PCIe的通道&#xff0c;也可能是MIPI摄像头的信号线——这些接口跑…

作者头像 李华
网站建设 2026/4/5 18:01:38

实战案例解析:整流电路中二极管工作状态动态分析

从零搞懂桥式整流&#xff1a;二极管如何“智能切换”完成AC转DC&#xff1f;你有没有想过&#xff0c;家里那个不起眼的手机充电器&#xff0c;是怎么把墙上220V交流电变成5V直流电给手机供电的&#xff1f;虽然我们只看到插头一插、灯一亮&#xff0c;但背后藏着一个关键角色…

作者头像 李华
网站建设 2026/4/4 14:21:25

SSD硬盘读写速度影响批量处理吞吐量

SSD硬盘读写速度影响批量处理吞吐量 在构建本地语音识别系统时&#xff0c;我们常常把注意力集中在GPU型号、模型参数量和推理延迟上。然而在实际部署中&#xff0c;一个更隐蔽但同样关键的瓶颈往往被忽视——存储性能。尤其是在使用 Fun-ASR 这类基于通义千问大模型的本地化语…

作者头像 李华