59

When I try to read data from the database, at least using

((Session)em.getDelegate()).createCriteria()

an exception is throws saying that a transaction is not present.

When I add the annotation:

@Transactional(
    value = SomeClass.TRANSACTIONAL_MANAGER, 
    propagation = Propagation.SUPPORTS, 
    readOnly = true
)

it works fine.

However, since reading will happen million of times per second to access and read data, I want to make sure that our environment is not clogged up unnecessarily.

If not, what is the cost of creating a read-only Propagation.Supports transaction?

Can I not create a Hibernate Criteria Query without a transaction, in combination with Spring?

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
mjs
  • 21,431
  • 31
  • 118
  • 200
  • possible duplicate of http://stackoverflow.com/questions/3713829/select-using-hibernate – OO7 Oct 12 '14 at 19:19
  • Yes, it was. I had accidentally commented on the wrong post. You should see my comments now on yours. But I've accepted it. – mjs Nov 17 '14 at 21:57

2 Answers2

199

All database statements are executed within the context of a physical transaction, even when we don’t explicitly declare transaction boundaries (BEGIN/COMMIT/ROLLBACK).

If you don't declare transaction boundaries, then each statement will have to be executed in a separate transaction (autocommit mode). This may even lead to opening and closing one connection per statement unless your environment can deal with connection-per-thread binding.

Declaring a service as @Transactional will give you one connection for the whole transaction duration, and all statements will use that single isolation connection. This is way better than not using explicit transactions in the first place.

On large applications, you may have many concurrent requests, and reducing database connection acquisition request rate will definitely improve your overall application performance.

JPA doesn't enforce transactions on read operations. Only writes end up throwing a TransactionRequiredException in case you forget to start a transactional context. Nevertheless, it's always better to declare transaction boundaries even for read-only transactions (in Spring @Transactional allows you to mark read-only transactions, which has a great performance benefit).

Now, if you use declarative transaction boundaries (e.g. @Transactional), you need to make sure that the database connection acquisition is delayed until there is a JDBC statement to be executed. In JTA, this is the default behavior. When using RESOURCE_LOCAL, you need to set the hibernate.connection.provider_disables_autocommit configuration property and make sure that the underlying connection pool is set to disable the auto-commit mode.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • 1
    See this: ibm.com/developerworks/library/j-ts1 It says: "Better yet, just avoid using the @Transactional annotation altogether when doing read operations, as shown in Listing 10:" .. so I got the impressions that one do not need one. Considering it is about executing a read sql statement, I do not see the need for the overhead – mjs Nov 17 '14 at 21:55
  • When it comes to "safety" it is not important here, whatever is available in the DB is fine. If not, I can fetch it 3 seconds later. – mjs Nov 17 '14 at 21:55
  • 5
    Other systems use the autocommit mode for very read-only single-statement transactions. The problem comes when you have more than one statements per one logical transaction (your service method). – Vlad Mihalcea Jan 30 '15 at 21:37
  • 1
    Connection overhead is one thing, but more importantly is the D-guarantee in ACID - with a commit - that is after each statement in autocommit mode! - the database has to guarantee that your changes hit the disk (and not only the disk cache!). In an open transaction operations on already read database blocks are solely performed in the DMBS memory until commit is performed. BTW, a rollback is the simple procedure of throwing away dirty memory blocks at comes at nearly no costs. – rgielen Nov 29 '17 at 09:38
  • 4
    The DB only fsyncs the redo log after commit, not the entire buffer pool which is flushed during checkpoints. The rollback is not necessarily cost free. On Oracle and MySQL, tuples need to be reconstructed back from rollback segments. Also, indexes have to be rebalance back. – Vlad Mihalcea Nov 29 '17 at 09:47
  • Using transactions for read only operations is perfectly useful once you want more isolation than READ COMMITTED - the I in ACID is about reads, not writes! Admittedly JPA's first level cache adds REPEATABLE READ like isolation for most operations even on top of a READ COMMITTED transaction. In MVCC DBMS this should perform well, while in pessimistic locking DBMS (looking at you, DB2 LUW!) this leads to concurrency penalties (as does any transaction > READ UNCOMMITTED) – rgielen Nov 29 '17 at 09:49
  • 5
    You always use transactions with a RDBMS, even in the auto-commit mode when you don't explicitly declare them. Auto-commit uses the default isolation level and wraps the statement in one transaction. – Vlad Mihalcea Nov 29 '17 at 10:27
  • This is not true: "JPA doesn't enforce transactions on read operations. Only writes end up throwing a transaction required exception in case you forget to start a transactional context." If you annotate your own repository read method with `MANDATORY` it *will* throw `IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'`; however if you call the existing repo methods, Spring Boot implicitly annotates these as REQUIRED if you don't put any annotation of your own (tested with Hibernate 5.2.x and spring-tx 5.0.x) – Andrew Spencer Oct 05 '18 at 12:19
  • @AndrewSpencer You are confusing JPA with Spring. I'm talking about the JPA spec, not how Spring transactions work. You are talking about the `org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'` exception which has nothing to do with the JPA spec. – Vlad Mihalcea Oct 05 '18 at 13:42
  • @VladMihalcea Ah yes, my confusion, it's coming from Spring indeed, not from JPA. When I read that "Only writes end up throwing a transaction required exception," I assumed it meant I would not get an exception due to not having a required transaction, in this situation. – Andrew Spencer Oct 05 '18 at 15:20
  • There is no Java EE feature you can use. Only if the application server supports some specific ways, then you might set the read-only flag. This is very easy to do with Spring though. – Vlad Mihalcea Oct 13 '19 at 11:32
  • @rgielen , vlad if one considers a finance application where data is populated in realtime to the system and things are popping on millions of users screens and updated all the time a transaction requirement overhead seems like a requirement too much. the user is fed data as it is available and it matters not if someone is trying to update it or not. the price for read in such a situation must have dire consequences even if we are just talking about simple reads. – mjs Mar 27 '20 at 19:26
  • The transaction requirement also is not a requirement when you do the reads without JPA and just use sql. – mjs Mar 27 '20 at 19:27
  • Even if you don't declare a transaction, there's always a transaction on the DB side. Only the scope differs between explicit and implicit transactions. – Vlad Mihalcea Mar 27 '20 at 20:05
  • @mmm a transaction does not really add overhead - a commit does! During a transaction, operation are performed in-memory, especially inserts and updates. A rollback is the process of *not* writing the dirty blocks to disk. A commit *guarantees* that data was written to disk (the slowest subsystem ...). So auto-commit=true (this is what "not using transactions" translates to) means: Every single DML operation is written to disk immediately, in a very inefficient manner. – rgielen Apr 05 '20 at 08:02
  • 1
    And, if during this particular request you are executing N queries, N auto-commit transactions will be created, instead of a single one. More, if you are using JPA and Hibernate, each statement will have to acquire and release a DB connection after each statement. Again, very inefficient. – Vlad Mihalcea Apr 05 '20 at 08:55
  • To delay connection acqusition by setting `provider_disables_autocommit`, is it required to use `@Transactional` even on read-only query methods? On setting autocommit=false and `provider_disables_autocommit=true`, I found ROLLBACK statement executed right after every SELECT query which has no transactional boundary around. There were error messages like: `(c.zaxxer.hikari.pool.ProxyConnection) [-:-] MainPool - Executed rollback on connection com.mysql.cj.jdbc.ConnectionImpl@103c24d0 due to dirty commit state on close().` – SunghoMoon Jun 21 '22 at 14:40
  • You need `@Transactional(readOnly=true)` on services that read data because that service method cam call multiple Repositories and you really want to share the connection, transaction, isolation level and avoid fetching a DB connection from the connection pool for every Repository method call. – Vlad Mihalcea Jun 21 '22 at 18:05
4

Accoring to my experience with JPA implementation in J2EE, a Transaction manager is always needed in order to perform CRUD operation safety, by guaranteeing a rollback in order to preserve data integrity.

Enterprise applications use different resources to save data and send messages like a database or message queue. If we want to query these resources sequentially and to cancel the whole operation once a problem occurs, we have to put this query in a unit of work so that will be executed as a whole.

You could define it:

  • by using related annotations (as shown in the questions); in this way, the container loads automatically the transaction manager for a given persistence context;

  • by injecting manually the transaction manager, as follows:

    public class sample {
    
        @PersistenceContext
        EntityManager em;
    
        // Injected transaction manager
        @Inject
        UserTransaction utx;
    
        private static final String[] GAME_TITLES = {
            "Super Mario Brothers",
            "Mario Kart",
            "F-Zero"
        };
    
        private void clearData() throws Exception {
            utx.begin();
            em.joinTransaction();
            System.out.println("Dumping old records...");
            em.createQuery("delete from Game").executeUpdate();
            utx.commit();
        }
    
        private void insertData() throws Exception {
            utx.begin();
            em.joinTransaction();
            System.out.println("Inserting records...");
            for (String title : GAME_TITLES) {
                Game game = new Game(title);
                em.persist(game);
            }
            utx.commit();
            // clear the persistence context (first-level cache)
            em.clear();
        }
    
        // ...
    
    }
    

Spring Data, as JPA-spec implementation, may follow the same approach.

You could find more information by reading the following article: Java_Persistence/Transactions.

vdenotaris
  • 13,297
  • 26
  • 81
  • 132