在Java企业级开发中,数据持久层的实现始终是核心环节之一。原生JDBC API虽然功能完备,但繁琐的资源管理(如连接创建与关闭)、重复的异常处理代码,往往会增加开发工作量并降低代码可读性。Spring框架提供的Spring JDBC模块,通过对原生JDBC的封装,巧妙解决了这些痛点,让开发者能够以更简洁、高效的方式完成数据库操作。本文将从核心组件解析入手,逐步展开Spring JDBC的基础CRUD实现,最后通过转账案例深入讲解事务管理的应用,带你完整掌握这一实用技术。
一、Spring JDBC核心组件:JdbcTemplate深度解析
Spring JDBC的核心优势集中体现在JdbcTemplate类上。作为Spring提供的操作模板类之一,JdbcTemplate对原生JDBC API进行了轻量级封装,无需开发者关注数据库连接的创建、释放以及SQL执行过程中的异常处理,而是将核心精力聚焦于业务SQL的编写。
JdbcTemplate提供了覆盖各类数据库操作的方法,按功能可分为四大核心类型,适配不同的业务场景:
-
execute方法:通用性最强,可执行任意SQL语句,尤其适合执行数据定义语言(DDL),如创建表、删除表等操作。
-
update与batchUpdate方法:专门用于执行数据操纵语言(DML)中的新增、修改、删除操作。其中batchUpdate方法支持批量处理,能有效提升批量数据操作的效率。
-
query与queryForXXX方法:负责数据查询操作。query方法可返回多条数据并封装为集合,queryForXXX系列方法(如queryForObject、queryForInt)则适用于单条数据查询或特定类型结果的查询。
-
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事务的嵌套使用、异常回滚策略等高级特性,让技术应用更加灵活高效。