Architecture, in the context of computing, refers to the systematic organization of hardware (HW), software (SW), and network resources that collectively enable the functioning of a system. It is a comprehensive blend of these components, structured to meet specific operational goals.

The classification of architectures depends on several parameters. One key parameter is the type and functionality of hardware resources involved in the system. Another critical aspect is the topology of links, which describes how the hardware components are interconnected to form a cohesive system. Additionally, the structure of software modules and their interrelationships play a significant role. This includes how software components communicate, share responsibilities, and integrate into the overall system.

A Brief History of Distributed Architectures

Distributed architectures have evolved significantly over the decades, influenced by advancements in technology and shifts in computing paradigms:

  1. 1960s-1970s: One-Tier Architectures
    During this era, computing systems were predominantly centralized, with a single-tier architecture. These systems relied on monolithic mainframes where both data and applications resided. Users accessed these systems via terminals that served as mere input/output devices.

  2. 1970s-1980s: Client-Server Architectures
    This period marked a shift toward two-tier client-server systems. Here, servers handled data management tasks while clients managed the presentation layer. This separation allowed more interactive and efficient use of computing resources.

  3. 1980s: Distributed Architectures Based on Remote Procedure Call (RPC)
    With the advent of distributed systems, Remote Procedure Call (RPC) mechanisms enabled applications to execute procedures on remote systems as if they were local, promoting distributed computing.

  4. 1990s: Object-Oriented Distributed Architectures
    Object-oriented programming introduced more modular and reusable architectures. Systems were designed with distributed objects that encapsulated both data and behavior, streamlining complex interactions.

  5. Late 1990s: Internet and Web-Based Architectures
    The rise of the internet transformed distributed architectures, emphasizing web-based interactions. HTTP, HTML, and emerging web technologies became foundational for connecting clients and servers globally.

  6. Today: Advanced Distributed and Cloud-Based Architectures
    Modern systems emphasize service-oriented architectures (SOA), including RESTful services and microservices. These approaches prioritize scalability, modularity, and ease of deployment. The proliferation of cloud computing has further evolved these architectures, enabling virtualization and Application Service Provisioning (ASP). Additionally, mobile computing has integrated seamlessly into these distributed environments.

The Client-Server Model

The client-server architecture is characterized by the division of tasks between clients and servers, where each has specialized roles.

  • Hardware Specialization: Servers are optimized for managing data, while clients focus on user interaction and presentation.
  • Topology: Typically organized in a Local Area Network (LAN), this setup involves one or more servers and multiple clients.
  • Software Structure: Client-server systems rely on functional partitioning. Client software combines business logic (processing and decision-making) and presentation logic (user interface rendering). Clients interact with servers by sending requests—often in the form of SQL queries—to access data. Servers handle these queries, enforce data integrity, and return results to the clients. This delineation of roles ensures efficient resource utilization.

Evolution to Three-Tier Architectures

The three-tier architecture emerged as an improvement over the client-server model, introducing a middle tier that acts as an intermediary between clients and servers. This architectural enhancement offers numerous benefits:

  1. Centralized Connection Management
    The middle tier consolidates connections to the data server, simplifying access and reducing redundancy.

  2. Data Abstraction
    By masking the data model, the middle tier ensures that clients are not directly exposed to the complexity of the underlying database structure.

  3. Scalability
    The middle tier can be replicated to handle increased load, making the architecture more scalable.

The middle tier’s functionality can vary depending on the software features it employs. For example, systems may implement Remote Procedure Calls (RPC) for remote interactions, Object-Oriented Middleware for modular design, Message-Oriented Middleware for asynchronous communication, or Web-Based Services for internet-based interactions. These variants provide flexibility to adapt to different use cases, enhancing the system’s robustness and scalability.

Web “Pure HTML” Three-Tier Architectures

Web-based three-tier architectures utilizing pure HTML represent a straightforward yet powerful design where the client, middle tier, and data tier operate in distinct layers.

  • Client Tier:
    The client in this architecture is typically a standard web browser. Its primary role is to handle the presentation layout, serving as a thin client. A thin client offloads computational tasks to the server, focusing solely on rendering the user interface using markup provided by the middle tier.

  • Middle Tier:
    The middle tier acts as the backbone of this architecture. It incorporates a web server that communicates using standard protocols such as HTTP. This tier houses the business logic, responsible for dynamically generating content from the raw data stored in the data tier. Additionally, it manages the presentation layout, assembling and delivering markup (e.g., HTML) that defines user interfaces. This division ensures flexibility and allows for scalable and maintainable applications.

  • Data Tier:
    While not detailed above, the data tier typically consists of a database or other persistent storage system. This layer provides the raw data that the middle tier processes and delivers to the client.

Rich Internet Applications (RIAs)

Rich Internet Applications (RIAs) bridge the gap between traditional desktop applications and web-based applications, providing an enriched user experience with desktop-like responsiveness.

RIAs leverage client-side scripting, primarily using JavaScript, to offer interactive and dynamic features. This makes them distinct from traditional thin-client web applications by introducing a fat client model.

  • Fat Client Characteristics:
    Unlike a thin client, a fat client takes on a more significant portion of the processing workload. It uses standard communication protocols like HTTP or WebSocket, standardized scripting languages such as JavaScript, and APIs like the Document Object Model (DOM) and HTML5. This design enables advanced functionalities while maintaining compatibility across platforms.

  • Technological Support:
    HTML5 is a pivotal standard for RIAs, enabling seamless functionality and modern features without relying on additional plugins.

  • Key Features of RIAs:

    1. Enhanced User Interfaces: HTML5 introduces new interface events tailored for mobile and touch applications, making RIAs highly adaptable to diverse devices.
    2. Asynchronous Interaction: RIAs use technologies like AJAX to send and receive data asynchronously, ensuring smoother user experiences without full page reloads.
    3. Client-Side Persistence: They offer mechanisms for storing data locally, such as web storage and IndexedDB, enhancing performance and enabling offline capabilities.
    4. Offline Functionality: RIAs support disconnected operations, allowing users to interact with applications even without an active internet connection.
    5. Multimedia and 3D Graphics: Native support for multimedia elements (audio, video) and advanced graphics (via WebGL) provides rich and engaging content without external dependencies.

By merging web and desktop functionalities, RIAs represent a pivotal step in modern application design, enabling robust, responsive, and versatile software solutions that cater to a wide range of user needs.

Three-Tier Web Applications with Java EE

Java EE (Java Platform, Enterprise Edition) is a comprehensive platform specifically designed for developing, deploying, and maintaining three-tier web applications. These applications are structured into three layers: the client tier (user interface), the middle tier (business logic), and the data tier (data storage and retrieval).

Java EE offers a robust ecosystem consisting of:

  1. API and Technology Specifications: A set of standard interfaces and guidelines for developing enterprise-grade applications.
  2. Development and Deployment Platforms: Frameworks and tools that simplify the creation and release of scalable and reliable applications.
  3. Reference Implementations: Example implementations to demonstrate Java EE technologies in practice.
  4. Compatibility Test Suites: Tools to ensure that applications adhere to Java EE standards and work seamlessly across compliant platforms.
  5. Reference Applications (Blueprints): Sample applications that provide best practices and templates for developers.

Java EE offers several key features that make it a preferred choice for enterprise application development:

  1. Component-Based Development
    Java EE emphasizes modularity by enabling developers to create applications through the assembly of loosely coupled, self-contained components. This approach promotes reusability, easier maintenance, and scalability.

  2. Container Services
    The Java EE runtime environment, often referred to as a container, provides built-in services that address both functional and non-functional requirements. These include:

    • Security: Authentication and authorization mechanisms.
    • Transactionality: Ensuring database consistency across operations.
    • Scalability: Seamless handling of increasing loads.
    • Failure Recovery: Automatic restoration mechanisms in case of system failures.
    • Interoperability: Compatibility with external systems, enabling integration across diverse platforms.
  3. Declarative Development
    Developers can use declarative programming to specify application behavior without writing detailed implementation code. For example:

    • Declarative Security: Defining user roles and permissions in configuration files.
    • Declarative Transactions: Specifying how transactions should behave in XML or annotations.
    • Declarative Object-to-Relational Mapping: Simplifying the translation of objects to database records using frameworks like JPA (Java Persistence API).

The Java EE Stack

The Java EE stack is a layered architecture that integrates multiple technologies to support enterprise application development. It includes foundational elements such as Java Database Connectivity (JDBC) for data access, Java Server Pages (JSP) for dynamic content generation, and more advanced frameworks like JTA (Java Transaction API) and JPA.

Java Database Connectivity (JDBC)

JDBC was one of the first industry standards introduced to enable database-independent connectivity between Java applications and relational databases. It remains a fundamental part of Java EE, though it is often supplemented or replaced by higher-level abstractions.

  1. Database Connection: JDBC allows applications to establish connections with databases or other tabular data sources.
  2. SQL Execution: Developers can send SQL queries and updates to the database directly from Java programs.
  3. Result Processing: The API includes methods for iterating over query results and extracting data.

While still relevant, JDBC has been largely superseded in many modern applications by technologies like JPA (Java Persistence API) for object-relational mapping and JTA (Java Transaction API) for managing complex transactions. These newer APIs provide higher-level abstractions, reducing boilerplate code and simplifying development.

Servlets

Servlets are a key Java technology for web application development, primarily used in the presentation tier of three-tier architectures. They provide a component-based, platform-independent framework for building web-based applications. Servlets run on the server side within a servlet container, which manages their lifecycle and ensures concurrency handling.

Servlets have seamless access to other Java APIs, such as JDBC, enabling them to interact with enterprise databases effectively. Their execution environment offers additional services like thread management and security, simplifying the development of robust and scalable web applications.

Enterprise JavaBeans (EJB)

Enterprise JavaBeans (EJB) technology serves as the cornerstone for developing server-side components in Java EE, focusing specifically on the business tier. EJB facilitates the creation of distributed, transactional, and secure applications, designed to be portable across platforms.

EJB components operate within an EJB container, which automates several critical tasks, including:

  • Lifecycle management: Automatically managing the state and behavior of EJB objects.
  • Transaction management: Supporting distributed transactions seamlessly.
  • Replication and scaling: Ensuring high availability and performance in distributed environments.

EJB uses advanced Java features, such as annotations for metadata and dependency injection to simplify configuration and enhance modularity. These components are commonly invoked by the web tier to handle complex business logic and interact with data access services, bridging the front-end and back-end layers of enterprise systems.

Java Persistence API (JPA)

The Java Persistence API (JPA) standardizes object-relational mapping (ORM) in Java, providing a robust framework for mapping relational database data to object-oriented models. Integrated into both Java Standard Edition (Java SE) and Java Enterprise Edition (Java EE), JPA simplifies database interaction and minimizes boilerplate code.

Key features of JPA include:

  • Core Package: The API is encapsulated in the javax.persistence package.
  • Query Language: It offers the Java Persistence Query Language (JPQL) for querying databases using an object-oriented approach.
  • Metadata: JPA provides a specification for defining metadata to map Java objects to relational database schemas effectively.

JPA has largely replaced older database connectivity methods like JDBC, as it abstracts much of the complexity involved in database access and offers enhanced productivity for developers.

Java Transaction API (JTA)

The Java Transaction API (JTA) is a standard interface for managing transactions in Java applications. It provides a resource-agnostic way to handle transactions, allowing components to manage multiple resources—such as databases or messaging services—within a single transaction.

With JTA, transactional properties can be specified in two primary ways:

  1. Declaratively: Using annotations like @Transactional, developers can define transaction boundaries without explicit code, method by method.
  2. Programmatically: The UserTransaction interface allows for manual transaction control with functions to start, commit, and rollback transactions.

This flexibility ensures that complex transactional requirements, such as distributed transactions, are handled efficiently while maintaining code clarity.

Connecting an Application to the Database in a Three-Tier Web Architecture

In a typical three-tier web application, interaction with the database is structured across three distinct layers:

  1. Client Tier
    The client tier is responsible for handling user interactions. It captures events, invokes the web tier to process requests, and renders results in the user interface (commonly referred to as the “view”).

  2. Web Tier
    The web tier acts as the intermediary between the client and business tiers. It dispatches client requests to the appropriate business logic components (acting as controllers), processes the results, and sends formatted responses back to the client.

  3. Business and Data Tier
    The business tier implements the core application logic, handling tasks such as decision-making, calculations, and rule enforcement. The data tier performs database queries and updates, ensuring data integrity within transactions. Tools like JPA and JTA facilitate efficient data handling and transaction management in this tier.

This separation of concerns ensures modularity, scalability, and maintainability, making three-tier architectures a popular choice for enterprise applications.


Case study: Expense Report

A Web application supports the management of travel expenses. After logging in, the user accesses a HOME page where there is a list of travel missions; a mission belongs to a user and has a date, a place, a description, a number of days of duration, and a status (“open”, “reported”, “closed”). The list shows the date and place of the missions, which are sorted by date descending. On the HOME page, there is a form with which the user can create a new mission by entering all the mandatory data. A new mission is always in the “open” state.

After creating a mission, the user is returned to the HOME page. When the user selects a mission in the list, a MISSION DETAIL page appears, showing all the mission data. If the mission is in the “open” state, a form appears for entering the expenses incurred during the mission; the form contains three fields: food costs, accommodation costs, and transport costs. Sending the form data causes the mission status to change from “open” to “reported”, and the return to the MISSION DETAIL page. Total expenses should be less than 100€; otherwise, the report is rejected with an error. If the mission is in the “reported” status, a CLOSE button appears, and the user can click on it to declare that he has received the reimbursement; this causes the mission status to change from “reported” to “closed” and the redisplay of the MISSION DETAIL page. If the mission is in the “closed” status, the MISSION DETAIL page shows the mission data completed with the value of the three types of expenditure.

Database Design

We have three entities: User, Mission, and Expenses. A user has a username, a password, a name, and a surname. A mission has a date, a destination, a state, a description, and a number of days. The state can be “open”, “reported”, or “closed”. An expense report has three fields: food, accommodation, and transport. The relationship between the entities is as follows:

PlantUML diagram

Logical Database Schema

From the previous diagram, we can derive the following logical database schema:

CREATE TABLE user (
  id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(45) NOT NULL,
  password varchar(45) NOT NULL,
  name varchar(45) NOT NULL,
  surname varchar(45) NOT NULL,
  PRIMARY KEY (id)
);
 
CREATE TABLE mission (
  id int(11) NOT NULL AUTO_INCREMENT,
  date date NOT NULL,
  destination varchar(45) NOT NULL,
  state int(11) NOT NULL DEFAULT '0',
  description varchar(45) NOT NULL,
  days int(11) NOT NULL,
  reporter int(11) NOT NULL,
  
  PRIMARY KEY (id),
  CONSTRAINT id_reporter 
    FOREIGN KEY (reporter) REFERENCES user (id) 
    ON DELETE CASCADE 
    ON UPDATE CASCADE
);
 
CREATE TABLE expenses (
  id int(11) NOT NULL AUTO_INCREMENT,
  food decimal(19,4) NOT NULL,
  accommodation decimal(19,4) NOT NULL,
  transport decimal(19,4) NOT NULL,
  mission int(11) NOT NULL,
  
  PRIMARY KEY (id),
  KEY id (mission),
  CONSTRAINT id 
    FOREIGN KEY (mission) REFERENCES mission (id) 
    ON DELETE CASCADE 
    ON UPDATE CASCADE,
  CONSTRAINT CHK_TotalExp 
	CHECK (((food + accommodation) + transport) < 100.00)
);

Application Design

At the application level, we can identify the following components:

Main ComponentSub Component
Model objects (Beans)User
Mission
ExpenseReport
Data Access Objects (Classes)UserDAO
- checkCredentials(username, pwd)
MissionDAO
- createMission(mission, userid)
- findMissionsByUser(userid)
- findMissionById(missionid)
- changeMissionStatus(missionid, status)
ExpensesDAO
- addExpenseReport(expenseReport, mission)
- findExpensesForMission(missionId)
Controllers (servlets)CheckLogin
GoToHomePage
CreateMission
GetMissionDetails
CreateExpenses
CloseMission
Logout
Views (Templates)Login
Home
MissionDetails

Database Connection in Web Tier

  • Web.xml configuration file in the WEB-INF folder of the web application server
<context-param>
    <param-name>dbUrl</param-name>
    <param-alue>
        jdbc:mysql://localhost:3306/db_mission
    </param-value>
</context-param>
<context-param>
    <param-name>dbUser</param-name>
    <param-value>piero</param-value>
</context-param>
<context-param>
    <param-name>dbPassword</param-name>
    <param-value>fraternali</param-value>
</context-param>
<context-param>
    <param-name>dbDriver</param-name>
    <param-value>
        com.mysql.cj.jdbc.Driver
    </param-value>
</context-param>
  • Utility functions called by all controllers that need be connected to the database. The ConnectionHandler class provides utility functions for managing database connections in a web application. It includes a getConnection method that retrieves a database connection using parameters defined in the web application’s context, and a closeConnection method that safely closes the connection. These methods ensure that database connections are consistently managed across different controllers, reducing redundancy and potential errors.
public class ConnectionHandler {
    public static Connection getConnection (ServletContext context) throws UnavailableException {
        Connection connection = null;
        try {
            String driver = context.getInitParameter("dbDriver");
            String url = context.getInitParameter("dbUrl");
            String user = context.getInitParameter("dbUser");
            String password = context.getInitParameter("dbPassword");
 
            Class.forName(driver);
 
            connection = DriverManager.getConnection(url, user, password);
        } catch (ClassNotFoundException e) {
            throw new UnavailableException("Can't load driver");
        } catch (SQLException e) {
            throw new UnavailableException("Couldn't get db connection");
        }
            return connection ;
    }
        
    public static void closeConnection (Connection connection) throws SQLException {
        if (connection != null) { connection.close(); }
    }
}

(Repetitive) Code in the Controllers

  • At initialization we create a ConnectionHandler object
public void init() throws ServletException {
    connection = ConnectionHandler.getConnection (
    getServletContext());
}
  • At termination we close the connection
public void destroy() {
    try {
        ConnectionHandler.closeConnection(connection);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

Every controller in the application repeats the same code for acquiring and releasing the database connection. This approach is inefficient as each controller maintains its own connection, stored as a data member of the controller class. Additionally, this method increases the risk of forgetting to release a connection, which can be costly in terms of resource management and application performance.

Persistent Data Management with JDBC

The following examples demonstrate how to manage persistent data in a Java application using the JDBC API, specifically focusing on extracting, creating, and modifying data in a database. These methods, part of a MissionDAO class, encapsulate the database interactions related to “mission” entities.

Data Extraction: Retrieving Missions by User

The findMissionsByUser method fetches all missions reported by a specific user, ordered by their start date in descending order.

// Method of the business object MissionDAO
public List<Mission> findMissionsByUser(int userId) throws SQLException {
    List<Mission> missions = new ArrayList<Mission>();
    
    String query = "SELECT * from mission where reporter =? ORDER BY date DESC";
    
    try (PreparedStatement pstatement = connection.prepareStatement(query);) {
        pstatement.setInt(1, userId);
        
        try (ResultSet result = pstatement.executeQuery();) {
            while (result.next()) {
                Mission mission = new Mission();
                
                // Copy cursor data into Java bean objects...
                mission.setId(result.getInt("id"));
                mission.setStartDate(result.getDate("date"));
                mission.setDestination(result.getString("destination"));
                mission.setStatus(result.getInt("status"));
                mission.setDescription(result.getString("description"));
                mission.setDays(result.getInt("days"));
                mission.setReporterID(userId);
                missions.add(mission);
            }
        }
    } // exception handling omitted for brevity
    return missions;
}
  1. A SQL query is prepared to filter missions by reporter.
  2. A PreparedStatement binds the userId to the query parameter.
  3. The result set is processed row by row to populate Mission objects, which are added to a list.
  4. This method returns a List<Mission> containing the extracted missions.

Data Creation: Adding a New Mission

The createMission method inserts a new mission into the database.

public void createMission(Date startDate, int days, String destination, String description, int reporterId) throws SQLException {
    String query = "INSERT INTO mission (date, destination, status, description, days, reporter) VALUES(?, ?, ?, ?, ?, ?)";
    
    try (PreparedStatement pstatement = connection.prepareStatement(query)) {
        pstatement.setDate(1, new java.sql.Date(startDate.getTime()));
        pstatement.setString(2, destination);
        pstatement.setInt(3, MissionStatus.OPEN.getValue());
        pstatement.setString(4, description);
        pstatement.setInt(5, days);
        pstatement.setInt(6, reporterId);
        pstatement.executeUpdate();
    }
}
  1. The SQL INSERT statement specifies the columns and values for a new mission.
  2. Parameters such as date, destination, and status are bound to the query using setXXX methods.
  3. The executeUpdate method commits the insertion into the database.

MissionStatus.OPEN.getValue() retrieves the numerical value representing the "open" status, likely defined in an enumeration.

Data Modification: Updating Mission Status

The changeMissionStatus method modifies the status of an existing mission.

// Method of the business object MissionDAO
public void changeMissionStatus(int missionId, MissionStatus missionStatus) throws SQLException {
    String query = "UPDATE mission SET status =? WHERE id =? ";
    
    try (PreparedStatement pstatement = connection.prepareStatement(query);) {
        // Copy the data to modify into the SQL query
        pstatement.setInt(1, missionStatus.getValue());
        pstatement.setInt(2, missionId);
        pstatement.executeUpdate();
    }
}
  1. The SQL UPDATE statement targets the status field of the mission table.
  2. Parameters, such as the new status and mission ID, are bound to the query.
  3. The executeUpdate method updates the database with the new status.

Transactions in Java Applications

Managing transactions is crucial when performing multiple operations that must succeed or fail together, ensuring data consistency. Below is an example method, addExpenseReport, demonstrating transactional handling using JDBC.

This method inserts a new expense report into the expenses table and updates the mission’s status atomically. If any operation fails, the transaction is rolled back, undoing all changes.

// Method of expenseDAO business object
public void addExpenseReport(ExpenseReport expenseReport, Mission mission) throws SQLException, BadMissionForExpReport {
    // Check that the mission exists and is in OPEN state
    ...
    MissionsDAO missionDAO = new MissionsDAO(connection);
    
    String query = "INSERT into expenses (food, accomodation, transport, mission) VALUES(?, ?, ?, ?)";
    
    // Delimit the transaction explicitly
    connection.setAutoCommit(false); // Override default commit after each statement
    try (PreparedStatement pstatement = connection.prepareStatement(query);) {
        pstatement.setDouble(1, expenseReport.getFood());
        pstatement.setDouble(2, expenseReport.getAccomodation());
        pstatement.setDouble(3, expenseReport.getTransportation());
        pstatement.setInt(4, expenseReport.getMissioId());
        pstatement.executeUpdate(); // 1st update
        // 2nd update, to be executed atomically
        missionDAO.changeMissionStatus(expenseReport.getMissioId(), MissionStatus.REPORTED);
        connection.commit();
    } catch (SQLException e) {
        connection.rollback(); // if update 1 OR 2 fails, roll back all work
        throw e;
    }
} finally {
    connection.setAutoCommit(true);
} // reset to standard
  1. Explicit Transaction Control

    • setAutoCommit(false): Prevents the database from committing changes after every statement.
    • commit(): Commits all changes as a single unit.
    • rollback(): Undoes all changes in the transaction when an exception occurs.
  2. Ensuring Consistency
    The method updates two tables (expenses and missions). These operations must either both succeed or both fail to maintain consistency.

  3. Proper Resource Management

    • try-with-resources: Ensures that the PreparedStatement is closed automatically.
    • finally: Restores the default auto-commit mode, leaving the connection in a consistent state.

While JDBC provides low-level control over transactions, modern frameworks like JPA (Java Persistence API) and EJB (Enterprise JavaBeans) simplify these operations significantly by abstracting the transactional logic:

  1. Automatic Mapping of Query Results
    JPA enables automatic mapping of query results to Java objects, eliminating the need to manually copy data between the database and application objects.

  2. Seamless Object Creation
    In JPA, persisting an object automatically creates the corresponding database record without manual SQL handling. For instance, calling entityManager.persist(expenseReport) would insert a new record for an ExpenseReport.

  3. Transparent Object Updates
    Changes made to a managed object are automatically synchronized with the database. For example, updating an ExpenseReport object’s fields and committing the transaction would update the corresponding database row.

  4. Connection and DAO Transparency
    Frameworks handle connection pooling and dispatching, reducing boilerplate code. DAOs are often replaced by repositories or services that integrate tightly with the persistence context.

  5. Declarative Transactions
    With annotations like @Transactional, methods can join or create transactions automatically, simplifying code:

@Transactional
public void addExpenseReport(ExpenseReport expenseReport, Mission mission) {
   // JPA handles transactions and connections seamlessly
}

Problem-Solution Matrix

RequirementTechnologyHow to
Avoiding the copy of data from query result cursor to native application typesJPA Object Relational MappingJava classes and relational tables can be associated via a declarative mapping
Avoiding the copy of data from program to UPDATE and INSERT SQL statementJPA Object Relational MappingCreation and changes to Java objects can be persisted automatically
Avoiding the manual management of connectionsJTA transactions, EJB dependency injectionConnection object masked by higher level objects (EntityManager) automatically injected into the components that need them
Avoiding manual demarcation of transactionsJTA transactions, EJB container managed transactionsGlobal transactions managed by the container and propagated to object methods that can “join” them

Data Extraction with JPA

When using JPA (Java Persistence API), data extraction becomes seamless by leveraging the entity relationships defined in your model. In this example, we retrieve a list of missions associated with a specific reporter using JPA’s EntityManager to query the database and map the result to Java objects automatically.

// Method of missionService EJB
public List<Mission> findMissionsByUser(int userId) {
    // Find the Reporter entity by its primary key (userId)
    Reporter reporter = em.find(Reporter.class, userId);
    // Retrieve the list of missions related to the reporter
    List<Mission> missions = reporter.getMissions();
    return missions;
}

Here, the findMissionsByUser method uses the EntityManager’s find method to look up a Reporter entity by its userId. Once the Reporter object is loaded, you can directly access its associated missions (assuming a mapped one-to-many relationship between Reporter and Mission). This eliminates the need for manually creating SQL queries or handling result sets.

Data Creation with JPA

JPA simplifies object creation and database persistence. Instead of manually writing SQL INSERT queries, you can instantiate an entity and rely on the EntityManager to persist it in the database. In this example, we add a new mission and link it to a reporter.

public void createMission(Date startDate, int days, String destination, String description, int reporterId) {
    // Find the Reporter entity by its primary key
    Reporter reporter = em.find(Reporter.class, reporterId);
    // Create a new Mission object
    Mission mission = new Mission(startDate, days, destination, description, reporter);
    // Add the mission to the reporter's mission list (assuming a bidirectional relationship)
    reporter.addMission(mission);
    // Persist the reporter, which will also persist the associated mission
    em.persist(reporter);
}

In the createMission method:

  • The Reporter entity is loaded using the EntityManager’s find method.
  • A new Mission object is created and associated with the reporter.
  • The em.persist(reporter) call persists both the reporter and the new mission due to the cascade setting on the relationship (if configured in the entity mapping). There is no need for explicit INSERT statements.

Data Modification with JPA

Data modification with JPA is simple and efficient. Once an entity is retrieved, you can modify its fields and the changes will be automatically tracked by the EntityManager. Upon committing the transaction, JPA will automatically synchronize these changes with the database.

// Method of the missionService business object
public void reportMission(int missionId, MissionStatus missionStatus) {
    // Retrieve the mission entity by its primary key
    Mission mission = em.find(Mission.class, missionId);
    // Update the mission status
    mission.setStatus(MissionStatus.REPORTED);
    // JPA automatically tracks changes to the entity and updates the database during commit
}

In the reportMission method:

  • The Mission entity is fetched using the EntityManager’s find method.
  • The mission’s status is updated, and JPA will automatically track this change.
  • The changes are persisted during the transaction commit, without requiring any SQL update statements.

Transaction Management in JPA

JPA provides an automatic and declarative way to manage transactions. By default, JPA operates within the context of a transaction, and changes made to entities are automatically committed when the transaction completes successfully.

Here is an example of handling a transaction while adding an expense report to a mission:

public void addExpenseReport(ExpenseReport expenseReport, int missionId) throws BadMissionForExpReport {
    // Check that the mission exists and is in OPEN state
    Mission mission = em.find(Mission.class, missionId); // Retrieve the mission entity
    if (mission == null || mission.getStatus() != MissionStatus.OPEN) {
        throw new BadMissionForExpReport("Mission cannot introduce expense report");
    }
    try { 
        // The code inside this block is transactional!
        mission.setStatus(MissionStatus.REPORTED); // Update mission status
        mission.setExpense(expenseReport); // Associate the expense report with the mission
        em.persist(mission); // Persist the updated mission entity
    } catch (PersistenceException e) {
        e.printStackTrace(); // Used for debugging
    }
}

In the addExpenseReport method:

  • A transaction is automatically managed by the JPA persistence context. If the transaction completes successfully, the mission’s status is updated, and the expense report is linked to the mission.
  • If any exception occurs during the process, changes made during the transaction are rolled back, ensuring data consistency.

JPA allows you to focus on the business logic, and it automatically handles the transaction lifecycle, commit, and rollback. In more complex cases, you can use annotations like @Transactional for declarative transaction management. This ensures that the database operations within the method are handled within a single transaction, promoting data integrity.