Pages

Wednesday, June 15, 2011

EJB3 Method Invocation Timeouts

I have an application calling a remote method on a stateless session bean. This mehtod performs a whole heap of JPA transactions and thus can take quite some time to execute. The exact time is dependent on parameters passed in but mostly the call times out before the method can complete execution.

Something strange I noticed was that the JBoss TM doesn't actually stop the remote method, which continues execution (usually until completion) but only times out and aborts the method call. I would have thought the manager would want to interrupt any active thread that is running within a transaction that has timed out. But it seems you can control the two timeouts separately.

To control the EJB method timeout set the transaction timeout to a higher value. This can be done on a per method basis by annotating the method with

        @org.jboss.annotation.ejb.TransactionTimeout(600) // eg: a 10 minute timeout.

Or to apply the timeout setting to the entire bean:

        @ActivationConfigProperty(propertyName="transactionTimeout", propertyValue="600")

For bean managed transactions you can set the timeput pn the UserTransaction as follows:

        UserTransaction ut = (UserTransaction)ctx.lookup        ("java:someApp/someBean/SomeTransaction");
        ut.setTransactionTimeout(600);
        ut.begin();
        ...
        ut.commit();



To set the read timeout for remote method invocations in JBoss (6), update the following line in <JBoss home>/server/default/deploy/ejb3-connectors-jboss-beans.xml with:

        </parameter>
        <parameter>socket://${hostforurl}:${port}?timeout=600000</parameter>
        <parameter>

Monday, June 6, 2011

JPA-style positional param was not an integral ordinal

My JPA named native query:

 @NamedNativeQuery(
   name = "getFailure",
   query = "SELECT * " +
   "FROM INTERFACE_FAILURES " +
   "WHERE server_id = ?1" +
   "AND interface_name = ?2" +
   "AND date_time = TO_DATE(?3, 'dd/MM/yyyy hh:mi:ss PM') ",
   resultClass = InterfaceFailure.class
 )

called as follows:

   Query query = entityManager.createNamedQuery("getFailure")
   .setParameter(1, serverId)
   .setParameter(2, interfaceId)
   .setParameter(3, dateTime);

   if(query.getResultList().size() > 0) {
     failure = (InterfaceFailure)query.getSingleResult();   
   }


results in the following:

15:54:33,438 ERROR [org.hibernate.impl.SessionFactoryImpl] Error in named query: getFailure: org.hibernate.QueryException: JPA-style positional param was not an integral ordinal
 at org.hibernate.engine.query.ParameterParser.parse(ParameterParser.java:111) [:3.6.0.Final]
 at org.hibernate.loader.custom.sql.SQLQueryParser.substituteParams(SQLQueryParser.java:290) [:3.6.0.Final]
...


The solution, which google did not help with at all, hence my post:

 @NamedNativeQuery(
   name = "getFailure",
   query = "SELECT * " +
   "FROM INTERFACE_FAILURES " +
   "WHERE server_id = (?1)" +
   "AND interface_name = (?2)" +
   "AND date_time = TO_DATE(?3, 'dd/MM/yyyy hh:mi:ss PM') ",
   resultClass = InterfaceFailure.class
 )


Add brackets around the integral positional parameters. 

Issues with JPA Queries with Date constraints

Lately I have been trying to update an Oracle table with a primary key made up of a start date and end date using JPA merge. The merge step was throwing ConstraintViolationExceptions saying that the primary key already existed, which was strange because as far as I thought merge was only supposed to try and insert rows if an existing match didn't exist, in which case we should not ever see constraint violations of this sort.


Due to my still sketchy understanding of JPA I figured creating a new entity object could somehow be causing an automatic database synchronisation step, which results in the Constraint violation exception.

I decided to try searching for the existing record before creating it if the search result came back with null. I added the following query:

   @NamedNativeQuery(
   name = "getUserLogEntry",
   query = "SELECT * " +
   "FROM USER_LOG_ENTRY " +
   "WHERE start_date_time = ?1 " +
   "AND end_date_time = ?2 ",
   resultClass = UserLogEntry.class
   )

Which is called passing in Date objects as parameters:

   Query query = entityManager.createNamedQuery("getUserLogEntry")
   .setParameter(1, startDate, TemporalType.DATETIME)
   .setParameter(2, endDate, TemporalType.DATETIME);

   if(query.getResultList().size() > 0) {
     entry = (UserLogEntry)query.getSingleResult();   
   }

This however, was not finding any entry in the database, even though there definitely was such an entry there!

After much wasted time I did find a way aroung this (although not a full understanding of why the previous attempts were not working). It seems passing java Date objects to the SQL query somehow scrambles somewhere in the Date translation, resulting in no matches. Therefore I casted my java dates to Strings and then used SQL's TO_DATE to format the dates exactly as I wanted:

   @NamedNativeQuery(
   name = "getUserLogEntry",
   query = "SELECT * " +
   "FROM HC_DYN_USER_LOG_ENTRY " +
   "WHERE start_date_time = TO_DATE(?1, 'dd/MM/yyyy hh:mi:ss PM') " +
   "AND end_date_time = TO_DATE(?2, 'dd/MM/yyyy hh:mi:ss PM') ",
   resultClass = UserLogEntry.class
   )


    private static SimpleDateFormat reportDateFormat = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss a"); 

...


  String startDateStr = reportDateFormat.format(startDateTime);
  String endDateStr = reportDateFormat.format(endDateTime);
  Query query = entityManager.createNamedQuery("getUserLogEntry")
   .setParameter(1, startDateStr)
   .setParameter(2, endDateStr);
 
   if(query.getResultList().size() > 0) {
     entry = (UserLogEntry)query.getSingleResult();   
   }

JPA Queries

Recently started having to use JPA (at the whim of some senior) without having had any proper training on it. As expected I have run into a myraid of issues, one of them being referring to attributes of an embedded primary key class (ie: an entity generated from a table named <tableName>PK). Trying to refer to attributes of the embedded class using standard JPQL would result in errors such as “No data type for node org.hibernate.hql.ast.tree.AggregateNode”.

My temporary solution is to use named native queries instead, allowing me to write straight SQL. For example, in the entity class itself, you would have something like:


@Entity
@Table(name="LOG_ENTRY")
@NamedNativeQueries ({
 @NamedNativeQuery(
   name = "getLatestLogEntryDate",
   query = "SELECT * " +
   "FROM LOG_ENTRY e " +
   "WHERE e.start_date_time = (SELECT MAX(start_date_time) FROM LOG_ENTRY) ",
   resultClass = LogEntry.class
 )
})


public class LogEntry implements Serializable {
 private static final long serialVersionUID = 1L;

 @EmbeddedId
 private LogEntryPK id;

 @Column(name="AVG_TIME")
 private BigDecimal avgTime;

...

}


@Embeddable
public class LogEntryPK implements Serializable {
 //default serial version id, required for serializable classes.
 private static final long serialVersionUID = 1L;

 @Column(name="SERVER_ID")
 private String serverId;

    @Temporal( TemporalType.TIMESTAMP)
 @Column(name="START_DATE_TIME")
 private java.util.Date startDateTime;

    @Temporal( TemporalType.TIMESTAMP)
 @Column(name="END_DATE_TIME")
 private java.util.Date endDateTime;

...

}


This allows reference to startDateTime within the ServiceLogEntryPK class.


Another issue was developing queries that needed to return composite data, which is not itself an entity. For example, returning server_id, start_date_time, and the average of avg_time, grouped by month. This can be done by adding a named native query to the LogEntry class as follows:


@NamedNativeQueries ({
 @NamedNativeQuery(
   name = "getLogEntriesAggregateMonthly",
   query = "SELECT 'All Servers' as server_id, TO_CHAR(start_date_time, 'YYYY-MM') DATE_FIELD, AVG(e.AVG_TIME) as AVG_TIME " + 
   "FROM LOG_ENTRY e " +  
   "WHERE start_date_time >= TO_DATE(?1, 'yy.MM.dd') " + 
   "AND end_date_time <= TO_DATE(?2, 'yy.MM.dd') " +
   "AND server_id like (?3) " +
   "GROUP BY TO_CHAR(start_date_time, 'YYYY-MM')",
         resultSetMapping = "LogSummary"
 )
})
@SqlResultSetMappings({
 @SqlResultSetMapping(name="LogSummary")
})


The SqlResultSetMapping name is just a String token, and can be pretty much any randomly chosen string. (Don't ask me what the purpose of this is)

A good discussion on the weaknesses of JPA queroes can be found here: http://heapspace.blogspot.com/2009/03/jpa-strengths-and-weaknesses.html

 
Powered by Blogger