1 概述
Spring为开发者提供了JDBCTemplate,可以简化很多数据库操作相关的代码,本文主要介绍JDBCTemplate的使用以及事务管理功能。
2 JDBC Template
2.1 配置
配置的话主要配置以下几项:
- 数据源:
org.springframework.jdbc.datasource.DriverManager.DataSource - 数据库驱动:
com.cj.mysql.jdbc.Driver,这里采用的是MySQL 8,注意MySQL 5.7以下的驱动名字不同,另外若是其他数据库请对应修改 - 数据库
URL:jdbc:mysql://localhost:3306/test,MySQL默认的3306端口,数据库test - 数据库用户名
- 数据库密码
JDBC模板:org.springframework.jdbc.core.jdbcTemplate
参考配置如下:
1<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 2 <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> 3 <property name="url" value="jdbc:mysql://localhost:3306/test"/> 4 <property name="username" value="test"/> 5 <property name="password" value="test"/> 6</bean> 7<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 8 <property name="dataSource" ref="dataSource"/> 9</bean> 10<context:component-scan base-package="pers.dao"/>
2.2 常用方法
int update(String sql,Object args[]):增/删/改操作,使用args设置其中的参数,返回更新的行数List<T> query(String sql,RowMapper<T> rowMapper,Object []args):查询操作,rowMapper将结果集映射到用户自定义的类中
2.3 示例
2.3.1 依赖
首先导入依赖:
1<dependency> 2 <groupId>org.springframework</groupId> 3 <artifactId>spring-jdbc</artifactId> 4 <version>5.2.9.RELEASE</version> 5</dependency> 6 7<dependency> 8 <groupId>mysql</groupId> 9 <artifactId>mysql-connector-java</artifactId> 10 <version>8.0.21</version> 11</dependency>
MySQL的版本请根据个人需要更改,或使用其他数据库的驱动。
2.3.2 配置文件
完整配置文件如下:
1<?xml version="1.0" encoding="utf-8" ?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xsi:schemaLocation="http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context 8 https://www.springframework.org/schema/context/spring-context.xsd"> 9 10 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 11 <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> 12 <property name="url" value="jdbc:mysql://localhost:3306/test"/> 13 <property name="username" value="test"/> 14 <property name="password" value="test"/> 15 </bean> 16 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 17 <property name="dataSource" ref="dataSource"/> 18 </bean> 19 <context:component-scan base-package="pers.dao"/> 20</beans>
2.3.3 实体类
1public class MyUser { 2 private Integer id; 3 private String uname; 4 private String usex; 5}
2.3.4 数据访问层
添加@Repository以及@RequiredArgsConstructor:
1@Repository 2@RequiredArgsConstructor(onConstructor = @__(@Autowired)) 3public class TestDao { 4 private final JdbcTemplate template; 5 6 public int update(String sql,Object[] args) 7 { 8 return template.update(sql,args); 9 } 10 11 public List<MyUser> query(String sql, Object[] args) 12 { 13 RowMapper<MyUser> mapper = new BeanPropertyRowMapper<>(MyUser.class); 14 return template.query(sql,mapper,args); 15 } 16}
因为直接使用@Autowired的话会提示不推荐:

所以利用了Lombok的注解@RequiredArgsConstructor,效果相当如下构造方法,只不过是简化了一点:
1@Autowired 2public TestDao(JdbcTemplate template) 3{ 4 this.template = template; 5}
2.3.5 测试
测试之前先建表:
1create table MyUser( 2 id INT AUTO_INCREMENT PRIMARY KEY , 3 uname varchar(20), 4 usex varchar(20) 5)
测试类:
1public class Main { 2 public static void main(String[] args) { 3 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 4 TestDao dao = (TestDao)context.getBean("testDao"); 5 String insertSql = "insert into MyUser(uname,usex) values(?,?)"; 6 String[] param1 = {"chenhengfa1","男"}; 7 String[] param2 = {"chenhengfa2","男"}; 8 String[] param3 = {"chenhengfa3","男"}; 9 String[] param4 = {"chenhengfa4","男"}; 10 11 dao.update(insertSql,param1); 12 dao.update(insertSql,param2); 13 dao.update(insertSql,param3); 14 dao.update(insertSql,param4); 15 16 String selectSql = "select * from MyUser"; 17 List<MyUser> list = dao.query(selectSql,null); 18 for(MyUser mu:list) 19 { 20 System.out.println(mu); 21 } 22 } 23}
输出:

如果出现异常或插入不成功等其他情况,请检查SQL语句是否编写正确,包括表名以及字段名。
3 事务管理
Spring中的事务管理有两种方法:
- 编程式事务管理:代码中显式调用
beginTransaction、commit、rollback等就是编程式事务管理 - 声明式事务管理:通过
AOP实现,不需要通过编程方式管理事务,因此不需要再业务逻辑代码中掺杂事务处理的代码,开发更加简单,便于后期维护
下面先来看一下编程式事务管理的实现。
3.1 编程式事务管理
编程式事务管理的配置又有两种方法:
- 基于底层
API - 基于
TransactionTemplate
需要的依赖如下:
1<dependency> 2 <groupId>org.springframework</groupId> 3 <artifactId>spring-tx</artifactId> 4 <version>5.2.9.RELEASE</version> 5</dependency> 6 7<dependency> 8 <groupId>org.springframework</groupId> 9 <artifactId>spring-expression</artifactId> 10 <version>5.2.9.RELEASE</version> 11</dependency> 12 13<dependency> 14 <groupId>org.springframework</groupId> 15 <artifactId>spring-aop</artifactId> 16 <version>5.2.9.RELEASE</version> 17</dependency> 18 19<dependency> 20 <groupId>org.aspectj</groupId> 21 <artifactId>aspectjweaver</artifactId> 22 <version>1.9.6</version> 23 <scope>runtime</scope> 24</dependency> 25 26<dependency> 27 <groupId>org.springframework</groupId> 28 <artifactId>spring-aspects</artifactId> 29 <version>5.2.9.RELEASE</version> 30</dependency>
3.1.1 底层API实现
根据PlatformTransactionManager、TransactionDefinition、TransactionStatus几个核心接口,通过编程方式进行事务管理,首先配置事务管理器:
1<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 2 <property name="dataSource" ref="dataSource"/> 3</bean>
接着修改数据库访问类:
1@Repository 2@RequiredArgsConstructor(onConstructor = @__(@Autowired)) 3public class TestDao { 4 private final JdbcTemplate template; 5 private final DataSourceTransactionManager manager; 6 7 public int update(String sql,Object[] args) 8 { 9 return template.update(sql,args); 10 } 11 12 public List<MyUser> query(String sql,Object[] args) 13 { 14 RowMapper<MyUser> mapper = new BeanPropertyRowMapper<>(MyUser.class); 15 return template.query(sql,mapper,args); 16 } 17 18 public void testTransaction() 19 { 20 TransactionDefinition definition = new DefaultTransactionDefinition(); 21 TransactionStatus status = manager.getTransaction(definition); 22 String message = "执行成功,没有事务回滚"; 23 24 try 25 { 26 String sql1 = "delete from MyUser"; 27 String sql2 = "insert into MyUser(id,uname,usex) values(?,?,?)"; 28 Object [] param2 = {1,"张三","男"}; 29 template.update(sql1); 30 template.update(sql2,param2); 31 template.update(sql2,param2); 32 manager.commit(status); 33 } 34 catch (Exception e) 35 { 36 e.printStackTrace(); 37 manager.rollback(status); 38 message = "主键重复,事务回滚"; 39 } 40 System.out.println(message); 41 } 42}
3.1.1.1 事务定义
TransactionDefinition是事务定义,是一个接口:

主要定义了:
- 事务隔离级别
- 事务传播行为
- 事务超时时间
- 是否为只读事务
而DefaultTransactionDefinition就是上面属性的一些默认配置,比如:

也就是定义了:
- 传播行为为
0:也就是常量PROPAGATION_REQUIREDE,表示如果当前存在一个事务,则加入当前事务,如果不存在任何事务,就创建一个新事务 - 隔离级别为
-1:这个也是TransactionDefinition的默认参数,表示使用数据库的默认隔离级别,通常情况下为Read Committed - 超时为
-1:默认设置不超时,如需要设置超时请调用setTimeout方法,比如如果设置为了60,那么相当于如果操作时间超过了60s,而且后面还涉及到CRUD操作,那么会抛出超时异常并回滚,如果超时操作的后面没有涉及到CRUD操作,那么不会回滚 - 只读事务为
false:默认为false,但是该变量不是表明“不能”进行修改等操作,而是一种暗示,如果不包含修改操作,那么JDBC驱动和数据库就有可能针对该事务进行一些特定的优化
3.1.1.2 具体执行流程
具体执行流程如下:
- 定义事务:实例类为
DefaultTransactionDefinition - 开启事务:通过
getTransaction(TransactionDefinition)开启 - 执行业务方法
- 根据业务方法是否出现异常手动调用
DataSourceTransaction的commit(TransactionStatus)进行提交 - 出现异常调用
rollback(TransactionStatus)进行回滚
测试如下:

3.1.2 基于TransactionTemplate
步骤:
- 通过调用
TransactionTemplate的execute实现 execute接受一个TransactionCallback接口参数TransactionCallback定义了一个doInTransaction方法- 通常以匿名内部类的方式实现
TransactionCallback接口,在其中的doInTransaction编写业务逻辑代码 doInTransaction有一个TransactionStatus的参数,可以调用setRollbackOnly进行回滚
默认的回滚规则如下:
- 如果抛出未检查异常或者手动调用
setRollbackOnly,则回滚 - 如果执行完成或抛出检查异常,则提交事务
示例如下,首先编写配置文件对Bean进行注入:
1<!--事务管理器--> 2<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 3 <property name="dataSource" ref="dataSource"/> 4</bean> 5<!--事务模板--> 6<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> 7 <property name="transactionManager" ref="txManager"/> 8</bean>
其次修改数据访问类,添加一个测试方法:
1public void testTransactionTemplate() 2{ 3 System.out.println(transactionTemplate.execute((TransactionCallback<Object>) transactionStatus -> { 4 String deleteSql = "delete from MyUser"; 5 String insertSql = "insert into MyUser(id,uname,usex) values(?,?,?)"; 6 Object[] parm = {1, "张三", "男"}; 7 try { 8 template.update(deleteSql); 9 template.update(insertSql, parm); 10 template.update(insertSql, parm); 11 } catch (Exception e) { 12 message = "主键重复,事务回滚"; 13 e.printStackTrace(); 14 } 15 return message; 16 })); 17}
大部分代码与第一个例子类似就不解释了,结果也是因为主键重复出现异常,造成事务回滚:

3.2 声明式事务管理
Spring声明式事务管理通过AOP实现,本质是在方法前后进行拦截,在目标方法开始之前创建或加入一个事务,执行目标方法完成之后根据执行情况提交或回滚事务。相比起编程式事务管理,声明式最大的优点就是不需要通过编程的方式管理事务,业务逻辑代码无需混杂事务代码,但是唯一不足的地方就是最细粒度只能作用到方法上,而不能做到代码块级别。
实现方式有如下两种:
- 基于
XML实现 - 基于
@Transactional实现
3.2.1 基于XML
Spring提供了tx命令空间来配置事务:
<tx:advice>:配置事务通知,一般需要指定id以及transaction-manager<tx:attributes>:配置多个<tx:method>指定执行事务的细节
3.2.1.1 配置文件
完整配置文件如下:
1<?xml version="1.0" encoding="utf-8" ?> 2<beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xmlns:cache="http://www.springframework.org/schema/cache" 7 xmlns:tx="http://www.springframework.org/schema/tx" 8 xsi:schemaLocation="http://www.springframework.org/schema/beans 9 http://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/context 11 https://www.springframework.org/schema/context/spring-context.xsd 12 http://www.springframework.org/schema/cache 13 http://www.springframework.org/schema/cache/spring-cache.xsd 14 http://www.springframework.org/schema/tx 15 http://www.springframework.org/schema/tx/spring-tx.xsd 16 http://www.springframework.org/schema/aop 17 http://www.springframework.org/schema/aop/spring-aop.xsd" 18> 19 20 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 21 <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> 22 <property name="url" value="jdbc:mysql://localhost:3306/test"/> 23 <property name="username" value="test"/> 24 <property name="password" value="test"/> 25 </bean> 26 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 27 <property name="dataSource" ref="dataSource"/> 28 </bean> 29 <context:component-scan base-package="pers.dao"/> 30 <!--事务管理器--> 31 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 32 <property name="dataSource" ref="dataSource"/> 33 </bean> 34 35 <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> 36 <property name="transactionManager" ref="txManager"/> 37 </bean> 38 39 <!--声明式事务--> 40 <tx:advice id="myAdvice" transaction-manager="txManager"> 41 <tx:attributes> 42 <!--任意方法--> 43 <tx:method name="*" /> 44 </tx:attributes> 45 </tx:advice> 46 <!--aop配置,具体可以看笔者之前的文章--> 47 <aop:config> 48 <!--定义切点,执行testXMLTranscation()时进行增强--> 49 <aop:pointcut id="txPointCut" expression="execution(* pers.dao.TestDao.testXMLTransaction())"/> 50 <!--切面--> 51 <aop:advisor advice-ref="myAdvice" pointcut-ref="txPointCut"/> 52 </aop:config> 53</beans>
3.2.1.2 测试
测试方法如下:
1public void testXMLTransaction() 2{ 3 String deleteSql = "delete from MyUser"; 4 String saveSql = "insert into MyUser(id,uname,usex) values(?,?,?)"; 5 Object [] parm = {1,"张三","男"}; 6 template.update(deleteSql); 7 template.update(saveSql,parm); 8 template.update(saveSql,parm); 9}
运行结果:

可以看到提示主键重复了。
3.2.2 基于@Transactional
@Transactional一般作用于类上,使得该类所有public方法都具有该类型的事务属性。下面创建一个示例。
3.2.2.1 配置文件
将上一个例子中的<aop:config>以及<tx:advice>注释掉,同时添加:
1<!--事务管理的注解驱动器--> 2<tx:annotation-driven transaction-manager="txManager"/>

3.2.2.2 测试
测试方法与上一个例子一致,结果也是如此:

4 参考源码
Java版:
Kotlin版: