事务的概念
事务是数据库中很重要的一个概念,理解事务的帖子也很多,本教程侧重从代码上来理解。放几个与事务相关的链接:事务解释1 事务解释2
从代码理解事务
本项目的目的是完成一笔转账交易,从A的账户中转出100到B的账户中。A、B账户各自原有1000。
- 创建工程,添加Jar包:本次添加的jar包为
添加的具体方法参考链接 - 在数据库中创建表:创建account数据表,内有三列,id(序号),username(存放账户名),money(存放账户钱数)。再插入两条数据。SQL文件存放到当前工程中。
创建SQL文件的方式
SQL语句如下:
create table account(
id int not null primary key auto_increment,
username varchar(10) not null,
money int not null
);
insert into account(username, money) values("A", "1000");
insert into account(username, money) values("B", "1000");
- 工程目录为:
- 设计思路
刚开始使用spring,对代码的分层不熟练,往往不知道service层和dao层里各要写什么样的函数。
dao层负责数据的存储与获取,往往负责的是一个数据库表的相关操作,该层中每个函数往往完成的是一个个原子性操作,例如数据库的插入、删除、更新、查询这四种操作,每一种操作就是一个原子性操作。
service层就叫业务层,顾名思义就是处理一个业务逻辑的。例如转账它就是一个业务,涉及到转出和转入这两个更新数据的原子操作,放入到dao层。工作后接触的项目可能一个业务逻辑一般都会涉及多张表,那就表示一个service需要由多个Dao来协助。随着见的案例多了,自然就会较合理划分了。 - 代码具体实现:
包com.edu.tjdz.geng.model中创建Account类
package com.edu.tjdz.geng.model;
public class Account {
private int id;
private String username;
private int money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
包com.edu.tjdz.geng.dao存放一个dao接口
package com.edu.tjdz.geng.dao;
import com.edu.tjdz.geng.model.Account;
public interface AccountDao {
public void out(Account outter, int money);
public void in(Account inner, int money);
}
包com.edu.tjdz.geng.dao存放一个dao的实现类
package com.edu.tjdz.geng.dao.impl;
import org.springframework.jdbc.core.JdbcTemplate;
import com.edu.tjdz.geng.dao.AccountDao;
import com.edu.tjdz.geng.model.Account;
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void out(Account outter, int money) {
String sql = "update account set money = money - ? where username = ?";
Object[] args = {money, outter.getUsername()};
jdbcTemplate.update(sql, args);
}
public void in(Account inner, int money) {
String sql = "update account set money = money + ? where username = ?";
Object[] args = {money, inner.getUsername()};
jdbcTemplate.update(sql, args);
}
}
包com.edu.tjdz.geng.service存放一个service接口
package com.edu.tjdz.geng.service;
import com.edu.tjdz.geng.model.Account;
public interface AccountService {
public void transfer(Account outter, Account inner, int money);
}
包com.edu.tjdz.geng.service.impl存放一个service接口的实现类
package com.edu.tjdz.geng.service.impl;
import com.edu.tjdz.geng.dao.AccountDao;
import com.edu.tjdz.geng.model.Account;
import com.edu.tjdz.geng.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(Account outter, Account inner, int money) {
accountDao.out(outter, money);
accountDao.in(inner, money);
}
}
包com.edu.tjdz.geng.test存放测试类
package com.edu.tjdz.geng.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.edu.tjdz.geng.model.Account;
import com.edu.tjdz.geng.service.AccountService;
public class Test {
public static void main(String[] args) {
//创建两个账户用来测试
Account outter = new Account();
Account inner = new Account();
outter.setUsername("A");
inner.setUsername("B");
int money = 100;
ApplicationContext appContext = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accService = (AccountService)appContext.getBean("accountService");
accService.transfer(outter, inner, money);
}
- 配置文件信息:包括一个数据库属性文件,一个spring xml
spring 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: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:property-placeholder location="classpath:db.properties"/>
<!--1、配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--2、配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 3、装配dao -->
<bean id="accountDao" class="com.edu.tjdz.geng.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 3、装配service -->
<bean id="accountService" class="com.edu.tjdz.geng.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
db.properties文件
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai
jdbc.user=root
jdbc.password=123456
- 运行测试类,数据库结果如下:
上图表明转账成功, - 修改代码,营造出事务的感觉
在AccountServiceImpl类的转账业务中,模拟一个故障,例如已经转成还未转入之际,银行突然断电
这个故障造成的后果是A账户已转成100,但是B账户还未收到钱
像转账这样的过程,如果中间某个环节出了错误了,便会引发一系列灾难性后果。不管从银行的角度还是用户的角度都非常不希望这种事情发生。
在数据库中,我们把这样有若干子操作且子操作要么都成功要么都不成功的一个整体性操作称为事务。
Spring 事务要做的就是替程序员处理事务相关的逻辑,让程序员将注意力更多放在与主业务逻辑相关。
配置事务管理
现在使用spring的事务管理功能,避免上面的情形发生
- 修改配置文件
(1)在beans标签里增加命名空间aop和tx以及他们的schema
(2)增加事务管理标签
以后需要修改的是<aop:advisor>标签中pointcut属性值,它指定了哪些类中的哪些方法会涉及到事务管理。
增加的是<tx:attributes>中<tx:method>的数量,针对每一个要被管理的函数,必须单独配置事务管理方式
(3)最终的配置文件源码
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:property-placeholder location="classpath:db.properties"/>
<!--1、配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--2、配置jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 3、装配dao -->
<bean id="accountDao" class="com.edu.tjdz.geng.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 4、装配service -->
<bean id="accountService" class="com.edu.tjdz.geng.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--5、配置事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置具体的事务管理方式 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 针对每一个要被管理的函数,配置个性化的事务管理方式 -->
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--配置拦截点,要对service层中的哪些函数进行事务管理 -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.edu.tjdz.geng.service.impl..*.*(..))"/>
</aop:config>
</beans>
- 类的改变
AccountServiceImpl的代码如下。其他类没有变化
package com.edu.tjdz.geng.service.impl;
import com.edu.tjdz.geng.dao.AccountDao;
import com.edu.tjdz.geng.model.Account;
import com.edu.tjdz.geng.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(Account outter, Account inner, int money) {
accountDao.out(outter, money);
int i = 1/0;
accountDao.in(inner, money);
}
}
- 测试的时候,先将 AccountServiceImpl类中int i = 1/0这一句注释,观察数据库结果,再讲这一句的注释取消后再运行,观察数据库结果。对比两次,检验事务管理是否生效。
如果xml中没有aop或tx标签的提示,请按照如下操作
以配置tx.xsd为例。
点击Window->preferences->输入xml cat->选中XML Catalog
选中User Specified Entries->点击Add
点击File System…
选择tx.xsd所在的本地路径。xsd文件在spring源码包中的路径是:
spring-framework-4.3.6.RELEASE\schema\tx
修改属性:将Key type改为Schema location
将key修改为http://www.springframework.org/schema/tx/spring-tx.xsd
(不同的命名空间,这个值也不同)
点击Apply and Close。之后关闭xml,重新打开就有提示了。
key值是图片红框的某一种,根据命名空间名称来选择。