一、概述
事务
一个数据库事务是一个被视为单一的工作单元的操作序列。这些操作应该要么完整地执行,要么完全不执行。
四个特性
- 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
- 一致性:是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性(数据库上预先设置的约束校验)。比如对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNT表中A和B的存款总额为2000元。
- 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
- 持久性:意味着在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。即使出现了任何事故比如断电等,事务一旦提交,则持久化保存在数据库中。
局部事务 VS 全局事物
局部事务是特定于一个单一的事务资源,如一个 JDBC 连接,而全局事务可以跨多个事务资源事务,如在一个分布式系统中的事务。
局部事务管理在一个集中的计算环境中是有用的,该计算环境中应用程序组件和资源位于一个单位点,而事务管理只涉及到一个运行在一个单一机器中的本地数据管理器。局部事务更容易实现。
全局事务管理需要在分布式计算环境中,所有的资源都分布在多个系统中。在这种情况下事务管理需要同时在局部和全局范围内进行。分布式或全局事务跨多个系统执行,它的执行需要全局事务管理系统和所有相关系统的局部数据管理人员之间的协调。
编程式事务 VS 声明式事务
- 编程式事务管理 :这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护。
- 声明式事务管理 :这意味着你从业务代码中分离事务管理。你仅仅使用注释或 XML 配置来管理事务。
二、编程式事务
【示例】
库表结构:
CREATE TABLE [dbo].[Student]( [id] [int] IDENTITY(1,1) NOT NULL, [name] [varchar](20) NOT NULL, [age] [int] NOT NULL )
Student类:
package com.my; /** * @Author jyy * @Description {} * @Date 2018/8/8 16:40 */ public class Student { private Integer age; private String name; private Integer id; public void setAge(Integer age) { this.age = age; } public Integer getAge() { return age; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setId(Integer id) { this.id = id; } public Integer getId() { return id; } }
接口类:
package com.my; import javax.sql.DataSource; import java.util.List; /** * @Author jyy * @Description {} * @Date 2018/8/8 16:39 */ public interface StudentDAO { /** * This is the method to be used to initialize * database resources ie. connection. */ void setDataSource(DataSource ds); /** * This is the method to be used to create * a record in the Student table. */ void create(String name, Integer age); /** * This is the method to be used to list down * a record from the Student table corresponding * to a passed student id. */ Student getStudent(Integer id); /** * This is the method to be used to list down * all the records from the Student table. */ List<Student> listStudents(); /** * This is the method to be used to delete * a record from the Student table corresponding * to a passed student id. */ void delete(Integer id); /** * This is the method to be used to update * a record into the Student table. */ void update(Integer id, Integer age); }
接口实现类:
package com.my; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import javax.sql.DataSource; import java.util.List; /** * @Author jyy * @Description {} * @Date 2018/8/8 16:42 */ public class StudentJDBCTemplate implements StudentDAO { private DataSource dataSource; private JdbcTemplate jdbcTemplateObject; private PlatformTransactionManager transactionManager;//事务管理 public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; this.jdbcTemplateObject = new JdbcTemplate(dataSource); } public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public void create(String name, Integer age) { TransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def);//TransactionStatus 对象帮助追踪当前的事务状态 String SQL = "insert into Student (name, age) values (?, ?)"; try { jdbcTemplateObject.update(SQL, name, age); transactionManager.commit(status); }catch(Exception e){ System.out.println("Error in creating record, rolling back"); transactionManager.rollback(status); } System.out.println("Created Record Name = " + name + " Age = " + age); return; } public Student getStudent(Integer id) { String SQL = "select * from Student where id = ?"; Student student = jdbcTemplateObject.queryForObject(SQL, new Object[]{id}, new StudentMapper()); return student; } public List<Student> listStudents() { String SQL = "select * from Student"; List<Student> students = jdbcTemplateObject.query(SQL, new StudentMapper()); return students; } public void delete(Integer id){ String SQL = "delete from Student where id = ?"; jdbcTemplateObject.update(SQL, id); System.out.println("Deleted Record with ID = " + id ); return; } public void update(Integer id, Integer age){ String SQL = "update Student set age = ? where id = ?"; jdbcTemplateObject.update(SQL, age, id); System.out.println("Updated Record with ID = " + id ); return; } }
实体转换:
package com.my; import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; /** * @Author jyy * @Description {} * @Date 2018/8/8 16:41 */ public class StudentMapper implements RowMapper<Student> { public Student mapRow(ResultSet rs, int rowNum) throws SQLException { Student student = new Student(); student.setId(rs.getInt("id")); student.setName(rs.getString("name")); student.setAge(rs.getInt("age")); return student; } }
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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <!-- Initialization for data source --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 使用外部属性文件的属性 --> <property name="user" value="sa"></property> <property name="password" value="123"></property> <property name="driverClass" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"></property> <property name="jdbcUrl" value="jdbc:sqlserver://localhost:1433;DatabaseName=TEST"></property> <property name="initialPoolSize" value="5"></property> <property name="maxPoolSize" value="10"></property> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!--Definition for studentJDBCTemplate bean--> <bean id="studentJDBCTemplate" class="com.my.StudentJDBCTemplate"> <property name="dataSource" ref="dataSource"/> <property name="transactionManager" ref="transactionManager" /> </bean> </beans>
package com.my; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @Author jyy * @Description {} * @Date 2018/7/13 10:06 */ public class MainApp { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); StudentJDBCTemplate studentJDBCTemplate = (StudentJDBCTemplate) context.getBean("studentJDBCTemplate"); System.out.println("------Records Creation--------"); studentJDBCTemplate.create("Zara", 11); studentJDBCTemplate.create("Nuha", 2); studentJDBCTemplate.create("Ayan", 15); System.out.println("------Listing Multiple Records--------"); } }
执行结果:数据库中插入了三条数据
------Records Creation-------- Created Record Name = Zara Age = 11 Created Record Name = Nuha Age = 2 Created Record Name = Ayan Age = 15 ------Listing Multiple Records--------
我们对接口实现类稍做修改:
package com.my; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import javax.sql.DataSource; import java.util.List; /** * @Author jyy * @Description {} * @Date 2018/8/8 16:42 */ public class StudentJDBCTemplate implements StudentDAO { private DataSource dataSource; private JdbcTemplate jdbcTemplateObject; private PlatformTransactionManager transactionManager;//事务管理 public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; this.jdbcTemplateObject = new JdbcTemplate(dataSource); } public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public void create(String name, Integer age) { TransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def);//TransactionStatus 对象帮助追踪当前的事务状态 String SQL = "insert into Student (name, age) values (?, ?)"; try { jdbcTemplateObject.update(SQL, name, age); int error = 1 / 0;//修改内容 transactionManager.commit(status); } catch (Exception e) { System.out.println("Error in creating record, rolling back"); transactionManager.rollback(status); } System.out.println("Created Record Name = " + name + " Age = " + age); return; } public Student getStudent(Integer id) { String SQL = "select * from Student where id = ?"; Student student = jdbcTemplateObject.queryForObject(SQL, new Object[]{id}, new StudentMapper()); return student; } public List<Student> listStudents() { String SQL = "select * from Student"; List<Student> students = jdbcTemplateObject.query(SQL, new StudentMapper()); return students; } public void delete(Integer id) { String SQL = "delete from Student where id = ?"; jdbcTemplateObject.update(SQL, id); System.out.println("Deleted Record with ID = " + id); return; } public void update(Integer id, Integer age) { String SQL = "update Student set age = ? where id = ?"; jdbcTemplateObject.update(SQL, age, id); System.out.println("Updated Record with ID = " + id); return; } }
再次执行:数据库中插入0条数据
------Records Creation-------- Error in creating record, rolling back Created Record Name = Zara Age = 11 Error in creating record, rolling back Created Record Name = Nuha Age = 2 Error in creating record, rolling back Created Record Name = Ayan Age = 15 ------Listing Multiple Records--------
以上示例,展示了一个简单事务的实现过程,事务增加了程序的安全性,保证数据可以安全可靠的到达目标端。但是这种实现方式,需要我们在每一个需要使用事务的地方都添加相应的代码,实现过程较为复杂。下面我们将提供一个便捷的方式,减少开发人员的工作量。
三、声明式事务
根据上面的示例,我们稍加改动,首先修改接口实现类
package com.my; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import javax.sql.DataSource; import java.util.List; /** * @Author jyy * @Description {} * @Date 2018/8/8 16:42 */ public class StudentJDBCTemplate implements StudentDAO { private DataSource dataSource; private JdbcTemplate jdbcTemplateObject; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; this.jdbcTemplateObject = new JdbcTemplate(dataSource); } public void create(String name, Integer age) { String SQL = "insert into Student (name, age) values (?, ?)"; jdbcTemplateObject.update(SQL, name, age); int j = 1 / 0; System.out.println("Created Record Name = " + name + " Age = " + age); return; } public Student getStudent(Integer id) { String SQL = "select * from Student where id = ?"; Student student = jdbcTemplateObject.queryForObject(SQL, new Object[]{id}, new StudentMapper()); return student; } public List<Student> listStudents() { String SQL = "select * from Student"; List<Student> students = jdbcTemplateObject.query(SQL, new StudentMapper()); return students; } public void delete(Integer id) { String SQL = "delete from Student where id = ?"; jdbcTemplateObject.update(SQL, id); System.out.println("Deleted Record with ID = " + id); return; } public void update(Integer id, Integer age) { String SQL = "update Student set age = ? where id = ?"; jdbcTemplateObject.update(SQL, age, id); System.out.println("Updated Record with ID = " + id); return; } }
再修改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: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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!-- Initialization for data source --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 使用外部属性文件的属性 --> <property name="user" value="sa"></property> <property name="password" value="123"></property> <property name="driverClass" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"></property> <property name="jdbcUrl" value="jdbc:sqlserver://localhost:1433;DatabaseName=TEST"></property> <property name="initialPoolSize" value="5"></property> <property name="maxPoolSize" value="10"></property> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="create"/> </tx:attributes> </tx:advice> <aop:config proxy-target-class="true"> <aop:pointcut id="createOperation" expression="execution(* com.my.StudentJDBCTemplate.create(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/> </aop:config> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- Definition for studentJDBCTemplate bean --> <bean id="studentJDBCTemplate" class="com.my.StudentJDBCTemplate"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
执行结果:
程序报错,数据库中未插入一条记录
再修改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: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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!-- Initialization for data source --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 使用外部属性文件的属性 --> <property name="user" value="sa"></property> <property name="password" value="123"></property> <property name="driverClass" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"></property> <property name="jdbcUrl" value="jdbc:sqlserver://localhost:1433;DatabaseName=TEST"></property> <property name="initialPoolSize" value="5"></property> <property name="maxPoolSize" value="10"></property> </bean> <!-- <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="create"/> </tx:attributes> </tx:advice> <aop:config proxy-target-class="true"> <aop:pointcut id="createOperation" expression="execution(* com.my.StudentJDBCTemplate.create(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/> </aop:config>--> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- Definition for studentJDBCTemplate bean --> <bean id="studentJDBCTemplate" class="com.my.StudentJDBCTemplate"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
执行结果:插入一条记录,然后报错
声明式事务虽然不如编程式事务灵活、更加有针对性,但是它为开发者提供了一种更加方便、可扩展性更强的方式去使用事务。