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

@OneToMany

📋 Deine Position im Kurs

TagThemaStatus
1Filter im Webcontainer✅ Abgeschlossen
2Listener im Webcontainer✅ Abgeschlossen
3Authentifizierung über Datenbank✅ Abgeschlossen
4Container-Managed Security & Jakarta Security API✅ Abgeschlossen
5Custom Tags & Tag Handler (SimpleTag)✅ Abgeschlossen
6Custom Tag Handler mit BodyTagSupport✅ Abgeschlossen
7JPA vs JDBC – Konfiguration & Provider✅ Abgeschlossen
8JPA Relationen (1): @OneToOne & @ManyToOne✅ Abgeschlossen
→ 9JPA Relationen (2): @OneToMany & @ManyToMany👉 DU BIST HIER!
10JSF Ü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?

  1. Pure JPA Servlet – Wiederholung von Tag 1-8 (Servlet-UI)
  2. JSF + Tomcat – Web Profile auf Webcontainer
  3. 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?

  1. @Named – Bean wird vom CDI-Container verwaltet
  2. @RequestScoped – Pro Request wird eine Bean-Instanz erstellt
  3. @Transactional – Methoden laufen automatisch in einer Transaktion
  4. @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:

ServletJSF + @Named Bean
HTML manuell schreibenDeklaratives UI mit .xhtml
resp.getWriter().println()<h:dataTable> Components
Viel BoilerplateWeniger Code
@EJB Injection@Inject (CDI)
Gut für APIsGut 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:

FeaturePure JPA ServletJSF TomcatJSF Payara
Web LayerServletsJSF FaceletsJSF Facelets
Service Layer@Stateless EJB@Named + @Transactional@Named + @Transactional
JPA ProviderHibernateHibernateEclipseLink
CDIBuilt-in (Payara)Weld (external)Built-in (Payara)
Transaction ManagerContainer (Payara)Atomikos JTAContainer (Payara)
ServerPayara 6.xTomcat 10.xPayara 6.x
DeploymentStandard WARWAR + DependenciesStandard WAR
ComplexityMittelHoch (viele Deps)Niedrig
2026-FaktorEJBs = schwer zu erklärenTomcat = 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:

  1. Projekt 1: Zeigt den „alten“ Weg (EJBs) – als Referenz
  2. Projekt 2: Zeigt den „modernen“ Weg für Tomcat
  3. 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:

  1. Erstelle eine Tag Entity
  2. Implementiere @ManyToMany zwischen Course und Tag
  3. Ein Course kann mehrere Tags haben (z.B. „Java“, „Web“, „Database“)
  4. Ein Tag kann zu mehreren Courses gehören
  5. Erstelle einen TagDAO mit @Named Bean
  6. 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

Frage 1 von 10

Was beschreibt eine @OneToMany-Relation in JPA?

Frage 2 von 10

Was beschreibt eine @ManyToMany-Relation in JPA?

Frage 3 von 10

Wie werden @ManyToMany-Relationen in der Datenbank abgebildet?

Frage 4 von 10

Welche Annotation definiert die Join-Tabelle bei @ManyToMany?

Frage 5 von 10

Was ist die Beziehung zwischen @OneToMany und @ManyToOne in einer bidirektionalen Relation?

Frage 6 von 10

Welcher Collection-Typ wird typischerweise für @OneToMany verwendet?

Frage 7 von 10

Was bewirkt orphanRemoval = true bei @OneToMany?

Frage 8 von 10

Wie heißen die Standard-Spalten in einer @ManyToMany Join-Tabelle?

Frage 9 von 10

Was ist ein typisches Beispiel für eine @ManyToMany-Relation?

Frage 10 von 10

Warum ist fetch = FetchType.LAZY der Default bei @OneToMany?


📚 Offizielle Jakarta EE Dokumentation

Jakarta EE Specifications


🚀 Jakarta EE 11 Release Information

Aktuelle Releases


📖 Tutorial & Getting Started Guides

Jakarta Faces Tutorials


🛠️ Implementation & Tools

Jakarta Faces Implementations

  • Mojarra (Referenz-Implementation): https://github.com/eclipse-ee4j/mojarraAnker-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


🏢 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


📊 JPA & Database

JPA Resources


🎨 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

ProjektFü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

Autor

  • Elyndra Valen

    28 Jahre alt, wurde kürzlich zur Senior Entwicklerin befördert nach 4 Jahren intensiver Java-Entwicklung. Elyndra kennt die wichtigsten Frameworks und Patterns, beginnt aber gerade erst, die tieferen Zusammenhänge und Architektur-Entscheidungen zu verstehen. Sie ist die Brücke zwischen Junior- und Senior-Welt im Team.