INTRODUCTION
In this Jakarta EE for Junior Developers tutorial, we will add the Address table to our database design, which will have a One-to-One relationship with the Employee table and will store employees' addresses. Additionally, we will add extra Jakarta Faces pages so that, with the help of ConversationScope, we can enhance our application to request information in steps.
One-to-One RELATIONSHIP
In the scenario we are working on, we already have a table named Employees where we can store employee information. However, we would also like to add details about their residence, such as the city and the area they live in.
Instead of adding extra columns to the Employees table, we choose to create a new table named Locations, which will store each employee's address. The Locations table will have a One-to-One relationship with the Employee table, meaning that each employee will have exactly one residential address.
This may not be the most realistic scenario, but at the very least, it gives us the opportunity to create a One-to-One relationship between two tables.
Let's first look at the code for both tables, and then we will explain the additional annotations we have included.
Location.java
package com.mycompany.jakartaee.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.io.Serializable;
@Entity
public class Location implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long location_id;
@Column(name = "loc_street", nullable = true, length = 40)
private String street_address;
@Column(name = "loc_zip", nullable = true, length = 12)
private String postal_code;
@Column(name = "loc_city", nullable = true, length = 30)
private String city;
@Column(name = "loc_state", nullable = true, length = 25)
private String state_province;
public String getStreet_address() {
return street_address;
}
public void setStreet_address(String street_address) {
this.street_address = street_address;
}
public String getPostal_code() {
return postal_code;
}
public void setPostal_code(String postal_code) {
this.postal_code = postal_code;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState_province() {
return state_province;
}
public void setState_province(String state_province) {
this.state_province = state_province;
}
public Long getLocation_id() {
return location_id;
}
public void setLocation_id(Long location_id) {
this.location_id = location_id;
}
}
Employee.java
package com.mycompany.jakartaee.entities;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name = "EMPLOYEES")
public class Employee implements Serializable {
public Employee() {
}
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long employee_id;
@Column(name = "emp_firstname", nullable = false)
private String firstName;
@Column(name = "emp_lastname", nullable = false)
private String lastName;
@Column(name = "emp_email")
private String email;
@Column(name = "emp_phone")
private String phoneNumber;
@Column(name = "emp_hiredate", nullable = false)
private Date hireDate;
@Column(name = "emp_salary", precision=0, nullable = false)
private Double monthlySalary;
@ManyToOne
@JoinColumn(name="dept_id")
private Department department;
@OneToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name="location_id",referencedColumnName = "location_id", unique=true)
private Location address;
public Location getAddress() {
return address;
}
public void setAddress(Location address) {
this.address = address;
}
public Long getEmployee_id() {
return employee_id;
}
public void setEmployee_id(Long employee_id) {
this.employee_id = employee_id;
}
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 String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Date getHireDate() {
return hireDate;
}
public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}
public Double getMonthlySalary() {
return monthlySalary;
}
public void setMonthlySalary(Double monthlySalary) {
this.monthlySalary = monthlySalary;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}
@OneToOne
: Defines a one-to-one relationship between Employee
and Location
.
cascade = CascadeType.ALL
: Any operation (persist, merge, remove) on Employee
will also affect Location
.
optional = false
: Ensures that an Employee
must have a corresponding Location
.
@JoinColumn(name="location_id", referencedColumnName = "location_id", unique=true)
: Specifies the foreign key column location_id
in the EMPLOYEES
table. The referencedColumnName = "location_id"
ensures it links to the primary key of Location
whereas the
unique=true
enforces a one-to-one constraint, ensuring each employee has a unique address.
ConversationScoped IN JAKARTA EE
As we have already explained in our previous tutorial, all beans have a scope.
The scope of a bean determines the lifecycle of its instances, and which
instances of the bean are visible to instances of other beans.
Let's review the available scopes one more time:
@RequestScoped: When you need data only for the duration of a request (e.g., form submissions).
@SessionScoped: Storing user session data (e.g., logged-in user info, shopping cart).
@ApplicationScoped: Shared global data (e.g., configuration, caching, statistics).
@ConversationScoped: Multi-step workflows (e.g., wizards, multi-step forms).
@Dependent: Typically used for helper classes (e.g., services, utilities).
As we can see from above description of the scopes, the @ConversationScoped
annotation defines a CDI bean with a longer lifespan than @RequestScoped
but shorter than @SessionScoped
. A conversation is a user-driven interaction that can span multiple HTTP requests. For example, this could be used for a multi-step form or a shopping cart that persists as the user navigates between pages.
The conversation is created when the bean is first accessed (using conversation.begin()
) and will persist across multiple HTTP requests until explicitly ended.
The conversation is manually ended using conversation.end()
. Once ended, the bean is destroyed, and its state is no longer available.
You can control when the conversation begins and ends, allowing you to create temporary data storage for user interactions without keeping that data around for the entire user session.
In our application, we want to ask the user to enter his/her personal information in mutliple steps. To achieve that, we first need to create a class annotated with the ConversationScoped which will be responsible for generating an object that will keep the information of the employee in memory for the duration of the process.
EmployeeInfo.java
package com.mycompany.beans;
import jakarta.enterprise.context.ConversationScoped;
import jakarta.inject.Named;
import java.io.Serializable;
@Named
@ConversationScoped
public class EmployeeInfo implements Serializable {
private String firstName;
private String lastName;
private String email;
private String date;
private String phoneNumber;
private Double monthlySalary;
private String dept;
private String street_address;
private String postal_code;
private String city;
private String state_province;
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 String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Double getMonthlySalary() {
return monthlySalary;
}
public void setMonthlySalary(Double monthlySalary) {
this.monthlySalary = monthlySalary;
}
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
public String getStreet_address() {
return street_address;
}
public void setStreet_address(String street_address) {
this.street_address = street_address;
}
public String getPostal_code() {
return postal_code;
}
public void setPostal_code(String postal_code) {
this.postal_code = postal_code;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState_province() {
return state_province;
}
public void setState_province(String state_province) {
this.state_province = state_province;
}
@Override
public String toString() {
return "EmployeeInfo{" +
"firstName=" +
firstName + ", lastName="
+ lastName + ", email=" +
email + ", date=" + date +
", phoneNumber=" + phoneNumber +
", monthlySalary=" + monthlySalary +
", dept=" + dept + ", street_address=" +
street_address + ", postal_code=" +
postal_code + ", city=" + city +
", state_province=" + state_province + '}';
}
}
We have also added an overwritten version of the toString method so we can check in every step what values the object contains.
Now we will modify our controller to receive the information in steps.
EmployeeInfoController.java
package com.mycompany.beans;
import com.mycompany.jakartaee.entities.Department;
import com.mycompany.jakartaee.entities.Employee;
import com.mycompany.jakartaee.entities.Location;
import com.mycompany.services.DepartmentAssignment;
import com.mycompany.services.ITAssignment;
import jakarta.annotation.Resource;
import jakarta.enterprise.context.Conversation;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.inject.Default;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.HeuristicMixedException;
import jakarta.transaction.HeuristicRollbackException;
import jakarta.transaction.NotSupportedException;
import jakarta.transaction.RollbackException;
import jakarta.transaction.SystemException;
import jakarta.transaction.UserTransaction;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
@Named
@RequestScoped
public class EmployeeInfoController implements Serializable {
@PersistenceContext
private EntityManager em;
@Resource
private UserTransaction userTransaction;
@Inject
private Conversation conversation;
@Inject
EmployeeInfo employeeInfo;
@Inject
private EmployeeInfo employee;
private static final Logger logger =
Logger.getLogger(EmployeeInfoController.class.getName());
@Inject
@Default
@ITAssignment
DepartmentAssignment departmentAssignment;
public String employeeInfoEntry() {
conversation.begin();
System.out.println(employee);
return "personal";
}
public String employeePersonalInfo() {
System.out.println(employee);
return "address";
}
public String employeePersonalBackInfo() {
System.out.println(employee);
return "personal";
}
public String employeeConfirmationPage() throws java.text.ParseException,
NotSupportedException, SystemException,
RollbackException, HeuristicMixedException,
HeuristicRollbackException {
System.out.println(employee);
Employee employee = new Employee();
Department department = new Department();
Location location = new Location();
if ("None".equals(employeeInfo.getDept())) {
department.setDept_id(departmentAssignment.generateDepartment());
} else {
switch (employeeInfo.getDept()) {
case "HR" ->
department.setDept_id(1L);
case "IT" ->
department.setDept_id(2L);
case "Finance" ->
department.setDept_id(3L);
case "Marketing" ->
department.setDept_id(4L);
default -> {
}
}
}
employee.setFirstName(employeeInfo.getFirstName());
employee.setLastName(employeeInfo.getLastName());
employee.setMonthlySalary(employeeInfo.getMonthlySalary());
Date dt = null;
dt = new SimpleDateFormat("yyyy-MM-dd").parse(employeeInfo.getDate());
employee.setHireDate(dt);
employee.setEmail(employeeInfo.getEmail());
employee.setPhoneNumber(employeeInfo.getPhoneNumber());
location.setCity(employeeInfo.getCity());
location.setPostal_code(employeeInfo.getPostal_code());
location.setState_province(employeeInfo.getState_province());
location.setStreet_address(employeeInfo.getStreet_address());
employee.setAddress(location);
employee.setDepartment(department);
System.out.println(employeeInfo.toString());
try {
userTransaction.begin();
em.persist(employee);
userTransaction.commit();
logger.log(Level.INFO,
"Transaction has been completed with employee id: {0}",
employee.getEmployee_id());
} catch (NotSupportedException
| SystemException | RollbackException
| HeuristicMixedException | HeuristicRollbackException
| SecurityException | IllegalStateException ex) {
Logger.getLogger(EmployeeInfoController.class.getName())
.log(Level.SEVERE, null, ex);
}
conversation.end();
return "confirmation";
}
}
Notice that in each step we use a System.out.println( ) method so we can have an idea of the object's state.
JAKARTA FACES
The last step is to define the Jakarta Faces page for each step. Here is a list of the pages in order they appear.
index.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html">
<h:head>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"></link>
<title>Welcome</title>
</h:head>
<h:body>
<h:form>
<img src="employee_registration.png" alt="employee registration" />
<h:commandButton class="btn btn-primary" value="Enter Employee Information" action="#{employeeInfoController.employeeInfoEntry()}"
style="font-size: 30px">
</h:commandButton>
</h:form>
</h:body>
</html>
personal.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"></link>
<title>Enter Employee Information</title>
</h:head>
<h:body class="container mt-5">
<h1 style="text-align: center">Employee Registration Form (Page 1 of 2)</h1>
<h:form class="card p-4 shadow-sm">
<div class="row">
<div class="col-md-6 mb-3">
<h:outputLabel for="firstName" value="First Name" class="col-md-6 mb-3"/>
<h:inputText id="firstName" value="#{employeeInfo.firstName}" class="form-control"/>
</div>
<div class="col-md-6 mb-3">
<h:outputLabel for="lastName" value="Last Name" class="col-md-6 mb-3"/>
<h:inputText id="lastName" value="#{employeeInfo.lastName}" class="form-control"/>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<h:outputLabel for="email" value="Email" class="col-md-6 mb-3"/>
<h:inputText id="email" value="#{employeeInfo.email}" class="form-control"/>
</div>
<div class="col-md-6 mb-3">
<h:outputLabel for="phone" value="Phone" class="col-md-6 mb-3"/>
<h:inputText id="phone" value="#{employeeInfo.phoneNumber}" class="form-control"/>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<h:outputLabel for="date" value="Hire Date" class="col-md-6 mb-3"/>
<h:inputText id="date" value="#{employeeInfo.date}" type="date" class="form-control"/>
</div>
<div class="col-md-6 mb-3">
<h:outputLabel for="salary" value="Monthly Salary" class="col-md-6 mb-3"/>
<h:inputText id="salary" value="#{employeeInfo.monthlySalary}" class="form-control"/>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<h:outputLabel for="department" value="Select Department:" class="col-md-6 mb-3"/>
<h:selectOneMenu id="department" value="#{employeeInfo.dept}">
<f:selectItem itemLabel="None" itemValue="None" />
<f:selectItem itemLabel="HR" itemValue="HR" />
<f:selectItem itemLabel="IT" itemValue="IT" />
<f:selectItem itemLabel="Finance" itemValue="Finance" />
<f:selectItem itemLabel="Marketing" itemValue="Marketing" />
</h:selectOneMenu>
</div>
</div>
<h:commandButton value="Next" action="#{employeeInfoController.employeePersonalInfo()}"
class="btn btn-primary custom-btn-size w-50"/>
</h:form>
</h:body>
</html>
address.xhtml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html"
xmlns:f="jakarta.faces.core">
<h:head>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"></link>
<title>Enter Employee Information</title>
</h:head>
<h:body class="container mt-5">
<h1 style="text-align: center">Employee Registration Form (Page 2 of 2)</h1>
<h:form class="card p-4 shadow-sm">
<div class="row">
<div class="col-md-6 mb-3">
<h:outputLabel for="street_address" value="Street Address" class="col-md-6 mb-3"/>
<h:inputText id="street_address" value="#{employeeInfo.street_address}" class="form-control"/>
</div>
<div class="col-md-6 mb-3">
<h:outputLabel for="postal_code" value="Postal Code" class="col-md-6 mb-3"/>
<h:inputText id="postal_code" value="#{employeeInfo.postal_code}" class="form-control"/>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<h:outputLabel for="cityl" value="City" class="col-md-6 mb-3"/>
<h:inputText id="city" value="#{employeeInfo.city}" class="form-control"/>
</div>
<div class="col-md-6 mb-3">
<h:outputLabel for="state_province" value="State Province" class="col-md-6 mb-3"/>
<h:inputText id="state_province" value="#{employeeInfo.state_province}" class="form-control"/>
</div>
</div>
<div class="row">
<h:commandButton value="Previous" action="#{employeeInfoController.employeePersonalBackInfo()}"
class="btn btn-primary custom-btn-size w-50"/>
<h:commandButton value="Next" action="#{employeeInfoController.employeeConfirmationPage()}"
class="btn btn-primary custom-btn-size w-50"/>
</div>
</h:form>
</h:body>
</html>
confirmation.xthml
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="jakarta.faces.html">
<h:head>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous"></link>
<title>Welcome</title>
</h:head>
<h:body>
<div class="p-3 mb-2 bg-primary text-white" align="center" style="font-size: 30px">
Thank you for registering one more new employee!!!
</div>
</h:body>
</html>
RUNNING THE APP




0 Comments
What do you think about Ground of Code?