Map an Item/Book/CD class hierarchy to MySQL using the Joined-Subclass strategy, then persist sample data.
Map Customer and Address with cascading persist/remove. Test persistence context membership.
week3db)Set up NetBeans IDE, JDK 21, MySQL Server, and the required Maven dependencies before attempting the projects.
Visit adoptium.net (Eclipse Temurin — free & open source)
or oracle.com/java/technologies/downloads
Select your OS: Windows x64 Installer (.msi) or macOS .pkg or Linux .deb/.rpm
Accept all defaults. The installer sets JAVA_HOME automatically on Windows.
Open a terminal / command prompt and run:
java -version // Expected: openjdk version "21.x.x" ... javac -version // Expected: javac 21.x.x
Check your version first. Jakarta EE 10 requires JDK 11 minimum, but JDK 21 is recommended. Older versions (JDK 8) will not work.
JAVA_HOMEC:\Program Files\Eclipse Adoptium\jdk-21Path → Add: %JAVA_HOME%\binjava -versionjava -version shows 21.x.x
Click Download → choose NetBeans IDE 21 (or latest). Download the installer for your OS.
Accept the licence. The installer will detect your JDK automatically. If not, point it to your JDK 21 folder.
Tools → Plugins → Installed → confirm Java SE and Java Web and EE are active.
NetBeans ships with Maven. Check: Tools → Options → Java → Maven — version should appear.
C:\Program Files\Eclipse Adoptium\jdk-21)netbeans.conf (in NetBeans/etc folder)netbeans_jdkhome="C:\...\jdk-21"Go to dev.mysql.com/downloads/installer
Choose MySQL Installer for Windows (Community) — the full ~450 MB package.
Choose Developer Default setup type. This installs MySQL Server 8, MySQL Workbench, and MySQL Shell.
Keep port 3306 (default). Set a root password — write it down, you'll need it in persistence.xml.
Windows: MySQL is installed as a Windows Service and starts automatically. Check via Services app or MySQL Installer → Status.
Open MySQL Workbench (installed with MySQL), connect to localhost:3306, and run:
CREATE DATABASE week3db; CREATE DATABASE week3db2; -- Verify: SHOW DATABASES;
localhost Port: 3306week3db User: rootbrew install mysql then brew services start mysql
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.
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).
Tools → Drivers → find MySQL (Connector/J driver) in the list → click Customize…
Click Add… → browse to the .jar file you extracted → OK. The driver class com.mysql.cj.jdbc.Driver should auto-populate.
Services panel → Databases → right-click your connection → Connect. A green check means it's working.
These are separate things:
pom.xml dependency puts the connector on your application's classpath so your code can connect to MySQLweek3db listed under Tablesjava -version shows 21.x.x in terminal| Symptom | Likely Cause | Fix |
|---|---|---|
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 |
java -version output), and the full error message from NetBeans Output panel.
Each entity maps to its own table. JPA joins them on primary key to rebuild the full object.
Choose Java with Maven → Java Application. Click Next.
e.g., Week3_Project1. Set Group ID to au.edu.youruni. Click Finish.
Open pom.xml and add EclipseLink + MySQL Connector dependencies.
Under src/main/resources, create a folder named META-INF.
Right-click META-INF → New → XML Document → name it persistence.
<!-- 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>
pom.xml, right-click the project → Download Dependencies (or Run build to trigger download).
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; } }
@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 valuesprotected (not private) so that subclasses like Book and CD can inherit these fields directly.
Click inside the class body → press Alt + Insert → choose Getter and Setter → tick all fields → Generate. NetBeans writes them all for you automatically!
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; } }
@Id or @Inheritance needed here — Book inherits those from Item automatically. Only add @Entity and Book's own fields.
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.
Click inside the class → Alt + Insert → Getter and Setter → tick all fields → Generate. Saves lots of typing!
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; } }
package au.edu.cqu.week3;import jakarta.persistence.*;cannot find symbol errors in Main.java when you try to call setTitle(), setIsbn() etc.
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>
week3db to your DB namecreate-tables creates tables if they don't existCREATE DATABASE week3db;
eclipselink.logging.level=FINE prints the generated SQL to the console — great for debugging!
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(); } }
The string "Week3PU" must exactly match the name attribute in your persistence.xml.
[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!
Window → Services (or Ctrl+5)
Connect if not already. Expand your week3db database.
Right-click the connection → Execute Command, then run the queries on the right.
2 rows
(Book + CD)
1 row
(Clean Code)
1 row
(Kind of Blue)
-- 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;
| Error / Symptom | Likely Cause | Fix |
|---|---|---|
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) |
@OneToOne with cascade PERSIST & REMOVEem.persist(customer))em.remove(customer))ID, FIRSTNAME, LASTNAME, EMAIL, ADDRESS_FK
ID, STREET1, CITY, ZIPCODE, COUNTRY
You can build on Project 1's structure, or start fresh. Use a different persistence unit name and database if needed (e.g., week3db2).
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 has no reference back to Customer. The Customer table holds the foreign key column (ADDRESS_FK) pointing to the Address.
Instead of typing getters & setters manually, click inside the class body → press Alt + Insert → Getter and Setter → tick all fields → Generate.
Add both classes using the full package path:
au.edu.cqu.week3.Customer
au.edu.cqu.week3.Address
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; } }
CascadeType.PERSIST — when you persist a Customer, JPA also persists the linked AddressCascadeType.REMOVE — when you remove a Customer, JPA also removes the Addresscascade = CascadeType.ALL to cover all cascade types, but using specific types is more precise and intentional.
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.
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();
em.persist(c1) and em.persist(c2) are calledCascadeType.PERSIST causes a1 and a2 to be persisted automaticallyForgetting to call c1.setAddress(a1) before em.persist(c1) means the cascade has nothing to propagate to!
// 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(entity) returns true if the entity is currently in the persistence context (i.e., MANAGED state). Once removed or detached, it returns false.
Customer in context: trueAfter remove: false
em.remove(found), the entity transitions to REMOVED state. At commit, JPA executes:
-- 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;
| Issue | Fix |
|---|---|
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 |
In Project 1, you annotate Item with @Inheritance(strategy = InheritanceType.JOINED). What annotation do you put on Book?
@Entity and @Inheritance(strategy = InheritanceType.JOINED)@Entity only — inheritance strategy is inherited from the root@MappedSuperclassIn Project 2, you only call em.persist(customer). Why is the Address also persisted?
CascadeType.PERSIST on the @OneToOne propagates the persist to AddressCreate a Vinyl class extending Item with fields rpm (Integer) and diameter (Float). Persist a Vinyl entity.
Change the strategy to SINGLE_TABLE on Item. Observe how the schema changes — only 1 table now with a DTYPE discriminator column.
Add @DiscriminatorColumn(name="ITEM_TYPE") to Item and @DiscriminatorValue("BOOK") to Book. Observe how the DTYPE column changes.
Add orphanRemoval = true to the @OneToOne. Test that setting customer.setAddress(null) also deletes the Address row.
Add a List<Order> to Customer with @OneToMany(cascade = CascadeType.ALL). Persist customers with multiple orders.
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.
@Entity + @Inheritance(JOINED)@Entitypersistence.xmlcascade = {CascadeType.PERSIST, CascadeType.REMOVE} on the owning sideem.contains() checks managed stateOnly Item carries @Inheritance. Subclasses just extend and add their own fields.
Without cascade, you must persist/remove every entity manually. With it, operations propagate automatically.
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.