Pesquisa personalizada

2008/08/07

Quickly: Using H2 Database with Glassfish and Toplink

For Glassfish: Configuring sun-resources.xml

To use H2 Database with Glassfish (or Sun AS), you must edit the sun-resources.xml file and configure a JDBC Resource and a JDBC Connection Pool using respectively the tags jdbc-resource and jdbc-connection-pool. Take care with the attribute datasource-classname of jdbc-connection-pool, because you must configure its value to org.h2.jdbcx.JdbcDataSource. If you configure this parameter with other value, like org.h2.Driver (it isn't a javax.sql.Datasource!), you get the error:

Caused by: Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2.0.1 (Build b04-fcs (04/11/2008))): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: Error in allocating a connection. Cause: org.h2.Driver cannot be cast to javax.sql.DataSource
Error Code: 0
        at oracle.toplink.essentials.exceptions.DatabaseException.sqlException(DatabaseException.java:305)
        at oracle.toplink.essentials.jndi.JNDIConnector.connect(JNDIConnector.java:150)
        at oracle.toplink.essentials.sessions.DatasourceLogin.connectToDatasource(DatasourceLogin.java:184)
        at oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl.loginAndDetectDatasource(DatabaseSessionImpl.java:582)
        at oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider.login(EntityManagerFactoryProvider.java:280)
        at oracle.toplink.essentials.internal.ejb.cmp3.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:229)
So, after configure the sun-resources.xml, I have:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
  <jdbc-resource enabled="true" jndi-name="jdbc/TrilhaH2" object-type="user" pool-name="trilhaH2Pool"/>
  <jdbc-connection-pool allow-non-component-callers="false" associate-with-thread="false" connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10" connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-method="auto-commit" datasource-classname="org.h2.jdbcx.JdbcDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="32" max-wait-time-in-millis="60000" name="trilhaH2Pool" non-transactional-connections="false" pool-resize-quantity="2" res-type="javax.sql.DataSource" statement-timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="false">
    <property name="User" value=""/>
    <property name="Password" value=""/>
    <property name="URL" value="jdbc:h2:tcp://localhost//store/h2/trilha"/>
    <property name="driverClass" value="org.h2.Driver"/>
  </jdbc-connection-pool>
</resources>

H2Platform: H2 Specific Behavior to Toplink

To use H2 Database with Toplink, may be do you want to instruct the Toplink to take full use of H2 SQL/JDBC features. If yes, take a look at H2Platform java source code and read its instructions. And after compile it and place it into the JAR, configure your persistence.xml including the property:
<property name="toplink.target-database" value="oracle.toplink.essentials.platform.database.H2Platform"/> .
Note: The old property toplink.platform.class.name is deprecated - instead, use toplink.target-database.

Warning: H2Platform is inaccurate

The H2Platform class (from the h2-2008-07-28.zip)has a poor and inaccurate implementation. If you want to use IDENTITY data type to (duh!) alto-increment a primary key field, with the H2Platform, it doesn't work! Instead you are obligated to use SEQUENCE. By default, Toplink expected a SEQUENCE with a increment of 50.

Eg., if you have an annotated field like this:
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "TERMINALHISTORY_ID", nullable = false, insertable = true, updatable = false)
    private Long id;


The H2Platform supplied by H2, fails and you should be use the following (exactly) sequence:
CREATE SEQUENCE SEQ_GEN_SEQUENCE start with 0 INCREMENT BY 50;

If you set the increment size to a value less than the expected by Toplink (50), do you get the warning:
The sequence named [SEQ_GEN_SEQUENCE] is setup incorrectly. Its increment does not match its pre-allocation size.
I saw into the H2Platform source code and found this (negligent) method:
    public ValueReadQuery buildSelectQueryForNativeSequence(String seqName, Integer size) {
        return new ValueReadQuery("CALL NEXT VALUE FOR " + getQualifiedSequenceName(seqName));
        // return new ValueReadQuery("SELECT " + getQualifiedSequenceName(seqName) + ".NEXTVAL FROM DUAL");
    }


See? It ignores the Toplink parameter: size.

A few better H2Platform

If you want to use the native H2 support to IDENTITY with Toplink, you must use better H2Platform implementation. You can use this:
/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the "License").  You may not use this file except
 * in compliance with the License.
 *
 * You can obtain a copy of the license at
 * glassfish/bootstrap/legal/CDDLv1.0.txt or
 * https://glassfish.dev.java.net/public/CDDLv1.0.html.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * HEADER in each file and include the License file at
 * glassfish/bootstrap/legal/CDDLv1.0.txt.  If applicable,
 * add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your
 * own identifying information: Portions Copyright [yyyy]
 * [name of copyright owner]
 */
// Copyright (c) 1998, 2006, Oracle. All rights reserved.
package net.marciowb.trilha.dao;

import java.util.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.databaseaccess.*;

/**
 * This platform provides H2 specific behaviour.
 * Use the following setting to enable this platform:
 * <pre>
 * &lt;property
 *   name="toplink.platform.class.name"
 *   value="oracle.toplink.essentials.platform.database.H2Platform"/>
 * </pre>
 * See also: https://glassfish.dev.java.net/issues/show_bug.cgi?id=4042
 * @author Originally H2 Team
 * @author 2008-08-09 00h07 BSB Marcio Wesley Borges Brasilia-DF,Brazil
 */
public class H2Platform extends DatabasePlatform {
    public H2Platform() {
    }

    protected Hashtable buildFieldTypes() {
        Hashtable fieldTypeMapping;

        fieldTypeMapping = super.buildFieldTypes();
        fieldTypeMapping.put(Boolean.class, new FieldTypeDefinition("TINYINT", false));
        fieldTypeMapping.put(Integer.class, new FieldTypeDefinition("INTEGER", false));
        fieldTypeMapping.put(Long.class, new FieldTypeDefinition("NUMERIC", 19));
        fieldTypeMapping.put(Float.class, new FieldTypeDefinition("REAL", false));
        fieldTypeMapping.put(Double.class, new FieldTypeDefinition("REAL", false));
        fieldTypeMapping.put(Short.class, new FieldTypeDefinition("SMALLINT", false));
        fieldTypeMapping.put(Byte.class, new FieldTypeDefinition("SMALLINT", false));
        fieldTypeMapping.put(java.math.BigInteger.class, new FieldTypeDefinition("NUMERIC", 38));
        fieldTypeMapping.put(java.math.BigDecimal.class, new FieldTypeDefinition("NUMERIC", 38).setLimits(38, -19, 19));
        fieldTypeMapping.put(Number.class, new FieldTypeDefinition("NUMERIC", 38).setLimits(38, -19, 19));
        fieldTypeMapping.put(Byte[].class, new FieldTypeDefinition("BINARY", false));
        fieldTypeMapping.put(Character[].class, new FieldTypeDefinition("LONGVARCHAR", false));
        fieldTypeMapping.put(byte[].class, new FieldTypeDefinition("BINARY", false));
        fieldTypeMapping.put(char[].class, new FieldTypeDefinition("LONGVARCHAR", false));
        fieldTypeMapping.put(java.sql.Blob.class, new FieldTypeDefinition("BINARY", false));
        fieldTypeMapping.put(java.sql.Clob.class, new FieldTypeDefinition("LONGVARCHAR", false));
        fieldTypeMapping.put(java.sql.Date.class, new FieldTypeDefinition("DATE", false));
        fieldTypeMapping.put(java.sql.Time.class, new FieldTypeDefinition("TIME", false));
        fieldTypeMapping.put(java.sql.Timestamp.class, new FieldTypeDefinition("TIMESTAMP", false));

        return fieldTypeMapping;
    }


    // public boolean isHSQL() {
    //     return true;
    // }

    public boolean isH2() {
        return true;
    }

    public boolean supportsForeignKeyConstraints() {
        return true;
    }

    public ValueReadQuery buildSelectQueryForNativeSequence(String seqName, Integer size) {
        return new ValueReadQuery("CALL NEXT VALUE FOR " + getQualifiedSequenceName(seqName));
        // return new ValueReadQuery("SELECT " + getQualifiedSequenceName(seqName) + ".NEXTVAL FROM DUAL");
    }

    public boolean supportsNativeSequenceNumbers() {
        return true;
    }

    protected String getQualifiedSequenceName(String seqName) {
        if (getTableQualifier().equals("")) {
            return seqName;
        } else {
            return getTableQualifier() + "." + seqName;
        }
    }

    public boolean supportsSelectForUpdateNoWait() {
        return true;
    }

    protected ExpressionOperator todayOperator() {
        return ExpressionOperator.simpleFunctionNoParentheses(ExpressionOperator.Today, "SYSDATE");
    }

    protected void initializePlatformOperators() {
        super.initializePlatformOperators();
        addOperator(ExpressionOperator.simpleMath(ExpressionOperator.Concat, "||"));
    }

    public boolean shouldUseJDBCOuterJoinSyntax() {
        return false;
    }

    
/** @return <code>true</code> = Of course! */ public boolean supportsIdentity() { return true; } /** * Now we implement the another missing method to support * IDENTITY data type field. * @return What it was lacking. */ public ValueReadQuery buildSelectQueryForIdentity() { ValueReadQuery selectQuery = new ValueReadQuery(); selectQuery.setSQLString("SELECT IDENTITY()"); return selectQuery; }
}

To use it, remember to config correctly your persistence.xml supplying the expected property, like this:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="TrilhaPU" transaction-type="RESOURCE_LOCAL">
    <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <class>net.marciowb.trilha.dao.Fleet</class>
    <class>net.marciowb.trilha.dao.Organization</class>
    <class>net.marciowb.trilha.dao.Terminal</class>
    <class>net.marciowb.trilha.dao.TerminalConfiguration</class>
    <class>net.marciowb.trilha.dao.TerminalHistory</class>
    <class>net.marciowb.trilha.dao.UserAccount</class>
    <class>net.marciowb.trilha.dao.Vehicle</class>
    <properties>
      <property name="toplink.jdbc.user" value=""/>
      <property name="toplink.jdbc.password" value=""/>
      <property name="toplink.jdbc.url" value="jdbc:h2:tcp://localhost//store/h2/trilha"/>
      <property name="toplink.jdbc.driver" value="org.h2.Driver"/>
      <property name="toplink.target-database" value="net.marciowb.trilha.dao.H2Platform"/>
    </properties>
  </persistence-unit>
</persistence>


If you wish, download the above H2Platform code here.

Thanks to

Labels: ,

8 Comments:

Blogger Thomas Mueller said...

Hi,

Thanks for the great article! I have updated the H2Platform and have included some documentation. See:

http://h2database.googlecode.com/svn/trunk/h2/src/tools/oracle/toplink/essentials/platform/database/H2Platform.java.txt

and

http://h2database.googlecode.com/svn/trunk/h2/src/docsrc/html/tutorial.html

(Using TopLink and Glassfish).

I have added you as an author in the source code. Is this OK for you?

Regards,
Thomas

05 October, 2008 08:34  
Blogger Thomas Mueller said...

This comment has been removed by a blog administrator.

05 October, 2008 08:36  
Anonymous Anonymous said...

If you want identity columns to be created properly by toplink, you'll also need this method in H2Platform:


@Override
public void printFieldIdentityClause(Writer writer)
throws ValidationException {
try {
writer.write(" IDENTITY");
} catch (final IOException ioException) {
throw ValidationException.fileError(ioException);
}
}

26 October, 2008 17:35  
Anonymous Anonymous said...

Hi,
Trying to use Netbeans 6.5 and H2 to produce a simple CRUD application using the Desktop/Database wizard (my table has an IDENTITY ID field). Have followed the instructions from both the H2 tutorial and this article, application starts ok - editing and saving are ok, but when I try to add a new record toplink falls over with messages relating to :-

[TopLink Warning]: 2008.10.30 12:31:22.686--ClientSession(3916302)--Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLException: Sequence SEQ_GEN_SEQUENCE not found; SQL statement:
SELECT MAX(NEXT VALUE FOR SEQ_GEN_SEQUENCE) FROM SYSTEM_RANGE(1, 50) [90036-102]
Error Code: 90036
Call: SELECT MAX(NEXT VALUE FOR SEQ_GEN_SEQUENCE) FROM SYSTEM_RANGE(1, 50)
Query: ValueReadQuery()

Unless I resolve this soon I will have to bandon using the Wizard and hand code all my dB access. Can you help please?
Roger L

29 October, 2008 23:41  
Blogger Thomas Mueller said...

Thanks! I will include your printFieldIdentityClause method in next release.

14 November, 2008 09:00  
Blogger pelegri said...

Hi Marcio. I wanted to do a spotlight on this at TheAquarium but, before I do, have you tried it with EclipseLink?

Thanks, - eduard/o

23 November, 2008 22:51  
Blogger marciowb said...

Hello Eduardo. Thank you for your interest. TheAquarium is a great blog. I sorry, but I never used EclipseLink before.
Regards,
Marcio Wesley Borges

24 November, 2008 08:54  
Blogger Buddy Hammond said...

I am using Netbeans CRUD and H2 (like Roger L) and when I try to do insert I get "SEQ_GEN_SEQUENCE" not found. Do you know if there is DDL for this?

Barton

24 October, 2010 03:57  

Post a Comment

<< Home