Zasady zapisu zmian do bazy danych w JPA
Z Jacek Laskowski - Wiki Projektanta Java EE
Kolejny artykuł z serii przedstawiającej tajniki specyfikacji Java Persistence API (JPA), który zainspirowany był pytaniem na grupie pl.comp.lang.java wątkiem EJB3 - select robi update, w którym Marx zapytał o moment wykonania zapisu zmian stanu encji do bazy danych.
Podobnie jak w poprzednich artykułach dotyczących JPA - Dynamiczny dostęp do wielu baz danych w JPA oraz Testowanie złączeń FETCH JOIN w JPA rozpoczynę od przedstawienia teorii JPA, którą następnie wdrożymy w życie, w postaci działającej aplikacji demonstracyjnej. Skorzystamy z konfiguracji projektu nadzorowanego przez Apache Maven 2, aby możliwe było uruchomienie go z 3 głównymi dostawcami JPA - Apache OpenJPA, Hibernate EntityManager oraz TopLink Essentials.
Kompletny projekt aplikacji jpa-getresults-update dostępny jest do pobrania jako jpa-getresults-update.zip.
Spis treści |
JPA a zapis zmian teoretycznie
Kilka faktów wprowadzających w temat zasad zapisu zmian (synchronizacji) stanu encji do bazy danych przedstawionych w specyfikacji JPA w rozdziale 3.2 Stadia rozwojowe encji:
- Zapisanie encji w bazie danych następuje poprzez wywołanie metody javax.persistence.EntityManager.persist() bądź przez kaskadowe wywołanie.
- Encja zostanie zapisana w bazie podczas lub przed zatwierdzeniem transakcji lub w wyniku wywołania metody javax.persistence.EntityManager.flush().
- Stan encji jest synchronizowany z bazą danych podczas zatwierdzania transakcji. Synchronizacja polega na zapisaniu wszystkich zmian encji oraz ich relacji.
- Aktualizacja stanu encji dotyczy przypisania jej nowej wartości do właściwości/pola trwałego jak również zmianę wartości właściwości/pola trwałego.
- Synchronizacja z bazą danych nie wykonuje odświeżenia jakichkolwiek zarządzanych encji, chyba że metoda javax.persistence.EntityManager.refresh() została wywołana bezpośrednio na tych encjach.
- Dostawca JPA ma prawo wykonać synchronizację z bazą danych w dowolnym czasie w trakcie trwania transakcji. Za pomocą metody javax.persistence.EntityManager.flush() można wymusić synchronizację, która dotyczy encji związanych z kontekstem trwałym. Metody javax.persistence.EntityManager.setFlushMode() oraz javax.persistence.Query.setFlushMode() służą do kontroli mechanizmu synchronizacji. Jeśli zdefiniowano FlushModeType.COMMIT (o nim za moment), zapis zmian nastąpi podczas zatwierdzenia transakcji. Mimo to, dostawca JPA ma prawo wykonać zapis zmian w dowolnym innym czasie. Jeśli transakcja nie jest aktywna, zabronione jest, aby dostawca zapisywał zmiany w bazie danych.
Uzupełniające informacje z rozdziału 3.6.2 Zapytania a FlushMode specyfikacji JPA:
- Za pomocą typu wyliczeniowego javax.persistence.FlushModeType istnieje możliwość zmiany zachowania dostawcy JPA dotyczącego zapisu zmian encji do bazy danych.
- FlushModeType.AUTO (domyślne ustawienie) - Jeśli zapytanie wykonywane jest w trakcie trwania transakcji, ustawienie EntityManager lub Query na podaną wartość, dostawca trwałości zobowiązany jest zapewnić, że zmiany w stanie encji są odzwierciedlone w wyniku zapytania. Dostawca JPA może realizować to wymaganie za pomocą zapisu zmian do bazy danych lub innymi mechanizmami.
- FlushModeType.COMMIT - Jeśli ustawiono ten tryb zapisu, wpływ zmian na zapytanie w ramach danego kontekstu trwałego jest nieokreślony.
- Dostawca JPA nie ma prawa zapisywać zmian do bazy danych bez aktywnej transakcji.
Na koniec należałoby wspomnieć o informacjach zawartych w dokumentacji Javadoc dla typu wyliczeniowego javax.persistence.FlushModeType:
- FlushModeType.COMMIT - zapis zmian wyłącznie podczas zatwierdzenia transakcji
- FlushModeType.AUTO (domyślne ustawnienie) - zapis zmian do bazy danych podczas wykonania zapytania
Opis FlushModeType.AUTO jest nieprecyzyjny w dokumentacji Javadoc. Może on niewłaściwie sugerować, że obowiązkiem dostawcy JPA przy FlushModeType.AUTO jest zapis zmian do bazy danych podczas wykonania zapytania, co już wiemy, że nie jest prawdą mając za nami lekturę specyfikacji JPA oraz w samym Javadoc (który jest nota bene kopią zapisów specyfikacji).
JPA a zapis zmian praktycznie - aplikacja demonstracyjna
Utworzenie projektu - jpa-getresults-update
Rozpoczynamy od utworzenia projektu z pomocą Maven2 poleceniem mvn archetype:create. W zasadzie to powinniśmy skorzystać z dobrodziejstw zintegrowanego środowiska programistycznego (IDE) już od tego kroku, ale przyzwyczajenie bierze górę. Przejdziemy do IDE w kolejnym kroku.
jlaskowski@dev /cygdrive/c $ mvn archetype:create -DgroupId=pl.jaceklaskowski.jpa -DartifactId=jpa-getresults-update -Dversion=1.0 [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'archetype'. [INFO] ------------------------------------------------------------------------ ... [INFO] Archetype created in dir: c:\jpa-getresults-update [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------
Kolejne polecenia wykonujemy w katalogu c:\jpa-getresults-update, co w przypadku importu projektu do wybranego IDE nie będzie miało większego znaczenia.
Zaleca się usunięcie dwóch klas tworzonych przez Maven2 domyślnie podczas tworzenia projektu:
- src/main/java/pl/jaceklaskowski/jpa/App.java
- src/test/java/pl/jaceklaskowski/jpa/AppTest.java
Encja Osoba
Do rozpoznania tematu potrzebujemy encji Osoba realizowanej przez klasę pl.jaceklaskowski.jpa.Osoba.
package pl.jaceklaskowski.jpa;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
@Entity
@NamedQuery(name="Osoba.wszystkieOsoby", query="SELECT o FROM Osoba o")
public class Osoba implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String imie;
public Osoba() {
}
public Osoba(String imie) {
this.imie = imie;
}
public void setId(int id) {
this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
public String getImie() {
return imie;
}
public void setImie(String imie) {
this.imie = imie;
}
@Override
public String toString() {
return "pl.jaceklaskowski.jpa.Osoba[id=" + getId() + ", imie=" + getImie() + "]";
}
}
Plik Osoba.java zapisujemy w katalogu src/main/java/pl/jaceklaskowski/jpa.
Klasa testowa - JpaGetResultsUpdateTest
Wykonanie doświadczeń oprzemy o wykonanie testów jednostkowych JUnit przez Maven2. Tworzymy klasę tstową JpaGetResultsUpdateTest, co umożliwia uruchamianie aplikacji bez konieczności zestawiania środowiska wykonawczego z dodatkowymi bibliotekami zależnymi. Kolejnym atutem jest możliwość wprowadzania zmian i ich uruchamiania bez dodatkowych kroków pomocniczych.
package pl.jaceklaskowski.jpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.FlushModeType;
import javax.persistence.Persistence;
import javax.persistence.Query;
import org.junit.Before;
import org.junit.Test;
public class JpaGetResultsUpdateTest {
private int osobaId;
private EntityManager em;
@Before
public void setUp() {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("derbyPU");
em = emf.createEntityManager();
em.getTransaction().begin();
Osoba osoba = new Osoba("Jacek");
em.persist(osoba);
em.getTransaction().commit();
// Nadanie identyfikatora może nastąpić dopiero po zatwierdzeniu transakcji
osobaId = osoba.getId();
}
@Test
public void metodaTestowa() {
{
System.out.println("\n=== Test #1: FlushModeType.AUTO z transakcja ===");
em.getTransaction().begin();
Osoba osoba = em.find(Osoba.class, osobaId);
System.out.println("\n>>> Zmieniamy imie osoby\n");
osoba.setImie("Agata");
System.out.println(">>> Wczytujemy wszystkie osoby\n");
Query query = em.createNamedQuery("Osoba.wszystkieOsoby");
List<Osoba> osoby = query.getResultList();
System.out.println("\n>>> Wszystkie osoby wczytane\n");
assert osoby.size() == 1 : "Liczba osób powinna być 1";
assert osoby.get(0).getImie().equals("Agata") : "Oczekujemy Agaty";
System.out.println(">>> Zatwierdzamy transakcje\n");
em.getTransaction().commit();
}
{
System.out.println("\n=== Test #2: FlushModeType.AUTO bez transakcji ===");
Osoba osoba = em.find(Osoba.class, osobaId);
System.out.println("\n>>> Zmieniamy imie osoby\n");
osoba.setImie("Agata");
System.out.println(">>> Wczytujemy wszystkie osoby\n");
Query query = em.createNamedQuery("Osoba.wszystkieOsoby");
List<Osoba> osoby = query.getResultList();
System.out.println("\n>>> Wszystkie osoby wczytane\n");
assert osoby.size() == 1 : "Liczba osób powinna być 1";
assert osoby.get(0).getImie().equals("Agata") : "Oczekujemy Agaty";
System.out.println(">>> Zatwierdzamy transakcje\n");
}
{
System.out.println("\n=== Test #3: FlushModeType.COMMIT na zapytaniu ===");
em.getTransaction().begin();
Osoba osoba = em.find(Osoba.class, osobaId);
System.out.println("\n>>> Zmieniamy imie osoby\n");
osoba.setImie("Agata");
System.out.println(">>> Wczytujemy wszystkie osoby\n");
Query query = em.createNamedQuery("Osoba.wszystkieOsoby");
query.setFlushMode(FlushModeType.COMMIT);
List<Osoba> osoby = query.getResultList();
System.out.println("\n>>> Wszystkie osoby wczytane\n");
assert osoby.size() == 1 : "Liczba osób powinna być 1";
assert osoby.get(0).getImie().equals("Agata") : "Oczekujemy Agaty";
System.out.println(">>> Zatwierdzamy transakcje\n");
em.getTransaction().commit();
}
{
System.out.println("\n=== Test #4: FlushModeType.COMMIT na EntityManager ===");
em.setFlushMode(FlushModeType.COMMIT);
em.getTransaction().begin();
Osoba osoba = em.find(Osoba.class, osobaId);
System.out.println("\n>>> Zmieniamy imie osoby\n");
osoba.setImie("Agata");
System.out.println(">>> Wczytujemy wszystkie osoby\n");
Query query = em.createNamedQuery("Osoba.wszystkieOsoby");
List<Osoba> osoby = query.getResultList();
System.out.println("\n>>> Wszystkie osoby wczytane\n");
assert osoby.size() == 1 : "Liczba osób powinna być 1";
assert osoby.get(0).getImie().equals("Agata") : "Oczekujemy Agaty";
System.out.println(">>> Zatwierdzamy transakcje\n");
em.getTransaction().commit();
}
{
System.out.println("\n=== Test #5: FlushModeType.COMMIT oraz em.flush() ===");
em.setFlushMode(FlushModeType.COMMIT);
em.getTransaction().begin();
Osoba osoba = em.find(Osoba.class, osobaId);
System.out.println("\n>>> Zmieniamy imie osoby\n");
osoba.setImie("Agata");
System.out.println(">>> Jawnie wywolujemy zapis zmian do bazy danych");
em.flush();
System.out.println(">>> Wczytujemy wszystkie osoby\n");
Query query = em.createNamedQuery("Osoba.wszystkieOsoby");
List<Osoba> osoby = query.getResultList();
System.out.println("\n>>> Wszystkie osoby wczytane\n");
assert osoby.size() == 1 : "Liczba osób powinna być 1";
assert osoby.get(0).getImie().equals("Agata") : "Oczekujemy Agaty";
System.out.println(">>> Zatwierdzamy transakcje\n");
em.getTransaction().commit();
}
}
}
Klasa testowa JpaGetResultsUpdateTest wykonuje 5 testów, które obrazują działanie FlushModeType na zapis zmian do bazy danych. Wszystkie 5 testów jest z punktu widzenia programisty JPA identyczne, jednakże różnią się znacząco patrząc na liczbę wykonywanych operacji w bazie danych, czyli również i na czas działania aplikacji. Biorąc pod uwagę zapisy specyfikacji JPA to jedynie ustawienie explicite FlushModeType.COMMIT gwarantuje nam identyczne zachowanie dostawców JPA pod względem wykonywanych uaktualnień w bazie danych. W przypadku domyślnej konfiguracji z FlushModeType.AUTO liczba wysłanych poleceń do bazy danych i ich wystąpienie jest nieokreślone i zależne od dostawcy JPA. Gwarantuje się, że wykonanie zapytania musi odzwierciedlać zmiany w encjach, które w przeciwnym przypadku mogłyby nie trafić do wyniku zapytania.
Plik JpaGetResultsUpdateTest.java zapisujemy w katalogu src/test/java/pl/jaceklaskowski/jpa.
Konfiguracja JPA - persistence.xml
Tworzymy samodzielną aplikację JPA, więc wiele z ustawień konfiguracyjnych musi znaleźć się w pliku konfiguracyjnym 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="derbyPU" transaction-type="RESOURCE_LOCAL">
<class>pl.jaceklaskowski.jpa.Osoba</class>
<properties>
<!-- Sekcja Apache OpenJPA -->
<property name="openjpa.ConnectionDriverName" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="openjpa.ConnectionURL" value="jdbc:derby:target/derbyDB;create=true" />
<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(SchemaAction='add,deleteTableContents')" />
<property name="openjpa.Log" value="DefaultLevel=WARN,SQL=TRACE" />
<!-- Sekcja Hibernate EntityManager -->
<property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="hibernate.connection.url" value="jdbc:derby:target/derbyDB;create=true" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- Sekcja Toplink Essentials -->
<property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="toplink.jdbc.url" value="jdbc:derby:target/derbyDB;create=true" />
<property name="toplink.ddl-generation" value="create-tables" />
<property name="toplink.logging.level" value="FINE"/>
</properties>
</persistence-unit>
</persistence>
Ciekawostką powyższego pliku persistence.xml jest brak deklaracji dostawcy JPA (za pomocą elementu <provider>) oraz konfiguracja specyficznych dla dostawców JPA zmiennych w sekcji <properties>. Zgodnie ze specyfikacją zmienne nieznane dla danego dostawcy JPA są ignorowane.
Plik persistence.xml zapisujemy w katalogu src/main/resources/META-INF.
Konfiguracja projektu - pom.xml
Na zakończenie tworzenia naszego projektu należałoby zaprezentować kompletny plik konfiguracyjny projektu pom.xml z wymaganymi zmianami dotyczącymi poszczególnych dostawców JPA. W celu uruchomienia testów z wybranym dostawcą JPA skorzystamy z mechanizmu profili w Maven2, co umożliwi dostosowanie projektu do wymagań naszego dostawcy.
Plik pom.xml jest tworzony podczas tworzenia projektu i należy podmienić jego zawartość na poniższą.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.jaceklaskowski.jpa</groupId>
<artifactId>jpa-getresults-update</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<name>jpa-getresults-update</name>
<url>http://www.jaceklaskowski.pl/wiki/Zasady_zapisu_zmian_do_bazy_danych_w_JPA</url>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.3.2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>openjpa</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>hibernate</id>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate</artifactId>
<version>3.2.5.ga</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.3.1.ga</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.3.0.ga</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>3.0.0.ga</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>3.0.0.ga</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>jboss</groupId>
<artifactId>jboss-archive-browsing</artifactId>
<version>5.0.0alpha-200607201-119</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>java.net</id>
<name>java.net Maven2 Repository</name>
<url>http://download.java.net/maven/2</url>
</repository>
</repositories>
</profile>
<profile>
<id>toplink</id>
<dependencies>
<dependency>
<groupId>toplink.essentials</groupId>
<artifactId>toplink-essentials</artifactId>
<version>2.1-13</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>java.net</id>
<name>java.net Maven1 Repository</name>
<url>http://download.java.net/maven/1</url>
<layout>legacy</layout>
</repository>
</repositories>
</profile>
</profiles>
</project>
Ciekawostką związaną z konfiguracją Maven2 na potrzeby naszego projektu jest zależność javax.persistence.persistence-api, która występuje w wersji 1.0 w repozytorium Maven 2 oraz w wersji 1.0.2 w repozytorium Maven 1 na java.net. Biorąc pod uwagę, kto nadzoruje wersjami w repozytorium java.net (Sun) wydaje się być stosowne stosowanie bibliotek Korporacyjnej Javy właśnie z tego repozytorium. I tak też czynimy.
Uruchomienie aplikacji
Po bardzo intensywnym przestudiowaniu tajników JPA oraz zestawieniu projektu pora zwieńczyć dzieło uruchomieniem testów za pomocą polecenia mvn clean test (argument clean jest opcjonalnym poleceniem, które służy do wyczyszczenia projektu, co gwarantuje powtarzalność wykonywania testów).
Domyślnie projekt wykonuje testy z Apache OpenJPA jako dostawcą JPA. Wybranie pozostałych, dwóch dostawców Hibernate EntityManager oraz TopLink Essentials sprowadza się do wykonania polecenia mvn clean test z dodatkowym parametrem -P <nazwa_dostawcy>, gdzie <nazwa_dostawcy> to odpowiednio hibernate lub toplink.
Rozpocznijmy analizę wykonania naszego testu z Apache OpenJPA. Zwróćmy uwagę na ilość wykonywanych aktualizacji w bazie danych przy domyślnych ustawieniach EntityManager i Query oraz przy ich zmianie, jak również przy wywołaniu EntityManager.flush(). Widać, że podejściem OpenJPA w kontekście zapisu zmian encji do bazy danych jest zapis zmian encji do bazy danych wyłącznie podczas zatwierdzenia transakcji, nawet pomimo wywołania metody EntityManager.flush().
jlaskowski@dev /cygdrive/c/jpa-getresults-update $ mvn clean test [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Building jpa-getresults-update [INFO] task-segment: [clean, test] [INFO] ------------------------------------------------------------------------ ... ------------------------------------------------------- T E S T S ------------------------------------------------------- Running pl.jaceklaskowski.jpa.JpaGetResultsUpdateTest 7672 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 11446032> executing stmnt 26918187 CREATE TABLE OPENJPA_SEQUENCE_TABLE (ID SMALLINT NOT NULL, SEQUENCE_VALUE BIGINT, PRIMARY KEY (ID)) 7875 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 11446032> [203 ms] spent 7875 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 2592387> executing stmnt 4512144 CREATE TABLE Osoba (id INTEGER NOT NULL, imie VARCHAR(255), PRIMARY KEY (id)) 8047 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 2592387> [172 ms] spent 8047 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 23022207> executing stmnt 12730771 DELETE FROM OPENJPA_SEQUENCE_TABLE 8079 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 23022207> [32 ms] spent 8079 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 23022207> executing stmnt 10923886 DELETE FROM Osoba 8079 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 23022207> [0 ms] spent 8344 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 15029693> executing prepstmnt 14327460 SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE WITH RR [params=(int) 0] 8344 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 15029693> [0 ms] spent 8360 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 22364723> executing prepstmnt 13685038 INSERT INTO OPENJPA_SEQUENCE_TABLE (ID, SEQUENCE_VALUE) VALUES (?, ?) [params=(int) 0, (int) 1] 8375 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 22364723> [15 ms] spent 8375 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 17303670> executing prepstmnt 33114244 SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE WITH RR [params=(int) 0] 8375 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 17303670> [0 ms] spent 8391 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 17303670> executing prepstmnt 19112841 UPDATE OPENJPA_SEQUENCE_TABLE SET SEQUENCE_VALUE = ? WHERE ID = ? AND SEQUENCE_VALUE = ? [params=(long) 51, (int) 0, (long) 1] 8391 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 17303670> [0 ms] spent 8407 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 7962652> executing prepstmnt 12254719 INSERT INTO Osoba (id, imie) VALUES (?, ?) [params=(int) 1, (String) Jacek] 8407 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 7962652> [0 ms] spent === Test #1: FlushModeType.AUTO z transakcja === >>> Zmieniamy imie osoby >>> Wczytujemy wszystkie osoby 8641 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 4536207> executing prepstmnt 25277396 SELECT t0.id, t0.imie FROM Osoba t0 8641 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 4536207> [0 ms] spent >>> Wszystkie osoby wczytane >>> Zatwierdzamy transakcje 8672 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 4349625> executing prepstmnt 32654289 UPDATE Osoba SET imie = ? WHERE id = ? [params=(String) Agata, (int) 1] 8672 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 4349625> [0 ms] spent === Test #2: FlushModeType.AUTO bez transakcji === >>> Zmieniamy imie osoby >>> Wczytujemy wszystkie osoby 8672 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 19492125> executing prepstmnt 22199751 SELECT t0.id, t0.imie FROM Osoba t0 8672 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 19492125> [0 ms] spent >>> Wszystkie osoby wczytane >>> Zatwierdzamy transakcje === Test #3: FlushModeType.COMMIT na zapytaniu === >>> Zmieniamy imie osoby >>> Wczytujemy wszystkie osoby 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 13842387> executing prepstmnt 24137973 SELECT t0.id, t0.imie FROM Osoba t0 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 13842387> [0 ms] spent >>> Wszystkie osoby wczytane >>> Zatwierdzamy transakcje 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 15865423> executing prepstmnt 15860788 UPDATE Osoba SET imie = ? WHERE id = ? [params=(String) Agata, (int) 1] 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 15865423> [0 ms] spent === Test #4: FlushModeType.COMMIT na EntityManager === >>> Zmieniamy imie osoby >>> Wczytujemy wszystkie osoby 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 23149395> executing prepstmnt 31361704 SELECT t0.id, t0.imie FROM Osoba t0 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 23149395> [0 ms] spent >>> Wszystkie osoby wczytane >>> Zatwierdzamy transakcje 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 3289716> executing prepstmnt 32879825 UPDATE Osoba SET imie = ? WHERE id = ? [params=(String) Agata, (int) 1] 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 3289716> [0 ms] spent === Test #5: FlushModeType.COMMIT oraz em.flush() === >>> Zmieniamy imie osoby >>> Jawnie wywolujemy zapis zmian do bazy danych 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 14011068> executing prepstmnt 27517983 UPDATE Osoba SET imie = ? WHERE id = ? [params=(String) Agata, (int) 1] 8688 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 14011068> [0 ms] spent >>> Wczytujemy wszystkie osoby 8704 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 14011068> executing prepstmnt 20828680 SELECT t0.id, t0.imie FROM Osoba t0 8704 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 14011068> [0 ms] spent >>> Wszystkie osoby wczytane >>> Zatwierdzamy transakcje 8719 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 14011068> executing prepstmnt 7806641 UPDATE Osoba SET imie = ? WHERE id = ? [params=(String) Agata, (int) 1] 8719 derbyPU TRACE [main] openjpa.jdbc.SQL - <t 33131864, conn 14011068> [0 ms] spent Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.11 sec Results : Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------
Kolejne uruchomienie wykonamy z Hibernate EntityManager za pomocą polecenia mvn clean test -P hibernate. Można zaobserwować, że domyślne działanie Hibernate to wykonanie zapisu zmian do bazy danych przed wykonaniem zapytania oraz każdorazowo, podczas wywołania metody EntityManager.flush(). Jest to odmienne zachowanie od Apache OpenJPA acz nadal zgodne ze specyfikacją. Może się wydawać, że jest to nieporządane zachowanie (czy to OpenJPA, czy Hibernate), które powoduje różne wyniki cząstkowe, ale właśnie ich ulotność powoduje, że nie ma to w zasadzie znaczenia dla osób rozpoczynających poznawanie JPA (a to można przypuszczać był główny cel określenia parametrów domyślnych). Jeśli to ma znaczenie, specyfikacja JPA umożliwia precyzyjne określenie zachowania dostawcy JPA przez odpowiednie zaaplikowanie FlushModeType na egzemplarzu EntityManager bądź Query, co powoduje zniesienie różnic i uwspólnienie zachowania.
jlaskowski@dev /cygdrive/c/jpa-getresults-update
$ mvn clean test -P hibernate
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building jpa-getresults-update
[INFO] task-segment: [clean, test]
[INFO] ------------------------------------------------------------------------
...
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running pl.jaceklaskowski.jpa.JpaGetResultsUpdateTest
2007-12-16 15:33:53 org.hibernate.cfg.annotations.Version <clinit>
INFO: Hibernate Annotations 3.3.0.GA
2007-12-16 15:33:53 org.hibernate.cfg.Environment <clinit>
INFO: Hibernate 3.2.5
2007-12-16 15:33:53 org.hibernate.cfg.Environment <clinit>
INFO: hibernate.properties not found
2007-12-16 15:33:53 org.hibernate.cfg.Environment buildBytecodeProvider
INFO: Bytecode provider name : cglib
2007-12-16 15:33:53 org.hibernate.cfg.Environment <clinit>
INFO: using JDK 1.4 java.sql.Timestamp handling
2007-12-16 15:33:53 org.hibernate.ejb.Version <clinit>
INFO: Hibernate EntityManager 3.3.1.GA
2007-12-16 15:33:54 org.hibernate.cfg.AnnotationBinder bindClass
INFO: Binding entity from annotated class: pl.jaceklaskowski.jpa.Osoba
2007-12-16 15:33:54 org.hibernate.cfg.annotations.QueryBinder bindQuery
INFO: Binding Named query: Osoba.wszystkieOsoby => SELECT o FROM Osoba o
2007-12-16 15:33:54 org.hibernate.cfg.annotations.EntityBinder bindTable
INFO: Bind entity pl.jaceklaskowski.jpa.Osoba on table Osoba
2007-12-16 15:33:54 org.hibernate.validator.Version <clinit>
INFO: Hibernate Validator 3.0.0.GA
2007-12-16 15:33:54 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: Using Hibernate built-in connection pool (not for production use!)
2007-12-16 15:33:54 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: Hibernate connection pool size: 20
2007-12-16 15:33:54 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: autocommit mode: true
2007-12-16 15:33:54 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: using driver: org.apache.derby.jdbc.EmbeddedDriver at URL: jdbc:derby:target/derbyDB;create=true
2007-12-16 15:33:54 org.hibernate.connection.DriverManagerConnectionProvider configure
INFO: connection properties: {autocommit=true, release_mode=auto}
2007-12-16 15:34:00 org.hibernate.cfg.SettingsFactory buildSettings
INFO: RDBMS: Apache Derby, version: 10.3.2.1 - (599110)
2007-12-16 15:34:00 org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC driver: Apache Derby Embedded JDBC Driver, version: 10.3.2.1 - (599110)
2007-12-16 15:34:00 org.hibernate.dialect.Dialect <init>
INFO: Using dialect: org.hibernate.dialect.DerbyDialect
2007-12-16 15:34:00 org.hibernate.transaction.TransactionFactoryFactory buildTransactionFactory
INFO: Transaction strategy: org.hibernate.transaction.JDBCTransactionFactory
2007-12-16 15:34:00 org.hibernate.transaction.TransactionManagerLookupFactory getTransactionManagerLookup
INFO: No TransactionManagerLookup configured (in JTA environment, use of read-write or transactional second-level cache is not recommended)
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic flush during beforeCompletion(): disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Automatic session close at end of transaction: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Scrollable result sets: enabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: JDBC3 getGeneratedKeys(): disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Connection release mode: auto
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default batch fetch size: 1
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Generate SQL with comments: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Order SQL updates by primary key: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Order SQL inserts for batching: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory createQueryTranslatorFactory
INFO: Query translator: org.hibernate.hql.ast.ASTQueryTranslatorFactory
2007-12-16 15:34:02 org.hibernate.hql.ast.ASTQueryTranslatorFactory <init>
INFO: Using ASTQueryTranslatorFactory
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query language substitutions: {}
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: JPA-QL strict compliance: enabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Second-level cache: enabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Query cache: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory createCacheProvider
INFO: Cache provider: org.hibernate.cache.NoCacheProvider
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Optimize cache for minimal puts: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Structured second-level cache entries: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Echoing all SQL to stdout
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Statistics: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Deleted entity synthetic identifier rollback: disabled
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Default entity-mode: pojo
2007-12-16 15:34:02 org.hibernate.cfg.SettingsFactory buildSettings
INFO: Named query checking : enabled
2007-12-16 15:34:02 org.hibernate.impl.SessionFactoryImpl <init>
INFO: building session factory
2007-12-16 15:34:02 org.hibernate.impl.SessionFactoryObjectFactory addInstance
INFO: Not binding factory to JNDI, no JNDI name configured
2007-12-16 15:34:02 org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: Running hbm2ddl schema export
2007-12-16 15:34:02 org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: exporting generated schema to database
2007-12-16 15:34:03 org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: schema export complete
2007-12-16 15:34:03 org.hibernate.util.JDBCExceptionReporter logWarnings
WARNING: SQL Warning: 10000, SQLState: 01J01
2007-12-16 15:34:03 org.hibernate.util.JDBCExceptionReporter logWarnings
WARNING: Database 'target/derbyDB' not created, connection made to existing database instead.
Hibernate:
insert
into
Osoba
(imie, id)
values
(?, ?)
=== Test #1: FlushModeType.AUTO z transakcja ===
>>> Zmieniamy imie osoby
>>> Wczytujemy wszystkie osoby
Hibernate:
update
Osoba
set
imie=?
where
id=?
Hibernate:
select
osoba0_.id as id0_,
osoba0_.imie as imie0_
from
Osoba osoba0_
>>> Wszystkie osoby wczytane
>>> Zatwierdzamy transakcje
=== Test #2: FlushModeType.AUTO bez transakcji ===
>>> Zmieniamy imie osoby
>>> Wczytujemy wszystkie osoby
Hibernate:
select
osoba0_.id as id0_,
osoba0_.imie as imie0_
from
Osoba osoba0_
>>> Wszystkie osoby wczytane
>>> Zatwierdzamy transakcje
=== Test #3: FlushModeType.COMMIT na zapytaniu ===
>>> Zmieniamy imie osoby
>>> Wczytujemy wszystkie osoby
Hibernate:
select
osoba0_.id as id0_,
osoba0_.imie as imie0_
from
Osoba osoba0_
>>> Wszystkie osoby wczytane
>>> Zatwierdzamy transakcje
=== Test #4: FlushModeType.COMMIT na EntityManager ===
>>> Zmieniamy imie osoby
>>> Wczytujemy wszystkie osoby
Hibernate:
select
osoba0_.id as id0_,
osoba0_.imie as imie0_
from
Osoba osoba0_
>>> Wszystkie osoby wczytane
>>> Zatwierdzamy transakcje
=== Test #5: FlushModeType.COMMIT oraz em.flush() ===
>>> Zmieniamy imie osoby
>>> Jawnie wywolujemy zapis zmian do bazy danych
>>> Wczytujemy wszystkie osoby
Hibernate:
select
osoba0_.id as id0_,
osoba0_.imie as imie0_
from
Osoba osoba0_
>>> Wszystkie osoby wczytane
>>> Zatwierdzamy transakcje
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 10.203 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
Jako pracę domową pozostawiam przeanalizowanie działania TopLink Essentials poleceniem mvn clean test -P toplink.
