OpenJPA jako dostawca JPA w samodzielnej aplikacji

Z Jacek Laskowski - Wiki Projektanta Java EE

Po ostatnich sukcesach z JPA w wydaniu Oracle TopLink, które opisywałem w poprzednim artykule - Java Persistence API w samodzielnej aplikacji postanowiłem sprawdzić możliwość podmiany implementacji JPA bez zmiany kodu źródłowego. Planowałem najpierw zająć się Hibernate EntityManager, czyli implementacja JPA w wydaniu Hibernate, jednakże pewne problemy skłoniły mnie do zainteresowania się innym projektem wolnodostępnym - Apache OpenJPA. Równie dobry jak Hibernate, choć nie tak popularny jak on. Na uwagę zasługuje fakt, że Apache OpenJPA jest projektem, który zapoczątkował swoje istnienie na podstawie kodów źródłowych projektu komercyjnego BEA Kodo. Kolejna interesująca informacja to fakt, że BEA Kodo™ 4.1 jest obecnie oparte o Apache OpenJPA, więc możnaby napisać, że uczeń przegonił mistrza (albo coś bardzo podobnego, ale nic lepszego nie przychodzi mi do głowy w tej chwili).

Zanim rozpoczniesz lekturę tego artykułu polecam najpierw zapoznać się z artykułem poprzednim - Java Persistence API w samodzielnej aplikacji, w którym przedstawiłem konfigurację projektu w NB i proces tworzenia przykładowej aplikacji opartej o JPA.

Spis treści

Uruchomienie Apache OpenJPA krok po kroku

Konfiguracja biblioteki OpenJPA w NetBeans IDE

Pierwszym krokiem do uruchomienia OpenJPA jest pobranie go ze strony projektu - http://incubator.apache.org/openjpa. Strona domowa projektu jest bardzo skromna i polecam od razu przejść na strony Wiki projektu - http://cwiki.apache.org/openjpa. Zawartość natychmiast zmienia oblicze projektu, po bardzo surowej poprzedniej stronie domowej. Okazuje się, żę istnieje również bardzo pouczająca strona na temat uruchomienia przykładowej aplikacji w OpenJPA w różnych IDE - http://cwiki.apache.org/openjpa/quick-start.html. Generalnie warto zapoznać się z zawartością strony.

Pobieramy OpenJPA ze strony OpenJPA Downloads.

Po rozpakowaniu paczki otrzymamy katalog, który zawiera biblioteki, które będą służyły do utworzenia definicji biblioteki OpenJPA w NetBeans.

  • openjpa-all-0.9.7-incubating-SNAPSHOT.jar
  • lib/*.jar

Rozpoczynamy konfigurację biblioteki od wybrania menu Add Library... w menu węzła Libraries pod prawym klawiszem myszy projektu JpaStandalone.

Grafika:openjpa-addlibrary.PNG

Wybieramy kolejno - przycisk Manage Libraries... i definiujemy bibliotekę OpenJPA z Library Classpath zawierającą wszystkie pliki jar projektu.

Grafika:openjpa-librarymanager.PNG

Mając zaznaczoną bibliotekę OpenJPA wciskamy przycisk Add Library. Otrzymamy widok projektu jak poniżej.

Grafika:openjpa-projectszopenjpa.PNG

Utworzenie konfiguracji JPA - persistence.xml

Włączenie projektu OpenJPA jako implementacji JPA w naszej aplikacji wymaga poza ustandaryzowanymi parametrami, podania również kilku specyficznych (czytaj: nieustandaryzowanych) parametrów. Podobnie jak to ma miejsce w JDBC tak i konfiguracja JPA wymaga znajomości środowiska, z którym przyjdzie nam pracować poza zakres samej specyfikacji.

Zmiany pliku konfiguracyjnego polegają głównie na podaniu nowej wartości znacznika provider, który będzie wskazywał na org.apache.openjpa.persistence.PersistenceProviderImpl. Wymagane jest podanie kilku innych parametrów, co ostatecznie daje następujący plik konfiguracyjny JPA - persistence.xml.

<?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="JpaStandalonePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
    <class>pl.jaceklaskowski.Pracownik</class>
    <properties>
      <property name="openjpa.ConnectionURL" value="jdbc:derby://localhost:1527/sample"/>
      <property name="openjpa.ConnectionUserName" value="app"/>
      <property name="openjpa.ConnectionDriverName" value="org.apache.derby.jdbc.ClientDriver"/>
      <property name="openjpa.ConnectionPassword" value="app"/>
      <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
      <property name="openjpa.Log" value="DefaultLevel=WARN,SQL=TRACE"/>
    </properties>
  </persistence-unit>
</persistence>

Uruchomienie aplikacji

Po wprowadzeniu zmian podchodzimy do uruchomienia aplikacji. Wciskamy przycisk F6 i z niecierpliwością przyglądamy się konsoli. I jak to bywa przy takim tempie pracy, mamy pierwszy wyjątek.

Exception in thread "main" <4|true|0.9.7-incubating-SNAPSHOT> org.apache.openjpa.persistence.ArgumentException: The type "class pl.jaceklaskowski.Pracownik" has not been enhanced.
        at org.apache.openjpa.meta.ClassMetaData.resolveMeta(ClassMetaData.java:1590)
        at org.apache.openjpa.meta.ClassMetaData.resolve(ClassMetaData.java:1564)
        at org.apache.openjpa.meta.MetaDataRepository.processBuffer(MetaDataRepository.java:654)
        at org.apache.openjpa.meta.MetaDataRepository.resolveMeta(MetaDataRepository.java:554)
        at org.apache.openjpa.meta.MetaDataRepository.resolve(MetaDataRepository.java:479)
        at org.apache.openjpa.meta.MetaDataRepository.getMetaData(MetaDataRepository.java:283)
        at org.apache.openjpa.kernel.BrokerImpl.newObjectId(BrokerImpl.java:1078)
        at org.apache.openjpa.kernel.DelegatingBroker.newObjectId(DelegatingBroker.java:254)
        at org.apache.openjpa.persistence.EntityManagerImpl.find(EntityManagerImpl.java:319)
        at pl.jaceklaskowski.Main.findPracownik(Main.java:40)
        at pl.jaceklaskowski.Main.main(Main.java:16)

Nie jest to żadnym dla nas zaskoczeniem, szczególnie dla tych, którzy zdążyli się już zorientować, że do uruchomienia klas uczestniczących w zapisie trwałym przez OpenJPA potrzeba je poinstruować (tj. zmodyfikować ich bytecode). Budowanie projektów w NetBeans odbywa się za pomocą Apache Ant, który konfigurowany jest w każdym projekcie za pomocą pliku build.xml. Znajdziemy do w katalogu głównym naszego projektu (zakładka Files). Modyfikujemy go o wpis uruchamiający Enhancer po kompilacji klas projektu.

<?xml version="1.0" encoding="UTF-8"?>
<project name="JpaStandalone" default="default" basedir=".">
    <description>Builds, tests, and runs the project JpaStandalone.</description>
    <import file="nbproject/build-impl.xml"/>
    <target name="-post-compile">
        <echo>Instruujemy klasy...</echo>
        <path id="cp">
            <path path="${run.classpath}" />
        </path>
        <taskdef name="openjpac" classname="org.apache.openjpa.ant.PCEnhancerTask" classpath="${run.classpath}" />
        <fileset id="enhance.path.ref"
                 dir="${project.build.testOutputDirectory}">
            <include name="**/*.class"/>
        </fileset>
        <openjpac>
            <classpath refid="cp"/>
            <fileset dir="${build.classes.dir}" />
        </openjpac>
    </target>
</project>

Ponownie uruchamiamy naszą aplikację (poprzez klawisz F6). I znowu wyjątek. Ech, a miało być tak przyjemnie.

Exception in thread "main" <0|false|0.9.7-incubating-SNAPSHOT> org.apache.openjpa.persistence.PersistenceException: java.net.ConnectException : Error opening socket to server localhost on port 1527 with message : Connection refused: connect
        at org.apache.openjpa.jdbc.meta.MappingTool.record(MappingTool.java:536)
        at org.apache.openjpa.jdbc.kernel.JDBCBrokerFactory.synchronizeMappings(JDBCBrokerFactory.java:167)
        at org.apache.openjpa.jdbc.kernel.JDBCBrokerFactory.newBrokerImpl(JDBCBrokerFactory.java:127)
        at org.apache.openjpa.kernel.AbstractBrokerFactory.newBroker(AbstractBrokerFactory.java:164)
        at org.apache.openjpa.kernel.DelegatingBrokerFactory.newBroker(DelegatingBrokerFactory.java:139)
        at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:187)
        at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:140)
        at org.apache.openjpa.persistence.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:52)
        at pl.jaceklaskowski.Main.findPracownik(Main.java:39)
        at pl.jaceklaskowski.Main.main(Main.java:16)
Caused by: org.apache.derby.client.am.DisconnectException: java.net.ConnectException : Error opening socket to server localhost on port 1527 with message : Connection refused: connect
        at org.apache.derby.client.net.NetAgent.<init>(Unknown Source)
        at org.apache.derby.client.net.NetConnection.newAgent_(Unknown Source)
        at org.apache.derby.client.am.Connection.<init>(Unknown Source)
        at org.apache.derby.client.net.NetConnection.<init>(Unknown Source)
        at org.apache.derby.jdbc.ClientDriver.connect(Unknown Source)
        at org.apache.openjpa.jdbc.schema.SimpleDriverDataSource.getConnection(SimpleDriverDataSource.java:67)
        at org.apache.openjpa.jdbc.schema.SimpleDriverDataSource.getConnection(SimpleDriverDataSource.java:62)
        at org.apache.openjpa.lib.jdbc.DelegatingDataSource.getConnection(DelegatingDataSource.java:110)
        at org.apache.openjpa.lib.jdbc.DecoratingDataSource.getConnection(DecoratingDataSource.java:90)
        at org.apache.openjpa.lib.jdbc.DelegatingDataSource.getConnection(DelegatingDataSource.java:110)
        at org.apache.openjpa.jdbc.schema.DataSourceFactory$DefaultsDataSource.getConnection(DataSourceFactory.java:261)
        at org.apache.openjpa.jdbc.schema.SchemaGenerator.generateSchema(SchemaGenerator.java:325)
        at org.apache.openjpa.jdbc.schema.SchemaGenerator.generateSchemas(SchemaGenerator.java:281)
        at org.apache.openjpa.jdbc.schema.SchemaTool.getDBSchemaGroup(SchemaTool.java:1077)
        at org.apache.openjpa.jdbc.schema.SchemaTool.add(SchemaTool.java:335)
        at org.apache.openjpa.jdbc.schema.SchemaTool.run(SchemaTool.java:314)
        at org.apache.openjpa.jdbc.meta.MappingTool.record(MappingTool.java:485)
        ... 9 more

Ale zaraz, przecież wyjątek dotyczy braku podłączenia do bazy danych, która faktycznie nie była przez nas jeszcze startowana. Przechodzimy do zakładki Runtime i w kategorii Databases wybieramy jdbc:derby://localhost:1527/sample [app on APP] i z menu pod prawym klawiszem myszy uruchamiamy bazę przy pomocy menu Connect....

Grafika:Jpa-netbeans-dbconnect.PNG

Uruchomiwszy bazę Java DB ponownie przystępujemy do uruchomienia aplikacji. Wciśnięcie F6 pozwala nam nacieszyć się działającą aplikacją.

937  TRACE  [main] openjpa.jdbc.SQL - <t 11024915, conn 8180602> executing prepstmnt 1812813 SELECT t0.id FROM Pracownik t0 WHERE t0.id = ? [params=(long) 51]
937  TRACE  [main] openjpa.jdbc.SQL - <t 11024915, conn 8180602> [0 ms] spent
Pobrano pl.jaceklaskowski.Pracownik[id=51]

Hurra! Kolejna aplikacja z JPA (tym razem w wydaniu Apache OpenJPA) pracuje jak zamierzaliśmy. Dla tych, którzy nie pamiętają, a są zdziwieni wynikiem przypomnę, że w poprzednim przykładzie zdefiniowaliśmy argument aplikacji i stąd wyszukanie pracownika zamiast jego stworzenie.

Już szykuję się na uruchomienie Hibernate EntityManager'a (aka Hibernate JPA).

Osobiste