samedi 6 juin 2015

Do I need to annotate JPA actions with @Transactional in Play Framework-1.x to prevent the connection leak?

As described here (relevant parts),

http://ift.tt/1Awq5MS

Play will automatically start the Hibernate entity manager when it finds one or more classes annotated with the @javax.persistence.Entity annotation... When the JPA entity manager is started you can get it from the application code, using the JPA helper... Play will automatically manage transactions for you. It will start a transaction for each HTTP request and commit it when the HTTP response is sent.

As described here (relevant parts),

http://ift.tt/1eUG7Gu

There is no built-in JPA implementation in Play 2.0; you can choose any available implementation.. Every JPA call must be done in a transaction so, to enable JPA for a particular action, annotate it with @play.db.jpa.Transactional. This will compose your action method with a JPA Action that manages the transaction for you.

In my project, there are several data sources referred from different controllers, for example:

Action of the MeteoController.java:

public static void meteoDeviceObjects() {
    Query query = JPA.em("meteo").createQuery("FROM MeteoDeviceObject m");
    List<MeteoDeviceObjectController> meteoDeviceObjects =  query.getResultList();

    renderJSON(meteoDeviceObjects);
}

Action of the ClientsController.java:

public static void connectedClients(String connected) {
    Query clientQuery = JPA.em("default").createQuery("FROM Client c WHERE c.connected=:connected");
    List<Client> connectedClientsList = clientQuery.setParameter("connected", connected).getResultList();

    renderJSON(connectedClientsList);
}

I found the connection leak, even with a relatively small load:

Oops: PersistenceException An unexpected error occured caused by exception PersistenceException: org.hibern ate.exception.GenericJDBCException: Could not open connection

play.exceptions.UnexpectedException: Unexpected Error
        at play.Invoker$Invocation.onException(Invoker.java:244)
        at play.Invoker$Invocation.run(Invoker.java:306)
        at Invocation.HTTP Request(Play!)
Caused by: javax.persistence.PersistenceException: org.hibernate.exception.Gener
icJDBCException: Could not open connection
        at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityMan
agerImpl.java:1387)
        at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityMan
agerImpl.java:1310)
        at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException
(AbstractEntityManagerImpl.java:1397)
        at org.hibernate.ejb.TransactionImpl.begin(TransactionImpl.java:62)
        at play.db.jpa.JPA.withTransaction(JPA.java:230)
        at play.db.jpa.JPA.withinFilter(JPA.java:195)
        at play.db.jpa.JPAPlugin$TransactionalFilter.withinFilter(JPAPlugin.java
:299)
        at play.Invoker$Invocation.withinFilter(Invoker.java:272)
        at play.Invoker$Invocation.run(Invoker.java:289)
        ... 1 more
Caused by: org.hibernate.exception.GenericJDBCException: Could not open connecti
on
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.conver
t(StandardSQLExceptionConverter.java:54)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlException
Helper.java:124)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlException
Helper.java:109)
        at org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.obtainConnec
tion(LogicalConnectionImpl.java:221)
        at org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.getConnectio
n(LogicalConnectionImpl.java:157)
        at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.doBegi
n(JdbcTransaction.java:67)
        at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.begin(Ab
stractTransactionImpl.java:160)
        at org.hibernate.internal.SessionImpl.beginTransaction(SessionImpl.java:
1351)
        at org.hibernate.ejb.TransactionImpl.begin(TransactionImpl.java:59)
        ... 6 more
Caused by: java.sql.SQLException: An attempt by a client to checkout a Connectio
n has timed out.
        at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:118)
        at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:77)
        at org.hibernate.service.jdbc.connections.internal.DatasourceConnectionP
roviderImpl.getConnection(DatasourceConnectionProviderImpl.java:141)
        at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectio
nAccess.obtainConnection(AbstractSessionImpl.java:301)
        at org.hibernate.engine.jdbc.internal.LogicalConnectionImpl.obtainConnec
tion(LogicalConnectionImpl.java:214)
        ... 11 more
Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out whil
e waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePo
ol@1cb35c -- timeout at awaitAvailable()
        at com.mchange.v2.resourcepool.BasicResourcePool.awaitAvailable(BasicRes
ourcePool.java:1461)
        at com.mchange.v2.resourcepool.BasicResourcePool.prelimCheckoutResource(
BasicResourcePool.java:639)
        at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicR
esourcePool.java:549)
        ... 14 more

I found some information about it: http://ift.tt/1KLNt95

The JPA.em() method gets the current entity manager that's associated with the thread, it's intended for use with the @Transactional annotation. It will get whichever entity manager the annotation says, so if you say @Transactional(value = "mydb"), it will get the mydb entity manager. The @Transactional annotation manages these for you, so that's why there's no leak when you use the em() method.

JPA.em(String) however does not get the entity manager from the thread, it always gets a new entity manager, hence you need to make sure that you clean it up by calling em.close() when you're finished with it (usually in a finally block).

Do I need to use @Transactional annotation in Play Framework-1.x to prevent the connection leak? And when I don't explicitly specifying entity manager? For example:

...
public static void update() {
    Contact contact = new Contact ();

    contact.FirstName = jsonContact.FirstName;
    contact.LastName = jsonContact.LastName;
    contact.Birthday = jsonContact.Birthday;
    contact.Email = jsonContact.Email;
    contact.Phone = jsonContact.Phone;
    contact.Comment = jsonContact.Comment;

    contact.save();
    renderJSON(contact);
}
...

I would be very grateful for the information. Thanks to all.

Aucun commentaire:

Enregistrer un commentaire