All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.ao.Transaction Maven / Gradle / Ivy

Go to download

This is the core library for Active Objects. It is generic and can be embedded in any environment. As such it is generic and won't contain all connection pooling, etc.

There is a newer version: 6.1.1
Show newest version
/*
 * Copyright 2007 Daniel Spiewak
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 * 
 *	    http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.ao;

import java.sql.Connection;
import java.sql.SQLException;

import static net.java.ao.sql.SqlUtils.closeQuietly;

/**
 * 

Allows for the syntactically simple use of database transactions within the * ActiveObjects API. This class's syntax is modeled after the transaction * do ... end syntax provided by Rails's ActiveRecord ORM. The intention * is to provide the simplest possible encapsulation around the transaction * functionality. As such, AO transactions lack some of the power of the * underlying database transaction function (such as arbitrary save-points).

* *

The design behind Transaction is modeled after the * following code snippet:

* *
new Transaction<Object>(manager) {
 *     public Object run() {
 *         Account a = getEntityManager().get(Account.class, 1);
 *         Account b = getEntityManager().get(Account.class, 2);
 *
 *         a.setBalance(a.getBalance() - 1000);
 *         a.save();
 *
 *         b.setBalance(b.getBalance() + 1000);
 *         b.save();
 *
 *         return null;
 *     }
 * }.execute();
* *

The transaction will be committed only after the run() * method returns. Thus, a.save() doesn't immediately modify * the database values, only upon the committal of the transaction. If any * conflicts are detected, JDBC will automatically throw an {@link SQLException}. * Transaction catches this exception and rolls back the * transaction, ensuring data integrity. Once the transaction is rolled back, the * exception is rethrown from the execute() method.

* *

In cases where the transaction generates data which must be returned, this * can be accomplished by returning from the {@link #run()} method against the * parameterized type. Thus if a transaction to create an account is utilized:

* *
Account result = new Transaction<Account>(manager) {
 *     public Account run() throws SQLException {
 *         Account back = getEntityManager().create(Account.class);
 *
 *         back.setBalance(0);
 *         back.save():
 *
 *         return back;
 *     }
 * }.execute();
* *

The value returned from run() will be passed back up the call * stack to execute(), which will return the value to the caller. * Thus in this example, result will be precisely the back * instance from within the transaction. This feature allows data to escape the * scope of the transaction, thereby achieving a greater usefulness.

* *

The JDBC transaction type used is {@link Connection#TRANSACTION_SERIALIZABLE}.

* * @author Daniel Spiewak * @see java.sql.Connection */ public abstract class Transaction { private final EntityManager manager; private enum TransactionState { START, RUNNING, COMMITTED } /** * Creates a new Transaction using the specified * {@link EntityManager} instance. If the specified instance is null, * an exception will be thrown. * * @param manager The EntityManager instance against which the * transaction should run. * @throws IllegalArgumentException If the {@link EntityManager} instance is null. */ public Transaction(EntityManager manager) { if (manager == null) { throw new IllegalArgumentException("EntityManager instance cannot be null"); } this.manager = manager; } protected final EntityManager getEntityManager() { return manager; } /** *

Executes the transaction defined within the overridden {@link #run()} * method. If the transaction fails for any reason (such as a conflict), it will * be rolled back and an exception thrown. The value returned from the * run() method will be returned from execute().

* *

Custom JDBC code can be executed within a transaction. However, one * should be a bit careful with the mutable state of the {@link Connection} * instance obtained from getEntityManager().getProvider().getConnection(). * This is because it is this exact instance which is used in all database * operations for that transaction. Thus it is technically possible to commit a * transaction prematurely, disable the transaction entirely, or otherwise really * mess up the internals of the implementation. You do not have to * call setAutoCommit(boolean) on the {@link Connection} * instance retrieved from the {@link DatabaseProvider}. The connection is * already initialized and within an open transaction by the time it gets to your * custom code within the transaction.

* * @return The value (if any) returned from the transaction run() * @throws SQLException If the transaction failed for any reason and was rolled back. * @see #run() */ public T execute() throws SQLException { final DatabaseProvider provider = manager.getProvider(); TransactionState state = TransactionState.START; Connection c = null; try { c = provider.startTransaction(); state = TransactionState.RUNNING; final T back = run(); provider.commitTransaction(c); state = TransactionState.COMMITTED; return back; } finally { if (state == TransactionState.RUNNING && c != null) { provider.rollbackTransaction(c); } provider.setCloseable(c, true); closeQuietly(c); } } /** *

Called internally by {@link #execute()} to actually perform the actions * within the transaction. Any SQLException(s) should be * allowed to propogate back up to the calling method, which will ensure * that the transaction is rolled back and the proper resources disposed. If * the transaction generates a value which must be passed back to the calling * method, this value may be returned as long as it is of the parameterized * type. If no value is generated, null is an acceptable return * value.

* *

Be aware that any operations performed within a transaction * (even if indirectly invoked by the run() method) will use * the exact same {@link Connection} instance. This is to ensure * integrity of the transaction's operations while at the same time allowing * custom JDBC code and queries within the transaction.

* * @return Any value which must be passed back to the calling point (outside * the transaction), or null. * @throws SQLException If something has gone wrong within the transaction and * it requires a roll-back. */ protected abstract T run() throws SQLException; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy