Results 1 to 6 of 6

Thread: Why "@Transactional" on a service method with "readOnly=true" on it's class?

Hybrid View

  1. #1

    Default Why "@Transactional" on a service method with "readOnly=true" on it's class?

    Hello,

    I use Spring Data JPA and it works like a charm for me, reducing the amount of code significantly. Now I have a question about transactions and the default transactional behaviour of the CRUD methods of a CrudRepository.

    Please, have a look at this example service class:

    Code:
    @Service
    @Transactional( readOnly = true )
    public class CustomerServiceImpl implements CustomerService
    {
    	@Resource
    	private CustomerRepository	customerRepository; // is a CrudRepository
    
    	public void doSomethingWithCustomer()
    	{
    		Customer customer = new Customer();
    		customer.doSomeThing(...)
    	
    		customerRepository.save( customer );
    	}
    	
    	public void anotherServiceMethod()
    	{
    		// ...
    	}
    }
    I think it is good practice to have a "@Transactional( readOnly = true )" at service classes. This means all methods are read-only-transactions by default. To define a write-transaction, I just have to use the @Transactional annotation again on a method to "override" the "readOnly" flag.

    In the above example, the method "doSomethingWithCustomer()" uses a "customerRepository" to actually WRITE an object to the database. I thought that I can omit the "@Transactional" on that method, because the CRUD-methods of Spring Data's CrudRepository ("customerRepository") already have the "@Transactional" annotation attached for the ".save"-Method.

    Bus this does not work - the customer does not get written to the databse. It only works when I add a "@Transactional" annotation to the "doSomethingWithCustomer()" method.

    To cut a long story short: I would like to understand why I still have to use the "@Transactional" annotation on the method "doSomethingWithCustomer()" - can someone help?


    Thanks a lot!
    Last edited by patb; Aug 13th, 2011 at 05:16 AM. Reason: Edited code example - added "implements ..."

  2. #2
    Join Date
    Dec 2006
    Posts
    311

    Default

    I suspect your configuration is such that the class isn't transactional at all. You need an interface to appropriately create the proxies via the default behavior. If an interface isn't an option for some reason you need to set it like this:

    <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionORAManager" />

    Your best bet is to put an interface on that service method though.

  3. #3

    Default

    Hello TerpInMD,

    thanks a lot for your answer. Well, it is very hard to get help if the example itself is wrong already :-/ I put together an example to demonstrate my problem, but it is no real class, just a sample. Of course, my service implementation is based upon an interface. I edited the original post so the class implements the interface "CustomerService". I'm really sorry for that!

    But the problem remains, without an "@Transactional" on a method of a class with "@Transactional( readOnly = true )", no data gets written to the database, although I use a Spring Data JPA "CrudRepository", which should have a "@Transactional" on it's "save"-Method. To my understanding, that "@Transactional" would "overwrite" the "@Transactional( readOnly = true )", making the "save"-Method a write-transactional one.

    (I checked the debug output, the JtaTransactionManager is starting a new transaction).

    Do you have any ideas?

  4. #4
    Join Date
    Dec 2006
    Posts
    311

    Default

    The propagation default behavior (required) is causing it to pick up the read only transaction. You could use propagation=Requires_new on your repository methods and that would solve the problem.

  5. #5

    Default

    Thanks a lot for your answer, TerpInMD!

  6. #6
    Join Date
    Apr 2006
    Location
    Dresden, Germany
    Posts
    483

    Default

    I wouldn't recommend reconfiguring the repository. The reason Spring Data repositories are annotated with @Transactional (and thus default to Propagation.REQUIRED) is that this essentially means: if you don't do any transaction configuration, we do fine grained, necessary defaults (as persisting data in JPA requires a transaction being in progress e.g.). At the same time the default config expresses: if you *do* transactions, we simply participate in what you configure.

    The phenomenon you see is simply Springs default transaction behavior for Propagation.REQUIRED. As there is a read only transaction in progress, we participate in it and thus you see the behavior described (which is essentially database specific). So I'd argue that you initial assumption is false for two reasons.

    1. As you lift transaction boundaries to the service layer your assumption would potentially lead to invalid results even if the repository transactions worked as you expected. Suppose you do a second save operation in that method. In which of the transaction shall that one run then?
    2. If you want to span a transaction around a service method you have to configure it according to your scenario at the most outer boundary.

    Cheers,
    Ollie

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •