本章主要内容:Spring的数据库编程、编程式事务管理、声明式事务管理。
前言
1.什么是编程式事务管理?在Spring中有哪几种编程式事务管理?
答:在代码中显式调用beginTransaction()、commit()、rollback()等事务处理相关的方法,这就是编程式事务管理。
在Spring中有基于底层API的编程式事务管理和基于TransactionTemplate的编程式事务管理。
2.简述声明式事务管理的处理方式。
答:Spring的声明式事务管理,是通过AOP技术实现的事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
Spring的声明式事务管理可以通过两种方式来实现,一是基于XML的方式,一是基于@Transactional注解的方式。
基于XML方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的。Spring框架提供了tx命名空间来配置事务,<tx:advice>元素来配置事务的通知。配置<tx:advice>元素时,一般需要指定id和transaction-manager属性,其中id属性是配置文件中的唯一标识,transaction-manager属性指定事务管理器。另外,还需要<tx:attributes>子元素,该子元素可配置多个<tx:method>子元素指定执行事务的细节。
@Transactional注解可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,也可以在方法级别使用该注解来覆盖类级别的定义。
5.1 Spring的数据库编程
数据库编程是互联网编程的基础,Spring框架为开发者提供了JDBC模板模式,即jdbcTemplate,它可以简化许多代码,但在实际应用中jdbcTemplate并不常用。工作更多的时候,用的是Hibernate框架和MyBatis框架进行数据库编程。
其实Spring JdbcTemplate的使用方法和API和JDBC十分类同,我们这里通过一个简单的例子讲解,本章重点是后面的声明式事务管理。
5.1.1 数据库准备工作
下面我们分步骤创建一个新的MySQL用户、数据库及我们例子所需要的表:
1 、登陆root用户:
mysql -u root -p root
我们首先查看有多少用户,方便创建一个新用户:
select host,user from mysql.user;
2 、然后创建用户:jdbc,密码是jdbc。localhost 就是本地连接,即127.0.0.1。%用于远程连接,即任意ip都可以链接:
CREATE USER 'jdbc'@'localhost' IDENTIFIED BY 'jdbc'; #本地登录
CREATE USER 'jdbc'@'%' IDENTIFIED BY 'jdbc'; #远程登录
创建成功后,我们查看一下结果:
3 、创建数据库springtest:
create database springtest DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
查看当前用户(root)下的数据库:
show databases;
接下来我们还要授权jdbc用户拥有springtest数据库的所有权限:
grant all privileges on `springtest`.* to 'jdbc'@'localhost' identified by 'jdbc';
grant all privileges on `springtest`.* to 'jdbc'@'%' identified by 'jdbc';
flush privileges; #刷新系统权限表
4 、我们切换到新建的用户:
mysql -u jdbc -p
选择数据库:
use springtest;
创建新表user:
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`uid` tinyint(2) NOT NULL AUTO_INCREMENT,
`uname` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`usex` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
查看当前用户拥有的表:
show tables;
5.1.2 Spring JDBC的配置
数据库准备工作完成后,我们还得导入jar包。本节Spring数据库编程主要使用Spring JDBC模块的core和dataSource包。core包是JDBC的核心功能包,包括常用的JdbcTemplate类;dataSource包是访问数据源的工具类包。使用Spring JDBC操作数据库,需要对其进行配置。
包结构图如下:
在src目录下创建配置文件applicationContext.xml,并在该文件中配置数据源和JDBC模板,代码如下:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 指定需要扫描的包(包括子包),使注解生效 -->
<context:component-scan base-package="com.ch5"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- MySQL数据库驱动 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!-- 连接数据库的URL -->
<property name="url" value="jdbc:mysql://localhost:3306/springtest?characterEncoding=utf8"/>
<!-- 连接数据库的用户名 -->
<property name="username" value="root"/>
<!-- 连接数据库的密码 -->
<property name="password" value="root"/>
</bean>
<!-- 配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
5.1.3 创建实体类和数据访问层
创建实体类MyUser,注意,该类属性和我们刚才建的数据库里面的user表字段一致哦:
package com.ch5;
public class MyUser {
private Integer uid;
private String uname;
private String usex;
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getUsex() {
return usex;
}
public void setUsex(String usex) {
this.usex = usex;
}
public String toString() {
return "myUser [uid=" + uid +", uname=" + uname + ", usex=" + usex + "]";
}
}
数据访问层TestDao接口和TestDaoImpl实现类,其中没有什么技术难点,大家直接看代码练手就行:
package com.ch5;
import java.util.List;
public interface TestDao {
public int update(String sql, Object[] param);
public List<MyUser> query(String sql, Object[] param);
}
package com.ch5;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
@Repository("testDao")
public class TestDaoImpl implements TestDao{
@Autowired
//使用配置文件中的JDBC模板
private JdbcTemplate jdbcTemplate;
/**
* 更新方法,包括添加、修改、删除
* param为sql中的参数,如通配符?
*/
@Override
public int update(String sql, Object[] param) {
return jdbcTemplate.update(sql, param);
}
/**
* 查询方法
* param为sql中的参数,如通配符?
*/
@Override
public List<MyUser> query(String sql, Object[] param) {
RowMapper<MyUser> rowMapper = new BeanPropertyRowMapper<MyUser>(MyUser.class);
return jdbcTemplate.query(sql, rowMapper, param);
}
}
老规矩测试类TestSpringJDBC:
package com.ch5;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestSpringJDBC {
public static void main(String[] args) {
ApplicationContext appCon = new ClassPathXmlApplicationContext("applicationContext.xml");
//从容器中,获取增强后的目标对象
TestDao td = (TestDao)appCon.getBean("testDao");
String insertSql = "insert into user values(null,?,?)";
//数组param的值与insertSql语句中?一一对应
Object param1[] = {"chenheng1", "男"};
Object param2[] = {"chenheng2", "女"};
Object param3[] = {"chenheng3", "男"};
Object param4[] = {"chenheng4", "女"};
//添加用户
td.update(insertSql, param1);
td.update(insertSql, param2);
td.update(insertSql, param3);
td.update(insertSql, param4);
//查询用户
String selectSql ="select * from user";
List<MyUser> list = td.query(selectSql, null);
for(MyUser mu : list) {
System.out.println(mu);
}
}
}
结果截图:
查看我们的数据库表里面也有了数据:
5.2 编程式事务管理
在代码中显式调用beginTransaction()、commit()、rollback()等事务处理相关的方法,这就是编程式事务管理。当只有少数事务操作时,编程式事务管理才比较合适。
5.2.1 基于底层API的编程式事务管理
基于底层API的编程式事务管理,就是根据PlatformTransactionManager、TransactionDefinition 和 TransactionStatus 三个核心接口,通过编程的方式来进行事务处理。下面通过一个实例讲解基于底层API的编程式事务管理。
1.给数据源配置事务管理器
在5.1.2节配置文件applicationContext.xml的基础上,使用PlatformTransactionManager接口的实现类org.springframework.jdbc.datasource.DataSourceTransactionManager为数据源添加事务管理器。
<!-- 为数据源添加事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
2.创建数据访问类
在com.ch5包中,创建数据访问类CodeTransaction,并注解为@Repository("codeTransaction")。在该类中使用编程式方式进行数据库事务管理。
package com.ch5;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Repository("codeTransaction")
public class CodeTransaction {
@Autowired
// 使用配置文件中的JDBC模板
private JdbcTemplate jdbcTemplate;
//DataSourceTransactionManager是PlatformTransactionManager接口的实现类
@Autowired
private DataSourceTransactionManager txManager;
public String test() {
// 默认事务定义,如隔离级别、传播行为等
TransactionDefinition tf = new DefaultTransactionDefinition();
// 开启事务ts
TransactionStatus ts = txManager.getTransaction(tf);
String message = "执行成功,没有事务回滚!";
try {
// 删除表中数据
String sql = " delete from user ";
// 添加数据
String sql1 = " insert into user values(?,?,?) ";
Object param[] = { 1, "陈恒", "男" };
// 先删除数据
jdbcTemplate.update(sql);
// 添加一条数据
jdbcTemplate.update(sql1, param);
// 添加相同的一条数据,使主键重复
jdbcTemplate.update(sql1, param);
// 提交事务
txManager.commit(ts);
} catch (Exception e) {
// 出现异常,事务回滚
txManager.rollback(ts);
message = "主键重复,事务回滚!";
e.printStackTrace();
}
return message;
}
}
3.创建测试类
在com.ch5包中,创建测试类TestCodeTransaction:
package com.ch5;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestCodeTransaction {
public static void main(String[] args) {
ApplicationContext appCon = new ClassPathXmlApplicationContext("applicationContext.xml");
CodeTransaction ct = (CodeTransaction)appCon.getBean("codeTransaction");
String result = ct.test();
System.out.println(result);
}
}
结果分析:我们之前这句sql删除了所有数据,后来插入重复数据导致抛出异常,事务回滚(整个事务回滚,这个事务包括了删除和插入数据。)我们看看我们的数据库是否是这样:
5.2.2 基于TransactionTemplate的编程式事务管理
事务处理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交以及回滚事务的样板代码。TransactionTemplate的execute()方法有一个TransactionCallback接口类型的参数,该接口中定义了一个doInTransaction()方法,通常以匿名内部类的方式实现TransactionCallback 接口,并在其doInTransaction()方法中书写业务逻辑代码。这里可以使用默认的事务提交和回滚规则,在业务代码中不需要显式调用任何事务处理的API。doInTransaction()方法有一个TransactionStatus类型的参数,可以在方法的任何位置调用该参数的setRollbackOnly()方法将事务标识为回滚,以执行事务回滚。
根据默认规则,如果在执行回调方法的过程中抛出了未检查异常,或者显式调用了setRollbackOnly()方法,则回滚事务;如果事务执行完成或者抛出了checked类型的异常,则提交事务。
基于TransactionTemplate的编程式事务管理的步骤如下:
1.为事务管理器添加事务模板
在5.2.1节配置文件applicationContext.xml的基础上,使用org.springframework.transaction.support.TransactionTemplate类为事务管理器添加事务模板。具体代码如下:
<!-- 为事务管理器txManager创建transactionTemplate -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"/>
</bean>
2.创建数据访问类
在com.ch5包中,创建数据访问类TransactionTemplateDao,并注解为@Repository("transactionTemplateDao")。在该类中使用编程式方式进行数据库事务管理。
package com.ch5;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Repository("transactionTemplateDao")
public class TransactionTemplateDao {
@Autowired
// 使用配置文件中的JDBC模板
private JdbcTemplate jdbcTemplate;
@Autowired
private TransactionTemplate transactionTemplate;
String message = "";
public String test() {
// 以匿名内部类的方式实现TransactionCallback 接口,使用默认的事务提交和回滚规则,在业务代码中不需要显式调用任何事务处理的API
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus arg0) {
try {
// 删除表中数据
String sql = " delete from user ";
// 添加数据
String sql1 = " insert into user values(?,?,?) ";
Object param[] = { 1, "陈恒", "男" };
// 先删除数据
jdbcTemplate.update(sql);
// 添加一条数据
jdbcTemplate.update(sql1, param);
// 添加相同的一条数据,使主键重复
jdbcTemplate.update(sql1, param);
message = "执行成功,没有事务回滚!";
} catch (Exception e) {
message = "主键重复,事务回滚!";
e.printStackTrace();
}
return message;
}
});
return message;
}
}
3.创建测试运行类
和5.2.1一模一样,不再赘述。但是我们分析下结果,我们这里用的是TransactionTemplate模板进行的编程式事务管理,上述程序发生错误,事务回滚,仅回滚了插入数据(前面的删除和正常插入数据不受影响)。而5.2.1中的基于底层API的编程式事务管理,回滚事务是回滚了整个JdbcTemplate的。
原因是他们用的事务管理器不一样!!
5.2.1中的基于底层API的编程式事务管理:
<!-- 为数据源添加事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
而5.2.2这个例子不一样,用的TransactionTemplate这个管理器:
<!-- 为数据源添加事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 为事务管理器txManager创建transactionTemplate -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"/>
</bean>
5.3 声明式事务管理
Spring的声明式事务管理,是通过AOP技术实现的事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务管理最大的优点是不需要通过编程的方式管理事务,因而不需要在业务逻辑代码中掺杂事务处理的代码,只需相关的事务规则声明,便可以将事务规则应用到业务逻辑中。通常情况下,在开发中使用声明式事务处理,不仅因为其简单,更主要是因为这样使得纯业务代码不被污染,极大方便后期的代码维护。
Spring的声明式事务管理可以通过两种方式来实现,一是基于XML的方式,一是基于@Transactional注解的方式。
5.3.1 基于XML方式的声明式事务管理
基于XML方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的。Spring框架提供了tx命名空间来配置事务,<tx:advice>元素来配置事务的通知。配置<tx:advice>元素时,一般需要指定id和transaction-manager属性,其中id属性是配置文件中的唯一标识,transaction-manager属性指定事务管理器。另外,还需要<tx:attributes>子元素,该子元素可配置多个<tx:method>子元素指定执行事务的细节。
当<tx:advice>元素配置了事务的增强处理后,就可以通过编写AOP配置,让Spring自动对目标对象生成代理。下面通过一个实例演示如何通过XML方式来实现Spring的声明式事务管理。为体现事务管理的流程,本实例创建了Dao、Service和Controller三层,具体实现步骤如下:
1.导入相关的JAR包
2.创建Dao层
在ch5的src目录下,创建包com.statement.dao,并在包中创建TestDao接口和TestDaoImpl实现类。数据访问层有两个数据操作方法save()和delete()。
package com.statement.dao;
public interface TestDao {
public int save(String sql, Object param[]);
public int delete(String sql, Object param[]);
}
package com.statement.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("TestDao")
public class TestDaoImpl implements TestDao{
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int save(String sql, Object[] param) {
return jdbcTemplate.update(sql,param);
}
@Override
public int delete(String sql, Object[] param) {
return jdbcTemplate.update(sql,param);
}
}
3.创建Service层
在ch5的src目录下,创建包com.statement.service,并在包中创建TestService接口和TestServiceImpl实现类。在Service层依赖注入数据访问层。
package com.statement.service;
public interface TestService {
public int save(String sql, Object param[]);
public int delete(String sql, Object param[]);
}
package com.statement.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.statement.dao.TestDao;
@Service("testService")
public class TestServiceImpl implements TestService{
@Autowired
private TestDao testDao;
@Override
public int save(String sql, Object[] param) {
return testDao.save(sql, param);
}
@Override
public int delete(String sql, Object[] param) {
return testDao.delete(sql, param);
}
}
4.创建Controller层
在ch5的src目录下,创建包com.statement.controller,并在包中创建StatementController控制器类。在控制层依赖注入Service层。
package com.statement.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.statement.service.TestService;
@Controller("statementController")
public class StatementController {
@Autowired
private TestService testService;
public String test() {
String message = "";
String deleteSql ="delete from user";
String saveSql = "insert into user values(?,?,?)";
Object param[] = {1,"基于AOP的声明式事务管理","男"};
try{
testService.delete(deleteSql, null);
testService.save(saveSql, param);
//插入两条主键重复的数据
testService.save(saveSql, param);
}catch(Exception e){
message = "主键重复,事务回滚!";
e.printStackTrace();
}
return message;
}
}
5.创建配置文件
在ch5的src目录下,创建包com.statement.xml,并在包中创建配置文件XMLstatementapplicationContext.xml。在配置文件中,使用<tx:advice>编写通知声明事务,使用<aop:config>编写AOP让Spring自动对目标对象生成代理。
其余同5.2.1
<!-- 编写通知声明事务 -->
<tx:advice id="myAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- *表示任意方法 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 编写AOP,让Spring自动对目标对象生成代理,需要使用AspectJ的表达式 -->
<aop:config>
<!-- 定义切入点 -->
<aop:pointcut expression="execution(* com.statement.service.*.*())" id="txPointCut"/>
<!-- 切面:将切入点与通知关联 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPointCut"/>
</aop:config>
6.创建测试类
在ch5的src目录下,创建包com.statement.test,并在包中创建测试类XMLTest。在测试类中通过访问Controller,测试基于XML方式的声明式事务管理。
package com.statement.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.statement.controller.StatementController;
public class XMLTest {
public static void main(String[] args) {
ApplicationContext appCon = new ClassPathXmlApplicationContext("/com/statement/xml/XMLstatementapplicationContext.xml");
StatementController ct = (StatementController)appCon.getBean("statementController");
String result = ct.test();
System.out.println(result);
}
}
结果同5.2.1是一样的,因为都是用的Spring底层API实现的,哈:
5.3.2 基于@Transactional注解的声明式事务管理
@Transactional注解可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,也可以在方法级别使用该注解来覆盖类级别的定义。虽然@Transactional注解可以作用于接口、接口方法、类以及类方法上,但是Spring小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。
如果不想对某个异常进行事务处理,示例代码如下:
//不对RuntimeException回滚生效
@Transactional(rollbackFor=RuntimeException.class)
// 不对Exception回滚生效
@Transactional(rollbackFor=Exception.class)
下面通过实例演示使用@Transactional注解进行事务管理的过程,该实例的Dao、Service和Controller层与5.3.1节中相同。具体步骤如下:
1.创建配置文件
在包com.statement.xml包中创建配置文件annotationstatementapplicationContext.xml。在配置文件中,使用<tx:annotation-driven>元素为事务管理器注册注解驱动器。
<!-- 为事务管理器注册注解驱动 -->
<tx:annotation-driven transaction-manager="txManager" />
2.为Service层添加@Transactional注解
在Spring MVC(后续章节讲解)中,通常通过Service层进行事务管理,因此需要为Service层添加@Transactional注解。
package com.statement.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.statement.dao.TestDao;
@Service("testService")
@Transactional
//加上注解@Transactional,就可以指定这个类需要受Spring的事务管理
//注意@Transactional只能针对public属性范围内的方法添加
public class TestServiceImpl implements TestService{
@Autowired
private TestDao testDao;
@Override
public int save(String sql, Object[] param) {
return testDao.save(sql, param);
}
@Override
public int delete(String sql, Object[] param) {
return testDao.delete(sql, param);
}
}
测试类和运行结果都和5.3.1一模一样。