<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="KuchenSimpleEJB">
</persistence-unit>
</persistence>
wird
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
<persistence-unit name="KuchenSimpleEJB">
</persistence-unit>
</persistence>
java:jboss/datasources/ExampleDS
eintragen (Erklärung im Abschnitt Der Weg für Harte: persistence.xml).<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
<persistence-unit name="kuchenPersistenceUnit">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="true"></property>
</properties>
</persistence-unit>
</persistence>
java:comp/DefaultDataSource
definiert, die man statt der oben verwendeten WildFly-spezifischen
Datenquelle java:jboss/datasources/ExampleDS
verwenden könnte.
<jta-data-source>java:comp/DefaultDataSource</jta-data-source>
@NamedQuery (name="findAllKuchen", query="select o from KuchenSimpleBean o")
@Entity()
public class KuchenSimpleBean implements Serializable
{
@NamedQueries({
@NamedQuery (name="findAllKuchen", query="select o from KuchenSimpleBean o"),
@NamedQuery (name="findByName", query="select o from KuchenSimpleBean o where o.name like ?1")
})
private Integer intId;
private String strName;
@Column()
@Id ()
@GeneratedValue ()
public Integer getId()
{
return this.intId;
}
public void setId(Integer int_Id)
{
this.intId = int_Id;
}
@Column()
public String getName()
{
return this.strName;
}
public void setName(String str_Name)
{
this.strName = str_Name;
}
@Override
public String toString()
{
return this.strName;
}
Die Properties werden mit der Annotation @jakarta.persistence.Column
als persistente Bean-Felder markiert.
Die Property "ID" wird mit der Annotation @jakarta.persistence.Id
als Primary-Key-Feld markiert. Der Wert soll vom Container
automatisch generiert werden (@jakarta.persistence.GeneratedValue
). Das Verfahren der Generierung bleibt dem Server überlassen,
beim JBoss und der H2-Datenbank wird daraus eine Auto-ID-Spalte (sprich: beim Insert wird die ID erzeugt).toString
wird überladen und gibt den Namen des Kuchens zurück. Das hat den Vorteil, dass wir die Kuchen im Client
als Objekt in eine Listbox packen und auch wieder auslesen können. <?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
<persistence-unit name="kuchenPersistenceUnit">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<properties>
<!-- Setzen dieser Property aktiviert das automatische Tabellen-Generieren und Löschen beim Deploy! -->
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<!-- SQL-Logging einschalten: -->
<property name="hibernate.show_sql" value="true"></property>
</properties>
</persistence-unit>
</persistence>
Es wird eine "persistence-unit" namens "kuchenPersistenceUnit" deklariert. Sie ist verbunden mit einer JDBC-Datenquelle des
Servers, die im JNDI unter dem Namen "java:jboss/datasources/ExampleDS" abgelegt ist und auf die JBoss-interne H2-Datenbank zeigt.
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
<property name="javax.persistence.schema-generation.create-source" value="metadata"/>
<property name="javax.persistence.schema-generation.drop-source" value="metadata"/>
Siehe JakartaEE Tutorial, Kapitel "Database Schema Creation": https://eclipse-ee4j.github.io/jakartaee-tutorial/persistence-intro006.html
javax.persistence.schema-generation.database.action
gibt an, ob und wie das Schema erzeugt wird. Der Wert "drop-and-create" löscht beim Deploy das vorhandene Schema und erzeugt es neu.hibernate.hbm2ddl.auto
wird hier beim Undeploy der Anwendung nichts gelöscht! Das Löschen erfolgt nur vor einem Redeploy. D.h. die neuen Properties bereinigen
Datenbanken im Testumfeld nicht.javax.persistence.schema-generation.create-source
und ....drop-source
geben an, wie die Tabellenerzeugung erfolgen soll: der Wert "metadata" bewirkt, dass die SQL-Befehle
auf Basis der Entity-Bean-Annotations (bzw. der Deklarationen in "ejb-jar.xml") erzeugt werden.
<property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create"/>
<property name="jakarta.persistence.schema-generation.create-source" value="metadata"/>
<property name="jakarta.persistence.schema-generation.drop-source" value="metadata"/>
Siehe https://jakarta.ee/learn/docs/jakartaee-tutorial/current/persist/persistence-intro/persistence-intro.html#_database_schema_creation
java.lang.Integer
) und "name" (vom Typ java.lang.String
)
angelegt.import javax.persistence.*;
wird
import jakarta.persistence.*;
@NamedQuery (name="findAllKuchen", query="select o from KuchenSimpleBean o")
@Entity()
public class KuchenSimpleBean implements Serializable
{
Die Property "id" muss außerdem mit der Annotation jakarta.persistence.GeneratedValue
versehen werden: @Id
@GeneratedValue()
public Integer getId()
{
return this.id;
}
Anschließend wird die toString
überladen (siehe Abschnitt Der Weg für Harte: Anlegen der Entity Bean ohne JPA). @PersistenceContext(unitName="kuchenPersistenceUnit")
private EntityManager entityManager = null;
Die Implementierung von "saveKuchen" sieht so aus: public void saveKuchen (KuchenSimpleBean kuchen)
{
this.entityManager.merge(kuchen);
}
Wichtig ist dass hier "merge" und nicht "persist" genommen wird, siehe Abschnitt Troubleshooting. public void deleteKuchen(KuchenSimpleBean kuchen)
{
kuchen = this.entityManager.find (KuchenSimpleBean.class, kuchen.getId() );
this.entityManager.remove(kuchen);
}
Wichtig ist dass das zu löschende Objekt eventuell "detached" ist und deshalb vorher unter Container-Verwaltung gestellt werden muss, siehe Abschnitt Troubleshooting. public List<KuchenSimpleBean> getKuchen()
{
Query query = this.entityManager.createNamedQuery("findAllKuchen");
List<KuchenSimpleBean> listKuchen = query.getResultList();
return listKuchen;
}
Query query = this.entityManager.createQuery("select o from KuchenSimpleBean o");
Type safety: The expression of type List needs unchecked conversion to conform to List<KuchenSimpleBean>
).
Dies könnten wir umgehen indem wir vor die entsprechende Zeile folgende Annotation eintragen:
@SuppressWarnings("unchecked")
List<KuchenSimpleBean> listKuchen = query.getResultList();
de.hsrm.jakartaee.knauf.kuchen.FrameKuchen
ist das Hauptfenster der Anwendung. Er besteht aus einer JList auf einem JScrollPane
und drei Buttons "Neu", "Bearbeiten" und "Löschen".KuchenDialog
zum Bearbeiten eines Kuchens (Textfeld für den Namen und OK/Abbrechen-Buttons)FrameKuchen
liegen, die gleichzeitig das Hauptfenster der Anwendung ist. FrameKuchen
gepackt:
public class FrameKuchen extends JFrame
{
@EJB
private static KuchenWorkerRemote kuchenWorker;
Manifest-Version: 1.0
Class-Path: KuchenSimpleEJB.jar
Main-Class: de.hsrm.jakartaee.knauf.kuchen.FrameKuchen
21:03:44,880 INFO [stdout] (ServerService Thread Pool -- 78) Hibernate: drop table if exists KuchenSimpleBean cascade
21:03:44,881 INFO [stdout] (ServerService Thread Pool -- 78) Hibernate: drop sequence if exists KuchenSimpleBean_SEQ
21:03:44,888 INFO [stdout] (ServerService Thread Pool -- 78) Hibernate: create sequence KuchenSimpleBean_SEQ start with 1 increment by 50
21:03:44,891 INFO [stdout] (ServerService Thread Pool -- 78) Hibernate: create table KuchenSimpleBean (id integer not null, name varchar(255), primary key (id))
20:57:30,018 WARN [org.jboss.modules.define] (MSC service thread 1-5) Failed to define class org.h2.server.web.JakartaWebServlet in Module "com.h2database.h2" version 2.2.220 from local module loader @2ece4966 (finder: local module finder @1dd0e7c4 (roots: C:\Temp\wildfly-30.0.0.Final\modules,C:\Temp\wildfly-30.0.0.Final\modules\system\layers\base)): java.lang.NoClassDefFoundError: Failed to link org/h2/server/web/JakartaWebServlet (Module "com.h2database.h2" version 2.2.220 from local module loader @2ece4966 (finder: local module finder @1dd0e7c4 (roots: C:\Temp\wildfly-30.0.0.Final\modules,C:\Temp\wildfly-30.0.0.Final\modules\system\layers\base))): jakarta/servlet/http/HttpServlet ...
<?xml version="1.0" encoding="UTF-8"?> <module-alias xmlns="urn:jboss:module:1.9" name="javax.servlet.api:h2" target-name="jakarta.servlet.api"/>
java -jar h2-2.2.220.jar
(Die Version von "h2-....jar" wird sich natürlich ändern) <subsystem xmlns="urn:jboss:domain:datasources:7.1">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=${wildfly.h2.compatibility.mode:REGULAR}</connection-url>
<driver>h2</driver>
...
</datasource>
...
</datasources>
</subsystem>
Neu: <subsystem xmlns="urn:jboss:domain:datasources:7.1">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
<connection-url>jdbc:h2:tcp://localhost/~/test</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
...
</datasources>
</subsystem>
Es wird also nur die "connection-url" geändert. public static void main(String[] args)
{
//...hier wird das Look&Feel gesetzt...
FrameKuchen frameKuchen = new FrameKuchen();
frameKuchen.setVisible(true);
}
In der Initialisierung des "FrameKuchen" würde man die "DefaultCloseOperation" auf "Exit on close" setzen, sprich der Javaprozess
endet durch das Schließen des Hauptfensters:
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Letzteres ist relativ offensichtlich ein Problem, weil unsere main-Methode hier ja vom JBoss-ApplicationClient-Launcher
gestartet wird und ein "System.exit()" kontraproduktiv wäre. this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frameKuchen.setVisible(true);
while (frameKuchen.isVisible() == true)
{
Thread.sleep(5000);
}
frameKuchen.dispose();
Meiner Erfahrung nach kostet ein "Thread.sleep" relativ viel Prozessorleistung (vielleicht gilt das in aktuellen Java-Versionen nicht mehr)java.util.concurrent.CountDownLatch
verwendet. Diese dient als eine Art Freigabezähler: beim Erzeugen der Klasse wird eine Anzahl von Freigabesignalen definiert, die erforderlich ist,
damit die Freigabe "erteilt" wird. Nach dem Sichtbarschalten des Fensters wartet die Main-Methode deshalb auf diese Freigabe. Im Schließen des
Fensters wird die Freigabe über die Methode CountDownLatch.countDown
erteilt. Hierzu wird ein "WindowListener" für das Event "windowClosed"
registriert. private static CountDownLatch frameClosedSignal;
public static void main(String[] args)
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e)
{
e.printStackTrace();
}
//Erzeugen des "warten auf Schließen des Fensters"-Signals.
FrameKuchen.frameClosedSignal = new CountDownLatch(1);
FrameKuchen frameKuchen = new FrameKuchen();
frameKuchen.setVisible(true);
//WindowListener erzeugen, der im "windowClosed" die CountDownLatch anstößt.
frameKuchen.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosed(WindowEvent windowEvent)
{
//Der CountDownLatch die "Freigabe" erteilen.
FrameKuchen.frameClosedSignal.countDown();
super.windowClosed(windowEvent);
}
});
// Jetzt darauf warten, dass das Fenster geschlossen wird (WindowListener reagiert!)
// Dieses await() blockiert bis die Methode "countDown" auf der CountDownLatch" aufgerufen wird.
try
{
FrameKuchen.frameClosedSignal.await();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
Ein kleiner Hinweis: "windowClosed" wird erst aufgerufen, wenn die Methode "dispose" des JFrame aufgerufen wird. Durch die
oben genannte "DefaultCloseOperation = DISPOSE_ON_CLOSE" haben wir das erreicht. Beim Default "HIDE_ON_CLOSE" würde "windowClosed" nicht aufrufen,
außer wir führen das "dispose" z.B in einem "windowClosing"-Eventhandler durch.
%JBOSS_HOME%\bin\appclient.bat c:\temp\KuchenSimple.ear#KuchenSimpleClient.jar
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private static final java.lang.Object javax.swing.JFrame.defaultLookAndFeelDecoratedKey accessible:
module java.desktop does not \"opens javax.swing\" to unnamed module @f7cd57e"}
set JAVA_OPTS=--add-opens=java.desktop/javax.swing=ALL-UNNAMED --add-opens=java.desktop/java.awt=ALL-UNNAMED
%WILDFLY_HOME%\bin\appclient.bat c:\temp\KuchenSimple.ear#KuchenSimpleClient.jar
--illegal-access=permit
setzen, aber dies wurde in Java 17 entfernt. <subsystem xmlns="urn:jboss:domain:datasources:1.1">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" enabled="true" use-java-context="true"
pool-name="java:jboss/datasources/ExampleDS">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
...
</datasource>
...
</datasources>
</subsystem>
insert into KuchenSimpleBean (id, name) values (?, ?)
subsystem xmlns="urn:jboss:domain:logging:8.0"
. Hier wird folgendes eingetragen (ACHTUNG: Datei als UTF-8 speichern !): <console-handler name="CONSOLE.HIBERNATE_PARAMETERS">
<level name="TRACE"/>
<formatter>
<pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
</formatter>
</console-handler>
Es wird ein neuer Handler definiert, der auf die Konsole (System.out) loggt. Einziger Unterschied zum vorhandenen Konsolen-Appender: sein
"Level" also das Loglevel, ab dem Meldungen durchgelassen wurde, wurde auf die maximale Detailstufe "TRACE" erhöht.
Anmerkung: es würde eigentlich reichen, den Standard-Konsolenappender auf "TRACE" umzustellen, allerdings würden wir dann in JBoss-Meldungen ertrinken. <logger category="org.hibernate.type">
<level name="TRACE"/>
<handlers>
<handler name="CONSOLE.HIBERNATE_PARAMETERS"/>
</handlers>
</logger>
<logger category="org.hibernate.orm.jdbc.bind">
<level name="TRACE"/>
<handlers>
<handler name="CONSOLE.HIBERNATE_PARAMETERS"/>
</handlers>
</logger>
<logger category="org.hibernate.orm.jdbc.extract">
<level name="TRACE"/>
<handlers>
<handler name="CONSOLE.HIBERNATE_PARAMETERS"/>
</handlers>
</logger>
19:35:12,829 INFO [stdout] (default task-1) Hibernate: select next value for KuchenSimpleBean_SEQ
19:35:12,857 INFO [stdout] (default task-1) Hibernate: insert into KuchenSimpleBean (name,id) values (?,?)
19:35:12,858 TRACE [org.hibernate.orm.jdbc.bind] (default task-1) binding parameter [1] as [VARCHAR] - [Mohnkuchen]
19:35:12,859 TRACE [org.hibernate.orm.jdbc.bind] (default task-1) binding parameter [2] as [INTEGER] - [1]
19:35:12,863 INFO [stdout] (default task-1) Hibernate: select k1_0.id,k1_0.name from KuchenSimpleBean k1_0
19:35:12,863 TRACE [org.hibernate.orm.jdbc.extract] (default task-1) extracted value ([1] : [INTEGER]) - [1]
19:35:12,864 TRACE [org.hibernate.orm.jdbc.extract] (default task-1) extracted value ([2] : [VARCHAR]) - [Mohnkuchen]
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/ejb-jar_4_0.xsd"
version="4.0">
<display-name>KuchenSimpleEJB</display-name>
<enterprise-beans>
<session>
<description>
<![CDATA[Stateless Session Bean für den Zugriff auf die Entitiy Bean
* "Kuchen". Enthält Methoden zum Speichern und Löschen eines einzelnen
* Kuchens sowie zum Holen einer Liste aller Kuchen.]]>
</description>
<display-name>KuchenWorkerBean</display-name>
<ejb-name>KuchenWorkerBean</ejb-name>
<remote>de.hsrm.jakartaee.knauf.kuchen.KuchenWorker</remote>
<ejb-class>de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerBean</ejb-class>
<session-type>Stateless</session-type>
<!--EntityManager-Injection -->
<persistence-context-ref>
<persistence-context-ref-name>KuchenPersistenceUnitRef</persistence-context-ref-name>
<persistence-unit-name>kuchenPersistenceUnit</persistence-unit-name>
<injection-target>
<injection-target-class>
de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerBean
</injection-target-class>
<injection-target-name>entityManager</injection-target-name>
</injection-target>
</persistence-context-ref>
</session>
<entity>
<description>
<![CDATA[Entity Bean für einen einzelnen Kuchen.]]>
</description>
<display-name>KuchenSimpleBean</display-name>
<ejb-name>KuchenSimpleBean</ejb-name>
<ejb-class>de.fhw.swtvertiefung.knauf.kuchen.KuchenSimpleBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.Integer</prim-key-class>
<reentrant>false</reentrant>
</entity>
</enterprise-beans>
</ejb-jar>
Neu in diesem Beispiel: <?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="https://jakarta.ee/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence/orm https://jakarta.ee/xml/ns/persistence/orm/orm_3_1.xsd"
version="3.1">
<named-query name="findAllKuchen">
<query>select o from KuchenSimpleBean o</query>
</named-query>
<entity class="de.hsrm.jakartaee.knauf.kuchen.KuchenSimpleBean" access="PROPERTY"
metadata-complete="true">
<attributes>
<id name="id">
<generated-value/>
</id>
<basic name="name">
</basic>
</attributes>
</entity>
</entity-mappings>
Das Element "entity" hat keine in der Schema-Definition angegebenen Pflicht-Unterelemente, trotzdem sind einige nötig damit unser Beispiel funktioniert. <?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="https://jakarta.ee/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence/orm https://jakarta.ee/xml/ns/persistence/orm/orm_3_1.xsd"
version="3.1">
<named-query name="findAllKuchen">
<query>select o from KuchenSimpleBean o</query>
</named-query>
<entity class="de.hsrm.jakartaee.knauf.kuchen.KuchenSimpleBean" access="PROPERTY"
metadata-complete="true">
<table name="KUCHENSIMPLEBEAN" />
<attributes>
<id name="id">
<column name="ID" />
<generated-value/>
</id>
<basic name="name">
<column name="NAME" />
</basic>
</attributes>
</entity>
</entity-mappings>
Wir geben hier den Tabellennamen an ("KUCHENSIMPLEBEAN"), außerdem die Attribute und die zugehörigen Datenbankspalten.
Für die ID-Spalte geben wir an dass der Wert automatisch generiert werden soll. <?xml version="1.0" encoding="UTF-8"?>
<application-client version="10"
xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/application-client_10.xsd">>
<display-name>KuchenSimpleClient</display-name>
<ejb-ref>
<ejb-ref-name>ejb/KuchenWorker</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerRemote</remote>
<injection-target>
<injection-target-class>de.hsrm.jakartaee.knauf.kuchen.FrameKuchen</injection-target-class>
<injection-target-name>kuchenWorker</injection-target-name>
</injection-target>
</ejb-ref>
</application-client>
<?xml version="1.0" encoding="UTF-8"?>
<jboss-client xmlns="urn:jboss:jakartaee:1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:jboss:jakartaee:1.0 https://www.jboss.org/schema/jbossas/jboss-client_9_0.xsd"
version="9.0">
<jndi-name>KuchenSimpleClient</jndi-name>
<ejb-ref>
<ejb-ref-name>ejb/KuchenWorker</ejb-ref-name>
<jndi-name>java:global/KuchenSimple/KuchenSimpleEJB/KuchenWorkerBean!de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerRemote</jndi-name>
</ejb-ref>
</jboss-client>
@Column()
@Id()
public Integer getId()
{
return this.intId;
}
KuchenSimpleBean erhält eine weitere @NamedQuery
-Annotation (die wir jetzt in eine Annotation
@NamedQueries
verpacken müssen): @NamedQueries( {
@NamedQuery(name = "findAllKuchen", query = "select o from KuchenSimpleBean o"),
@NamedQuery(name = "getMaxKuchenId", query = "select max (o.id) from KuchenSimpleBean o")
})
public class KuchenSimpleBean implements Serializable
{
...
KuchenWorkerBean.saveKuchen sieht so aus: public void saveKuchen (KuchenSimpleBean kuchen)
{
if (kuchen.getId() == null)
{
Query query = this.entityManager.createNamedQuery("getMaxKuchenId");
Integer intMaxId = (Integer) query.getSingleResult();
//Beim ersten Aufruf kommt hier NULL zurück weil keine ID da ist.
if (intMaxId == null)
{
//Wir starten mit der ID "1"
kuchen.setId(1);
}
else
{
//MaxID + 1 verwenden:
kuchen.setId(intMaxId + 1);
}
}
this.entityManager.merge(kuchen);
}
Neu ist der fette Code: wenn der übergebene Kuchen keine ID enthält, dann nehmen wir an dass er neu ist.
In diesem Fall führen wir unsere Query aus. Sie liefert beim ersten Aufruf NULL zurück, in diesem Fall
setzen wir die ID auf "1". Finden wir eine ID erhöhen wir diese um 1. mariadb-install-db.exe -p jbosstest
mariadbd.exe --console
mariadb.exe -u root -p
create database jbosstest;
20:52:04,408 INFO [org.jboss.as.server.deployment] (MSC service thread 1-5) WFLYSRV0027: Starting deployment of "mariadb-java-client-3.3.2.jar" (runtime-name: "mariadb-java-client-3.3.2.jar")
20:52:04,506 INFO [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-5) WFLYJCA0004: Deploying JDBC-compliant driver class org.mariadb.jdbc.Driver (version 3.3)
20:52:04,508 INFO [org.jboss.as.connector.deployers.jdbc] (MSC service thread 1-4) WFLYJCA0018: Started Driver service with driver-name = mariadb-java-client-3.3.2.jar
20:52:04,528 INFO [org.jboss.as.server] (DeploymentScanner-threads - 2) WFLYSRV0010: Deployed "mariadb-java-client-3.3.2.jar" (runtime-name : "mariadb-java-client-3.3.2.jar")
<subsystem xmlns="urn:jboss:domain:datasources:7.1">
,
in der bereits der Eintrag für die H2-Datenbank steckt. <subsystem xmlns="urn:jboss:domain:datasources:7.1">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
...
</datasource>
<datasource jndi-name="java:jboss/datasources/MariaDBDS" pool-name="MariaDBDS" enabled="true">
<connection-url>jdbc:mariadb://localhost:3306/JBOSSTEST</connection-url>
<driver>mariadb-java-client-3.3.2.jar</driver>
<security user-name="root" password="jbosstest"/>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
...
</driver>
</drivers>
</datasources>
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.9" name="com.mariadb">
<resources>
<resource-root path="mariadb-java-client-3.3.2.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
</dependencies>
</module>
<subsystem xmlns="urn:jboss:domain:datasources:7.1">
,
in der bereits der Eintrag für die H2-Datenbank steckt. <subsystem xmlns="urn:jboss:domain:datasources:7.1">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
...
</datasource>
<datasource jndi-name="java:jboss/datasources/MariaDBDS" pool-name="MariaDBDS" enabled="true">
<connection-url>jdbc:mariadb://localhost:3306/JBOSSTEST</connection-url>
<driver>com.mariadb</driver>
<security user-name="root" password="jbosstest"/>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
...
</driver>
<driver name="com.mariadb" module="com.mariadb">
<xa-datasource-class>org.mariadb.jdbc.MariaDbDataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>
/subsystem=datasources/jdbc-driver=com.mariadb:add(driver-name="com.mariadb",driver-module-name="com.mariadb",driver-xa-datasource-class-name=org.mariadb.jdbc.MariaDbDataSource)
[standalone@localhost:9999 /] data-source add --name=MariaDBDS2 --jndi-name=java:jboss/datasources/MariaDBDS2 --driver-name=com.mariadb --enabled=true --connection-url=jdbc:mariadb://localhost:3306/JBOSSTEST --user-name=root --password=jbosstest
/subsystem=datasources/data-source=MariaDBDS2:add(jndi-name="java:jboss/datasources/MariaDBDS2",driver-name=com.mariadb,enabled=true,connection-url="jdbc:mariadb://localhost:3306/JBOSSTEST",user-name=root,password=jbosstest)
[standalone@localhost:9999 /] data-source remove --name=MariaDBDS2
21:49:18,099 INFO [org.jboss.as.connector.subsystems.datasources] (ServerService Thread Pool -- 44) WFLYJCA0004: Deploying JDBC-compliant driver class org.h2.Driver (version 2.2)
21:49:18,113 INFO [org.jboss.as.connector.subsystems.datasources] (ServerService Thread Pool -- 44) WFLYJCA0004: Deploying JDBC-compliant driver class org.mariadb.jdbc.Driver (version 3.3)
oder auch über diesen CLI-Befehl: /subsystem=datasources:installed-drivers-list
[standalone@localhost:9999 /] /subsystem=datasources:installed-drivers-list
{
"outcome" => "success",
"result" => [
{
"driver-name" => "com.mariadb",
"deployment-name" => undefined,
"driver-module-name" => "com.mariadb",
"module-slot" => "main",
"driver-datasource-class-name" => "",
"driver-xa-datasource-class-name" => "org.mariadb.jdbc.MariaDbDataSource",
"datasource-class-info" => [{"org.mariadb.jdbc.MariaDbDataSource" => {
"loginTimeout" => "int",
"password" => "java.lang.String",
"url" => "java.lang.String",
"user" => "java.lang.String"
}}],
"driver-class-name" => "org.mariadb.jdbc.Driver",
"driver-major-version" => 3,
"driver-minor-version" => 3,
"jdbc-compliant" => true
},
{
"driver-name" => "h2",
"deployment-name" => undefined,
"driver-module-name" => "com.h2database.h2",
"module-slot" => "main",
"driver-datasource-class-name" => "",
"driver-xa-datasource-class-name" => "org.h2.jdbcx.JdbcDataSource",
"datasource-class-info" => [{"org.h2.jdbcx.JdbcDataSource" => {
"URL" => "java.lang.String",
"description" => "java.lang.String",
"loginTimeout" => "int",
"password" => "java.lang.String",
"url" => "java.lang.String",
"user" => "java.lang.String"
}}],
"driver-class-name" => "org.h2.Driver",
"driver-major-version" => 2,
"driver-minor-version" => 2,
"jdbc-compliant" => true
}
]
}
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
version="3.0">
<persistence-unit name="kuchenPersistenceUnit" transaction-type="JTA">
<jta-data-source>java:jboss/datasources/MariaDBDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="true"></property>
</properties>
</persistence-unit>
</persistence>
21:17:08,428 ERROR [org.jboss.as.controller.management-operation] (Thread-45) WFLYCTL0013: Operation ("deploy") failed - address: ([("deployment" => "KuchenSimple.ear")]) - failure description: {
"WFLYCTL0412: Required services that are not installed:" => ["jboss.naming.context.java.jboss.datasources.MariaDBDS"],
"WFLYCTL0180: Services with missing/unavailable dependencies" => [
"service jboss.persistenceunit.\"KuchenSimple.ear/KuchenSimpleEJB.jar#kuchenPersistenceUnit\".__FIRST_PHASE__ is missing [jboss.naming.context.java.jboss.datasources.MariaDBDS]",
"service jboss.persistenceunit.\"KuchenSimple.ear/KuchenSimpleEJB.jar#kuchenPersistenceUnit\" is missing [jboss.naming.context.java.jboss.datasources.MariaDBDS]"
]
}
21:18:16,680 ERROR [org.jboss.as.controller.management-operation] (Controller Boot Thread) WFLYCTL0013: Operation ("add") failed - address: ([ ("subsystem" => "datasources"), ("data-source" => "MariaDBDS") ]) - failure description: { "WFLYCTL0412: Required services that are not installed:" => ["jboss.jdbc-driver.mariadb-java-client-3_3_3_jar"], "WFLYCTL0180: Services with missing/unavailable dependencies" => [ "service jboss.data-source.\"jboss.naming.context.java.jboss.datasources.MariaDBDS\" is missing [jboss.jdbc-driver.mariadb-java-client-3_3_3_jar]", "service jboss.driver-demander.java:jboss/datasources/MariaDBDS is missing [jboss.jdbc-driver.mariadb-java-client-3_3_3_jar]" ] }
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> <persistence-unit name="kuchenZutatInheritancePersistenceUnit" transaction-type="JTA"> <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source> <properties> <property name="hibernate.hbm2ddl.auto" value="create-drop" /> <property name="hibernate.show_sql" value="true"></property> <property name="jboss.as.jpa.managed" value="false"></property> </properties> </persistence-unit> </persistence>Erklärung siehe https://docs.wildfly.org/32/Developer_Guide.html#persistence-unit-properties:
Dies würde bedeuten, dass zwei EAR-Dateien erstellt werden müssen: eine für das Deploy auf dem Server (inklusive vollständiger "persistence.xml") und eines für das Starten des Application Client.
21:17:23,597 ERROR [org.jboss.as.ejb3.invocation] (default task-1) WFLYEJB0034: Jakarta Enterprise Beans Invocation failed on component KuchenWorkerBean for
method public abstract void de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerRemote.saveKuchen(de.hsrm.jakartaee.knauf.kuchen.KuchenSimpleBean): jakarta.ejb.EJBException:
org.hibernate.PersistentObjectException: detached entity passed to persist: de.hsrm.jakartaee.knauf.kuchen.KuchenSimpleBean
at org.jboss.as.ejb3@30.0.0.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInOurTx(CMTTxInterceptor.java:251)
...
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: de.hsrm.jakartaee.knauf.kuchen.KuchenSimpleBean
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultPersistEventListener.persist(DefaultPersistEventListener.java:88)
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:77)
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:54)
at org.hibernate@6.2.13.Final//org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate@6.2.13.Final//org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:755)
at org.hibernate@6.2.13.Final//org.hibernate.internal.SessionImpl.persist(SessionImpl.java:739)
at org.jboss.as.jpa@30.0.0.Final//org.jboss.as.jpa.container.AbstractEntityManager.persist(AbstractEntityManager.java:566)
at deployment.KuchenSimple.ear.KuchenSimpleEJB.jar//de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerBean.saveKuchen(KuchenWorkerBean.java:49)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
Lösung: die zu speichernde, bereits in der Datenbank vorhandene Entity wurde in der KuchenWorkerBean nicht mehr "entityManager.merge()"
gespeichert sondern mittels "entityManager.persist()". Dies klappt aber NUR bei neuen Objekten, nicht bei bereits vorhandenen Daten !
21:20:22,747 ERROR [org.jboss.as.ejb3.invocation] (default task-1) WFLYEJB0034: Jakarta Enterprise Beans Invocation failed on component KuchenWorkerBean for
method public abstract void de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerRemote.deleteKuchen(de.hsrm.jakartaee.knauf.kuchen.KuchenSimpleBean):
jakarta.ejb.EJBException: java.lang.IllegalArgumentException: Removing a detached instance de.hsrm.jakartaee.knauf.kuchen.KuchenSimpleBean#1
at org.jboss.as.ejb3@30.0.0.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInOurTx(CMTTxInterceptor.java:251)
at org.jboss.as.ejb3@30.0.0.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:373)
at org.jboss.as.ejb3@30.0.0.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:143)
at org.jboss.invocation@2.0.0.Final//org.jboss.invocation.InterceptorContext.proceed(InterceptorContext.java:422)
...
Caused by: java.lang.IllegalArgumentException: Removing a detached instance de.hsrm.jakartaee.knauf.kuchen.KuchenSimpleBean#1
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultDeleteEventListener.disallowDeletionOfDetached(DefaultDeleteEventListener.java:310)
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultDeleteEventListener.performDetachedEntityDeletionCheck(DefaultDeleteEventListener.java:298)
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultDeleteEventListener.deleteTransientInstance(DefaultDeleteEventListener.java:173)
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultDeleteEventListener.delete(DefaultDeleteEventListener.java:156)
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:95)
at org.hibernate@6.2.13.Final//org.hibernate.event.internal.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:83)
at org.hibernate@6.2.13.Final//org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate@6.2.13.Final//org.hibernate.internal.SessionImpl.fireDelete(SessionImpl.java:961)
at org.hibernate@6.2.13.Final//org.hibernate.internal.SessionImpl.delete(SessionImpl.java:892)
at org.hibernate@6.2.13.Final//org.hibernate.internal.SessionImpl.remove(SessionImpl.java:2359)
at org.jboss.as.jpa@30.0.0.Final//org.jboss.as.jpa.container.AbstractEntityManager.remove(AbstractEntityManager.java:641)
at deployment.KuchenSimple.ear.KuchenSimpleEJB.jar//de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerBean.deleteKuchen(KuchenWorkerBean.java:36)
...
Lösung:die Entities vorher müssen "attached" werden, d.h. in eine vom Container verwaltete Entity umgewandelt
werden:
kuchen = this.entityManager.find (KuchenSimpleBean.class, kuchen.getId() );
this.entityManager.remove(kuchen);
Vor dem Aufruf von "remove" müßte eigentlich noch eine Prüfung erfolgen ob der Datensatz in der Datenbank gefunden wurde.21:25:46,634 ERROR [org.jboss.as.ejb3.invocation] (default task-1) WFLYEJB0034: Jakarta Enterprise Beans Invocation failed on component KuchenWorkerBean for method public abstract java.util.List de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerRemote.getKuchen(): jakarta.ejb.EJBException: java.lang.IllegalArgumentException: No query is registered under the name `findAllKuchen`
at org.jboss.as.ejb3@30.0.0.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.invokeInOurTx(CMTTxInterceptor.java:251)
at org.jboss.as.ejb3@30.0.0.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.required(CMTTxInterceptor.java:373)
at org.jboss.as.ejb3@30.0.0.Final//org.jboss.as.ejb3.tx.CMTTxInterceptor.processInvocation(CMTTxInterceptor.java:143)
...
Caused by: java.lang.IllegalArgumentException: No query is registered under the name `findAllKuchen`
at org.hibernate@6.2.13.Final//org.hibernate.internal.AbstractSharedSessionContract.buildNamedQuery(AbstractSharedSessionContract.java:1023)
at org.hibernate@6.2.13.Final//org.hibernate.internal.AbstractSharedSessionContract.createNamedQuery(AbstractSharedSessionContract.java:899)
at org.hibernate@6.2.13.Final//org.hibernate.internal.SessionImpl.createNamedQuery(SessionImpl.java:190)
at org.jboss.as.jpa@30.0.0.Final//org.jboss.as.jpa.container.AbstractEntityManager.createNamedQuery(AbstractEntityManager.java:362)
at deployment.KuchenSimple.ear.KuchenSimpleEJB.jar//de.hsrm.jakartaee.knauf.kuchen.KuchenWorkerBean.getKuchen(KuchenWorkerBean.java:62)
...
Caused by: org.hibernate.query.UnknownNamedQueryException: No query is registered under the name `findAllKuchen`
at org.hibernate@6.2.13.Final//org.hibernate.internal.AbstractSharedSessionContract.buildNamedQuery(AbstractSharedSessionContract.java:1008)
at org.hibernate@6.2.13.Final//org.hibernate.internal.AbstractSharedSessionContract.buildNamedQuery(AbstractSharedSessionContract.java:1013)
... 81 more
Die NamedQuery ist aber scheinbar korrekt deklariert.