The getTransaction method on the EntityManager only works for REQUEST_LOCAL persistence units, and throws an exception in a JTA context.
Here's my ultimate solution (not yet tested for thread safety though) object EM extends ScalaEntityManager() { val context = new InitialContext() private object tVar extends RequestVar[UserTransaction](context.lookup("java:comp/UserTransaction").asInstanceOf[UserTransaction]) def t = tVar.is override def openEM() : EntityManager = { t.begin() context.lookup("java:comp/env/persistence/em").asInstanceOf[EntityManager] } override def closeEM(em : EntityManager) = { t.commit() } } Thanks again for all your help, Kris On Wed, Sep 10, 2008 at 12:01 PM, Derek Chen-Becker <[EMAIL PROTECTED]> wrote: > I agree; I don't think you should be using the transaction in a global > object directly. Most of my experience with JPA has been with > container-managed JTA, so a lot of this isn't even needed. I'm a little > unfamiliar with the more manual management of the transaction. If I remember > correctly, the lookup for "java:comp/UserTransaction" should return the same > transaction if you call it multiple times, so I think this should work if > you just change the "val t" into "def t". There's also the "getTransaction" > method on the EntityManager which may do some of this for you. Usually in my > caller-managed code it looks like > > em.getTransaction.begin > ... > em.getTransaction.commit > > > Derek > > On Wed, Sep 10, 2008 at 9:27 AM, Kris Nuttycombe <[EMAIL PROTECTED]> > wrote: >> >> Okay, I finally resolved the issue; it turned out to be my >> unfamiliarity with JTA that was the primary problem. I hadn't realized >> that in a servlet one needs to explicitly begin the JTA transaction. >> Here's what my Model class looks like now: >> >> import javax.naming.InitialContext >> import javax.persistence.EntityManager >> import javax.transaction.UserTransaction >> >> object Model extends ScalaEntityManager { >> val context = new InitialContext() >> >> val t : UserTransaction = >> context.lookup("java:comp/UserTransaction").asInstanceOf[UserTransaction] >> >> override def openEM() : EntityManager = { >> t.begin() >> >> context.lookup("java:comp/env/persistence/em").asInstanceOf[EntityManager] >> } >> >> override def closeEM(em : EntityManager) = { >> t.commit() >> } >> } >> >> I decided that passing the persistenceName as a parameter to >> ScalaEntityManager was a bit silly when ScalaEntityManager doesn't in >> fact use the variable, but otherwise it's the same as your most recent >> sample. The only question I have is thread safety - it doesn't seem >> like I should really be using a variable on the singleton to store the >> transaction; should I instead be creating a separate RequestVar to >> hold it? >> >> Thanks, >> >> Kris >> >> On Wed, Sep 10, 2008 at 7:00 AM, Derek Chen-Becker >> <[EMAIL PROTECTED]> wrote: >> > You should be able to control the transaction handling in your >> > persistence.xml if you really want to. The transaction-type attribute on >> > the >> > persistence-unit element can be used to choose whether you do your own >> > transactions (RESOURCE_LOCAL) or if it attempts to use JTA directly >> > (JTA). >> > Of course, if you don't specify it, it'll default based on whether >> > you're >> > using a JTA datasource or providing your own connection info. Usually in >> > a >> > JEE environment you use a datasource that provides JTA anyway, and then >> > you >> > don't deal directly with transactions at all. Which are you using? >> > >> > Derek >> > >> > On Tue, Sep 9, 2008 at 11:34 AM, Kris Nuttycombe >> > <[EMAIL PROTECTED]> >> > wrote: >> >> >> >> The principal reason I'm using Hibernate is because the base JPA >> >> unfortunately doesn't provide support for non-standard type >> >> persistence, and so a few of my entities are annotated with @Type in >> >> order to use custom mappings for some of the org.joda.time classes >> >> (the Order object in question being one of them.) >> >> >> >> I've now got debug logging working; here is one interesting bit; after >> >> adding a logging check to see whether the EntityManager is open: >> >> >> >> def list(xhtml: NodeSeq) : NodeSeq = { >> >> Log.info("Entity manager is open: " + em.isOpen().toString()) >> >> val orders = em.createQuery("FROM >> >> Order").getResultList().asInstanceOf[java.util.List[Order]] >> >> >> >> I get: >> >> >> >> INFO lift - Entity manager is open: true >> >> DEBUG impl.SessionImpl - opened session at timestamp: 12209812983 >> >> DEBUG ejb.AbstractEntityManagerImpl - Looking for a JTA transaction to >> >> join >> >> DEBUG ejb.AbstractEntityManagerImpl - No JTA transaction found >> >> >> >> Since there's no JTA transaction found, I'm suspecting that Hibernate >> >> is creating its own session to use for the fetch, and is closing it >> >> afterward. The failure to find a JTA transaction is a bit telling, >> >> though - I wonder if in the standard Glassfish stack, JTA transactions >> >> are established in a servlet filter that's not being used with Lift - >> >> that Lift does request handling too far upstream or something. >> >> >> >> DEBUG ast.QueryTranslatorImpl - parse() - HQL: FROM >> >> com.gaiam.gcsi.entities.subscription.Order >> >> DEBUG ast.HqlParser - weakKeywords() : new LT(1) token - >> >> ["Order",<120> previously: <41>,line=1,col=43,possibleID=true] >> >> DEBUG ast.AST - --- HQL AST --- >> >> \-[QUERY] 'query' >> >> \-[SELECT_FROM] 'SELECT_FROM' >> >> \-[FROM] 'FROM' >> >> \-[RANGE] 'RANGE' >> >> \-[DOT] '.' >> >> +-[DOT] '.' >> >> | +-[DOT] '.' >> >> | | +-[DOT] '.' >> >> | | | +-[DOT] '.' >> >> | | | | +-[IDENT] 'com' >> >> | | | | \-[IDENT] 'gaiam' >> >> | | | \-[IDENT] 'gcsi' >> >> | | \-[IDENT] 'entities' >> >> | \-[IDENT] 'subscription' >> >> \-[IDENT] 'Order' >> >> >> >> DEBUG ast.ErrorCounter - throwQueryException() : no errors >> >> DEBUG antlr.HqlSqlBaseWalker - select << begin [level=1, >> >> statement=select] >> >> DEBUG tree.FromElement - FromClause{level=1} : >> >> com.gaiam.gcsi.entities.subscription.Order (no alias) -> order0_ >> >> DEBUG antlr.HqlSqlBaseWalker - select : finishing up [level=1, >> >> statement=select] >> >> DEBUG ast.HqlSqlWalker - processQuery() : ( SELECT ( >> >> FromClause{level=1} orders order0_ ) ) >> >> DEBUG ast.HqlSqlWalker - Derived SELECT clause created. >> >> DEBUG util.JoinProcessor - Using FROM fragment [orders order0_] >> >> DEBUG antlr.HqlSqlBaseWalker - select >> end [level=1, >> >> statement=select] >> >> DEBUG ast.AST - --- SQL AST --- >> >> \-[SELECT] QueryNode: 'SELECT' querySpaces (orders) >> >> +-[SELECT_CLAUSE] SelectClause: '{derived select clause}' >> >> | +-[SELECT_EXPR] SelectExpressionImpl: 'order0_.id as id506_' >> >> {FromElement{explicit,not a collection join,not a fetch join,fetch >> >> non-lazy >> >> >> >> properties,classAlias=null,role=null,tableName=orders,tableAlias=order0_,origin=null,colums={,className=com.gaiam.gcsi.entities.subscription.Order}}} >> >> | \-[SQL_TOKEN] SqlFragment: 'order0_.uuid as uuid506_, >> >> order0_.affiliate_id as affiliate5_506_, order0_.order_date as >> >> order3_506_, order0_.payment_source_id as payment6_506_, >> >> order0_.shipping_address_id as shipping7_506_, order0_.source_id as >> >> source8_506_, order0_.state as state506_, order0_.user_id as >> >> user9_506_' >> >> \-[FROM] FromClause: 'FROM' FromClause{level=1, >> >> fromElementCounter=1, fromElements=1, fromElementByClassAlias=[], >> >> fromElementByTableAlias=[order0_], fromElementsByPath=[], >> >> collectionJoinFromElementsByPath=[], impliedElements=[]} >> >> \-[FROM_FRAGMENT] FromElement: 'orders order0_' >> >> FromElement{explicit,not a collection join,not a fetch join,fetch >> >> non-lazy >> >> >> >> properties,classAlias=null,role=null,tableName=orders,tableAlias=order0_,origin=null,colums={,className=com.gaiam.gcsi.entities.subscription.Order}} >> >> DEBUG ast.ErrorCounter - throwQueryException() : no errors >> >> DEBUG ast.QueryTranslatorImpl - HQL: FROM >> >> com.gaiam.gcsi.entities.subscription.Order >> >> DEBUG ast.QueryTranslatorImpl - SQL: select order0_.id as id506_, >> >> order0_.uuid as uuid506_, order0_.affiliate_id as affiliate5_506_, >> >> order0_.order_date as order3_506_, order0_.payment_source_id as >> >> payment6_506_, order0_.shipping_address_id as shipping7_506_, >> >> order0_.source_id as source8_506_, order0_.state as state506_, >> >> order0_.user_id as user9_506_ from orders order0_ >> >> DEBUG ast.ErrorCounter - throwQueryException() : no errors >> >> DEBUG jdbc.AbstractBatcher - about to open PreparedStatement (open >> >> PreparedStatements: 0, globally: 0) >> >> DEBUG jdbc.ConnectionManager - opening JDBC connection >> >> DEBUG hibernate.SQL - select order0_.id as id506_, order0_.uuid as >> >> uuid506_, order0_.affiliate_id as affiliate5_506_, order0_.order_date >> >> as order3_506_, order0_.payment_source_id as payment6_506_, >> >> order0_.shipping_address_id as shipping7_506_, order0_.source_id as >> >> source8_506_, order0_.state as state506_, order0_.user_id as >> >> user9_506_ from orders order0_ >> >> DEBUG jdbc.AbstractBatcher - about to open ResultSet (open ResultSets: >> >> 0, globally: 0) >> >> DEBUG loader.Loader - result row: >> >> EntityKey[com.gaiam.gcsi.entities.subscription.Order#1] >> >> DEBUG jdbc.AbstractBatcher - about to close ResultSet (open >> >> ResultSets: 1, globally: 1) >> >> DEBUG jdbc.AbstractBatcher - about to close PreparedStatement (open >> >> PreparedStatements: 1, globally: 1) >> >> DEBUG jdbc.ConnectionManager - aggressively releasing JDBC connection >> >> DEBUG jdbc.ConnectionManager - releasing JDBC connection [ (open >> >> PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)] >> >> DEBUG engine.TwoPhaseLoad - resolving associations for >> >> [com.gaiam.gcsi.entities.subscription.Order#1] >> >> DEBUG loader.Loader - loading entity: >> >> [com.gaiam.gcsi.entities.user.PaymentSource#2] >> >> DEBUG jdbc.AbstractBatcher - about to open PreparedStatement (open >> >> PreparedStatements: 0, globally: 0) >> >> DEBUG jdbc.ConnectionManager - opening JDBC connection >> >> DEBUG hibernate.SQL - select paymentsou0_.id as id483_3_, >> >> paymentsou0_.uuid as uuid483_3_, paymentsou0_.description as >> >> descript3_483_3_, paymentsou0_.reusable as reusable483_3_, >> >> paymentsou0_.user_id as user6_483_3_, paymentsou0_.valid as >> >> valid483_3_, paymentsou0_1_.billing_address_id as billing7_492_3_, >> >> paymentsou0_1_.cardholder as cardholder492_3_, >> >> paymentsou0_1_.exp_month as exp2_492_3_, paymentsou0_1_.exp_year as >> >> exp3_492_3_, paymentsou0_1_.last_digits as last4_492_3_, >> >> paymentsou0_1_.type as type492_3_, case when paymentsou0_1_.id is not >> >> null then 1 when paymentsou0_.id is not null then 0 end as clazz_3_, >> >> user1_.id as id481_0_, user1_.uuid as uuid481_0_, user1_.created_at as >> >> created3_481_0_, user1_.email as email481_0_, >> >> user1_.encrypted_password as encrypted5_481_0_, >> >> user1_.encryption_algorithm as encryption6_481_0_, user1_.first_name >> >> as first7_481_0_, user1_.last_name as last8_481_0_, user1_.login as >> >> login481_0_, address2_.id as id503_1_, address2_.uuid as uuid503_1_, >> >> address2_.address1 as address3_503_1_, address2_.address2 as >> >> address4_503_1_, address2_.city as city503_1_, address2_.latitude as >> >> latitude503_1_, address2_.longitude as longitude503_1_, >> >> address2_.phone as phone503_1_, address2_.postal_code as >> >> postal9_503_1_, address2_.recipient as recipient503_1_, >> >> address2_.region_id as region11_503_1_, address2_.user_id as >> >> user12_503_1_, region3_.id as id501_2_, region3_.ecometry_code as >> >> ecometry2_501_2_, region3_.iso as iso501_2_, region3_.iso3 as >> >> iso4_501_2_, region3_.name as name501_2_, region3_.num_code as >> >> num6_501_2_, region3_.parent_id as parent10_501_2_, region3_.real_name >> >> as real7_501_2_, region3_.shipping_code as shipping8_501_2_, >> >> region3_.type as type501_2_ from payment_source paymentsou0_ left >> >> outer join credit_card paymentsou0_1_ on >> >> paymentsou0_.id=paymentsou0_1_.id left outer join users user1_ on >> >> paymentsou0_.user_id=user1_.id left outer join address address2_ on >> >> paymentsou0_1_.billing_address_id=address2_.id left outer join region >> >> region3_ on address2_.region_id=region3_.id where paymentsou0_.id=? >> >> DEBUG jdbc.AbstractBatcher - about to open ResultSet (open ResultSets: >> >> 0, globally: 0) >> >> DEBUG loader.Loader - result row: >> >> EntityKey[com.gaiam.gcsi.entities.user.User#3], >> >> EntityKey[com.gaiam.gcsi.entities.user.Address#4], >> >> EntityKey[com.gaiam.gcsi.entities.Region#5], >> >> EntityKey[com.gaiam.gcsi.entities.user.PaymentSource#2] >> >> DEBUG jdbc.AbstractBatcher - about to close ResultSet (open >> >> ResultSets: 1, globally: 1) >> >> DEBUG jdbc.AbstractBatcher - about to close PreparedStatement (open >> >> PreparedStatements: 1, globally: 1) >> >> DEBUG jdbc.ConnectionManager - aggressively releasing JDBC connection >> >> DEBUG jdbc.ConnectionManager - releasing JDBC connection [ (open >> >> PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)] >> >> DEBUG engine.TwoPhaseLoad - resolving associations for >> >> [com.gaiam.gcsi.entities.user.User#3] >> >> DEBUG engine.TwoPhaseLoad - done materializing entity >> >> [com.gaiam.gcsi.entities.user.User#3] >> >> DEBUG engine.TwoPhaseLoad - resolving associations for >> >> [com.gaiam.gcsi.entities.user.Address#4] >> >> DEBUG engine.TwoPhaseLoad - done materializing entity >> >> [com.gaiam.gcsi.entities.user.Address#4] >> >> DEBUG engine.TwoPhaseLoad - resolving associations for >> >> [com.gaiam.gcsi.entities.Region#5] >> >> DEBUG engine.TwoPhaseLoad - done materializing entity >> >> [com.gaiam.gcsi.entities.Region#5] >> >> DEBUG engine.TwoPhaseLoad - resolving associations for >> >> [com.gaiam.gcsi.entities.user.CreditCard#2] >> >> DEBUG engine.TwoPhaseLoad - done materializing entity >> >> [com.gaiam.gcsi.entities.user.CreditCard#2] >> >> DEBUG loader.Loader - done entity load >> >> DEBUG loader.Loader - loading entity: >> >> [com.gaiam.gcsi.entities.GaiamDivision#7] >> >> DEBUG jdbc.AbstractBatcher - about to open PreparedStatement (open >> >> PreparedStatements: 0, globally: 0) >> >> DEBUG jdbc.ConnectionManager - opening JDBC connection >> >> DEBUG hibernate.SQL - select gaiamdivis0_.id as id496_0_, >> >> gaiamdivis0_.ecometry_company_id as ecometry2_496_0_, >> >> gaiamdivis0_.ecometry_division_id as ecometry3_496_0_, >> >> gaiamdivis0_.name as name496_0_, gaiamdivis0_.paymentech_login as >> >> paymentech5_496_0_, gaiamdivis0_.paymentech_merchant_id as >> >> paymentech6_496_0_, gaiamdivis0_.paymentech_password as >> >> paymentech7_496_0_, gaiamdivis0_.paymentech_terminal_id as >> >> paymentech8_496_0_ from gaiam_division gaiamdivis0_ where >> >> gaiamdivis0_.id=? >> >> DEBUG jdbc.AbstractBatcher - about to open ResultSet (open ResultSets: >> >> 0, globally: 0) >> >> DEBUG loader.Loader - result row: >> >> EntityKey[com.gaiam.gcsi.entities.GaiamDivision#7] >> >> DEBUG jdbc.AbstractBatcher - about to close ResultSet (open >> >> ResultSets: 1, globally: 1) >> >> DEBUG jdbc.AbstractBatcher - about to close PreparedStatement (open >> >> PreparedStatements: 1, globally: 1) >> >> DEBUG jdbc.ConnectionManager - aggressively releasing JDBC connection >> >> DEBUG jdbc.ConnectionManager - releasing JDBC connection [ (open >> >> PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)] >> >> DEBUG engine.TwoPhaseLoad - resolving associations for >> >> [com.gaiam.gcsi.entities.GaiamDivision#7] >> >> DEBUG engine.TwoPhaseLoad - done materializing entity >> >> [com.gaiam.gcsi.entities.GaiamDivision#7] >> >> DEBUG loader.Loader - done entity load >> >> DEBUG engine.TwoPhaseLoad - done materializing entity >> >> [com.gaiam.gcsi.entities.subscription.Order#1] >> >> DEBUG engine.StatefulPersistenceContext - initializing non-lazy >> >> collections >> >> >> >> So Hibernate is doing fine with the primary query and the eager fetch, >> >> but then we're on to the previously seen error. >> >> >> >> ERROR hibernate.LazyInitializationException - failed to lazily >> >> initialize a collection of role: >> >> com.gaiam.gcsi.entities.subscription.Order.subscriptions, no session >> >> or session was closed >> >> org.hibernate.LazyInitializationException: failed to lazily initialize >> >> a collection of role: >> >> com.gaiam.gcsi.entities.subscription.Order.subscriptions, no session >> >> or session was closed >> >> >> >> Guess I'm going to have to dive into the Glassfish code. Bleah. >> >> >> >> Kris >> >> >> >> >> >> On Tue, Sep 9, 2008 at 11:08 AM, Derek Chen-Becker >> >> <[EMAIL PROTECTED]> wrote: >> >> > I'm pretty sure that the RequestVar should be around for the life of >> >> > the >> >> > Lift session, which means that you should still have a valid lift >> >> > session in >> >> > *any* snippet that would get called. Viktor's correct that this is a >> >> > common >> >> > error that people run into with JPA, but it's usually because they're >> >> > doing >> >> > something wrong. For instance, in Struts, you might naievely open and >> >> > close >> >> > an EntityManager within one of your Action classes, but forget that >> >> > the >> >> > lists won't actually be fetched until the resulting JSP is processed. >> >> > In >> >> > Lift, the RequestVar should be set up before any of your code is >> >> > called >> >> > and >> >> > shouldn't be shut down until all of your code is finished. I haven't >> >> > used >> >> > GlassFish at all, but I would assume that they have some debugging of >> >> > their >> >> > EM implementation that may be helpful in tracking this down. If >> >> > you're >> >> > getting the same error immediately following the call then that's >> >> > pretty >> >> > weird. One other thing: GlassFish provides its own JPA stack; is >> >> > there a >> >> > reason you're still using the Hibernate EM? The fact that you're >> >> > using >> >> > JNDI >> >> > from inside GlassFish makes me worried that you may have some >> >> > implementation >> >> > conflicts between GlassFish's JPA stack (TopLink, I believe) and the >> >> > Hibernate one that would get packaged automatically if you haven't >> >> > edited >> >> > the POM. I think you probably want to change the scope on the >> >> > persistence >> >> > pom so that the javax.persistence's scope is "provided" and the >> >> > Hibernate >> >> > and HSQLDB are "test". Let me know what you find or if that helps. >> >> > >> >> > Derek >> >> > >> >> > On Tue, Sep 9, 2008 at 9:24 AM, Kris Nuttycombe >> >> > <[EMAIL PROTECTED]> >> >> > wrote: >> >> >> >> >> >> > On Tue, Sep 9, 2008 at 10:09 AM, Oliver <[EMAIL PROTECTED]> wrote: >> >> >> >> >> >> >> >> Actually, is this the essence of the cookie. Why has the object >> >> >> >> been >> >> >> >> detached in the example Kris gives - is there something wrong >> >> >> >> with >> >> >> >> RequestVar lifecycle? >> >> >> >> >> >> Right; sorry, I should have been more clear about my question. I >> >> >> expected that the persistence session would be bound to the scope of >> >> >> the request at the appserver level, and that since Lift runs as a >> >> >> servlet filter that this binding would persist throughout the Lift >> >> >> call stack. >> >> >> >> >> >> > Actually, just add a simple debug-print when the session is >> >> >> > created >> >> >> > and >> >> >> > when >> >> >> > it's closed. And if it prints that it is closed _before_ the >> >> >> > execption >> >> >> > is >> >> >> > raised then there certainly is a problem with the requestvar >> >> >> > lifecycle. >> >> >> >> >> >> At this point, I'm beginning to think that this is not even >> >> >> Lift-related; when I add a log statement immediately after running >> >> >> the >> >> >> query I'm seeing the same result. It may be that the EntityManager >> >> >> is >> >> >> not being bound correctly to the request in the first place and that >> >> >> all retrieved objects are immediately detached, though I'm not >> >> >> certain >> >> >> how to go about tracking that down. >> >> >> >> >> >> > Ideally the entitymanager should be managed outside the scope of >> >> >> > request-vars, since the order of request-var destruction isn't >> >> >> > given, >> >> >> > which >> >> >> > means that if the cleanup is done in the "wrong" order, the >> >> >> > Session >> >> >> > is >> >> >> > closed before referenced objects are cleaned up, which introduces >> >> >> > the >> >> >> > possibility for weirdness. >> >> >> >> >> >> Hrm; in the other Lift/JPA thread folks seem to be moving *toward* >> >> >> the >> >> >> RequestVar solution, particularly taking advantage of the cleanup >> >> >> hooks in 0.10. >> >> >> >> >> >> Thanks, >> >> >> >> >> >> Kris >> >> >> >> >> >> >> >> > >> >> > >> >> > > >> >> > >> >> >> >> >> > >> > >> > > >> > >> >> > > > > > --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Lift" group. To post to this group, send email to liftweb@googlegroups.com 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 -~----------~----~----~----~------~----~------~--~---