Enterprise Computing · Week 3 Lab

JPA Inheritance & Cascading

Two hands-on projects to cement this week's concepts
Project 1 JPA Inheritance Mapping
Item → Book & CD (Joined Subclass)
Project 2 Event Cascading
Customer ↔ Address with cascade

Before You Start

Setup & Notes

📋 What You'll Build

Project 1 — Inheritance Mapping

Map an Item/Book/CD class hierarchy to MySQL using the Joined-Subclass strategy, then persist sample data.

Project 2 — Event Cascading

Map Customer and Address with cascading persist/remove. Test persistence context membership.

💡 Tip: Complete sample projects are on Moodle. Try building your own version first — you'll learn more from the struggle!

✅ Pre-flight Checklist

  • NetBeans IDE is open
  • MySQL Server is running (check Services panel)
  • You can connect to MySQL via NetBeans
  • Your target database exists (e.g., week3db)
  • You reviewed the Week 3 lecture slides
  • You know how to create a JPA project (Week 1 Lab Part 2)
🆘 If you can't create a JPA project, refer to Week 1 Lab – Part 2 before continuing.
Before The Labs

Installation Guide

Set up NetBeans IDE, JDK 21, MySQL Server, and the required Maven dependencies before attempting the projects.

JDK 21
🛠️NetBeans 21+
🐬MySQL 8
📦Maven deps

Step 1 — Install JDK 21

Java Development Kit

Download & Install

1

Go to the download page

Visit adoptium.net (Eclipse Temurin — free & open source)
or oracle.com/java/technologies/downloads

2

Choose JDK 21 (LTS)

Select your OS: Windows x64 Installer (.msi) or macOS .pkg or Linux .deb/.rpm

3

Run the installer

Accept all defaults. The installer sets JAVA_HOME automatically on Windows.

4

Verify installation

Open a terminal / command prompt and run:

java -version
// Expected: openjdk version "21.x.x" ...
javac -version
// Expected: javac 21.x.x

⚠️ Already have Java installed?

Check your version first. Jakarta EE 10 requires JDK 11 minimum, but JDK 21 is recommended. Older versions (JDK 8) will not work.

🪟 Windows — Set JAVA_HOME manually if needed

  1. Search for Environment Variables in Start menu
  2. Under System Variables → New:
    Name: JAVA_HOME
    Value: C:\Program Files\Eclipse Adoptium\jdk-21
  3. Edit Path → Add: %JAVA_HOME%\bin
  4. Restart your terminal and re-run java -version
✅ You're ready for NetBeans installation once java -version shows 21.x.x

Step 2 — Install Apache NetBeans

IDE Setup

Download & Install

1

Go to netbeans.apache.org

Click Download → choose NetBeans IDE 21 (or latest). Download the installer for your OS.

2

Run the installer

Accept the licence. The installer will detect your JDK automatically. If not, point it to your JDK 21 folder.

3

First launch — activate Java SE plugin

Tools → Plugins → Installed → confirm Java SE and Java Web and EE are active.

4

Verify Maven is built in

NetBeans ships with Maven. Check: Tools → Options → Java → Maven — version should appear.

Configure the JDK in NetBeans

Tools → Java Platforms

  1. Click Add Platform…
  2. Select Java Standard Edition → Next
  3. Browse to your JDK 21 folder
    (e.g., C:\Program Files\Eclipse Adoptium\jdk-21)
  4. Click Finish. JDK 21 should now appear in the list.

⚠️ NetBeans won't start?

  • Edit netbeans.conf (in NetBeans/etc folder)
  • Set: netbeans_jdkhome="C:\...\jdk-21"

🛠️ Essential plugins to enable

  • Java SE — core Java support
  • Java Web and EE — Jakarta EE support
  • Maven — dependency management (usually bundled)
  • Database — Services panel for MySQL browsing

Step 3 — Install MySQL

Database Setup

Download & Install MySQL

1

Download MySQL Installer

Go to dev.mysql.com/downloads/installer
Choose MySQL Installer for Windows (Community) — the full ~450 MB package.

2

Run the installer — Developer Default

Choose Developer Default setup type. This installs MySQL Server 8, MySQL Workbench, and MySQL Shell.

3

Configure the server

Keep port 3306 (default). Set a root password — write it down, you'll need it in persistence.xml.

4

Start MySQL Server

Windows: MySQL is installed as a Windows Service and starts automatically. Check via Services app or MySQL Installer → Status.

Create the Lab Database

Open MySQL Workbench (installed with MySQL), connect to localhost:3306, and run:

CREATE DATABASE week3db;
CREATE DATABASE week3db2;
-- Verify:
SHOW DATABASES;

Connect NetBeans to MySQL

  1. NetBeans → Window → Services (Ctrl+5)
  2. Right-click Databases → New Connection
  3. Driver: MySQL (Connector/J)
    Host: localhost   Port: 3306
  4. Database: week3db   User: root
  5. Enter your root password → Test Connection ✅
🍎 macOS users: Download MySQL Community Server from dev.mysql.com or install via Homebrew: brew install mysql then brew services start mysql

Step 4 — Add MySQL Connector/J to NetBeans

Driver Registration

NetBeans needs the MySQL JDBC driver to browse your database in the Services panel. Your Maven project downloads it automatically, but the IDE itself also needs it registered.

Register the driver in NetBeans

1

Download Connector/J JAR

Go to dev.mysql.com/downloads/connector/j
Select Platform Independent → download the ZIP. Extract it — you need the .jar file inside (e.g., mysql-connector-j-8.3.0.jar).

2

Open NetBeans driver manager

Tools → Drivers → find MySQL (Connector/J driver) in the list → click Customize…

3

Add the JAR

Click Add… → browse to the .jar file you extracted → OK. The driver class com.mysql.cj.jdbc.Driver should auto-populate.

4

Test it

Services panel → Databases → right-click your connection → Connect. A green check means it's working.

💡 Maven project vs IDE driver

These are separate things:

  • The Maven pom.xml dependency puts the connector on your application's classpath so your code can connect to MySQL
  • The NetBeans driver registration lets the IDE's Services panel browse and query MySQL visually
You need both.

Confirm everything works

  • ☑ MySQL service is running (green in Workbench)
  • ☑ NetBeans Services shows a connected MySQL connection
  • ☑ You can see week3db listed under Tables
  • java -version shows 21.x.x in terminal
  • ☑ NetBeans shows JDK 21 in Tools → Java Platforms
✅ All five items checked? You're fully set up and ready to start the lab projects!

Installation Troubleshooting

Common Problems
SymptomLikely CauseFix
java not recognised in terminal JDK not on PATH Add %JAVA_HOME%\bin to System PATH; restart terminal
NetBeans won't launch / blank screen Wrong JDK pointed at NetBeans Edit netbeans.conf → set netbeans_jdkhome to JDK 21 path
MySQL Workbench can't connect MySQL service not running Windows: open Services app → start MySQL80 service. Mac: brew services start mysql
Access denied for user 'root' Wrong password Re-run MySQL Installer → Reconfigure → reset root password
NetBeans DB connection fails with No suitable driver Connector/J JAR not registered in IDE Tools → Drivers → MySQL Connector/J → Add the .jar file (see Step 4)
Maven dependencies not downloading No internet / proxy blocking Check network; try on uni WiFi; right-click project → Download Dependencies
Port 3306 conflict Another MySQL instance already running Open Task Manager → end any existing mysqld processes, then start fresh
Plugin not activated in NetBeans Java Web/EE plugin disabled Tools → Plugins → Installed → find Java Web and EE → tick Activate
💬 Still stuck? Post a screenshot of your error on the unit discussion board and include: your OS, Java version (java -version output), and the full error message from NetBeans Output panel.
Project 1 of 2

JPA Inheritance Mapping

Map an Item/Book/CD class hierarchy to MySQL using the
Joined-Subclass strategy, then persist a Book and CD entity.

Project 1 — Domain Model

What you're mapping

Class Hierarchy

Item id: Long title: String price, description extends extends Book isbn, publisher nbOfPage, illustrations CD musicCompany, gender numberOfCDs, duration

Database Target (Joined-Subclass)

Each entity maps to its own table. JPA joins them on primary key to rebuild the full object.

ITEM table

ID (PK), DTYPE, TITLE, PRICE, DESCRIPTION

BOOK table

ID (FK→ITEM), ISBN, PUBLISHER, NB_OF_PAGE, ILLUSTRATIONS

CD table

ID (FK→ITEM), MUSIC_COMPANY, NUMBER_OF_CDS, TOTAL_DURATION, GENDER
🎯 Goal: Run the main class and see 3 tables created with correctly linked rows in MySQL.

Project 1 — Create the JPA Project

Step 1

In NetBeans

1

File → New Project

Choose Java with Maven → Java Application. Click Next.

2

Name the project

e.g., Week3_Project1. Set Group ID to au.edu.youruni. Click Finish.

3

Add JPA dependencies (pom.xml)

Open pom.xml and add EclipseLink + MySQL Connector dependencies.

4

Create META-INF folder

Under src/main/resources, create a folder named META-INF.

5

Add persistence.xml

Right-click META-INF → New → XML Document → name it persistence.

pom.xml dependencies

<!-- EclipseLink JPA -->
<dependency>
  <groupId>org.eclipse.persistence
  </groupId>
  <artifactId>eclipselink</artifactId>
  <version>4.0.2</version>
</dependency>

<!-- MySQL Connector -->
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <version>8.3.0</version>
</dependency>
After editing pom.xml, right-click the project → Download Dependencies (or Run build to trigger download).

Project 1 — Item Entity

Step 2

Add Item.java inside your existing package au.edu.cqu.week3.

package au.edu.cqu.week3;

import jakarta.persistence.*;

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Item {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  protected Long id;
  protected String title;
  protected Float price;
  protected String description;

  public Item() {}

  // Getters
  public Long   getId()          { return id; }
  public String getTitle()       { return title; }
  public Float  getPrice()       { return price; }
  public String getDescription() { return description; }

  // Setters
  public void setId(Long id)                  { this.id = id; }
  public void setTitle(String title)           { this.title = title; }
  public void setPrice(Float price)            { this.price = price; }
  public void setDescription(String desc)      { this.description = desc; }
}

🔑 Key annotations on Item

  • @Entity — marks this as a JPA entity
  • @Inheritance(strategy = InheritanceType.JOINED) — joined-subclass strategy for the whole hierarchy
  • @Id — marks the primary key field
  • @GeneratedValue — auto-generate ID values
⚠️ Use protected (not private) so that subclasses like Book and CD can inherit these fields directly.

💡 NetBeans shortcut for getters & setters

Click inside the class body → press Alt + Insert → choose Getter and Setter → tick all fields → Generate. NetBeans writes them all for you automatically!

Project 1 — Book Entity

Step 3a

Book.java

package au.edu.cqu.week3;

import jakarta.persistence.*;

@Entity
public class Book extends Item {

  private String  isbn;
  private String  publisher;
  private Integer nbOfPage;
  private Boolean illustrations;

  public Book() {}

  // Getters
  public String  getIsbn()           { return isbn; }
  public String  getPublisher()       { return publisher; }
  public Integer getNbOfPage()        { return nbOfPage; }
  public Boolean getIllustrations()   { return illustrations; }

  // Setters
  public void setIsbn(String isbn)              { this.isbn = isbn; }
  public void setPublisher(String publisher)    { this.publisher = publisher; }
  public void setNbOfPage(Integer nbOfPage)     { this.nbOfPage = nbOfPage; }
  public void setIllustrations(Boolean illus)   { this.illustrations = illus; }
}
No @Id or @Inheritance needed here — Book inherits those from Item automatically. Only add @Entity and Book's own fields.

Package must match exactly

All three classes — Item, Book, CD — must be in the same package: au.edu.cqu.week3. This allows Book to see and extend Item without any import.

💡 NetBeans shortcut

Click inside the class → Alt + InsertGetter and Setter → tick all fields → Generate. Saves lots of typing!

Project 1 — CD Entity

Step 3b

CD.java

package au.edu.cqu.week3;

import jakarta.persistence.*;

@Entity
public class CD extends Item {

  private String  musicCompany;
  private Integer numberOfCDs;
  private Float   totalDuration;
  private String  gender;

  public CD() {}

  // Getters
  public String  getMusicCompany()   { return musicCompany; }
  public Integer getNumberOfCDs()    { return numberOfCDs; }
  public Float   getTotalDuration()  { return totalDuration; }
  public String  getGender()         { return gender; }

  // Setters
  public void setMusicCompany(String mc)       { this.musicCompany = mc; }
  public void setNumberOfCDs(Integer n)        { this.numberOfCDs = n; }
  public void setTotalDuration(Float td)       { this.totalDuration = td; }
  public void setGender(String gender)          { this.gender = gender; }
}

What JPA creates automatically

  • ITEM table — ID, DTYPE, TITLE, PRICE, DESCRIPTION
  • BOOK table — ID (FK→ITEM), ISBN, PUBLISHER, NB_OF_PAGE, ILLUSTRATIONS
  • CD table — ID (FK→ITEM), MUSIC_COMPANY, NUMBER_OF_CDS, TOTAL_DURATION, GENDER
All three entity files should now look like:
  • Line 1: package au.edu.cqu.week3;
  • Line 3: import jakarta.persistence.*;
  • Fields declared
  • Public no-arg constructor
  • Getters and setters for every field
⚠️ Missing a getter or setter causes cannot find symbol errors in Main.java when you try to call setTitle(), setIsbn() etc.

Project 1 — persistence.xml

Step 4

Create src/main/resources/META-INF/persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="3.0"
  xmlns="https://jakarta.ee/xml/ns/persistence">

  <persistence-unit name="Week3PU"
    transaction-type="RESOURCE_LOCAL">

    <class>au.edu.cqu.week3.Item</class>
    <class>au.edu.cqu.week3.Book</class>
    <class>au.edu.cqu.week3.CD</class>

    <properties>
      <property name="jakarta.persistence.jdbc.driver"
        value="com.mysql.cj.jdbc.Driver"/>
      <property name="jakarta.persistence.jdbc.url"
        value="jdbc:mysql://localhost:3306/week3db"/>
      <property name="jakarta.persistence.jdbc.user"
        value="root"/>
      <property name="jakarta.persistence.jdbc.password"
        value="password"/>
      <property name="eclipselink.ddl-generation"
        value="create-tables"/>
      <property name="eclipselink.logging.level"
        value="FINE"/>
    </properties>

  </persistence-unit>
</persistence>

🔧 Customise these values

  • jdbc.url — change week3db to your DB name
  • jdbc.user — your MySQL username
  • jdbc.password — your MySQL password
  • ddl-generationcreate-tables creates tables if they don't exist
⚠️ Make sure the database already exists in MySQL before running. JPA can create tables but not the database itself. Run:

CREATE DATABASE week3db;
💡 eclipselink.logging.level=FINE prints the generated SQL to the console — great for debugging!

Project 1 — Main Class

Step 5

Create Main.java inside the same package au.edu.cqu.week3:

package au.edu.cqu.week3;

import jakarta.persistence.*;

public class Main {
  public static void main(String[] args) {

    EntityManagerFactory emf =
      Persistence.createEntityManagerFactory(
        "Week3PU"
      );
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    tx.begin();

    // Create a Book
    Book book = new Book();
    book.setTitle("Clean Code");
    book.setPrice(49.99f);
    book.setIsbn("978-0132350884");
    book.setPublisher("Prentice Hall");
    book.setNbOfPage(464);
    em.persist(book);

    // Create a CD
    CD cd = new CD();
    cd.setTitle("Kind of Blue");
    cd.setPrice(12.99f);
    cd.setMusicCompany("Columbia");
    cd.setNumberOfCDs(1);
    cd.setTotalDuration(46.4f);
    em.persist(cd);

    tx.commit();
    System.out.println("✅ Done!");

    em.close();
    emf.close();
  }
}

📌 Match the PU name

The string "Week3PU" must exactly match the name attribute in your persistence.xml.

Expected Console Output

[EclipseLink] Creating tables...
INSERT INTO ITEM (ID, DTYPE, TITLE, ...)
INSERT INTO BOOK (ID, ISBN, ...)
INSERT INTO ITEM (ID, DTYPE, TITLE, ...)
INSERT INTO CD (ID, MUSIC_COMPANY, ...)
✅ Done!
✅ If you see INSERT statements and "Done!" — the project works! Go verify in MySQL Workbench or the NetBeans Services panel.

Project 1 — Verify in MySQL

Step 6 — Verify

Using NetBeans Services Panel

1

Open Services tab

Window → Services (or Ctrl+5)

2

Expand Databases → your connection

Connect if not already. Expand your week3db database.

3

Execute SQL queries

Right-click the connection → Execute Command, then run the queries on the right.

Expected Tables

ITEM

2 rows
(Book + CD)

BOOK

1 row
(Clean Code)

CD

1 row
(Kind of Blue)

Verification SQL

-- Check all tables exist
SHOW TABLES FROM week3db;

-- Check ITEM table (root)
SELECT * FROM ITEM;

-- Should show:
-- ID | DTYPE | TITLE      | PRICE
-- 1  | Book  | Clean Code | 49.99
-- 2  | CD    | Kind of... | 12.99

-- Check subclass tables
SELECT * FROM BOOK;
SELECT * FROM CD;

-- Verify the JOIN works
SELECT i.title, b.isbn
FROM ITEM i
JOIN BOOK b ON i.ID = b.ID;
🐛 No tables created? Check that:
  • The database exists in MySQL
  • Connection URL is correct
  • All 3 entities are listed in persistence.xml
  • No compilation errors in the entity classes

Project 1 — Troubleshooting

Common Issues
Error / SymptomLikely CauseFix
Unknown database 'week3db' Database doesn't exist Run CREATE DATABASE week3db; in MySQL first
Communications link failure MySQL not running or wrong port Start MySQL service; check URL uses correct port (usually 3306)
No persistence provider for "Week3PU" EclipseLink not on classpath, or PU name mismatch Check pom.xml has EclipseLink; name in code must match persistence.xml exactly
Tables created but no data Transaction not committed Ensure tx.commit() is called and no exception swallowed it
Entity not mapped for Book or CD Missing <class> tag in persistence.xml Add <class>au.edu.cqu.week3.Book</class> and <class>au.edu.cqu.week3.CD</class>
No default constructor exception Entity missing no-arg constructor Add public Item() {}, public Book() {}, etc. to each entity
Old test data still showing after re-run create-tables does not drop existing tables Change ddl-generation to drop-and-create-tables to start fresh (careful: deletes all data)
Project 2 of 2

Event Cascading

Map Customer and Address as entities with a one-to-one relationship.
Configure cascade persist & remove, test persistence context membership.

Project 2 — Domain Model & Plan

What you're building

Entities

Customer id: Long firstName, lastName email address: Address @OneToOne cascade=ALL Address id: Long street1, city zipcode country

Your Task List

  1. Declare both classes as entities
  2. Set up @OneToOne with cascade PERSIST & REMOVE
  3. Persist 2 customers (only calling em.persist(customer))
  4. Check if a customer is in the persistence context
  5. Delete a customer (only calling em.remove(customer))
  6. Check if the customer is still in the context

Database Result

CUSTOMER table

ID, FIRSTNAME, LASTNAME, EMAIL, ADDRESS_FK

ADDRESS table

ID, STREET1, CITY, ZIPCODE, COUNTRY

💡 When you remove a Customer, the linked Address should automatically be deleted too — that's cascade REMOVE in action.

Tip: Create a new project

You can build on Project 1's structure, or start fresh. Use a different persistence unit name and database if needed (e.g., week3db2).

Project 2 — Address Entity

Step 1

Address.java

package au.edu.cqu.week3;

import jakarta.persistence.*;

@Entity
public class Address {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  private String street1;
  private String city;
  private String zipcode;
  private String country;

  public Address() {}

  public Address(String street1, String city,
                 String zipcode, String country) {
    this.street1 = street1;
    this.city    = city;
    this.zipcode = zipcode;
    this.country = country;
  }

  // Getters
  public Long   getId()       { return id; }
  public String getStreet1()  { return street1; }
  public String getCity()     { return city; }
  public String getZipcode()  { return zipcode; }
  public String getCountry()  { return country; }

  // Setters
  public void setId(Long id)            { this.id = id; }
  public void setStreet1(String s)      { this.street1 = s; }
  public void setCity(String city)      { this.city = city; }
  public void setZipcode(String z)      { this.zipcode = z; }
  public void setCountry(String c)      { this.country = c; }
}

Address is the "inverse" side

Address has no reference back to Customer. The Customer table holds the foreign key column (ADDRESS_FK) pointing to the Address.

No relationship annotations needed on Address — all configuration lives on the Customer side.

💡 Alt + Insert shortcut

Instead of typing getters & setters manually, click inside the class body → press Alt + InsertGetter and Setter → tick all fields → Generate.

Update persistence.xml too

Add both classes using the full package path:
au.edu.cqu.week3.Customer
au.edu.cqu.week3.Address

Project 2 — Customer Entity

Step 2

Customer.java

package au.edu.cqu.week3;

import jakarta.persistence.*;

@Entity
public class Customer {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long    id;
  private String  firstName;
  private String  lastName;
  private String  email;

  @OneToOne(cascade = {
    CascadeType.PERSIST,
    CascadeType.REMOVE
  })
  private Address address;

  public Customer() {}

  // Getters
  public Long    getId()         { return id; }
  public String  getFirstName()  { return firstName; }
  public String  getLastName()   { return lastName; }
  public String  getEmail()      { return email; }
  public Address getAddress()    { return address; }

  // Setters
  public void setId(Long id)               { this.id = id; }
  public void setFirstName(String fn)       { this.firstName = fn; }
  public void setLastName(String ln)        { this.lastName = ln; }
  public void setEmail(String email)        { this.email = email; }
  public void setAddress(Address address)   { this.address = address; }
}

🔑 The cascade attribute

  • CascadeType.PERSIST — when you persist a Customer, JPA also persists the linked Address
  • CascadeType.REMOVE — when you remove a Customer, JPA also removes the Address
You could also write cascade = CascadeType.ALL to cover all cascade types, but using specific types is more precise and intentional.

@OneToOne generates a FK column

JPA adds an ADDRESS_FK column to the CUSTOMER table — the foreign key pointing to ADDRESS.ID. You don't need to configure this manually unless you want to customise the column name.

Project 2 — Main Class

Step 3
package au.edu.cqu.week3;

import jakarta.persistence.*;

public class Main2 {
 public static void main(String[] args) {

  EntityManagerFactory emf =
    Persistence.createEntityManagerFactory(
      "Week3PU2");
  EntityManager em = emf.createEntityManager();
  EntityTransaction tx = em.getTransaction();

  tx.begin();

  // Customer 1 — only persist customer!
  Address a1 = new Address(
    "10 George St","Brisbane","4000","AU");
  Customer c1 = new Customer();
  c1.setFirstName("Alice");
  c1.setLastName("Smith");
  c1.setEmail("alice@example.com");
  c1.setAddress(a1);
  em.persist(c1); // a1 auto-persisted!

  // Customer 2
  Address a2 = new Address(
    "5 Queen St","Sydney","2000","AU");
  Customer c2 = new Customer();
  c2.setFirstName("Bob");
  c2.setLastName("Jones");
  c2.setEmail("bob@example.com");
  c2.setAddress(a2);
  em.persist(c2); // a2 auto-persisted!

  tx.commit();

✅ What's happening

  • Only em.persist(c1) and em.persist(c2) are called
  • The CascadeType.PERSIST causes a1 and a2 to be persisted automatically
  • 4 INSERT statements fire at commit: 2 for ADDRESS, 2 for CUSTOMER
Because of the foreign key, JPA always inserts the Address first, then the Customer — so the FK column can be populated correctly.

⚠️ Common mistake

Forgetting to call c1.setAddress(a1) before em.persist(c1) means the cascade has nothing to propagate to!

Project 2 — Checking Context & Removing

Step 4

Check if managed, then remove

// Start a new transaction
tx.begin();

// Find customer 1
Customer found = em.find(
  Customer.class, c1.getId()
);

// Task 4: Is the entity managed?
boolean isManaged = em.contains(found);
System.out.println(
  "Customer in context: " + isManaged
);
// Expected: Customer in context: true

// Task 5: Remove — cascade fires!
em.remove(found);
// Address will also be removed

tx.commit();

// Task 6: Is it still managed?
boolean afterRemove = em.contains(found);
System.out.println(
  "After remove: " + afterRemove
);
// Expected: After remove: false

em.contains() explained

em.contains(entity) returns true if the entity is currently in the persistence context (i.e., MANAGED state). Once removed or detached, it returns false.

Expected console output:

Customer in context: true
After remove: false
When you call em.remove(found), the entity transitions to REMOVED state. At commit, JPA executes:
  • DELETE FROM CUSTOMER WHERE ID = ?
  • DELETE FROM ADDRESS WHERE ID = ? (cascade!)

Project 2 — Verify & Troubleshoot

Step 5

Verification SQL

-- After persisting 2 customers:
SELECT * FROM CUSTOMER;
-- Expect: 2 rows (Alice, Bob)

SELECT * FROM ADDRESS;
-- Expect: 2 rows (even though you
-- only called persist on Customer!)

-- After removing Customer 1:
SELECT * FROM CUSTOMER;
-- Expect: 1 row (only Bob)

SELECT * FROM ADDRESS;
-- Expect: 1 row (Alice's address
-- was also deleted via cascade!)

-- Verify the FK link
SELECT c.firstname, a.city
FROM CUSTOMER c
JOIN ADDRESS a
  ON c.ADDRESS_FK = a.ID;

Common Issues

IssueFix
Address not deleted after remove Check CascadeType.REMOVE is set on the @OneToOne in Customer
Two rows in Address but one in Customer Normal if you removed one customer — cascade deleted its address correctly ✅
Detached entity passed to remove Must call em.find() to get a managed instance first, then em.remove()
em.contains() returns false before remove Entity may have been detached — check for em.clear() or transaction boundaries
Project 2 complete when:
  • Only 2 em.persist() calls → 4 DB rows created
  • em.contains() returns true then false as expected
  • Removing 1 customer removes its address automatically

✏️ Check Your Understanding

In Project 1, you annotate Item with @Inheritance(strategy = InheritanceType.JOINED). What annotation do you put on Book?

A @Entity and @Inheritance(strategy = InheritanceType.JOINED)
B @Entity only — inheritance strategy is inherited from the root
C @MappedSuperclass
D No annotation needed — JPA auto-detects subclasses

In Project 2, you only call em.persist(customer). Why is the Address also persisted?

A JPA automatically persists all related objects by default
B Address is persisted because it's listed in persistence.xml
C CascadeType.PERSIST on the @OneToOne propagates the persist to Address
D The no-arg constructor triggers automatic persistence

Extension Challenges

Finished Early?

Project 1 Extensions

Challenge 1

Add a third subclass: Vinyl

Create a Vinyl class extending Item with fields rpm (Integer) and diameter (Float). Persist a Vinyl entity.

Challenge 2

Switch to Single-Table

Change the strategy to SINGLE_TABLE on Item. Observe how the schema changes — only 1 table now with a DTYPE discriminator column.

Challenge 3

Custom discriminator column

Add @DiscriminatorColumn(name="ITEM_TYPE") to Item and @DiscriminatorValue("BOOK") to Book. Observe how the DTYPE column changes.

Project 2 Extensions

Challenge 4

Add orphanRemoval

Add orphanRemoval = true to the @OneToOne. Test that setting customer.setAddress(null) also deletes the Address row.

Challenge 5

Add a One-to-Many relationship

Add a List<Order> to Customer with @OneToMany(cascade = CascadeType.ALL). Persist customers with multiple orders.

Challenge 6

Test detach + merge

After persisting, call em.detach(c1), modify the email, then call em.merge(c1) in a new transaction. Verify the update lands in the DB.

Lab Summary

What You Learned

✅ Project 1 — Inheritance

  • Annotate the root class with @Entity + @Inheritance(JOINED)
  • Subclasses only need @Entity
  • JPA creates separate tables, linked by PK/FK
  • All entities must be listed in persistence.xml

✅ Project 2 — Cascading

  • cascade = {CascadeType.PERSIST, CascadeType.REMOVE} on the owning side
  • Persist/remove the parent → child follows automatically
  • em.contains() checks managed state
  • Removed entities leave the persistence context

🗝 Key Concepts to Remember

Inheritance strategy lives on the root

Only Item carries @Inheritance. Subclasses just extend and add their own fields.

Cascade is "opt-in"

Without cascade, you must persist/remove every entity manually. With it, operations propagate automatically.

Persistence context = your safety net

While a transaction is open, all managed entities are tracked. Changes are auto-flushed at commit. Once closed, you're working with plain Java objects again.

📁 Remember: complete sample projects are available on Moodle if you get stuck — but try your own version first!