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表,并插入测试数据:

sql 复制代码
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驱动依赖:

XML 复制代码
<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 复制代码
<?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方法用于属性的赋值与获取:

java 复制代码
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实例,再调用其对应的方法完成具体操作:

java 复制代码
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 创建账户表
sql 复制代码
create table alipay(
    aliname varchar (60),
    amount double
);

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

3.1.2 编写DAO层接口与实现类

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

java 复制代码
public interface AlipayDao {
    public void transfer(String fromA,String toB,int amount);
}

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

java 复制代码
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 复制代码
<?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:

java 复制代码
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 复制代码
<?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 复制代码
<?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注解:

java 复制代码
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事务的嵌套使用、异常回滚策略等高级特性,让技术应用更加灵活高效。

相关推荐
野人李小白12 分钟前
DBeaver 界面友好,支持多种数据库,具备强大的 SQL 编辑、可视化查询、数据迁移及插件扩展功能,是开发者首选的数据库管理工具。
数据库·sql
山峰哥20 分钟前
SQL索引优化实战:3000字深度解析查询提速密码
大数据·数据库·sql·编辑器·深度优先
观音山保我别报错1 小时前
消息队列项目基础知识总结
linux·服务器·数据库
jghhh011 小时前
MATLAB分形维数计算:1D/2D/3D图形的盒维数实现
数据库·matlab
重生之绝世牛码2 小时前
Linux软件安装 —— PostgreSQL高可用集群安装(postgreSQL + repmgr主从复制 + keepalived故障转移)
大数据·linux·运维·数据库·postgresql·软件安装·postgresql高可用
数据知道2 小时前
PostgreSQL 实战:详解 UPSERT(INSERT ON CONFLICT)
数据库·python·postgresql
源力祁老师2 小时前
Odoo日志系统核心组件_logger
网络·数据库·php
洋不写bug4 小时前
数据库基础核心操作——CRUD,超详细解析,搭配表格讲解和需求的实现。
数据库
马猴烧酒.4 小时前
JAVA后端用户登录与鉴权详解
java·数据库·sql
heartbeat..4 小时前
Redis 常用命令全解析:基础、进阶与场景化实战
java·数据库·redis·缓存