可编程事务管理
什么是可编程的事务管理,简单的将就是自己通过编程来管理事务,这种方式的事务管理具有相当程度的复杂性,不是那么容易maintain. 为了更好的描述这种事务管理方式,我们首先创建两个table: Students and Marks
Student表用来记录Student的相关信息.
CREATE TABLE Student(
ID INT NOT NULL AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
AGE INT NOT NULL,
PRIMARY KEY (ID)
);
Marks表用来维护近年来某学生的成绩, 这里SID是foreign key。
CREATE TABLE Marks(
SID INT NOT NULL,
MARKS INT NOT NULL,
YEAR INT NOT NULL
);
PlatformTransactionManager提供的相关接口来进行事务管理。
public interface PlatformTransactionManager {
/**
* Return a currently active transaction or create a new one, according to
* the specified propagation behavior.
* <p>Note that parameters like isolation level or timeout will only be applied
* to new transactions, and thus be ignored when participating in active ones.
* <p>Furthermore, not all transaction definition settings will be supported
* by every transaction manager: A proper transaction manager implementation
* should throw an exception when unsupported settings are encountered.
* <p>An exception to the above rule is the read-only flag, which should be
* ignored if no explicit read-only mode is supported. Essentially, the
* read-only flag is just a hint for potential optimization.
* @param definition TransactionDefinition instance (can be {@code null} for defaults),
* describing propagation behavior, isolation level, timeout etc.
* @return transaction status object representing the new or current transaction
* @throws TransactionException in case of lookup, creation, or system errors
* @throws IllegalTransactionStateException if the given transaction definition
* cannot be executed (for example, if a currently active transaction is in
* conflict with the specified propagation behavior)
* @see TransactionDefinition#getPropagationBehavior
* @see TransactionDefinition#getIsolationLevel
* @see TransactionDefinition#getTimeout
* @see TransactionDefinition#isReadOnly
*/
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
/**
* Commit the given transaction, with regard to its status. If the transaction
* has been marked rollback-only programmatically, perform a rollback.
* <p>If the transaction wasn't a new one, omit the commit for proper
* participation in the surrounding transaction. If a previous transaction
* has been suspended to be able to create a new one, resume the previous
* transaction after committing the new one.
* <p>Note that when the commit call completes, no matter if normally or
* throwing an exception, the transaction must be fully completed and
* cleaned up. No rollback call should be expected in such a case.
* <p>If this method throws an exception other than a TransactionException,
* then some before-commit error caused the commit attempt to fail. For
* example, an O/R Mapping tool might have tried to flush changes to the
* database right before commit, with the resulting DataAccessException
* causing the transaction to fail. The original exception will be
* propagated to the caller of this commit method in such a case.
* @param status object returned by the {@code getTransaction} method
* @throws UnexpectedRollbackException in case of an unexpected rollback
* that the transaction coordinator initiated
* @throws HeuristicCompletionException in case of a transaction failure
* caused by a heuristic decision on the side of the transaction coordinator
* @throws TransactionSystemException in case of commit or system errors
* (typically caused by fundamental resource failures)
* @throws IllegalTransactionStateException if the given transaction
* is already completed (that is, committed or rolled back)
* @see TransactionStatus#setRollbackOnly
*/
void commit(TransactionStatus status) throws TransactionException;
/**
* Perform a rollback of the given transaction.
* <p>If the transaction wasn't a new one, just set it rollback-only for proper
* participation in the surrounding transaction. If a previous transaction
* has been suspended to be able to create a new one, resume the previous
* transaction after rolling back the new one.
* <p><b>Do not call rollback on a transaction if commit threw an exception.</b>
* The transaction will already have been completed and cleaned up when commit
* returns, even in case of a commit exception. Consequently, a rollback call
* after commit failure will lead to an IllegalTransactionStateException.
* @param status object returned by the {@code getTransaction} method
* @throws TransactionSystemException in case of rollback or system errors
* (typically caused by fundamental resource failures)
* @throws IllegalTransactionStateException if the given transaction
* is already completed (that is, committed or rolled back)
*/
void rollback(TransactionStatus status) throws TransactionException;
}
那么如何使用PlatformTransactionManager呢?通过阅读注释我们可以观察到, 首先要获取一个TransactionDefinition 的instance, 简单地,在这里创建一个DefaultTransactionDefinition 的instance。关于DefaultTransactionDefinition
/**
* Default implementation of the {@link TransactionDefinition} interface,
* offering bean-style configuration and sensible default values
* (PROPAGATION_REQUIRED, ISOLATION_DEFAULT, TIMEOUT_DEFAULT, readOnly=false).
*
* <p>Base class for both {@link TransactionTemplate} and
* {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}.
*
* @author Juergen Hoeller
* @since 08.05.2003
*/
@SuppressWarnings("serial")
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
/** Prefix for the propagation constants defined in TransactionDefinition */
public static final String PREFIX_PROPAGATION = "PROPAGATION_";
/** Prefix for the isolation constants defined in TransactionDefinition */
public static final String PREFIX_ISOLATION = "ISOLATION_";
/** Prefix for transaction timeout values in description strings */
public static final String PREFIX_TIMEOUT = "timeout_";
/** Marker for read-only transactions in description strings */
public static final String READ_ONLY_MARKER = "readOnly";
/** Constants instance for TransactionDefinition */
static final Constants constants = new Constants(TransactionDefinition.class);
private int propagationBehavior = PROPAGATION_REQUIRED;
private int isolationLevel = ISOLATION_DEFAULT;
private int timeout = TIMEOUT_DEFAULT;
private boolean readOnly = false;
private String name;
/**
* Create a new DefaultTransactionDefinition, with default settings.
* Can be modified through bean property setters.
* @see #setPropagationBehavior
* @see #setIsolationLevel
* @see #setTimeout
* @see #setReadOnly
* @see #setName
*/
public DefaultTransactionDefinition() {
}
/**
* Copy constructor. Definition can be modified through bean property setters.
* @see #setPropagationBehavior
* @see #setIsolationLevel
* @see #setTimeout
* @see #setReadOnly
* @see #setName
*/
public DefaultTransactionDefinition(TransactionDefinition other) {
this.propagationBehavior = other.getPropagationBehavior();
this.isolationLevel = other.getIsolationLevel();
this.timeout = other.getTimeout();
this.readOnly = other.isReadOnly();
this.name = other.getName();
}
/**
* Create a new DefaultTransactionDefinition with the given
* propagation behavior. Can be modified through bean property setters.
* @param propagationBehavior one of the propagation constants in the
* TransactionDefinition interface
* @see #setIsolationLevel
* @see #setTimeout
* @see #setReadOnly
*/
public DefaultTransactionDefinition(int propagationBehavior) {
this.propagationBehavior = propagationBehavior;
}
/**
* Set the propagation behavior by the name of the corresponding constant in
* TransactionDefinition, e.g. "PROPAGATION_REQUIRED".
* @param constantName name of the constant
* @exception IllegalArgumentException if the supplied value is not resolvable
* to one of the {@code PROPAGATION_} constants or is {@code null}
* @see #setPropagationBehavior
* @see #PROPAGATION_REQUIRED
*/
public final void setPropagationBehaviorName(String constantName) throws IllegalArgumentException {
if (constantName == null || !constantName.startsWith(PREFIX_PROPAGATION)) {
throw new IllegalArgumentException("Only propagation constants allowed");
}
setPropagationBehavior(constants.asNumber(constantName).intValue());
}
/**
* Set the propagation behavior. Must be one of the propagation constants
* in the TransactionDefinition interface. Default is PROPAGATION_REQUIRED.
* @exception IllegalArgumentException if the supplied value is not
* one of the {@code PROPAGATION_} constants
* @see #PROPAGATION_REQUIRED
*/
public final void setPropagationBehavior(int propagationBehavior) {
if (!constants.getValues(PREFIX_PROPAGATION).contains(propagationBehavior)) {
throw new IllegalArgumentException("Only values of propagation constants allowed");
}
this.propagationBehavior = propagationBehavior;
}
@Override
public final int getPropagationBehavior() {
return this.propagationBehavior;
}
/**
* Set the isolation level by the name of the corresponding constant in
* TransactionDefinition, e.g. "ISOLATION_DEFAULT".
* @param constantName name of the constant
* @exception IllegalArgumentException if the supplied value is not resolvable
* to one of the {@code ISOLATION_} constants or is {@code null}
* @see #setIsolationLevel
* @see #ISOLATION_DEFAULT
*/
public final void setIsolationLevelName(String constantName) throws IllegalArgumentException {
if (constantName == null || !constantName.startsWith(PREFIX_ISOLATION)) {
throw new IllegalArgumentException("Only isolation constants allowed");
}
setIsolationLevel(constants.asNumber(constantName).intValue());
}
/**
* Set the isolation level. Must be one of the isolation constants
* in the TransactionDefinition interface. Default is ISOLATION_DEFAULT.
* @exception IllegalArgumentException if the supplied value is not
* one of the {@code ISOLATION_} constants
* @see #ISOLATION_DEFAULT
*/
public final void setIsolationLevel(int isolationLevel) {
if (!constants.getValues(PREFIX_ISOLATION).contains(isolationLevel)) {
throw new IllegalArgumentException("Only values of isolation constants allowed");
}
this.isolationLevel = isolationLevel;
}
@Override
public final int getIsolationLevel() {
return this.isolationLevel;
}
/**
* Set the timeout to apply, as number of seconds.
* Default is TIMEOUT_DEFAULT (-1).
* @see #TIMEOUT_DEFAULT
*/
public final void setTimeout(int timeout) {
if (timeout < TIMEOUT_DEFAULT) {
throw new IllegalArgumentException("Timeout must be a positive integer or TIMEOUT_DEFAULT");
}
this.timeout = timeout;
}
@Override
public final int getTimeout() {
return this.timeout;
}
/**
* Set whether to optimize as read-only transaction.
* Default is "false".
*/
public final void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Override
public final boolean isReadOnly() {
return this.readOnly;
}
/**
* Set the name of this transaction. Default is none.
* <p>This will be used as transaction name to be shown in a
* transaction monitor, if applicable (for example, WebLogic's).
*/
public final void setName(String name) {
this.name = name;
}
@Override
public final String getName() {
return this.name;
}
/**
* This implementation compares the {@code toString()} results.
* @see #toString()
*/
@Override
public boolean equals(Object other) {
return (other instanceof TransactionDefinition && toString().equals(other.toString()));
}
/**
* This implementation returns {@code toString()}'s hash code.
* @see #toString()
*/
@Override
public int hashCode() {
return toString().hashCode();
}
/**
* Return an identifying description for this transaction definition.
* <p>The format matches the one used by
* {@link org.springframework.transaction.interceptor.TransactionAttributeEditor},
* to be able to feed {@code toString} results into bean properties of type
* {@link org.springframework.transaction.interceptor.TransactionAttribute}.
* <p>Has to be overridden in subclasses for correct {@code equals}
* and {@code hashCode} behavior. Alternatively, {@link #equals}
* and {@link #hashCode} can be overridden themselves.
* @see #getDefinitionDescription()
* @see org.springframework.transaction.interceptor.TransactionAttributeEditor
*/
@Override
public String toString() {
return getDefinitionDescription().toString();
}
/**
* Return an identifying description for this transaction definition.
* <p>Available to subclasses, for inclusion in their {@code toString()} result.
*/
protected final StringBuilder getDefinitionDescription() {
StringBuilder result = new StringBuilder();
result.append(constants.toCode(this.propagationBehavior, PREFIX_PROPAGATION));
result.append(',');
result.append(constants.toCode(this.isolationLevel, PREFIX_ISOLATION));
if (this.timeout != TIMEOUT_DEFAULT) {
result.append(',');
result.append(PREFIX_TIMEOUT).append(this.timeout);
}
if (this.readOnly) {
result.append(',');
result.append(READ_ONLY_MARKER);
}
return result;
}
}
只要TransactionDefinition 创建成功,就可以通过 getTransaction()来开启事务,该方法返回TransactionStatus的实例。TransactionStatus可以帮助我们追踪当前事务的状态。如果一切运行良好,就可以调用PlatformTransactionManager 的commit() 方法来commit这个事物,反之调用rollback()来rollback。
结合操作Student和Marks Table, 接下来简单描述如何管理事务。给出interface文件StudentDAO.java
package com.tutorialspoint;
import java.util.List;
import javax.sql.DataSource;
public interface StudentDAO {
/**
* This is the method to be used to initialize
* database resources ie. connection.
*/
public void setDataSource(DataSource ds);
/**
* This is the method to be used to create
* a record in the Student and Marks tables.
*/
public void create(String name, Integer age, Integer marks, Integer year);
/**
* This is the method to be used to list down
* all the records from the Student and Marks tables.
*/
public List<StudentMarks> listStudents();
}
StudentMarks.java
package com.tutorialspoint;
public class StudentMarks {
private Integer age;
private String name;
private Integer id;
private Integer marks;
private Integer year;
private Integer sid;
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;
}
public void setMarks(Integer marks) {
this.marks = marks;
}
public Integer getMarks() {
return marks;
}
public void setYear(Integer year) {
this.year = year;
}
public Integer getYear() {
return year;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public Integer getSid() {
return sid;
}
}
StudentMarksMapper.java
package com.tutorialspoint;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class StudentMarksMapper implements RowMapper<StudentMarks> {
public StudentMarks mapRow(ResultSet rs, int rowNum) throws SQLException {
StudentMarks studentMarks = new StudentMarks();
studentMarks.setId(rs.getInt("id"));
studentMarks.setName(rs.getString("name"));
studentMarks.setAge(rs.getInt("age"));
studentMarks.setSid(rs.getInt("sid"));
studentMarks.setMarks(rs.getInt("marks"));
studentMarks.setYear(rs.getInt("year"));
return studentMarks;
}
}
StudentJDBCTemplate.java
package com.tutorialspoint;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
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;
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, Integer marks, Integer year){
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
String SQL1 = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update( SQL1, name, age);
// Get the latest student id to be used in Marks table
String SQL2 = "select max(id) from Student";
int sid = jdbcTemplateObject.queryForInt( SQL2 );
String SQL3 = "insert into Marks(sid, marks, year) " + "values (?, ?, ?)";
jdbcTemplateObject.update( SQL3, sid, marks, year);
System.out.println("Created Name = " + name + ", Age = " + age);
transactionManager.commit(status);
}
catch (DataAccessException e) {
System.out.println("Error in creating record, rolling back");
transactionManager.rollback(status);
throw e;
}
return;
}
public List<StudentMarks> listStudents() {
String SQL = "select * from Student, Marks where Student.id=Marks.sid";
List <StudentMarks> studentMarks = jdbcTemplateObject.query(SQL,
new StudentMarksMapper());
return studentMarks;
}
}
MainApp.java
package com.tutorialspoint;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.tutorialspoint.StudentJDBCTemplate;
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, 99, 2010);
studentJDBCTemplate.create("Nuha", 20, 97, 2010);
studentJDBCTemplate.create("Ayan", 25, 100, 2011);
System.out.println("------Listing all the records--------" );
List<StudentMarks> studentMarks = studentJDBCTemplate.listStudents();
for (StudentMarks record : studentMarks) {
System.out.print("ID : " + record.getId() );
System.out.print(", Name : " + record.getName() );
System.out.print(", Marks : " + record.getMarks());
System.out.print(", Year : " + record.getYear());
System.out.println(", Age : " + record.getAge());
}
}
}
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"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ">
<!-- Initialization for data source -->
<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/TEST"/>
<property name = "username" value = "root"/>
<property name = "password" value = "password"/>
</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.tutorialspoint.StudentJDBCTemplate">
<property name = "dataSource" ref = "dataSource" />
<property name = "transactionManager" ref = "transactionManager" />
</bean>
</beans>
让我们来执行以下代码
------Records creation--------
Created Name = Zara, Age = 11
Created Name = Nuha, Age = 20
Created Name = Ayan, Age = 25
------Listing all the records--------
ID : 1, Name : Zara, Marks : 99, Year : 2010, Age : 11
ID : 2, Name : Nuha, Marks : 97, Year : 2010, Age : 20
ID : 3, Name : Ayan, Marks : 100, Year : 2011, Age : 25