Announcement Announcement Module
Collapse
No announcement yet.
Controllers and transactions Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Controllers and transactions

    Hi,

    In section 5.3 of the user guide, the following has been stated:

    Essentially, Grails automatically binds a Hibernate session to the currently executing request. This allows you to use the save and delete methods as well as other GORM methods transparently.
    Also, in section 5.6, the following is mentioned:

    Grails is built on Spring and hence uses Spring's Transaction abstraction for dealing with programmatic transactions. However, GORM classes have been enhanced to make this more trivial through the withTransaction method which accepts a block the first argument to which is the Spring TransactionStatus object.
    However, in section 8.1, the following is written:

    Services are typically involved with co-ordinating logic between domain classes, and hence often involved with persistence that spans large operations. Given the nature of services they frequently require transactional behaviour. You can of course use programmatic transactions with the withTransaction method, however this is repetitive and doesn't fully leverage the power of Spring's underlying transaction abstraction.

    Services allow the enablement of transaction demarcation, which is essentially a declarative way of saying all methods within this service are to be made transactional. All services have transaction demarcation enabled by default - to disable it, simply set the transactional property to false.
    This is all too confusing.

    Does Grails create a Hibernate session and bind it with every request regardless of whether a service is used?

    If a service is NOT used, is the persistence code written in a controller running within a transaction or not?

    If yes, is this transaction committed at the end of the request?

    If yes, then how is this different from the transaction demarcation done at the service method level?

    What use is the "withTransaction" method other than to rollback a transaction?

    When I'm using Spring in a Java EE application, and I use the @Transactional annotation on my service methods, I know where the transaction starts and ends. But in Grails, it seems that if I make changes to a persistent object in a controller, those changes are saved, even if I don't call save; so that essentially means the session is open until the request is complete as in the "Open Session in View" pattern, which I don't use.

    Is there a way to change this behavior?

    Regards,
    Tarek

  • #2
    Originally posted by tnabil View Post
    This is all too confusing.

    Does Grails create a Hibernate session and bind it with every request regardless of whether a service is used?
    Yes.

    Originally posted by tnabil View Post
    If a service is NOT used, is the persistence code written in a controller running within a transaction or not?
    Only if you put it inside a withTransaction block. Controller actions are not by default transactional. This is one reason we recommend you put database update code in services.

    Originally posted by tnabil View Post
    If yes, is this transaction committed at the end of the request?

    If yes, then how is this different from the transaction demarcation done at the service method level?

    What use is the "withTransaction" method other than to rollback a transaction?
    It allows you to declare fine-grained transactions and can be used with a controller action if you don't wish to create a service to do the database updates.

    Originally posted by tnabil View Post
    When I'm using Spring in a Java EE application, and I use the @Transactional annotation on my service methods, I know where the transaction starts and ends. But in Grails, it seems that if I make changes to a persistent object in a controller, those changes are saved, even if I don't call save; so that essentially means the session is open until the request is complete as in the "Open Session in View" pattern, which I don't use.

    Is there a way to change this behavior?
    The simple answer is no - that's the way Hibernate works. What you may be able to do is add a Grails filter that sets the session flush mode to manual, in which case the session will only be flushed when you explicitly tell Grails to do so or when a transaction finishes successfully. Would that work for you?

    Peter

    Comment


    • #3
      Considering that its the Grails' team recommendation to put business logic in services, I find it surprising that no services are generated when you run "generate-all" for a certain domain, and the generated controllers contain all the code.

      I'm not sure why no transaction is created when you're not using services. Hibernate provides a very easy way to create a one-transaction-per-session.

      Does it mean that with the current approach, when Hibernate starts flushing the changes done in the controller, it does that in auto-commit mode?

      Would that work for you?
      Well, I'm not sure. I faced this problem because I wanted to overwrite something in the domain while doing a "show" and I didn't expect it to get persisted. I guess it has to do with the fact that I'm not used to using my domain objects in the web tier (I usually use DTOs but doing that would take away all the time saving Grails gives me).

      I guess I'll have to think about that. The problem with the fact that I have no transaction in the controller is that I will need to move the querying of my domain classes and hence the binding of parameters to the service. That means I'll either:
      1- Use command objects
      2- Or pass the "params" variable to the service.

      The first again introduces one more class and reduces the benefit of Grails and the second makes the service interface less explicit, which I don't like.

      Comment


      • #4
        It's unfortunate that the generated controller contains persistence code and seems to imply that that's where the code should be. But creating a service for every domain class would be overkill. So either way you need to refactor.

        Hibernate flushes changes because there's an OpenSessionInView interceptor registered. It opens a session at the beginning of each request and flushes and closes it at the end. This autoflushes changes without needing to call save(), and also helps avoid lazy loading exceptions. You can call discard() on an instance that's been changed but shouldn't be persisted.

        There's a 3rd option for calling services from controllers - create methods with proper signatures just like you would outside of Grails.

        Comment


        • #5
          Thanks, Burt, for your response.

          Originally posted by burtbeckwith View Post
          There's a 3rd option for calling services from controllers - create methods with proper signatures just like you would outside of Grails.
          That's what I meant by number 1. Usually, you need two types of objects:
          1- Command objects to capture user input.
          2- View model objects to use in your views.

          Grails by default uses domain objects for both.

          You can pass scalar values to your services, but usually service methods take too many parameters that command objects seem a better choice.

          For the view model, domain objects might work some times but not always.

          As I said earlier, I never used the domain model in my view layer before, so I'm still exploring.

          Comment


          • #6
            I was just looking at the "OSIV" pattern here, and I came to undertand that this pattern extends the scope of the transaction till the view is rendered.

            Another variation described under the heading "Can I use two transactions in one Session?" suggests using two transactions; one for the service method which is read/write and one for the view, which is read-only.

            This seems to be a good solution, since it would not allow the modification of domain objects in the view layer. Is there a way to configure Grails to work this way?

            Comment


            • #7
              OSIV doesn't extend the scope of any transactions - it extends the scope of the session. The scope of a transaction is independent. It's typically just for the duration of a single method call on a transactional service (and whatever other methods are called by that method) or the code inside a withTransaction block.

              Comment


              • #8
                So, that means the transaction will finish once the service method completes. How about any changes to the domain objects that happen afterwards in the controller? How will the session flush those changes; in auto-commit mode?

                If not, then it's another transaction.

                Comment


                • #9
                  Changes to the domain class after the controller will be flushed either when you call save/delete(flush: true) or at the end of the action. This is not technically done in a transaction since you can't roll the changes back.

                  Comment


                  • #10
                    Thanks, Peter; I just saw your response.

                    I think it could be that we are not on the same page when it comes to the definition of a transaction in this particular context.

                    In this context, when I say a statement executed against the DB is not done within a transaction, I mean that the connection used to execute it is in auto-commit mode.

                    If not, and a sequence of statements are executed together and then the connection is committed (assuming JTA is not involved), then I consider that to be done witihin a transaction.

                    So, if that's the case, then my understanding is that if a service class is used with transaction demarcation done around its methods, then a transaction will start at the beginning of the service method and end at its end.

                    Any statements executed before the service method starts will be committed when it starts (just assuming).

                    Any statements executed after the service method ends will be committed when the response is returned to the client (because the session is closed).

                    Is that understanding correct?

                    IMHO, it's important to clearly document those hidden details of how the framework works under the hood as it can save developers a lot of debugging and agony.

                    Thanks again for your support.

                    Comment


                    • #11
                      Originally posted by tnabil View Post
                      Thanks, Peter; I just saw your response.

                      I think it could be that we are not on the same page when it comes to the definition of a transaction in this particular context.

                      In this context, when I say a statement executed against the DB is not done within a transaction, I mean that the connection used to execute it is in auto-commit mode.
                      Agreed.

                      Originally posted by tnabil View Post
                      If not, and a sequence of statements are executed together and then the connection is committed (assuming JTA is not involved), then I consider that to be done witihin a transaction.

                      So, if that's the case, then my understanding is that if a service class is used with transaction demarcation done around its methods, then a transaction will start at the beginning of the service method and end at its end.
                      This is how transactional services work, yes. Of course, you can explicitly start a transaction before the service method is called, in which case the service method joins that transaction by default. Another subtlety, but probably not relevant here.

                      Originally posted by tnabil View Post
                      Any statements executed before the service method starts will be committed when it starts (just assuming).
                      I'm not sure this is the case. No guarantees are made about when those changes will be flushed to the database other than they will be flushed no later than when the transaction is committed. But it's generally a Bad Idea to make changes before or after a transaction, particularly before.

                      Originally posted by tnabil View Post
                      Any statements executed after the service method ends will be committed when the response is returned to the client (because the session is closed).
                      Unless you explicitly flush the session, this is correct. Remember that you can force a flush at any time.

                      Comment

                      Working...
                      X