Transactions and Their Types

Transaction is a set of operations that must all succeed or all fail. In case operations within a transaction fail, they are all reversed back to the state before the transaction. They play a huge role when dealing with the persistence layer (databases). Most importantly, we can use transactions to keep the system in a consistent state.

Transactions are divided into two major categories:

  • Local transactions
  • Global transactions

Local Transactions

Local transactions are transactions that deal with a single resource, like a JDBC connection, to a single database. Therefore these types of transactions don’t need any complicated transaction management and are fairly simple to use.

If a resource is configured to use local transactions, a connection will work on a single transaction at a time. When all of the work for a transaction has been completed and no failures happen, all of the changes are committed. Otherwise the transaction is just rolled back to the initial state. To point out, before the actual commit is done none of the changes are visible to anyone. Only after the commit is run the changes can be seen.

local transaction
Successful transaction (Saving user to the database)

In the image above we can see the process of how a user gets saved to the users table. This is done by using a local transaction. As an extra detail we can imagine that we are using a PostgreSQL database. First, the transaction starts with the begin keyword. Next follows the actual statement we want to execute- saving a user. In case everything is fine and no exceptions are thrown, the change is committed and user becomes visible in the users table.

We could also write the same thing as a SQL statement:

BEGIN;

INSERT INTO users (name, age) VALUES ('Ada', 32);

COMMIT;

Note: Every SQL statement in PostgreSQL in actually treated as a transaction and in a simple case we don’t need to specify the BEGIN and COMMIT statement. PostgreSQL adds those automatically. So you could write the same thing as the following instead:

INSERT INTO users (name, age) VALUES ('Ada', 32);

Another example where transactions are incredibly useful is within the banking field. Let’s say we want to send money from a checking account to a savings account. In order to do that we should have the following steps:

  • Step 1: Take money from the checking account.
  • Step 2: Put money to the savings account.

Either all or non of those steps should succeed to prevent the accounts from getting into an inconsistent state. In order to do that we can put those two operations into a transaction. Now, if we fail on executing one of the steps, no money gets taken away from the checking account and no money gets put to the savings account.

BEGIN;

UPDATE accounts SET balance = balance - 200.00
    WHERE name = 'Ada';
UPDATE accounts SET balance = balance + 200.00
    WHERE name = 'James';

COMMIT;

This is quite a simple example and in real life we would probably need to check if Ada has enough money on the account as well. But hopefully the idea of local transactions is much clearer now.

Global Transactions (XA)

Global transactions are more complex than local ones as they allow to work with multiple transactional resources (e.g database, message queue) in a single transaction. They are also known under the name XA transactions or distributed transactions.

In order to handle transactions over multiple resources the creation of a global transaction is usually in the hands of a transaction manager. Transaction manager is the component that coordinates all the commits and rollbacks of the participating transactional resources. To do all of this, it uses a protocol called two-phase commit. But for a resource to actually be able to participate in a global transaction it has to have the support for it.

Now, let’s make a simple example of where we could use a global transaction. We can imagine we have an online shop. Every time a customer buys something we get a call to a system that does the following:

  • Save the order to the database.
  • Send the order for shipment to the logistics department over a queue.

These two steps will always have to succeed in order for the system to be in a consistent state. And most importantly, the customer should always get what they paid for. So to do that we can put all of this logic into a global transaction. The transaction manager checks if the database and queue can both commit the changes. In case everything is alright a commit is made to both of the resources. Otherwise there will be a rollback in both of them. In that case it would be great if we would have a backup plan in our system for potential rollbacks.

To use global transactions in Java we could use the Java Transaction API (JTA).

Conclusion

Transaction is a set of operations that must all succeed or all fail. There are two types of transactions we can use: local and global ones. In a most common case we are using local transactions when dealing with a single transactional resource. But in case we need to have a more complex coordination over multiple transactional resources that need to be in a consistent state, we could consider using global transactions instead.

Be the first to reply

Leave a Reply

Your email address will not be published. Required fields are marked *