580 likes | 805 Views
Java III. Mastering Transactions In Java. Transactions: Basics. Transactions: Basics. • In the world of programming a Transaction is a series of actions that must occur as a group. • If any portion of the group of actions fails, the entire Transaction fails.
E N D
Mastering Transactions In Java
Transactions: Basics
Transactions: Basics • In the world of programming a Transaction is a series of actions that must occur as a group. • If any portion of the group of actions fails, the entire Transaction fails. This lecture is based in part on the book Java Transaction Design Strategies by Mark Richards. This lecture is based in part onJava Transaction Processing by Mark Little, Jon Maron and Greg Pavlik.
Transactions: Basics • A.C.I.D. is the well-known acronym for the characteristics of a successful and valid transaction. • Atomicity • Consistency • Isolation • Durability These principles were first described by James Gray in the late 1970s and in 1983 the acronym A.C.I.D. was coined by Andreas Reuter and Theo Härder.
Transactions: Basics • Atomicity—follows the metaphor of the “Atom”. • You cannot have half an atom. • If a transaction does not complete perfectly, it gets undone and the database should be left in a state as if nothing at all ever happened.
Transactions: Basics • Consistency—change must take the database from one consistent state before the transaction started to another consistent state after the transaction has finished. • Referential integrity must be maintained. • The data meets all rules for validation. • Numeric fields must only hold numeric data, etc.
Transactions: Basics • Isolation—by its nature a transaction consists of several small steps. All the intermediate steps must be completed before the entire transaction can be called complete. • While the transaction is in progress but not yet complete, the in-progress changes must be hidden or isolated from the rest of the database and other transactions. • If I deduct money from the checking account and then deposit that money in the savings account, the checking deduction must not be visible to anyone until the transaction is done.
Transactions: Basics • Durability—once a transaction is deemed complete, the transaction will not be lost. • Don’t announce success until everything is committed. • Transaction Log—a common strategy used to back up the contents of the transaction.
Transactions: Local Transactions
Transactions: Local Transactions • A Local Transaction is a punt. • We let the database handle our transactions entirely. • This works fine if our Logical Unit of Work [LUW] consists of exactly one query. • Not exactly what we have in mind when we think of a “transaction”.
Transactions: Local Transactions • If we want to do two things, such as subtract from a savings account and deposit into a checking account, a Local Database transaction is not much help. • If we subtract $100 from the savings account and then the system crashes before we get the deposit done—we just lost $100. • So, already, we have reached the end of the usefulness of a database-local transaction. • Java offers several ways to help us out.
Transactions: Gotchas In Java
Transactions: Gotchas In Java • In the Java world we are fortunate to have great APIs such as the JTA (Java Transaction API) • Thanks to Rod Johnson, we have the wonderful Spring Framework. • After the horror story that was EJB2.1 Entity beans, JPA was a reason to pump your fist in joy. • Thanks to EJB 3.0, we have some great frameworks that take away all the peril of using transactions… right?
Transactions: Gotchas In Java • Consider this simple code. It runs flawlessly. • So this JPA code will happily insert the deposit and return the idof the generated row, right? public class JpaBankServiceImpl { @PersistenceContext(unitName=“bank”) EntityManager entityMan; public long makeDeposit( Deposit deposit ) throws Exception { entityMan.persist( deposit ); return deposit.getDepositId(); } }
Transactions: Gotchas In Java • Consider this simple code. It runs flawlessly. • So this code will happily insert the deposit and return the idof the generated row, right? • Nope! It won’t throw an exception, will return 0 and leave the databaseuntouched. It won’t do a damn thing! public class JpaBankServiceImpl { @PersistenceContext(unitName=“bank”) EntityManager entityMan; public long makeDeposit( Deposit deposit ) throws Exception { entityMan.persist( deposit ); return deposit.getDepositId(); } }
Transactions: Gotchas In Java • What? The newbie asks. “What went wrong?” • Nothing. For this to work you have to wrap it in a transaction. • But it won’t tell you anything is wrong. public class JpaBankServiceImpl { @PersistenceContext(unitName=“bank”) EntityManager entityMan; public long makeDeposit( Deposit deposit ) throws Exception { entityMan.persist( deposit ); return deposit.getDepositId(); } }
Transactions: Gotchas In Java • Your changes were placed in the L1object cache. • But only a transactiontells JPA to synchronize its object cache with the database.
Sidebar: What is the Object Cache? • When an object and its dependencies are pulled from the database, it may require several queries. • The L1 transaction Object Cache is part of every EnityManagerand is not shared. • Not to be confused with the L2 shared cache, stored in the EntityManagerFactory, which is visible to all EnityManagers. • EachEnityManagerkeeps its ownObject Cache.
Transactions: Gotchas In Java • In JPA, when you persistan object, it only writes the object to its Object Cache. • JPA onlywrites its object to the database upon the commitof a transaction. • So, no transaction =no commit.
Transactions: Gotchas In Java: Spring
Transactions: Gotchas In Java: Spring • Spring does a lot for us but we still need to think and understand. • Remember there are two broad categories: programmatic transactions declarative transactions • Using programmatictransactions, we have to code everything. • Using declarativetransactions, we add an annotationto our code and a framework such as Spring does the rest, right?
Transactions: Gotchas In Java: Spring • In the case of Spring, our declarative model asks us to provide an annotation such as @Transactional. public class SpringBankServiceImpl { @PersistenceContext(unitName=“bank”) EntityManager entityMan; @Transactional public long makeDeposit( Deposit deposit ) throws Exception { entityMan.persist( deposit ); return deposit.getDepositId(); } } • You test it again and no error, no exception and no change to the DB.
Transactions: Gotchas In Java: Spring • The problem is, like with all things Spring, you need to update the Spring configuration file. <tx:annotation-driven transaction-manager=“transactionManager” /> • So, transactionManagermust be a Spring bean that is defined elsewhere in your Spring configuration file. • Spring will implement this using a cool Spring feature called an interceptorthat in turn will use another annotation, @Transaction.
Transactions: Gotchas In Java: Spring • Okay—sweet—now we’re good. <tx:annotation-driven transaction-manager=“transactionManager” /> public class SpringBankServiceImpl { @PersistenceContext(unitName=“bank”) EntityManager entityMan; @Transactional public long makeDeposit( Deposit deposit ) throws Exception { entityMan.persist( deposit ); return deposit.getDepositId(); } } • But wait a second. There are a lot of choices involved in setting up a transaction. We’re using the defaults here.
Transactions: Gotchas In Java: Spring • When we use @Transactional by itself with no parameters, what are the default values for things like propagation mode, the read-only flag and transaction isolation? • Most importantly, what happens when we need to roll back?
Transactions: Gotchas In Java: Spring • When we use a bare @Transactional, the default values make it as if we had written this: • Most importantly, the transaction will not roll back on a checked exception. @Transactional( read-only=false, propagation=REQUIRED, isolation-level=READ_COMMITTED )
Transactions: Gotchas In Java: Spring • Here’s a great example from Mark Richards: @Transactional(readOnly=true, propagation=Propagation.SUPPORTS) public long insertDeposit( Deposit deposit ) throws Exception { // JDBC insert code } • Notice this is read-only. So, what happens? A. It throws a read-only connection exception. B. It inserts the deposit and commits the data. C. It does nothing because the propagation level says supports and there is no transaction to support.
Transactions: Gotchas In Java: Spring • Here’s a great example from Mark Richards: @Transactional(readOnly=true, propagation=Propagation.SUPPORTS) public long insertDeposit( Deposit deposit ) throws Exception { // JDBC insert code } • Notice this is read-only. So, what happens? A. It throws a read-only connection exception. B. It inserts the deposit and commits the data. C. It does nothing because the propagation level says supports and there is no transaction to support. WTF?
Transactions: Gotchas In Java: Spring @Transactional(readOnly=true, propagation=Propagation.SUPPORTS) public long insertDeposit( Deposit deposit ) throws Exception { // JDBC insert code } • B. It inserts the deposit and commits the data. • The read-only flag is only honored when there is a transaction. Since it’s SUPPORTS and there was no already-started transaction to support, it never got one. • The JDBC code used a local (database) transaction and committed the code.
Transactions: Gotchas In Java: Spring • Okay, what if we make the Propagation REQUIRED? @Transactional(readOnly=true, propagation=Propagation.REQUIRED) public long insertDeposit( Deposit deposit ) throws Exception { // JDBC insert code } A. It throws a read-only connection exception. B. It inserts the deposit and commits the data. C. It does nothing because the readOnly flag is set to true.
Transactions: Gotchas In Java: Spring • Okay, what if we make the Propagation REQUIRED? @Transactional(readOnly=true, propagation=Propagation.REQUIRED) public long insertDeposit( Deposit deposit ) throws Exception { // JDBC insert code } A. It throws a read-only connection exception. B. It inserts the deposit and commits the data. C. It does nothing because the readOnly flag is set to true. • Because there was a transaction [REQUIRED], the readOnly flag was honored.
Transactions: Gotchas In Java: Spring • There are a lot of hidden problems with all of the various propagation modes and you need to be very careful.
Transactions: Gotchas In Java: Spring • Next, I asked: what happens when you need a roll back? @Transactional(propagation=Propagation.REQUIRED) public long transferCash( Transfer transfer) throws Exception { try { deductFromSavings( transfer ); depositIntoChecking( transfer ); return tranfer.getTransactionId(); } catch( Exception e ) { // log error throw e; } } • What if depositIntoChecking() throws a checked Exception? Does everything get rolled back?
Transactions: Gotchas In Java: Spring • No! The update from deductFromSavings() is committed! @Transactional(propagation=Propagation.REQUIRED) public long transferCash( Transfer transfer) throws Exception { try { deductFromSavings( transfer ); depositIntoChecking( transfer ); return tranfer.getTransactionId(); } catch( Exception e ) { // log error throw e; } }
Transactions: Gotchas In Java: Spring • In this scenario ( using default values) for both Spring and EJB 3, only runtime(unchecked) exceptions cause the transaction to roll back. • When you code your annotation like this, the transaction will NOT roll back for any checked exception. @Transactional(propagation=Propagation.REQUIRED) • Why on earth would Spring or EJB be set up that way? • The explanation is that there might be some exceptions that you can recover from. So, to not preclude that alternative, they are set up this way.
Transactions: Gotchas In Java: Spring • So, the take away in Spring declarative transactions is that you must specify which checked exceptions warrant a roll back. @Transactional(propagation=Propagation.REQUIRED, rollbackFor=SQLException.class) @Transactional(propagation=Propagation.REQUIRED, rollbackFor=SQLException.class) • The rollbackForparameter takes either a single exception or an array of them. • There is another alternative rollbackForClassNameand yet another for noRollbackFor.
Transactions: 3 Alternative Models
Transactions: 3 Alternative Models •There are three broad approaches to doing transactions in Java. • These are known as “Transaction Models”. Local (database) Transactions Programmatic Transactions Declarative Transactions
Transactions: 3 Alternative Models: Local • Local (database) Transactions are an abdication on the part of the Java tier. • The database does 100% of the work. • The Connection is the closest you get can to the transactions. • By turning off the setAutoCommit( false ); in your Java code you can achieve ACID over several database actions and by then calling either commit() at the end or rollback(); in the case of an Exception.
Transactions: 3 Alternative Models: Programmatic • With Programmatic Transactionsyou write program logic to manage the transactions. • Instead of managing the Connection, you manage the transaction from your Java code.
Transactions: 3 Alternative Models: Programmatic Spring public class SpringProgrammaticTransaction { @PersistenceContext(unitName=“banking”) EntityManager em; JpaTransactionManager transManager= null; public void doTransfer( TransactionValues modifications ) { TransactionStatus ts = jtm.getTransaction( new DefaultTransactionDefinition() ); try { em.persist( modifications ); AcctState acctState = em.find( AcctState.class, modifications.getTransactionId() ); applyTransactionModifications( acctState, modifications ); em.persist( acctState ); transManager.commit( ts ); } catch( Exception e ) { transManager.rollback(ts ); } } // Spring injected public void setTransManager( JpaTransactionManager transManager ) { this.transManager = transManager; } }
Transactions: 3 Alternative Models: Declarative • With Declarative Transactions you signal your wishes for the transaction and rely on your understanding of the nuances of the annotations and their various attribute values. • Another name for CMT—Container-Managed Transactions. • Spring uses the @Transactional attribute. • EJB 3.0 uses @TransactionAttribute. • In declarative transactions automatic roll-back is not a given after a checked exception occurs.
Transactions: 3 Alternative Models: Declarative EJB3 @Stateless public class EJB30DeclarativeBankingServiceimplements BankingService { @PersistenceContext(unitName=“banking”) EntityManager em; @Resource SessionContextctx; @TransactionalAttribute( TransactionAttributeType.REQUIRED ) public void transferMoney( Transfer transfer ) throws Exception { try { AcctInfoacctInfo = em.find( AccountInfo.class, transfer.getTransferId(); if( transferAcceptible( acctInfo, transfer ) ) { em.persist( transfer ); em.persist( acctInfo ); } } catch( Exception e ) { ctx.setRollbackOnly(); } } }
Transactions: 3 Alternative Models: Declarative Spring public class SpringDeclarativeBankingService { @PersistenceContext(unitName=“banking”) EntityManager em; @Transactional( propagation=Propagation.REQUIRED, rollbackFor=Exception.class ) public void transferMoney( Transfer transfer ) throws Exceptio { em.persist( transfer ); AcctDataacctData = em.find( AcctData.class, transfer.getTransferId() ); if( accountAgreesWithTransfer( transfer, acctData ) ) { acctData.applyTransfer( transfer ); } } }
Transactions: High Concurrency Strategies
Transactions: High Concurrency Strategies • For many concurrent transactions, the transaction strategy changes. • If your transactions take too long, you shorten the transaction. • You remove operations such as reads and processing that do not need to be transactional.
Transactions: High Concurrency Strategies • Naked reads—you read a value without a transaction—even though you are planning to update it. • Another write could have gotten in there right after your read and then you would lose that update. • Each read gets a version stamp and then after your optimistic read you trust but verify and compare the version stamp.
Transactions: High Concurrency Strategies: Not! @Stateless @Remote(BankingService.class) public class BankingServiceImpl implements BankingService { @Resource SessionContextctx; @PersistenceContext( unitName=“banking” ) EntityManager em; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void transferMoney( Transfer transfer ) throws Exception { try { AccountHolder holder = em.find( AccountHolder.class, transfer.getAccountId() ); Withdrawlwithdrawl = sourceAccountContainsSufficientFunds( holder, transfer ) if( withdrawl != null ) { em.persist( withdrawl ); verifySpamBeingSent( holder ); verifyAddressSentToCatalogCompanies( holder ); verifyAccountIsEligibleForOverdraftProtection( holder ); verifySentEmailOfferingToStopSpam( holder ); Deposit deposit = buildDepositFromTransfer( holder, transfer ); em.persist( deposit ); } } catch( Exception e ) { ctx.setRollbackOnly(); } } }
Transactions: High Concurrency Strategies: Not! @Stateless @Remote(BankingService.class) public class BankingServiceImpl implements BankingService { @Resource SessionContextctx; @PersistenceContext( unitName=“banking” ) EntityManager em; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void transferMoney( Transfer transfer ) throws Exception { try { AccountHolder holder = em.find( AccountHolder.class, transfer.getAccountId() ); Withdrawlwithdrawl = sourceAccountContainsSufficientFunds( holder, transfer ) if( withdrawl != null ) { em.persist( withdrawl ); verifySpamBeingSent( holder ); verifyAddressSentToCatalogCompanies( holder ); verifyAccountIsEligibleForOverdraftProtection( holder ); verifySentEmailOfferingToStopSpam( holder ); Deposit deposit = buildDepositFromTransfer( holder, transfer ); em.persist( deposit ); } } catch( Exception e ) { ctx.setRollbackOnly(); } } } Everything in black is included in the transaction. Bad!