Java Web Aufbau – Tag 9 von 10
Von Elyndra Valen, Senior Developer bei Java Fleet Systems Consulting

📋 Deine Position im Kurs
| Tag | Thema | Status |
|---|---|---|
| 1 | Filter im Webcontainer | ✅ Abgeschlossen |
| 2 | Listener im Webcontainer | ✅ Abgeschlossen |
| 3 | Authentifizierung über Datenbank | ✅ Abgeschlossen |
| 4 | Container-Managed Security & Jakarta Security API | ✅ Abgeschlossen |
| 5 | Custom Tags & Tag Handler (SimpleTag) | ✅ Abgeschlossen |
| 6 | Custom Tag Handler mit BodyTagSupport | ✅ Abgeschlossen |
| 7 | JPA vs JDBC – Konfiguration & Provider | ✅ Abgeschlossen |
| 8 | JPA Relationen (1): @OneToOne & @ManyToOne | ✅ Abgeschlossen |
| → 9 | JPA Relationen (2): @OneToMany & @ManyToMany | 👉 DU BIST HIER! |
| 10 | JSF Überblick – Component-Based UI | 🔒 Noch nicht freigeschaltet |
Modul: Java Web Aufbau
Gesamt-Dauer: 10 Arbeitstage
Dein Ziel: Von JPA zu JSF – Moderne Webanwendungen ohne EJB-Komplexität
📋 Voraussetzungen für diesen Tag
Du brauchst:
- ✅ JDK 21 LTS installiert
- ✅ Apache NetBeans 22 (oder neuer)
- ✅ Payara Server 6.x ODER Apache Tomcat 10.x
- ✅ MySQL Datenbank läuft
- ✅ Tag 1-8 abgeschlossen (besonders Tag 7 & 8!)
- ✅ @OneToOne und @ManyToOne verstanden
- ✅ Bereitschaft für den Übergang zu JSF
Datenbank-Setup:
Falls noch nicht geschehen:
bash
docker run --name mysql-jpa -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=jpadb -p 3306:3306 -d mysql:8
Tag verpasst?
Kein Problem! Spring zurück zu Tag 8 für @OneToOne und @ManyToOne Basics.
Setup-Probleme?
Schreib uns: elyndra.valen@java-developer.online
⚡ Das Wichtigste in 30 Sekunden
Heute lernst du:
- ✅ @OneToMany für 1:N Beziehungen (User → Orders)
- ✅ @ManyToMany für M:N Beziehungen (Students ↔ Courses)
- ✅ @Named Beans statt EJBs (2026-kompatibel!)
- ✅ @Transactional für Transaction Management
- ✅ JSF Facelets als moderne UI-Alternative zu Servlets
- ✅ 3 Projekt-Varianten: Pure JPA, JSF-Tomcat, JSF-Payara
Am Ende des Tages kannst du: Komplexe Domain-Modelle mit Collections aufbauen, moderne JSF-Anwendungen ohne EJBs entwickeln und verstehen, warum der Übergang zu JSF 2026 einfacher ist als EJBs zu erklären!
Schwierigkeitsgrad: Fortgeschritten – aber du schaffst das!
👋 Willkommen zu Tag 9!
Hi! 👋
Elyndra hier. Vorletzter Tag – und heute wird’s richtig praktisch!
Jakarta EE 2025: Web Profile im Fokus
Die Jakarta EE Landschaft hat sich in den letzten Jahren stark entwickelt. Heute gibt es drei Profile:
📦 Jakarta EE Core Profile (2024)
→ Für Microservices & Cloud-Native
→ Minimal, leichtgewichtig
→ REST APIs, JSON, CDI Lite
📦 Jakarta EE Web Profile (Standard für Web-Apps!)
→ Für klassische Webanwendungen
→ Jakarta Faces (JSF), Servlets, JPA, CDI
→ Läuft auf Webcontainern (Tomcat, Jetty) UND Application Servern
📦 Jakarta EE Platform (Full Profile)
→ Für große Enterprise-Systeme (ERP, Legacy)
→ Alle Specs (Messaging, Batch, etc.)
→ Nur wenn du WIRKLICH alles brauchst
Die 2025-Realität:
✅ 90% aller Web-Anwendungen → Web Profile
✅ Webcontainer (Tomcat) → Vordergrund
✅ Jakarta Faces → Standard-UI-Framework im Web Profile
✅ @Named Beans + CDI → Moderne Dependency Injection
Full Platform Application Server? Nur für Spezialfälle (große ERPs, wenn du Messaging/Batch brauchst).
Heute lernst du den Standard-Stack für Webanwendungen 2025:
- ✅ Jakarta Faces (JSF 4.0) – Component-based UI
- ✅ @Named Beans (CDI) – Dependency Injection
- ✅ @Transactional – Transaction Management
- ✅ JPA mit Relations – Persistenz
- ✅ Web Profile – Der richtige Ansatz
Warum drei Projekt-Varianten?
- Pure JPA Servlet – Wiederholung von Tag 1-8 (Servlet-UI)
- JSF + Tomcat – Web Profile auf Webcontainer
- JSF + Payara – Web Profile auf Application Server
So verstehst du: Selber Code, verschiedene Container!
🔑 Web Profile 2025: @Named Beans + CDI
Der Jakarta EE Web Profile Standard
Jakarta EE Web Profile enthält:
✅ Jakarta Faces 4.0 (JSF) – UI Framework
✅ CDI 4.0 (Contexts & Dependency Injection)
✅ JPA 3.2 (Persistence)
✅ Jakarta Transactions 2.0 (@Transactional)
✅ Servlets, JSON, REST, Security uvm.
Was NICHT im Web Profile ist:
❌ Full EJB (nur EJB Lite)
❌ Jakarta Messaging (JMS)
❌ Jakarta Batch
Für 90% aller Webanwendungen brauchst du das auch nicht!
@Named Beans: Der CDI-Standard
Was sind @Named Beans?
CDI (Contexts and Dependency Injection) ist der Standard für Dependency Injection in Jakarta EE. @Named Beans sind CDI-Managed Beans mit einem Namen für EL-Expression Language (z.B. in JSF).
Beispiel:
java
@Named // ← CDI Managed Bean mit Name
@RequestScoped // ← Bean lebt für einen Request
@Transactional // ← Automatisches Transaction Management
public class OrderDAO {
@PersistenceContext
private EntityManager em;
public void createOrder(Order order) {
em.persist(order); // Transaction wird automatisch gemanaged!
}
}
Was passiert hier?
- @Named – Bean wird vom CDI-Container verwaltet
- @RequestScoped – Pro Request wird eine Bean-Instanz erstellt
- @Transactional – Methoden laufen automatisch in einer Transaktion
- @PersistenceContext – EntityManager wird injiziert
Warum @Named + CDI?
✅ Standard – Teil von Jakarta EE Web Profile
✅ Flexibel – Verschiedene Scopes (@RequestScoped, @SessionScoped, @ApplicationScoped)
✅ Modern – Verwendet von allen modernen Jakarta EE Apps
✅ Kompatibel – Läuft auf Webcontainern (mit Weld CDI) UND Application Servern
Vergleich: Servlet vs. JSF Controller
Tag 1-8: Servlet-Ansatz
java
@WebServlet("/orders")
public class OrderServlet extends HttpServlet {
@EJB // ← EJB Dependency Injection
private OrderService orderService;
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
List<Order> orders = orderService.findAll();
// HTML manuell generieren
resp.getWriter().println("<html><body>");
resp.getWriter().println("<h1>Orders</h1>");
for (Order o : orders) {
resp.getWriter().println("<p>" + o.getOrderNumber() + "</p>");
}
resp.getWriter().println("</body></html>");
}
}
Tag 9: JSF + @Named Bean Ansatz
java
@Named // ← CDI Managed Bean
@SessionScoped // ← Bean lebt für die Session
public class OrderController implements Serializable {
@Inject // ← CDI Dependency Injection
private OrderDAO orderDAO;
private List<Order> orders;
// JSF Action Method
public void loadOrders() {
this.orders = orderDAO.findAll();
}
// Getter für JSF-EL
public List<Order> getOrders() {
if (orders == null) {
loadOrders();
}
return orders;
}
}
JSF Facelets (index.xhtml):
xml
<h:dataTable value="#{orderController.orders}" var="order">
<h:column>
<f:facet name="header">Order#</f:facet>
#{order.orderNumber}
</h:column>
</h:dataTable>
Unterschied:
| Servlet | JSF + @Named Bean |
|---|---|
| HTML manuell schreiben | Deklaratives UI mit .xhtml |
resp.getWriter().println() | <h:dataTable> Components |
| Viel Boilerplate | Weniger Code |
| @EJB Injection | @Inject (CDI) |
| Gut für APIs | Gut für UIs |
🟢 GRUNDLAGEN: @OneToMany mit @Named Beans
Domain Model (identisch für alle 3 Projekte)
User Entity:
java
package com.javafleet.model;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String email;
// @OneToMany: Ein User hat VIELE Orders
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// @OneToOne: Ein User hat EIN Profile
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "profile_id")
private UserProfile profile;
// Constructor
public User() {}
public User(String username, String email) {
this.username = username;
this.email = email;
}
// === HELPER METHODS für bidirektionale Synchronisation ===
public void addOrder(Order order) {
orders.add(order);
order.setUser(this); // Bidirektionale Sync!
}
public void removeOrder(Order order) {
orders.remove(order);
order.setUser(null); // Bidirektionale Sync!
}
// Getters & Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public List<Order> getOrders() { return orders; }
public void setOrders(List<Order> orders) { this.orders = orders; }
public UserProfile getProfile() { return profile; }
public void setProfile(UserProfile profile) { this.profile = profile; }
}
Order Entity:
java
package com.javafleet.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_number", nullable = false, unique = true)
private String orderNumber;
@Column(name = "total_amount", precision = 10, scale = 2)
private BigDecimal totalAmount;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Enumerated(EnumType.STRING)
private OrderStatus status;
// @ManyToOne: Viele Orders gehören zu EINEM User
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
// Constructor
public Order() {}
public Order(String orderNumber, BigDecimal totalAmount) {
this.orderNumber = orderNumber;
this.totalAmount = totalAmount;
this.status = OrderStatus.PENDING;
}
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
// Getters & Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getOrderNumber() { return orderNumber; }
public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; }
public BigDecimal getTotalAmount() { return totalAmount; }
public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; }
public LocalDateTime getCreatedAt() { return createdAt; }
public OrderStatus getStatus() { return status; }
public void setStatus(OrderStatus status) { this.status = status; }
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
}
package com.javafleet.model;
public enum OrderStatus {
PENDING,
CONFIRMED,
SHIPPED,
DELIVERED,
CANCELLED
}
🟢 GRUNDLAGEN: DAO mit @Named Bean
Moderner DAO-Layer (JSF-Projekte)
OrderDAO.java (für Projekt 2 & 3):
java
package com.javafleet.dao;
import com.javafleet.model.*;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Named;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
@Named // ← CDI Managed Bean (KEIN EJB!)
@RequestScoped // ← Bean lebt für einen Request
@Transactional // ← Automatisches Transaction Management
public class OrderDAO {
@PersistenceContext
private EntityManager em;
// User mit Profile erstellen
public User createUserWithProfile(String username, String email,
String firstName, String lastName,
LocalDate dateOfBirth) {
UserProfile profile = new UserProfile(firstName, lastName);
profile.setDateOfBirth(dateOfBirth);
User user = new User(username, email);
user.setProfile(profile);
em.persist(user); // Transaction wird automatisch gemanaged!
return user;
}
// Order erstellen und User zuordnen
public Order createOrder(Long userId, String orderNumber, BigDecimal amount) {
User user = em.find(User.class, userId);
if (user == null) {
throw new IllegalArgumentException("User not found: " + userId);
}
Order order = new Order(orderNumber, amount);
user.addOrder(order); // Bidirektionale Sync!
em.persist(order);
return order;
}
// User mit allen Orders laden (JOIN FETCH)
public User findUserWithOrders(Long userId) {
return em.createQuery(
"SELECT DISTINCT u FROM User u " +
"LEFT JOIN FETCH u.orders " +
"WHERE u.id = :id",
User.class
)
.setParameter("id", userId)
.getSingleResult();
}
// Orders mit User-Info laden (JOIN FETCH)
public List<Order> findRecentOrders(int limit) {
return em.createQuery(
"SELECT o FROM Order o JOIN FETCH o.user " +
"ORDER BY o.createdAt DESC",
Order.class
)
.setMaxResults(limit)
.getResultList();
}
// Order Status aktualisieren
public void updateOrderStatus(Long orderId, OrderStatus newStatus) {
Order order = em.find(Order.class, orderId);
if (order != null) {
order.setStatus(newStatus);
// Automatisches UPDATE durch Dirty Checking!
}
}
// User löschen (mit allen Orders und Profile)
public void deleteUser(Long userId) {
User user = em.find(User.class, userId);
if (user != null) {
em.remove(user);
// Orders und Profile werden durch cascade gelöscht!
}
}
}
Unterschied zum EJB-Ansatz:
java
// ALT (Projekt 1 - mit EJB):
@Stateless // ← EJB Session Bean
public class OrderManagementService {
@PersistenceContext
private EntityManager em;
// ...
}
// NEU (Projekt 2+3 - ohne EJB):
@Named // ← CDI Managed Bean
@RequestScoped
@Transactional // ← Deklaratives Transaction Management
public class OrderDAO {
@PersistenceContext
private EntityManager em;
// ...
}
Warum besser? ✅ Einfacher zu verstehen – Keine EJB-Magie
✅ Tomcat-kompatibel – Läuft überall
✅ Modern – CDI statt proprietäre EJBs
✅ Flexibel – Scopes klar definiert
🟡 PROFESSIONALS: JSF Controller mit @Named
Der JSF Managed Bean Controller
OrderController.java (für Projekt 2 & 3):
java
package com.javafleet.controller;
import com.javafleet.dao.OrderDAO;
import com.javafleet.model.*;
import jakarta.enterprise.context.SessionScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@Named // ← Für JSF-EL: #{orderController}
@SessionScoped // ← Bean lebt für die Session
public class OrderController implements Serializable {
@Inject // ← CDI Dependency Injection
private OrderDAO orderDAO;
// Form Data
private String username;
private String email;
private String firstName;
private String lastName;
private LocalDate dateOfBirth;
private String orderNumber;
private BigDecimal orderAmount;
private List<Order> orders;
// Constructor
public OrderController() {
this.orders = new ArrayList<>();
}
// Actions für JSF
public String createTestData() {
User user = orderDAO.createUserWithProfile(
"alice_" + System.currentTimeMillis(),
"alice@example.com",
"Alice",
"Johnson",
LocalDate.of(1990, 5, 15)
);
orderDAO.createOrder(
user.getId(),
"ORD-" + System.currentTimeMillis(),
new BigDecimal("99.99")
);
refreshOrders();
return null; // Stay on same page
}
public void refreshOrders() {
this.orders = orderDAO.findRecentOrders(20);
}
// Getters & Setters für JSF-EL
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public LocalDate getDateOfBirth() { return dateOfBirth; }
public void setDateOfBirth(LocalDate dateOfBirth) { this.dateOfBirth = dateOfBirth; }
public String getOrderNumber() { return orderNumber; }
public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; }
public BigDecimal getOrderAmount() { return orderAmount; }
public void setOrderAmount(BigDecimal orderAmount) { this.orderAmount = orderAmount; }
public List<Order> getOrders() {
if (orders.isEmpty()) {
refreshOrders();
}
return orders;
}
}
🟡 PROFESSIONALS: JSF Facelets (.xhtml)
Die moderne UI mit JSF
index.xhtml (für Projekt 2 & 3):
xml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head>
<title>Java Web Aufbau Tag 9 - JSF CRUD Demo</title>
<h:outputStylesheet library="css" name="style.css"/>
</h:head>
<h:body>
<div class="container">
<header>
<h1>☕ Java Web Aufbau Tag 9</h1>
<p class="subtitle">JPA + JSF ohne EJBs</p>
<p class="subtitle">@Named Beans + @Transactional</p>
</header>
<main>
<h:form>
<h2>🚀 Test-Daten erstellen</h2>
<p>Erstelle einen User mit Profile und Order:</p>
<h:commandButton value="Test-Daten generieren"
action="#{orderController.createTestData}"
styleClass="button"/>
</h:form>
<h:form>
<h2>📋 Recent Orders</h2>
<h:dataTable value="#{orderController.orders}"
var="order"
styleClass="orders-table"
rendered="#{not empty orderController.orders}">
<h:column>
<f:facet name="header">Order#</f:facet>
#{order.orderNumber}
</h:column>
<h:column>
<f:facet name="header">User</f:facet>
#{order.user.username}
</h:column>
<h:column>
<f:facet name="header">Amount</f:facet>
€#{order.totalAmount}
</h:column>
<h:column>
<f:facet name="header">Status</f:facet>
<span class="status-#{order.status}">
#{order.status}
</span>
</h:column>
<h:column>
<f:facet name="header">Date</f:facet>
<h:outputText value="#{order.createdAt}">
<f:convertDateTime pattern="dd.MM.yyyy HH:mm"/>
</h:outputText>
</h:column>
</h:dataTable>
<h:panelGroup rendered="#{empty orderController.orders}">
<div class="info">
<p>Keine Orders vorhanden. Erstelle Test-Daten oben.</p>
</div>
</h:panelGroup>
</h:form>
<div class="info">
<h3>ℹ️ Was demonstriert diese App?</h3>
<ul>
<li><strong>@Named Beans</strong> statt EJBs – einfacher!</li>
<li><strong>@Transactional</strong> – deklaratives TX Management</li>
<li><strong>JSF Facelets</strong> – moderne UI mit .xhtml</li>
<li><strong>JPA Relations</strong> – @OneToOne, @OneToMany</li>
<li><strong>Tomcat-kompatibel</strong> – läuft überall! (Projekt 2)</li>
</ul>
</div>
</main>
<footer>
<p>© 2025 Java Fleet Systems Consulting</p>
<p>Java Web Aufbau - Tag 9 von 10</p>
</footer>
</div>
</h:body>
</html>
🟡 PROFESSIONALS: Die 3 Projekt-Unterschiede
Vergleichstabelle:
| Feature | Pure JPA Servlet | JSF Tomcat | JSF Payara |
|---|---|---|---|
| Web Layer | Servlets | JSF Facelets | JSF Facelets |
| Service Layer | @Stateless EJB | @Named + @Transactional | @Named + @Transactional |
| JPA Provider | Hibernate | Hibernate | EclipseLink |
| CDI | Built-in (Payara) | Weld (external) | Built-in (Payara) |
| Transaction Manager | Container (Payara) | Atomikos JTA | Container (Payara) |
| Server | Payara 6.x | Tomcat 10.x | Payara 6.x |
| Deployment | Standard WAR | WAR + Dependencies | Standard WAR |
| Complexity | Mittel | Hoch (viele Deps) | Niedrig |
| 2026-Faktor | EJBs = schwer zu erklären | Tomcat = läuft überall! | EclipseLink = Referenz |
🟢 GRUNDLAGEN: @ManyToMany mit Collections
Was ist eine @ManyToMany Beziehung?
Definition: Viele Entities sind mit vielen anderen Entities verbunden.
Beispiele aus der Praxis:
- Students ↔ Courses (ein Student belegt VIELE Kurse, ein Kurs hat VIELE Studenten)
- Products ↔ Tags (ein Produkt hat VIELE Tags, ein Tag hat VIELE Produkte)
- Users ↔ Roles (ein User hat VIELE Rollen, eine Rolle hat VIELE User)
@ManyToMany braucht eine Join Table!

Das Problem: In relationalen Datenbanken kann eine Spalte nur EINEN Wert haben.
Die Lösung: Join Table (Zwischentabelle)
sql
-- students Tabelle
CREATE TABLE students (
id BIGINT PRIMARY KEY,
name VARCHAR(255)
);
-- courses Tabelle
CREATE TABLE courses (
id BIGINT PRIMARY KEY,
title VARCHAR(255)
);
-- JOIN TABLE: student_courses
CREATE TABLE student_courses (
student_id BIGINT,
course_id BIGINT,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);
Student & Course Entities
Student Entity:
java
package com.javafleet.model;
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false, unique = true)
private String email;
// @ManyToMany: Ein Student hat VIELE Courses
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(
name = "student_courses",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
public Student() {}
public Student(String name, String email) {
this.name = name;
this.email = email;
}
// Helper Methods
public void enrollInCourse(Course course) {
courses.add(course);
course.getStudents().add(this);
}
public void dropCourse(Course course) {
courses.remove(course);
course.getStudents().remove(this);
}
// Getters & Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Set<Course> getCourses() { return courses; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return id != null && id.equals(student.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
Course Entity:
java
package com.javafleet.model;
import jakarta.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "courses")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
@Column(length = 1000)
private String description;
private int credits;
// @ManyToMany: Ein Course hat VIELE Students (inverse Seite)
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
public Course() {}
public Course(String title, int credits) {
this.title = title;
this.credits = credits;
}
// Getters & Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public int getCredits() { return credits; }
public void setCredits(int credits) { this.credits = credits; }
public Set<Student> getStudents() { return students; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Course)) return false;
Course course = (Course) o;
return id != null && id.equals(course.id);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
🔵 BONUS: Pädagogischer Wert der 3 Projekte
Für Teilnehmer:
Nach Projekt 1 (Pure JPA Servlet):
- „Okay, das kenne ich – Servlets + EJBs“
- Problem: „Was ist eine EJB nochmal? Warum @Stateless?“
Nach Projekt 2 (JSF Tomcat):
- „Wow, @Named Bean ist viel einfacher!“
- „Das läuft auf Tomcat – cool!“
- „JSF ist gar nicht so schwer“
- „Keine EJBs erklären müssen – Erleichterung!“
Nach Projekt 3 (JSF Payara):
- „Identischer Code, anderer Server“
- „EclipseLink vs Hibernate – nur Config-Unterschied“
- „Payara macht vieles automatisch“
💡 Real Talk: Jakarta EE Web Profile 2025
Von Elyndra:
Honest talk: Vor 10 Jahren war Jakarta EE (damals Java EE) = Full Platform Application Server. Riesig, schwer, viele Features die niemand brauchte.
Was sich geändert hat:
2014-2020: Java EE / Jakarta EE
→ Full Platform war Standard
→ Application Server (GlassFish, WildFly) = normal
→ EJBs überall
→ Viele Features, die kaum jemand nutzte
2020-2025: Jakarta EE Evolution
→ Web Profile wurde populär (für Web-Apps)
→ Core Profile kam hinzu (für Microservices)
→ Webcontainer (Tomcat) + CDI = gängig
→ Fokus auf das was man wirklich braucht
Das Web Profile IST der Standard 2025:
✅ Jakarta Faces – Standard-UI-Framework
✅ CDI + @Named – Standard Dependency Injection
✅ @Transactional – Standard Transaction Management
✅ Läuft auf Tomcat – Nicht nur auf Application Servern!
Wann brauchst du Full Platform?
- Große ERP-Systeme
- Wenn du Jakarta Messaging (JMS) brauchst
- Wenn du Jakarta Batch brauchst
- Legacy-Migration
Für normale Web-Anwendungen? Web Profile reicht!
Bottom line: Web Profile ist 2025 der richtige Ansatz. Full Platform ist Overkill für 90% der Use-Cases.
❓ Häufig gestellte Fragen
Frage 1: Ist @Named + CDI wirklich Standard in Jakarta EE?
Ja! CDI ist Kern-Bestandteil des Web Profile.
Die Fakten:
- Jakarta EE Web Profile 11 (März 2025 released) enthält CDI 4.0
- Jakarta Faces 4.0 nutzt CDI für Managed Beans
- @Named ist der Standard-Weg für JSF Backing Beans
- EJBs? Nur EJB Lite ist im Web Profile (für spezielle Use-Cases)
In der Praxis:
- Moderne Jakarta EE Apps nutzen CDI + @Named
- EJBs sind für spezielle Enterprise-Features (Messaging, Timer)
- Web-Apps → CDI
- Full Platform → CDI + EJBs (wenn nötig)
Frage 2: Läuft das wirklich auf Tomcat?
Ja! Projekt 2 ist speziell für Tomcat:
- Weld CDI für Dependency Injection
- Atomikos für JTA Transactions
- Hibernate für JPA
- Alle Dependencies im WAR
Vorteil: Kein vollwertiger Application Server nötig!
Frage 3: Warum 3 Projekte statt nur eins?
Pädagogischer Wert:
- Projekt 1: Zeigt den „alten“ Weg (EJBs) – als Referenz
- Projekt 2: Zeigt den „modernen“ Weg für Tomcat
- Projekt 3: Zeigt den „Enterprise“ Weg für Payara
Du lernst: Unterschiede, Vor-/Nachteile, wann was sinnvoll ist.
Frage 4: Was ist besser – Hibernate oder EclipseLink?
Kommt drauf an:
Hibernate (Projekt 2):
- ✅ Populärer, mehr Community-Support
- ✅ Läuft auf Tomcat out-of-the-box
- ❌ Nicht die Referenz-Implementation
EclipseLink (Projekt 3):
- ✅ Jakarta EE Referenz-Implementation
- ✅ Teil von Payara/GlassFish
- ❌ Weniger Community-Tutorials
Bottom line: Beide sind gut. Wähle basierend auf Server und Team-Erfahrung.
Frage 5: Ist JSF nicht veraltet? Was ist mit React/Vue?
Real talk:
JSF 2026:
- ✅ Teil von Jakarta EE – aktiv entwickelt
- ✅ Server-seitiges Rendering – einfacher für Backends
- ✅ Component-basiert – ähnlich wie React
- ✅ Direkte Java-Integration – keine API nötig
React/Vue:
- ✅ Client-seitig – bessere UX
- ❌ Braucht REST API – mehr Aufwand
- ❌ Zwei getrennte Codebases (Frontend/Backend)
Wann JSF: Enterprise-Anwendungen, Backend-fokussiert, Team kennt Java
Wann React: Consumer-Apps, Mobile-First, separate Frontend-Teams
Für diesen Kurs: JSF ist perfekt, um den Übergang von Servlets zu modernen UIs zu zeigen!
Frage 6: Bernd fragte: „Warum so kompliziert? Kann ich nicht einfach Servlets nutzen?“
Lowkey, Bernd hat einen Punkt! 😄
Servlets (Projekt 1):
- ✅ Einfach, direkt, klar
- ❌ Viel Boilerplate für HTML
- ❌ Keine Component-Reuse
JSF (Projekt 2+3):
- ✅ Component-basiert – wiederverwendbar
- ✅ Weniger Boilerplate
- ✅ Data Binding automatisch
- ❌ Lernkurve am Anfang
Real talk: Für kleine Apps sind Servlets okay. Für Enterprise-Apps mit komplexen UIs ist JSF besser.
Frage 7: Ist JSF wirklich die Standard-Webtechnologie im Web Profile?
Ja! Jakarta Faces ist Teil des Jakarta EE Web Profile.
Die Fakten:
- Jakarta EE Web Profile enthält Jakarta Faces als Standard-UI-Framework
- Jakarta Faces 4.0 (seit Jakarta EE 10) ist moderne, component-based UI
- Mojarra (Referenz-Implementation) ist in GlassFish/Payara integriert
- Apache MyFaces ist alternative Implementation
Was bedeutet das?
✅ Jeder Web Profile-Server MUSS Jakarta Faces unterstützen
✅ JSF ist nicht „veraltet“ – es ist aktiver Standard
✅ Jakarta Faces 4.0 ist modern (CDI, Lambda Support, Extensionless Mapping)
Alternativen:
- REST + React/Vue – Für SPAs (Single Page Applications)
- Servlets + Templates – Für einfache Seiten
- Jakarta MVC – Alternative MVC-Framework (nicht im Web Profile!)
Wann JSF?
✅ Server-Side Rendering
✅ Component-based UI (wie React, aber Java)
✅ Rapid Development
✅ Enterprise-Anwendungen mit komplexen Forms
Wann NICHT JSF?
❌ Reine APIs (nutze REST)
❌ Single Page Apps (nutze React/Vue + REST Backend)
❌ Einfachste Seiten (Servlets reichen)
Aber: Morgen (Tag 10) zeige ich dir PrimeFaces – dann siehst du, warum JSF sich lohnt! 🔥
📝 Mini-Challenge
Aufgabe: Erweitere das Student-Course-System mit Tags!
Requirements:
- Erstelle eine
TagEntity - Implementiere @ManyToMany zwischen
CourseundTag - Ein Course kann mehrere Tags haben (z.B. „Java“, „Web“, „Database“)
- Ein Tag kann zu mehreren Courses gehören
- Erstelle einen
TagDAOmit @Named Bean - Implementiere:
addTagToCourse(Long courseId, String tagName)findCoursesByTag(String tagName)removeTagFromCourse(Long courseId, Long tagId)
Bonus:
- Verhindere doppelte Tags (gleicher Name)
- Erstelle einen JSF Controller für Tag-Management
- Baue eine .xhtml Seite zum Verwalten von Tags
Lösung: Findest du am Anfang von Tag 10! 🚀
Jakarta Web Aufbau - Tag 9
Tag 9 Quiz
📚 Offizielle Jakarta EE Dokumentation
Jakarta EE Specifications
- Jakarta EE Web Profile: https://jakarta.ee/specifications/webprofile/ → Anker-Text: „Jakarta EE Web Profile Specification“ → Kontext: Web Profile Sektion
- Jakarta EE Core Profile: https://jakarta.ee/specifications/coreprofile/ → Anker-Text: „Jakarta EE Core Profile“ → Kontext: Profil-Übersicht
- Jakarta Faces (JSF): https://jakarta.ee/specifications/faces/ → Anker-Text: „Jakarta Faces Specification“ → Kontext: JSF Einführung
- Jakarta Persistence (JPA): https://jakarta.ee/specifications/persistence/ → Anker-Text: „Jakarta Persistence API Specification“ → Kontext: JPA Relations Sektion
- Jakarta CDI: https://jakarta.ee/specifications/cdi/ → Anker-Text: „Jakarta Contexts and Dependency Injection“ → Kontext: @Named Beans Sektion
🚀 Jakarta EE 11 Release Information
Aktuelle Releases
- Jakarta EE 11 Web Profile Release (März 2025): https://foojay.io/today/jakarta-ee-11-web-profile-released-enabled-by-eclipse-glassfish/ → Anker-Text: „Jakarta EE 11 Web Profile wurde im März 2025 veröffentlicht“ → Kontext: Willkommens-Sektion / Web Profile Erklärung
- Jakarta EE 11 Core Profile Release (Dezember 2024): https://www.infoq.com/news/2025/01/jakarta-ee-11-core-profile/ → Anker-Text: „Jakarta EE 11 Core Profile (Dezember 2024)“ → Kontext: Profil-Übersicht
- Jakarta EE 11 Platform Overview: https://www.infoq.com/news/2025/07/jakarta-ee-11-updates/ → Anker-Text: „Jakarta EE 11 Platform mit 16 aktualisierten Specifications“ → Kontext: FAQ oder Einleitung
📖 Tutorial & Getting Started Guides
Jakarta Faces Tutorials
- Getting Started with Jakarta Faces (Payara): https://blog.payara.fish/getting-started-with-jakarta-ee-9-jakarta-faces-jsf → Anker-Text: „Jakarta Faces Getting Started Guide“ → Kontext: JSF Einführung oder „Weiterführende Ressourcen“
- Jakarta Faces 4.0 by Examples: https://itnext.io/an-introduction-to-jakarta-faces-4-0-by-examples-d949a7093236 → Anker-Text: „Jakarta Faces 4.0 Tutorial mit Code-Beispielen“ → Kontext: JSF Facelets Sektion
- Jakarta Faces 4.0 Features: https://blog.payara.fish/a-quick-look-at-faces-jsf-4.0-in-jakarta-ee-10 → Anker-Text: „Neue Features in Jakarta Faces 4.0“ → Kontext: Bonus-Sektion
🛠️ Implementation & Tools
Jakarta Faces Implementations
- Mojarra (Referenz-Implementation): https://github.com/eclipse-ee4j/mojarra → Anker-Text: „Mojarra – Jakarta Faces Reference Implementation“ → Kontext: Setup / Server-Konfiguration
- Apache MyFaces: https://myfaces.apache.org/ → Anker-Text: „Apache MyFaces – Alternative Jakarta Faces Implementation“ → Kontext: FAQ „Welche JSF Implementation?“
CDI Implementations
- Weld (CDI für Tomcat): https://weld.cdi-spec.org/ → Anker-Text: „Weld – CDI Implementation für Tomcat“ → Kontext: Projekt 2 (JSF-Tomcat) Beschreibung
🎓 Learning Resources
Jakarta EE Learning
- Eclipse Starter for Jakarta EE: https://start.jakarta.ee/ → Anker-Text: „Jakarta EE Project Starter“ → Kontext: Setup-Anleitung oder „Quick Start“
- Why JSF for Jakarta EE Starter: https://foojay.io/today/why-did-we-choose-jakarta-faces-for-the-ui-of-the-eclipse-starter-for-jakarta-ee/ → Anker-Text: „Warum Jakarta Faces für Enterprise-Projekte?“ → Kontext: FAQ „Ist JSF veraltet?“
🏢 Server & Container Documentation
Application Server
- Payara Server Documentation: https://docs.payara.fish/ → Anker-Text: „Payara Server Dokumentation“ → Kontext: Projekt 3 Setup
- Eclipse GlassFish: https://glassfish.org/ → Anker-Text: „Eclipse GlassFish“ → Kontext: Alternative Server
Webcontainer
- Apache Tomcat 10.x: https://tomcat.apache.org/tomcat-10.1-doc/ → Anker-Text: „Apache Tomcat 10 Documentation“ → Kontext: Projekt 2 Setup
📊 JPA & Database
JPA Resources
- Hibernate ORM: https://hibernate.org/orm/ → Anker-Text: „Hibernate ORM – JPA Implementation“ → Kontext: Projekt 2 (Hibernate)
- EclipseLink: https://eclipse.dev/eclipselink/ → Anker-Text: „EclipseLink – Jakarta EE Referenz-Implementation für JPA“ → Kontext: Projekt 3 (EclipseLink)
- JPA Best Practices (Vlad Mihalcea): https://vladmihalcea.com/tutorials/hibernate/ → Anker-Text: „JPA Performance Best Practices“ → Kontext: Performance Bonus-Sektion
🎨 UI Frameworks & Extensions
PrimeFaces (Ausblick Tag 10)
- PrimeFaces Official: https://www.primefaces.org/ → Anker-Text: „PrimeFaces – Rich UI Components für Jakarta Faces“ → Kontext: „Ausblick Tag 10“ / Footer
- PrimeFaces Showcase: https://www.primefaces.org/showcase/ → Anker-Text: „PrimeFaces Component Showcase“ → Kontext: „Was kommt in Tag 10?“
🎉 Tag 9 geschafft!
Slay! Du hast es geschafft! 🚀
Das hast du heute gerockt:
- ✅ @OneToMany & @ManyToMany Collection-Relationen
- ✅ @Named Beans statt EJBs (einfacher!)
- ✅ @Transactional für TX Management
- ✅ JSF Facelets als moderne UI
- ✅ 3 Projekt-Varianten verstanden
- ✅ Tomcat-Kompatibilität erreicht
Von EJB-Komplexität zu modernen @Named Beans!
Main Character Energy: Unlocked! ✨
🚀 Wie geht’s weiter?
Morgen (Tag 10): JSF Überblick + PrimeFaces
Was dich erwartet:
- PrimeFaces Components (DataTable, Charts, Dialogs)
- Ajax-Support in JSF
- Mobile-Responsive Layouts
- Das große Finale mit schöner UI! 🎉
Tipp: Alle 3 Projekte werden morgen mit PrimeFaces erweitert!
📚 Downloads
| Projekt | Für wen? | Download |
|---|---|---|
| Tag9-Pure-JPA-Servlet.zip | 🟢 Referenz (mit EJBs) | ⬇️ Download |
| Tag9-JSF-Tomcat.zip | 🔶 Tomcat-Version | ⬇️ Download |
| Tag9-JSF-Payara.zip | 🟣 Payara-Version | ⬇️ Download |
💬 Feedback?
War Tag 9 hilfreich? Sind @Named Beans klarer als EJBs?
Schreib uns: elyndra.valen@java-developer.online
Bis morgen – zum großen Finale mit PrimeFaces! 👋
Elyndra
Senior Developer bei Java Fleet Systems Consulting
Java Web Aufbau – Tag 9 von 10
© 2025 Java Fleet Systems Consulting

