I'm not really very clear on how all this works, but I've had a terrible time with merging objects.
I built a multi-page survey with forty-some questions on it. One of the questions asks users to fill out a table in which each row is an object -- call it a Row. I don't know in advance whether there will be two rows or twenty. I set this up so that the user gets a blank row. They can click an "add a row" link to add another row. They can click a "-" link at the end of each row to remove that row. On the server, this creates a new Row() and adds it to the survey (Survey.rows.add(new Row())). Then it populates a table row and sends it back to the browser. I had a TERRIBLE time making this work (and I'm still not convinced it's working consistently). For one thing, the fields in the table update the server on blur using AJAX. That was a hassle. Each time I tabbed from one field to the next, instead of updating the current Row object, it would create a new one. The reason seemed to be because the object was detached. I tried all sorts of things: merging, flushing, even trying to pull the row back out from the Survey object before merging it. This drove me crazy for two days. Maybe I'm wrong, but the idea of keeping the EntityManager open for the duration of the session, manually calling begin and end transaction, and only merging and flushing as necessary appeals to me. In this instance, I'd push the changes to the database probably on every request to be safe, but I'd prefer it if objects didn't detach themselves. It would also be nice to be able to create new objects, and then only persist them if they need to be persisted. On the above project, I have a dirty field on the Row. Each time the page is reloaded, I delete all rows that are not dirty, then recreate new rows as necessary. This burns up a lot of ids needlessly. It won't make a difference on this survey, but it seems a kludgy way to do things. Scala is wonderful, Lift is amazing, and JPA/Hibernate mostly rocks. But when working with multiple objects associated in a one-to-many relationship to a parent object, there simply *has* to be a better way. This is one spot where the SQL-centric Rails seems infinitely superior. So I don't know if the problems I experienced and am experiencing are because of some basic misunderstanding on my part, or because of the volatile nature of the EntityManager as it is currently managed in JPA/Lift, but if it's the latter, then I'm all for the sorts of changes that Philip is suggesting. What do you think, Derek? Am I explaining this well enough that you can understand what I'm talking about? Is there a simple solution that I'm missing? I'm happy to post code if that helps. Chas. Derek Chen-Becker wrote: > I think you misunderstand the lifecycle of a stateful snippet. A > stateful snippet has specific techniques to keep it resident (using > this.link, this.redirect, etc) so you have to be careful about how you > use it or you lose the instance. The HTTP session would be the most > appropriate place for a JPA EM that crosses request boundaries because > it will *always* be available to snippets (I had forgotten about the > EXTENDED type, I've never used it). > > Another issue, orthogonal to how you keep the session around, is that > when you hold a session open you're also holding a transaction open > unless you use RESOURCE_LOCAL transactions (unmanaged JDBC connections, > essentially). That has implications on data visibility to other > connections, how you handle exceptions, etc. > > Don't get me wrong, I think that there's a place for extended JPA EMs, I > just want to make sure you're aware of all of the tradeoffs so that you > don't get any nasty surprises. It's my opinion that for most use cases > opening a session for each request (and the merge/getReference that > doing it this way implies) is the best way to do it. JPA providers are > usually pretty intelligent about efficiently using database operations > to keep things coherent, so I don't think that "avoiding merge" is > necessarily a worthwhile goal in and of itself. > > Cheers, > > Derek > > On Tue, Dec 9, 2008 at 6:39 AM, philip <[EMAIL PROTECTED] > <mailto:[EMAIL PROTECTED]>> wrote: > > > Hi Derek, > > I think I found the solution to the problem I am thinking of. > Its called JPA Extended Persistence Context. Its suggested that a > extended persistence context be created which starts at some time and > ends at some time and is stored in HttpSession. From the bits I read > they said its no good to store in a Stateful Session Bean because the > bean may be passivated and the context lost. Well we are not using > stateful session bean so it doesn't matter, we can put it in the > session or put it on some object that hands around like a session such > as a ... in our case I believe a Snippet. > > So in the JPA demo, you would then open as > override def openEM () = { > val em = factory.createEntityManager > (PersistenceContextType.EXTENDED) > em.getTransaction().begin() > em > } > > So then I would suggest the em be stored in the snippet which is a > stateful thing that hangs around. > According to the docs, many transactions can happen on the em, until > em.close() is called and therefore it is then flushed to the database. > NORMALLY Extended persistence context is managed by the container, > SEAM or EJB this is called container managed, but in our case we can > do our own management. In EJB its something insane like this > @Stateful > @Remote(ShoppingCart.class) > public class ShoppingCartBean implements ShoppingCart > { > @PersistenceContext(type=PersistenceContextType.EXTENDED) > EntityManager em; > ... > But we do it ourselves... > Why do this? The benefit is that you can keep using the same JPA > object references without doing a merge. So if you have a Employee > object which is a JPA/Hibernate object, and you changed its data and > you want to save it to the database, merge is not needed. > Why is that good? Because merge normally re-loads an entity from the > database at the merge call, then it overwrites the entity contents and > you might loose data that was previously written by another user! > (unless you used a version number). In our case without merge, then > the entity that was originally used by the EM is still in use. It is > not a detached entity. Also you can navigate lazy relationships from > request to request, from an Employee to a Manager which is really good > as you can just display a list of Employees, then when someone clicks > on the manager link of the employee it will just be able to navigate > as a object reference lazy loaded from DB. > Without the extended persistence context then the entity would be > called detached because the original persistence context of the entity > has been closed. > > Now - I haven't actually tested this! but I feel reasonably convinced > I'm right - please tell me I am wrong. I want to at least solve these > problems before I can start on the larger project. It then makes sense > for me to use JPA, I'll reverse engineer first from mysql then convert > from Java to Scala and use the JPA example project as a starting > point, I can do the project in waterfall steps. > > Thanks, Philip > > On Dec 8, 2:01 pm, "Derek Chen-Becker" <[EMAIL PROTECTED] > <mailto:[EMAIL PROTECTED]>> wrote: > > I use request vars to pass these things around in my own code, so > merging is > > pretty trivial. For example: > > > > object employee extends RequestVar[Can[Employee]](Empty) > > > > def viewEmployee (xhtml : NodeSeq) : NodeSeq = > > employee.map(Model.merge(_)).map({ empl => > > ... > > bind(...) > > > > }) openOr Text("Invalid Employee for View") > > > > Note the double map call. The first merges, the second actually > does the > > binding. If you preferred to load from the DB each time you could do > > > > def viewEmployee (xhtml : NodeSeq) : NodeSeq = > > employee.map(Model.getReference(classOf[Employee], _.id)).map({ > empl => > > > > instead. In either case this is simple enough that I haven't > really bothered > > looking for any cleaner solution. One thing I'd note is that > generally in > > Hibernate and JPA, keeping a session open across multiple web > requests is > > frowned upon. I don't know enough about the guts of SEAM to know > if they're > > really doing that, or using some magic to make it appear that > they are. If I > > remember correctly, in SEAM your objects are injected and > outjected by SEAM > > itself, so they could easily be intercepting things to make it > kosher. In > > any case, using the EM this way is outside my experience so if > things start > > going screwy I don't know how much I can help. > > > > Derek > > > > On Sun, Dec 7, 2008 at 8:44 AM, philip <[EMAIL PROTECTED] > <mailto:[EMAIL PROTECTED]>> wrote: > > > > > Hi Derek, > > > > > The problem I am thinking about is if you loaded for example, a > list > > > of "Employees" your going to show in a table. > > > In your table you have a view button to view the employee, > since you > > > used JPA to load the list in the first place but now since the > EM is > > > finished from the first request and now we are on a second request > > > with a new EM instance. If we try to navigate the lazy loading > > > relationships, it won't work, it will throw a LazyLoadingException. > > > > > So what you do - is your merge the entity back and the load it back > > > from the database and then navigate the relationship. > > > But if the EM was around all the time, cross requests, over the > > > session then you could navigate the relationships without merge. I > > > was suggesting the stateful snippit which is holding these lazy > > > loading relationships should hold the EM since the entitys are > > > normally on the stateful snippet. > > > > > Thanks, Philip > > > > > On Dec 7, 10:47 pm, "Derek Chen-Becker" <[EMAIL PROTECTED] > <mailto:[EMAIL PROTECTED]>> wrote: > > > > I've been doing Hibernate and JPA long enough that > reattaching instances > > > at > > > > method entry is somewhat habitual for me. Josh makes a good > point about > > > > being aware of the transaction, but in my experience the > request method > > > is > > > > short-lived and it's usually not a problem. As for using a > Stateful > > > session > > > > snippet to hold the EM, I think you want to look at the Model > class; we > > > use > > > > a request var that essentially exists for the lifetime of the > request to > > > > hold the EM. That avoids tying the EM to a single snippet > class (which > > > may > > > > or may not exist for every request) and makes it globally > available to > > > all > > > > of your code if you want to use separate logic handling classes. > > > > > > Derek > > > > > > On Sun, Dec 7, 2008 at 6:18 AM, Josh Suereth > <[EMAIL PROTECTED] <mailto:[EMAIL PROTECTED]> > > > >wrote: > > > > > > > As a side note... > > > > > > > The LazyInitialization exception wasn't an issue for me > when using the > > > > > "OpenSessionInView" pattern. However it became a large > issue when > > > trying > > > > > to use hibernate to back a thick-client GUI. With what > I've used of > > > lift, > > > > > I can't see JPA being any more difficult to manage then > using another > > > > > web-framework (perhaps even simpler in some regard). The > key is just > > > to > > > > > understand what leaving the hibernate session open does to your > > > transaction > > > > > length and how you cna remedy troubles as you see them. > > > > > > > -Josh > > > > > > > On Sat, Dec 6, 2008 at 9:59 PM, philip > <[EMAIL PROTECTED] <mailto:[EMAIL PROTECTED]>> wrote: > > > > > > >> Hi Derek, > > > > > > >> Thanks, these are good reasons to use the JPA. > > > > > > >> About the use of the JPA in SEAM - the SEAM in Action (http:// > > > > >> manning.com/dallen/ <http://manning.com/dallen/>) book says > > > > >> "Lazy loading of entity associations has unfortunately > established a > > > > >> bad reputation. The first thing that comes to mind in most > developers' > > > > >> minds when you talk about lazy loading is Hibernate's > > > > >> LazyInitializationException. The culprit is the detached > entity > > > > >> instance. > > > > >> ... > > > > >> If the persistence manager is closed after the action > method is > > > > >> invoked, the Course instance is detached when the view is > rendered. > > > > >> ... > > > > >> A call to the method getHoles() triggers an exception > because the > > > > >> EntityManager that loaded the Course instance is no longer > available > > > > >> to further communicate with the database. The same problem > arises on > > > > >> postback, even if a lazy association wasn't hit in the view." > > > > > > >> So within Liftweb it would then make sense to place the JPA > > > > >> persistence manager within a stateful snippet. Since the > snippet is > > > > >> bound to the session, then navigation of the lazy loading > entitys > > > > >> wouldn't cause a LazyInitializationException? > > > > >> Is this what is happening on the JPA example within > Liftweb? it > > > > >> doesn't look like it - in file Books.scala > > > > >> def add (xhtml : NodeSeq) : NodeSeq = { > > > > >> def doAdd () = { > > > > >> Model.merge(book) > > > > >> redirectTo("list.html") > > > > >> } > > > > > > >> The SEAM book says > > > > >> "Merging is a crude operation and should be avoided if > possible. It > > > > >> first loads an entity > > > > >> instance with the same identifier as the detached instance > into the > > > > >> current persistence > > > > >> context, resulting in a database read. Then, the property > values from > > > > >> the detached > > > > >> instance are copied onto the properties of the managed > instance. The > > > > >> main problem > > > > >> with this operation is that merging clobbers any changes > that may have > > > > >> been made to > > > > >> the database record since the detached instance was > retrieved (unless > > > > >> object version- > > > > >> ing is used). There are other problems as well. If an > entity instance > > > > >> with the same > > > > >> identifier as the detached instance has already been > loaded into the > > > > >> current persis- > > > > >> tence context, a non-unique object exception is thrown > because the > > > > >> uniqueness con- > > > > >> tract of entities in a persistence context is violated. > You may also > > > > >> run into a lazy > > > > >> loading exception if you hit an uninitialized association > on the > > > > >> detached instance dur- > > > > >> ing the merge. Avoid merging if at all possible. " > > > > > > >> Thanks, Philip > > > > > > >> On Dec 6, 10:47 pm, "Derek Chen-Becker" > <[EMAIL PROTECTED] <mailto:[EMAIL PROTECTED]>> > > > wrote: > > > > >> > Here are a couple of features that JPA has that can be > very useful: > > > > > > >> > 1. More powerful query language. The tradeoff is that > HQL is not > > > > >> > type-safe like Mapper's findXXX methods, so you need > to test your > > > > >> query > > > > >> > syntax to make sure it returns what you want. > Granted, you can > > > use > > > > >> SQL > > > > >> > directly in Mapper, but it's tied directly to the DB > while HQL > > > allows > > > > >> you to > > > > >> > abstract away some of the smaller details, > particularly with join > > > > >> > associations. However, HQL does allow you to easily > > > > >> > 1. Do flexible joins between large graphs of objects > > > > >> > 2. Select scalar values (i.e. "select distinct > player.age from > > > > >> > TeamMember player where ...") > > > > >> > 3. Prefetch otherwise lazily loaded properties > ("select player > > > > >> from > > > > >> > TeamMember left join fetch player.coach") > > > > >> > 2. IMHO, better definition of associations between > classes. > > > > >> > HasManyThrough works well for a subset of usages, but it: > > > > >> > 1. Can't be treated as a real, live collection (no > updates, no > > > > >> > cascades) > > > > >> > 2. Only runs once (it's lazy). Given the duration > of a web > > > request > > > > >> > cycle this would usually not be a big deal unless > you have a > > > > >> > high number of > > > > >> > concurrent users operating on the same data > > > > >> > 3. Doesn't map well to many-to-many relationships; > you have to > > > > >> build > > > > >> > your own binding entity to represent the join table > > > > >> > 3. JPA has a number of implementations available for an > > > intermediate > > > > >> > cache to improve performance > > > > >> > 4. JPA supports locking of objects for read and write > > > > > > >> > That's what I can come up with off the top of my head. > Having SEAM > > > > >> generate > > > > >> > the classes for you is also nice, although I'm sure a > similar tool > > > could > > > > >> be > > > > >> > written for Mapper. Now, as for SEAM's (ab)use of the > entity manager > > > > >> (I'm > > > > >> > assuming that's what you mean by transaction manager), > I'm not so > > > sure > > > > >> that > > > > >> > it's actually keeping the EM open across the entire > "conversation", > > > but > > > > >> > rather providing an interface that makes it appear that > it is. I saw > > > > >> Gavin > > > > >> > King here give a presentation on SEAM a few years ago > and it sounded > > > > >> like > > > > >> > they were doing some magic behind the scenes to keep > everything > > > > >> coherent. I > > > > >> > don't really know enough about it to argue for or > against it, but > > > > >> generally > > > > >> > the idea is that by holding a transaction across the > scope of an > > > entire > > > > >> > conversation you can treat a series of forms/pages as an > atomic unit > > > of > > > > >> > work. I'm pretty sure Mapper doesn't have anything > comparable, and > > > > >> Mapper's > > > > >> > concept of lazy loading isn't tied to having an open EM > like it is > > > in > > > > >> JPA. > > > > >> > That means that if something in Mapper is going to be > lazily loaded, > > > > >> it'll > > > > >> > work no matter what. > > > > > > >> > Derek > > > > > > >> > On Fri, Dec 5, 2008 at 6:24 PM, philip > <[EMAIL PROTECTED] <mailto:[EMAIL PROTECTED]>> > > > wrote: > > > > ... > > > > read more ยป > > > > > > --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Lift" group. To post to this group, send email to [email protected] To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/liftweb?hl=en -~----------~----~----~----~------~----~------~--~---
