Hardjee-business15 min read

Business Delegate Pattern

1. What is Business Delegate Pattern?

Definition

The Business Delegate pattern is a JEE Business Tier Pattern. It acts as a bridge between the presentation tier (servlet/controller) and the business tier (service + DAO classes). The Business Delegate and DAO classes together are capable of processing requests and generating results.

One line: A class that sits between the controller and the DAO layer, handling data conversion, exception translation, and transaction management so the presentation tier stays loosely coupled from the business tier.

Web Application Layered Architecture: view comp (jsp/html) servlet/controller (Java class) | | Business Delegate DAO (Java class) v v | | request -----> (monitoring ----> (b.logic) -----> (Persistence ----> DB s/w response <---- logic) <---- (other <----- logic) <---- Persistent logic) (CRUD operations) Data |---- Presentation tier comps ----|---- Business tier comps --------| (view, controller comps are (Business Delegate + DAO classes are capable of taking requests from capable of processing requests browser and sending responses and generating results) back to browser)

Why loose coupling between tiers?

It is always recommended to develop presentation tier and business tier comps as loosely coupled comps, i.e., the degree of dependency should be less between both tiers. So the modifications done in presentation tier comps should not affect business tier comps and vice-versa.

The PM/Project Leader divides team into two parties:

  • Party 1: Business tier developers - uses Java classes or Spring or Hibernate to develop business logic and persistence logic
  • Party 2: Presentation tier developers - uses Servlet/JSP technologies and develops view/controller layer logics

Since these two parties can work parallelly either by staying in the same company or in two different branches of the same company, we need loose coupling between presentation tier comps and business tier comps.

2. Three Responsibilities of Business Delegate

Business Delegate in Layered App will be used for 3 operations/use-cases
  1. Converts VO class object to BO class object by collecting all inputs from enduser in the form of String values
  2. Converts DAO class generated technology-specific exception to project-specific user-defined exception
  3. Provides single JDBC connection object to multiple related DAO classes for Transaction Management support

3. Responsibility a) VO to BO Conversion

Problem (without Business Delegate)

Problem: DTO/VO class changes when Business tier BO class changes form page (View comp) Servlet comp Business tier emp name: [____] read form EmployeeBussDelegate DAO emp salary: [____] data to b.logic SQL DB s/w emp addr: [____] DTO class obj gross,net salary query emp desg: [____] (c2) (c3) BO class obj If "salary" column in DB changes from int to float: - BO class (Business tier): salary type changes to double - DTO class (Presentation tier): ALSO must change to double - This kills loose coupling! Presentation tier should NOT know about DB changes.
// Problem: DTO class mirrors BO class types
public class EmployeeDTO {         // Presentation tier
    private Integer empno;
    private String ename;
    private String eaddr;
    private String salary;         // If DB changes salary to double,
    private String desg;           // this must change too!
    // setters and getters
}

public class EmployeeBO {          // Business tier
    private Integer empno;
    private String ename;
    private String eaddr;
    private Integer salary;        // Changed to double in DB
    private Integer grossSalary;   // Business tier level
    private Integer netSalary;     // Business tier level
    // setters and getters
}

Solution: Business Delegate converts VO to BO

Solution: Servlet stores ALL inputs as Strings in VO class. Business Delegate converts VO to BO as needed. form page (View comp) Servlet comp BusinessDelegate DAO emp name: [____] read form b.logic SQL DB s/w emp salary: [____] data to gross,net salary emp addr: [____] VO class obj Convert VO to emp desg: [____] (all Strings) BO class obj NO CHANGES! as needed for DAO Employee VO class (Presentation tier): Employee BO class (Business tier): private String empno; private Integer empno; private String ename; NO CHANGE private String ename; change to double private String eaddr; needed in private String eaddr; private String salary; VO class! private Integer salary; private String desg; Always String. private Integer grossSalary; netSalary; change to double

Key insight: For any change happened in Business tier DB table, we are forced to modify other business tier comps like BO, DAO, Business Delegate (not a problem because they are in the same tier). But there is no need modifying any code in VO class, Servlet comp of presentation tier, because VO class keeps all the inputs as String values always.

4. Responsibility b) Exception Translation

Problem (without Business Delegate)

Problem: Technology-specific exceptions leak to presentation tier Browser --> View comp --> Servlet comp --> BusinessDelegate --> DAO class --> DB s/w | | catches and throws SQLException displays to user (if JDBC) OR HibernateException (if Hibernate) If DAO technology changes from JDBC to Hibernate: SQLException changes to HibernateException --> BusinessDelegate code must change --> Controller/Servlet comp must change! --> This kills loose coupling between tiers.

Solution: Convert to project-specific exception

// Custom project-specific exception
public class InternalProblemException extends Exception {
    public InternalProblemException(String msg) {
        super(msg);
    }
}

// In BusinessDelegate: catch technology-specific, throw project-specific
public class EmployeeOperations {
    // BusinessDelegate class

    public String registerEmployee(EmployeeVO vo) throws InternalProblemException {
        try {
            // ... business logic + DAO calls
        } catch (SQLException sqle) {
            // Catch technology-specific exception
            // Convert to project-specific user-defined exception
            throw new InternalProblemException(sqle.getMessage());
        }
    }
}

// In Servlet: catches ONLY InternalProblemException
// If DAO changes from JDBC to Hibernate, only BusinessDelegate changes.
// Servlet code stays the same.

// Controller Servlet code:
try {
    // ... business logic
    // ... invoke service method
} catch (InternalProblemException ipe) {
    // project-specific exception, not technology-specific
    ipe.printStackTrace();
}
Key Benefit

Because of this code written in BusinessDelegate class, if the technology and exception changes in DAO class, we need to just modify BusinessDelegate class code exception handling. The Servlet comp code exception because business delegate is converting the technology-specific exception to project-specific user-defined exception.

5. Responsibility c) Transaction Management

Transaction Management

The process of combining related operations into single unit and executing them by applying do everything or nothing principle is called Transaction Management (Tx Mgmt).

  • Use case 1: Transfer Money - The withdraw and deposit operations should be executed by applying do everything or nothing principle.
  • Use case 2: Employee Registration - Insertion of record in HR DB table AND insertion of record in Finance DB table should be executed by applying do everything or nothing principle.

Transaction Management (TxMgmt) deals with 3 operations:

  1. Begin Tx - con.setAutoCommit(false)
  2. Execute logic - execute the queries
  3. Commit or Rollback Tx - con.commit() or con.rollback()

Problem: Separate connections per DAO

Problem: Each DAO gets its own JDBC connection EmployeeBusinessDelegate form page | name: [__] | HREmployeeDAO Finance_Employee addr: [__] --> | --> jdbc conn1 --------> (eno, ename, eaddr, salary) salary: [__] | con1.commit() | con1.rollback() HR_Employee | --> FinanceEmployeeDAO --> (eno, ename, eaddr, salary, netSalary) jdbc conn2 con2.commit() con2.rollback() Since both DAO classes are using their own separate jdbc con objects, we CANNOT perform commit and rollback operations on both DB tables at a time with the support of Transaction Management. If HREmployeeDAO succeeds but FinanceEmployeeDAO fails, we have an inconsistent state! Cannot rollback HR insert.

Solution: Single shared connection

Solution: Business Delegate provides ONE jdbc connection to both DAOs EmployeeBusinessDelegate form page | gets conn obj Finance_Employee name: [__] | from ConnectionFactory (eno, ename, eaddr, salary, netSalary) addr: [__] --> | | salary: [__] | v | HREmployeeDAO ---(conn obj)---> HR_Employee | | (eno, ename, eaddr, salary) | FinanceEmployeeDAO --(conn obj) | | conn.setAutoCommit(false) // begin tx | hrDAO.insertRecord() | finDAO.insertRecord() | conn.commit() // both succeed: commit | OR | conn.rollback() // any fails: rollback BOTH We can make both DAO classes using single jdbc con object to perform persistence operations of each request, and we can use that jdbc con object either to commit (if both DAO have successfully inserted) or to rollback (if any DAO has failed to insert the record).

6. Full-Length Industry Project: Employee Registration

Business Scenario

When a new employee joins National Bank of India, their data must be inserted into two database tables: HR_Employee (HR department) and Finance_Employee (Finance department with salary details). Both inserts must succeed or both must fail (transaction). The Business Delegate handles: (1) converting VO (all Strings from form) to BO (correct types), (2) catching technology-specific exceptions and throwing project-specific ones, (3) managing the transaction across both DAOs using a shared JDBC connection.

High-Level Design (HLD)

+----------------------------------------------+ | PRESENTATION TIER | | Browser Window (employee_register.jsp) | | emp name: [____] | | emp addr: [____] | | emp salary: [____] | | emp desg: [____] | | [Register Employee] | | v | | MainControllerServlet | | - reads form data into EmployeeVO | | - calls BusinessDelegate method | | - forwards to show_result.jsp or error.jsp | +----------------------------------------------+ | v +----------------------------------------------+ | BUSINESS TIER | | EmployeeOperations (BusinessDelegate) | | - converts EmployeeVO to BO class objects | | - gets Connection from ConnectionFactory | | - creates both DAO class objects | | - invokes methods on both DAO classes | | - manages transaction (commit/rollback) | | - catches SQLException, throws | | InternalProblemException | | | | HREmployeeDAO FinanceEmployeeDAO | | - INSERT into - INSERT into | | HR_Employee Finance_Employee | | - uses shared conn - uses shared conn | +----------------------------------------------+ | v +----------------------------------------------+ | DATA TIER | | ConnectionFactory | | - reads jdbc.properties | | - returns Connection | | | | Oracle/MySQL Database | | HR_Employee (eno, ename, eaddr, salary) | | Finance_Employee (eno, ename, eaddr, | | salary, netSalary) | +----------------------------------------------+

Low-Level Design (LLD)

+-----------------------------------------------------+ | EmployeeVO (Presentation tier - all Strings) | +-----------------------------------------------------+ | - empno: String | | - ename: String | | - eaddr: String | | - salary: String | | - desg: String | +-----------------------------------------------------+ +-----------------------------------------------------+ | HREmployeeBO (Business tier - correct types) | +-----------------------------------------------------+ | - empno: int | | - ename: String | | - eaddr: String | | - salary: double | +-----------------------------------------------------+ +-----------------------------------------------------+ | FinanceEmployeeBO (Business tier) | +-----------------------------------------------------+ | - empno: int | | - ename: String | | - eaddr: String | | - salary: double | | - netSalary: double | +-----------------------------------------------------+ +-----------------------------------------------------+ | EmployeeOperations (BusinessDelegate) | +-----------------------------------------------------+ | + registerEmployee(EmployeeVO): String | | throws InternalProblemException | | 1. Convert VO to HREmployeeBO + FinanceEmployeeBO | | 2. Get Connection from ConnectionFactory | | 3. con.setAutoCommit(false) | | 4. Create HREmployeeDAO(con) | | 5. Create FinanceEmployeeDAO(con) | | 6. hrDAO.insertRecord(hrBO) | | 7. finDAO.insertRecord(finBO) | | 8. con.commit() or con.rollback() | | 9. Catch SQLException -> throw InternalProblem | +-----------------------------------------------------+ +-----------------------------------------------------+ | HREmployeeDAO | +-----------------------------------------------------+ | - con: Connection (injected by BusinessDelegate) | | - INSERT_QUERY: String [static final] | +-----------------------------------------------------+ | + insertRecord(HREmployeeBO): int | +-----------------------------------------------------+ +-----------------------------------------------------+ | FinanceEmployeeDAO | +-----------------------------------------------------+ | - con: Connection (injected by BusinessDelegate) | | - INSERT_QUERY: String [static final] | +-----------------------------------------------------+ | + insertRecord(FinanceEmployeeBO): int | +-----------------------------------------------------+

Project Structure

nbi-employee-registration/ ├── src/main/java/com/nbi/employee/ │ ├── vo/ │ │ └── EmployeeVO.java (all String properties) │ ├── bo/ │ │ ├── HREmployeeBO.java (correct types) │ │ └── FinanceEmployeeBO.java │ ├── dao/ │ │ ├── HREmployeeDAO.java │ │ └── FinanceEmployeeDAO.java │ ├── delegate/ │ │ └── EmployeeOperations.java (BusinessDelegate) │ ├── exception/ │ │ └── InternalProblemException.java │ ├── factory/ │ │ └── ConnectionFactory.java │ └── Application.java ├── src/main/resources/ │ └── jdbc.properties └── pom.xml

EmployeeVO.java (Presentation tier - all Strings)

package com.nbi.employee.vo;

public class EmployeeVO {
    private String empno;
    private String ename;
    private String eaddr;
    private String salary;
    private String desg;

    // All properties are String regardless of actual DB types.
    // Presentation tier never changes when DB types change.

    public String getEmpno() { return empno; }
    public void setEmpno(String empno) { this.empno = empno; }
    public String getEname() { return ename; }
    public void setEname(String ename) { this.ename = ename; }
    public String getEaddr() { return eaddr; }
    public void setEaddr(String eaddr) { this.eaddr = eaddr; }
    public String getSalary() { return salary; }
    public void setSalary(String salary) { this.salary = salary; }
    public String getDesg() { return desg; }
    public void setDesg(String desg) { this.desg = desg; }
}

HREmployeeBO.java and FinanceEmployeeBO.java

// HREmployeeBO.java
package com.nbi.employee.bo;

public class HREmployeeBO {
    private int empno;
    private String ename;
    private String eaddr;
    private double salary;

    public HREmployeeBO(int empno, String ename, String eaddr, double salary) {
        this.empno = empno;
        this.ename = ename;
        this.eaddr = eaddr;
        this.salary = salary;
    }

    public int getEmpno() { return empno; }
    public String getEname() { return ename; }
    public String getEaddr() { return eaddr; }
    public double getSalary() { return salary; }
}

// FinanceEmployeeBO.java
package com.nbi.employee.bo;

public class FinanceEmployeeBO {
    private int empno;
    private String ename;
    private String eaddr;
    private double salary;
    private double netSalary;

    public FinanceEmployeeBO(int empno, String ename, String eaddr,
                              double salary, double netSalary) {
        this.empno = empno;
        this.ename = ename;
        this.eaddr = eaddr;
        this.salary = salary;
        this.netSalary = netSalary;
    }

    public int getEmpno() { return empno; }
    public String getEname() { return ename; }
    public String getEaddr() { return eaddr; }
    public double getSalary() { return salary; }
    public double getNetSalary() { return netSalary; }
}

InternalProblemException.java

package com.nbi.employee.exception;

public class InternalProblemException extends Exception {
    public InternalProblemException(String msg) {
        super(msg);
    }
}

HREmployeeDAO.java

package com.nbi.employee.dao;

import com.nbi.employee.bo.HREmployeeBO;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class HREmployeeDAO {
    private static final String INSERT_QUERY =
        "INSERT INTO HR_Employee (empno, ename, eaddr, salary) VALUES (?, ?, ?, ?)";

    private final Connection con;

    // Connection injected by BusinessDelegate (not created internally)
    public HREmployeeDAO(Connection con) {
        this.con = con;
    }

    public int insertRecord(HREmployeeBO bo) throws Exception {
        PreparedStatement ps = con.prepareStatement(INSERT_QUERY);
        ps.setInt(1, bo.getEmpno());
        ps.setString(2, bo.getEname());
        ps.setString(3, bo.getEaddr());
        ps.setDouble(4, bo.getSalary());
        return ps.executeUpdate();
    }
}

FinanceEmployeeDAO.java

package com.nbi.employee.dao;

import com.nbi.employee.bo.FinanceEmployeeBO;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class FinanceEmployeeDAO {
    private static final String INSERT_QUERY =
        "INSERT INTO Finance_Employee (empno, ename, eaddr, salary, net_salary) VALUES (?, ?, ?, ?, ?)";

    private final Connection con;

    public FinanceEmployeeDAO(Connection con) {
        this.con = con;
    }

    public int insertRecord(FinanceEmployeeBO bo) throws Exception {
        PreparedStatement ps = con.prepareStatement(INSERT_QUERY);
        ps.setInt(1, bo.getEmpno());
        ps.setString(2, bo.getEname());
        ps.setString(3, bo.getEaddr());
        ps.setDouble(4, bo.getSalary());
        ps.setDouble(5, bo.getNetSalary());
        return ps.executeUpdate();
    }
}

EmployeeOperations.java (Business Delegate - all 3 responsibilities)

package com.nbi.employee.delegate;

import com.nbi.employee.bo.HREmployeeBO;
import com.nbi.employee.bo.FinanceEmployeeBO;
import com.nbi.employee.dao.HREmployeeDAO;
import com.nbi.employee.dao.FinanceEmployeeDAO;
import com.nbi.employee.exception.InternalProblemException;
import com.nbi.employee.factory.ConnectionFactory;
import com.nbi.employee.vo.EmployeeVO;
import java.sql.Connection;

public class EmployeeOperations {

    public String registerEmployee(EmployeeVO vo) throws InternalProblemException {
        Connection con = null;
        try {
            // RESPONSIBILITY a) Convert VO (all Strings) to BO (correct types)
            int empno = Integer.parseInt(vo.getEmpno());
            double salary = Double.parseDouble(vo.getSalary());
            double netSalary = salary * 0.75;  // business logic: 25% tax

            HREmployeeBO hrBO = new HREmployeeBO(
                empno, vo.getEname(), vo.getEaddr(), salary);
            FinanceEmployeeBO finBO = new FinanceEmployeeBO(
                empno, vo.getEname(), vo.getEaddr(), salary, netSalary);

            // RESPONSIBILITY c) Single connection for Transaction Management
            con = ConnectionFactory.getConnection();
            con.setAutoCommit(false);  // BEGIN TRANSACTION

            // Create both DAO classes with SHARED connection
            HREmployeeDAO hrDAO = new HREmployeeDAO(con);
            FinanceEmployeeDAO finDAO = new FinanceEmployeeDAO(con);

            // Execute both inserts
            int hrCount = hrDAO.insertRecord(hrBO);
            int finCount = finDAO.insertRecord(finBO);

            // Both succeeded: COMMIT
            con.commit();
            System.out.println("Transaction committed. HR: " + hrCount
                + ", Finance: " + finCount);
            return "Employee registration successful";

        } catch (NumberFormatException nfe) {
            return "Invalid input format: " + nfe.getMessage();

        } catch (Exception e) {
            // RESPONSIBILITY b) Convert technology-specific exception
            // to project-specific user-defined exception
            try {
                if (con != null) {
                    con.rollback();  // ROLLBACK both tables
                    System.out.println("Transaction rolled back");
                }
            } catch (Exception rollbackEx) {
                rollbackEx.printStackTrace();
            }

            // Wrap technology-specific exception (SQLException/HibernateException)
            // into project-specific InternalProblemException
            throw new InternalProblemException(
                "Employee registration failed: " + e.getMessage());

        } finally {
            ConnectionFactory.closeConnection(con);
        }
    }
}

Application.java (simulates Controller Servlet)

package com.nbi.employee;

import com.nbi.employee.delegate.EmployeeOperations;
import com.nbi.employee.exception.InternalProblemException;
import com.nbi.employee.vo.EmployeeVO;

public class Application {
    public static void main(String[] args) {
        EmployeeOperations delegate = new EmployeeOperations();

        // Scenario 1: Successful registration
        System.out.println("=== Scenario 1: Successful Registration ===");
        EmployeeVO vo1 = new EmployeeVO();
        vo1.setEmpno("1001");
        vo1.setEname("Rajesh Kumar");
        vo1.setEaddr("Hyderabad");
        vo1.setSalary("75000");
        vo1.setDesg("Senior Developer");

        try {
            String result = delegate.registerEmployee(vo1);
            System.out.println("Result: " + result);
            // forward to show_result.jsp
        } catch (InternalProblemException ipe) {
            System.out.println("Error: " + ipe.getMessage());
            // forward to error.jsp
        }

        System.out.println();

        // Scenario 2: Invalid input (VO to BO conversion fails)
        System.out.println("=== Scenario 2: Invalid Salary ===");
        EmployeeVO vo2 = new EmployeeVO();
        vo2.setEmpno("1002");
        vo2.setEname("Priya Singh");
        vo2.setEaddr("Mumbai");
        vo2.setSalary("not_a_number");  // invalid!
        vo2.setDesg("Manager");

        try {
            String result = delegate.registerEmployee(vo2);
            System.out.println("Result: " + result);
        } catch (InternalProblemException ipe) {
            System.out.println("Error: " + ipe.getMessage());
        }

        System.out.println();

        // Scenario 3: DB failure (simulated - table doesn't exist)
        System.out.println("=== Scenario 3: DB Failure ===");
        try {
            String result = delegate.registerEmployee(vo1);
            System.out.println("Result: " + result);
        } catch (InternalProblemException ipe) {
            // This is InternalProblemException, NOT SQLException
            // Controller doesn't know which DB technology is used
            System.out.println("Error (project-specific): " + ipe.getMessage());
        }
    }
}

Expected Output

=== Scenario 1: Successful Registration === Transaction committed. HR: 1, Finance: 1 Result: Employee registration successful === Scenario 2: Invalid Salary === Result: Invalid input format: For input string: "not_a_number" === Scenario 3: DB Failure === Transaction rolled back Error (project-specific): Employee registration failed: Table 'HR_Employee' doesn't exist

Key observations from the project

  1. VO class has ALL String properties. Presentation tier never changes when DB column types change. Only BusinessDelegate handles the conversion.
  2. Shared connection enables transaction. Both HREmployeeDAO and FinanceEmployeeDAO receive the same Connection object. con.commit() commits both inserts. con.rollback() rolls back both.
  3. Exception translation protects presentation tier. Controller catches InternalProblemException (project-specific), never SQLException or HibernateException. If DAO technology changes, only BusinessDelegate's catch block changes.
  4. Business logic lives in BusinessDelegate. Net salary calculation (75% of gross) is done here, not in DAO or Controller. DAO has only SQL.
  5. If FinanceEmployeeDAO fails, HREmployeeDAO insert is also rolled back. Do everything or nothing principle. Consistent state guaranteed.

7. DB Schema (from course notes)

-- HR Employee table
CREATE TABLE HR_Employee (
    empno   NUMBER(10) NOT NULL ENABLE,
    ename   VARCHAR2(20 BYTE),
    eaddr   VARCHAR2(20 BYTE),
    desg    VARCHAR2(20 BYTE),
    CONSTRAINT HR_Employee_PK PRIMARY KEY (empno)
);

-- Finance Employee table
CREATE TABLE Finance_Employee (
    empno      NUMBER(10) NOT NULL ENABLE,
    ename      VARCHAR2(20 BYTE),
    eaddr      VARCHAR2(20 BYTE),
    desg       VARCHAR2(20 BYTE),
    salary     FLOAT(126),
    CONSTRAINT Finance_Employee_PK PRIMARY KEY (empno)
);

-- Sequence for auto-generated employee IDs
CREATE SEQUENCE empno_seq MINVALUE 1 MAXVALUE 1000
    INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE;

8. When to Use and When NOT to Use

When to use Business Delegate

ScenarioWhy
Multi-tier web applicationDecouples presentation from business tier
Multiple DAO operations in one requestShared connection enables transaction management
Technology-agnostic error handlingConverts DB exceptions to project exceptions
Data format conversion neededConverts String VO to typed BO
Team separationPresentation and business teams work independently

When NOT to use

ScenarioAlternative
Spring Boot applicationSpring @Transactional handles transactions. @ControllerAdvice handles exceptions. ModelMapper handles VO-to-BO.
Single DAO per requestNo need for shared connection or transaction coordination
Microservice with REST APIService layer with DTOs replaces BusinessDelegate

9. Quick Reference Card

ResponsibilityWhat it doesWithout BDWith BD
VO to BOConverts all-String VO to typed BOVO changes when DB types changeOnly BD changes
Exception translationConverts tech exception to project exceptionController catches SQLExceptionController catches project exception
Transaction MgmtShared connection, atomic commit/rollbackEach DAO has own connectionAll DAOs share one connection
JEE Pattern Business Tier Hard

Business Delegate Design Pattern in Java

Complete Tutorial | 10 Sections | Full Employee Registration Project with HLD and LLD | VO to BO Conversion | Exception Translation | Transaction Management | Multiple Scenarios | 12 Interview Questions

1. What is Business Delegate Pattern?

Definition

The Business Delegate pattern is a JEE Business Tier Pattern. It acts as a bridge between the presentation tier (servlet/controller) and the business tier (service + DAO classes). The Business Delegate and DAO classes together are capable of processing requests and generating results.

One line: A class that sits between the controller and the DAO layer, handling data conversion, exception translation, and transaction management so the presentation tier stays loosely coupled from the business tier.

Web Application Layered Architecture: view comp (jsp/html) servlet/controller (Java class) | | Business Delegate DAO (Java class) v v | | request -----> (monitoring ----> (b.logic) -----> (Persistence ----> DB s/w response <---- logic) <---- (other <----- logic) <---- Persistent logic) (CRUD operations) Data |---- Presentation tier comps ----|---- Business tier comps --------| (view, controller comps are (Business Delegate + DAO classes are capable of taking requests from capable of processing requests browser and sending responses and generating results) back to browser)

Why loose coupling between tiers?

It is always recommended to develop presentation tier and business tier comps as loosely coupled comps, i.e., the degree of dependency should be less between both tiers. So the modifications done in presentation tier comps should not affect business tier comps and vice-versa.

The PM/Project Leader divides team into two parties:

  • Party 1: Business tier developers - uses Java classes or Spring or Hibernate to develop business logic and persistence logic
  • Party 2: Presentation tier developers - uses Servlet/JSP technologies and develops view/controller layer logics

Since these two parties can work parallelly either by staying in the same company or in two different branches of the same company, we need loose coupling between presentation tier comps and business tier comps.

2. Three Responsibilities of Business Delegate

Business Delegate in Layered App will be used for 3 operations/use-cases
  1. Converts VO class object to BO class object by collecting all inputs from enduser in the form of String values
  2. Converts DAO class generated technology-specific exception to project-specific user-defined exception
  3. Provides single JDBC connection object to multiple related DAO classes for Transaction Management support

3. Responsibility a) VO to BO Conversion

Problem (without Business Delegate)

Problem: DTO/VO class changes when Business tier BO class changes form page (View comp) Servlet comp Business tier emp name: [____] read form EmployeeBussDelegate DAO emp salary: [____] data to b.logic SQL DB s/w emp addr: [____] DTO class obj gross,net salary query emp desg: [____] (c2) (c3) BO class obj If "salary" column in DB changes from int to float: - BO class (Business tier): salary type changes to double - DTO class (Presentation tier): ALSO must change to double - This kills loose coupling! Presentation tier should NOT know about DB changes.
// Problem: DTO class mirrors BO class types
public class EmployeeDTO {         // Presentation tier
    private Integer empno;
    private String ename;
    private String eaddr;
    private String salary;         // If DB changes salary to double,
    private String desg;           // this must change too!
    // setters and getters
}

public class EmployeeBO {          // Business tier
    private Integer empno;
    private String ename;
    private String eaddr;
    private Integer salary;        // Changed to double in DB
    private Integer grossSalary;   // Business tier level
    private Integer netSalary;     // Business tier level
    // setters and getters
}

Solution: Business Delegate converts VO to BO

Solution: Servlet stores ALL inputs as Strings in VO class. Business Delegate converts VO to BO as needed. form page (View comp) Servlet comp BusinessDelegate DAO emp name: [____] read form b.logic SQL DB s/w emp salary: [____] data to gross,net salary emp addr: [____] VO class obj Convert VO to emp desg: [____] (all Strings) BO class obj NO CHANGES! as needed for DAO Employee VO class (Presentation tier): Employee BO class (Business tier): private String empno; private Integer empno; private String ename; NO CHANGE private String ename; change to double private String eaddr; needed in private String eaddr; private String salary; VO class! private Integer salary; private String desg; Always String. private Integer grossSalary; netSalary; change to double

Key insight: For any change happened in Business tier DB table, we are forced to modify other business tier comps like BO, DAO, Business Delegate (not a problem because they are in the same tier). But there is no need modifying any code in VO class, Servlet comp of presentation tier, because VO class keeps all the inputs as String values always.

4. Responsibility b) Exception Translation

Problem (without Business Delegate)

Problem: Technology-specific exceptions leak to presentation tier Browser --> View comp --> Servlet comp --> BusinessDelegate --> DAO class --> DB s/w | | catches and throws SQLException displays to user (if JDBC) OR HibernateException (if Hibernate) If DAO technology changes from JDBC to Hibernate: SQLException changes to HibernateException --> BusinessDelegate code must change --> Controller/Servlet comp must change! --> This kills loose coupling between tiers.

Solution: Convert to project-specific exception

// Custom project-specific exception
public class InternalProblemException extends Exception {
    public InternalProblemException(String msg) {
        super(msg);
    }
}

// In BusinessDelegate: catch technology-specific, throw project-specific
public class EmployeeOperations {
    // BusinessDelegate class

    public String registerEmployee(EmployeeVO vo) throws InternalProblemException {
        try {
            // ... business logic + DAO calls
        } catch (SQLException sqle) {
            // Catch technology-specific exception
            // Convert to project-specific user-defined exception
            throw new InternalProblemException(sqle.getMessage());
        }
    }
}

// In Servlet: catches ONLY InternalProblemException
// If DAO changes from JDBC to Hibernate, only BusinessDelegate changes.
// Servlet code stays the same.

// Controller Servlet code:
try {
    // ... business logic
    // ... invoke service method
} catch (InternalProblemException ipe) {
    // project-specific exception, not technology-specific
    ipe.printStackTrace();
}
Key Benefit

Because of this code written in BusinessDelegate class, if the technology and exception changes in DAO class, we need to just modify BusinessDelegate class code exception handling. The Servlet comp code exception because business delegate is converting the technology-specific exception to project-specific user-defined exception.

5. Responsibility c) Transaction Management

Transaction Management

The process of combining related operations into single unit and executing them by applying do everything or nothing principle is called Transaction Management (Tx Mgmt).

  • Use case 1: Transfer Money - The withdraw and deposit operations should be executed by applying do everything or nothing principle.
  • Use case 2: Employee Registration - Insertion of record in HR DB table AND insertion of record in Finance DB table should be executed by applying do everything or nothing principle.

Transaction Management (TxMgmt) deals with 3 operations:

  1. Begin Tx - con.setAutoCommit(false)
  2. Execute logic - execute the queries
  3. Commit or Rollback Tx - con.commit() or con.rollback()

Problem: Separate connections per DAO

Problem: Each DAO gets its own JDBC connection EmployeeBusinessDelegate form page | name: [__] | HREmployeeDAO Finance_Employee addr: [__] --> | --> jdbc conn1 --------> (eno, ename, eaddr, salary) salary: [__] | con1.commit() | con1.rollback() HR_Employee | --> FinanceEmployeeDAO --> (eno, ename, eaddr, salary, netSalary) jdbc conn2 con2.commit() con2.rollback() Since both DAO classes are using their own separate jdbc con objects, we CANNOT perform commit and rollback operations on both DB tables at a time with the support of Transaction Management. If HREmployeeDAO succeeds but FinanceEmployeeDAO fails, we have an inconsistent state! Cannot rollback HR insert.

Solution: Single shared connection

Solution: Business Delegate provides ONE jdbc connection to both DAOs EmployeeBusinessDelegate form page | gets conn obj Finance_Employee name: [__] | from ConnectionFactory (eno, ename, eaddr, salary, netSalary) addr: [__] --> | | salary: [__] | v | HREmployeeDAO ---(conn obj)---> HR_Employee | | (eno, ename, eaddr, salary) | FinanceEmployeeDAO --(conn obj) | | conn.setAutoCommit(false) // begin tx | hrDAO.insertRecord() | finDAO.insertRecord() | conn.commit() // both succeed: commit | OR | conn.rollback() // any fails: rollback BOTH We can make both DAO classes using single jdbc con object to perform persistence operations of each request, and we can use that jdbc con object either to commit (if both DAO have successfully inserted) or to rollback (if any DAO has failed to insert the record).

6. Full-Length Industry Project: Employee Registration

Business Scenario

When a new employee joins National Bank of India, their data must be inserted into two database tables: HR_Employee (HR department) and Finance_Employee (Finance department with salary details). Both inserts must succeed or both must fail (transaction). The Business Delegate handles: (1) converting VO (all Strings from form) to BO (correct types), (2) catching technology-specific exceptions and throwing project-specific ones, (3) managing the transaction across both DAOs using a shared JDBC connection.

High-Level Design (HLD)

+----------------------------------------------+ | PRESENTATION TIER | | Browser Window (employee_register.jsp) | | emp name: [____] | | emp addr: [____] | | emp salary: [____] | | emp desg: [____] | | [Register Employee] | | v | | MainControllerServlet | | - reads form data into EmployeeVO | | - calls BusinessDelegate method | | - forwards to show_result.jsp or error.jsp | +----------------------------------------------+ | v +----------------------------------------------+ | BUSINESS TIER | | EmployeeOperations (BusinessDelegate) | | - converts EmployeeVO to BO class objects | | - gets Connection from ConnectionFactory | | - creates both DAO class objects | | - invokes methods on both DAO classes | | - manages transaction (commit/rollback) | | - catches SQLException, throws | | InternalProblemException | | | | HREmployeeDAO FinanceEmployeeDAO | | - INSERT into - INSERT into | | HR_Employee Finance_Employee | | - uses shared conn - uses shared conn | +----------------------------------------------+ | v +----------------------------------------------+ | DATA TIER | | ConnectionFactory | | - reads jdbc.properties | | - returns Connection | | | | Oracle/MySQL Database | | HR_Employee (eno, ename, eaddr, salary) | | Finance_Employee (eno, ename, eaddr, | | salary, netSalary) | +----------------------------------------------+

Low-Level Design (LLD)

+-----------------------------------------------------+ | EmployeeVO (Presentation tier - all Strings) | +-----------------------------------------------------+ | - empno: String | | - ename: String | | - eaddr: String | | - salary: String | | - desg: String | +-----------------------------------------------------+ +-----------------------------------------------------+ | HREmployeeBO (Business tier - correct types) | +-----------------------------------------------------+ | - empno: int | | - ename: String | | - eaddr: String | | - salary: double | +-----------------------------------------------------+ +-----------------------------------------------------+ | FinanceEmployeeBO (Business tier) | +-----------------------------------------------------+ | - empno: int | | - ename: String | | - eaddr: String | | - salary: double | | - netSalary: double | +-----------------------------------------------------+ +-----------------------------------------------------+ | EmployeeOperations (BusinessDelegate) | +-----------------------------------------------------+ | + registerEmployee(EmployeeVO): String | | throws InternalProblemException | | 1. Convert VO to HREmployeeBO + FinanceEmployeeBO | | 2. Get Connection from ConnectionFactory | | 3. con.setAutoCommit(false) | | 4. Create HREmployeeDAO(con) | | 5. Create FinanceEmployeeDAO(con) | | 6. hrDAO.insertRecord(hrBO) | | 7. finDAO.insertRecord(finBO) | | 8. con.commit() or con.rollback() | | 9. Catch SQLException -> throw InternalProblem | +-----------------------------------------------------+ +-----------------------------------------------------+ | HREmployeeDAO | +-----------------------------------------------------+ | - con: Connection (injected by BusinessDelegate) | | - INSERT_QUERY: String [static final] | +-----------------------------------------------------+ | + insertRecord(HREmployeeBO): int | +-----------------------------------------------------+ +-----------------------------------------------------+ | FinanceEmployeeDAO | +-----------------------------------------------------+ | - con: Connection (injected by BusinessDelegate) | | - INSERT_QUERY: String [static final] | +-----------------------------------------------------+ | + insertRecord(FinanceEmployeeBO): int | +-----------------------------------------------------+

Project Structure

nbi-employee-registration/ ├── src/main/java/com/nbi/employee/ │ ├── vo/ │ │ └── EmployeeVO.java (all String properties) │ ├── bo/ │ │ ├── HREmployeeBO.java (correct types) │ │ └── FinanceEmployeeBO.java │ ├── dao/ │ │ ├── HREmployeeDAO.java │ │ └── FinanceEmployeeDAO.java │ ├── delegate/ │ │ └── EmployeeOperations.java (BusinessDelegate) │ ├── exception/ │ │ └── InternalProblemException.java │ ├── factory/ │ │ └── ConnectionFactory.java │ └── Application.java ├── src/main/resources/ │ └── jdbc.properties └── pom.xml

EmployeeVO.java (Presentation tier - all Strings)

package com.nbi.employee.vo;

public class EmployeeVO {
    private String empno;
    private String ename;
    private String eaddr;
    private String salary;
    private String desg;

    // All properties are String regardless of actual DB types.
    // Presentation tier never changes when DB types change.

    public String getEmpno() { return empno; }
    public void setEmpno(String empno) { this.empno = empno; }
    public String getEname() { return ename; }
    public void setEname(String ename) { this.ename = ename; }
    public String getEaddr() { return eaddr; }
    public void setEaddr(String eaddr) { this.eaddr = eaddr; }
    public String getSalary() { return salary; }
    public void setSalary(String salary) { this.salary = salary; }
    public String getDesg() { return desg; }
    public void setDesg(String desg) { this.desg = desg; }
}

HREmployeeBO.java and FinanceEmployeeBO.java

// HREmployeeBO.java
package com.nbi.employee.bo;

public class HREmployeeBO {
    private int empno;
    private String ename;
    private String eaddr;
    private double salary;

    public HREmployeeBO(int empno, String ename, String eaddr, double salary) {
        this.empno = empno;
        this.ename = ename;
        this.eaddr = eaddr;
        this.salary = salary;
    }

    public int getEmpno() { return empno; }
    public String getEname() { return ename; }
    public String getEaddr() { return eaddr; }
    public double getSalary() { return salary; }
}

// FinanceEmployeeBO.java
package com.nbi.employee.bo;

public class FinanceEmployeeBO {
    private int empno;
    private String ename;
    private String eaddr;
    private double salary;
    private double netSalary;

    public FinanceEmployeeBO(int empno, String ename, String eaddr,
                              double salary, double netSalary) {
        this.empno = empno;
        this.ename = ename;
        this.eaddr = eaddr;
        this.salary = salary;
        this.netSalary = netSalary;
    }

    public int getEmpno() { return empno; }
    public String getEname() { return ename; }
    public String getEaddr() { return eaddr; }
    public double getSalary() { return salary; }
    public double getNetSalary() { return netSalary; }
}

InternalProblemException.java

package com.nbi.employee.exception;

public class InternalProblemException extends Exception {
    public InternalProblemException(String msg) {
        super(msg);
    }
}

HREmployeeDAO.java

package com.nbi.employee.dao;

import com.nbi.employee.bo.HREmployeeBO;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class HREmployeeDAO {
    private static final String INSERT_QUERY =
        "INSERT INTO HR_Employee (empno, ename, eaddr, salary) VALUES (?, ?, ?, ?)";

    private final Connection con;

    // Connection injected by BusinessDelegate (not created internally)
    public HREmployeeDAO(Connection con) {
        this.con = con;
    }

    public int insertRecord(HREmployeeBO bo) throws Exception {
        PreparedStatement ps = con.prepareStatement(INSERT_QUERY);
        ps.setInt(1, bo.getEmpno());
        ps.setString(2, bo.getEname());
        ps.setString(3, bo.getEaddr());
        ps.setDouble(4, bo.getSalary());
        return ps.executeUpdate();
    }
}

FinanceEmployeeDAO.java

package com.nbi.employee.dao;

import com.nbi.employee.bo.FinanceEmployeeBO;
import java.sql.Connection;
import java.sql.PreparedStatement;

public class FinanceEmployeeDAO {
    private static final String INSERT_QUERY =
        "INSERT INTO Finance_Employee (empno, ename, eaddr, salary, net_salary) VALUES (?, ?, ?, ?, ?)";

    private final Connection con;

    public FinanceEmployeeDAO(Connection con) {
        this.con = con;
    }

    public int insertRecord(FinanceEmployeeBO bo) throws Exception {
        PreparedStatement ps = con.prepareStatement(INSERT_QUERY);
        ps.setInt(1, bo.getEmpno());
        ps.setString(2, bo.getEname());
        ps.setString(3, bo.getEaddr());
        ps.setDouble(4, bo.getSalary());
        ps.setDouble(5, bo.getNetSalary());
        return ps.executeUpdate();
    }
}

EmployeeOperations.java (Business Delegate - all 3 responsibilities)

package com.nbi.employee.delegate;

import com.nbi.employee.bo.HREmployeeBO;
import com.nbi.employee.bo.FinanceEmployeeBO;
import com.nbi.employee.dao.HREmployeeDAO;
import com.nbi.employee.dao.FinanceEmployeeDAO;
import com.nbi.employee.exception.InternalProblemException;
import com.nbi.employee.factory.ConnectionFactory;
import com.nbi.employee.vo.EmployeeVO;
import java.sql.Connection;

public class EmployeeOperations {

    public String registerEmployee(EmployeeVO vo) throws InternalProblemException {
        Connection con = null;
        try {
            // RESPONSIBILITY a) Convert VO (all Strings) to BO (correct types)
            int empno = Integer.parseInt(vo.getEmpno());
            double salary = Double.parseDouble(vo.getSalary());
            double netSalary = salary * 0.75;  // business logic: 25% tax

            HREmployeeBO hrBO = new HREmployeeBO(
                empno, vo.getEname(), vo.getEaddr(), salary);
            FinanceEmployeeBO finBO = new FinanceEmployeeBO(
                empno, vo.getEname(), vo.getEaddr(), salary, netSalary);

            // RESPONSIBILITY c) Single connection for Transaction Management
            con = ConnectionFactory.getConnection();
            con.setAutoCommit(false);  // BEGIN TRANSACTION

            // Create both DAO classes with SHARED connection
            HREmployeeDAO hrDAO = new HREmployeeDAO(con);
            FinanceEmployeeDAO finDAO = new FinanceEmployeeDAO(con);

            // Execute both inserts
            int hrCount = hrDAO.insertRecord(hrBO);
            int finCount = finDAO.insertRecord(finBO);

            // Both succeeded: COMMIT
            con.commit();
            System.out.println("Transaction committed. HR: " + hrCount
                + ", Finance: " + finCount);
            return "Employee registration successful";

        } catch (NumberFormatException nfe) {
            return "Invalid input format: " + nfe.getMessage();

        } catch (Exception e) {
            // RESPONSIBILITY b) Convert technology-specific exception
            // to project-specific user-defined exception
            try {
                if (con != null) {
                    con.rollback();  // ROLLBACK both tables
                    System.out.println("Transaction rolled back");
                }
            } catch (Exception rollbackEx) {
                rollbackEx.printStackTrace();
            }

            // Wrap technology-specific exception (SQLException/HibernateException)
            // into project-specific InternalProblemException
            throw new InternalProblemException(
                "Employee registration failed: " + e.getMessage());

        } finally {
            ConnectionFactory.closeConnection(con);
        }
    }
}

Application.java (simulates Controller Servlet)

package com.nbi.employee;

import com.nbi.employee.delegate.EmployeeOperations;
import com.nbi.employee.exception.InternalProblemException;
import com.nbi.employee.vo.EmployeeVO;

public class Application {
    public static void main(String[] args) {
        EmployeeOperations delegate = new EmployeeOperations();

        // Scenario 1: Successful registration
        System.out.println("=== Scenario 1: Successful Registration ===");
        EmployeeVO vo1 = new EmployeeVO();
        vo1.setEmpno("1001");
        vo1.setEname("Rajesh Kumar");
        vo1.setEaddr("Hyderabad");
        vo1.setSalary("75000");
        vo1.setDesg("Senior Developer");

        try {
            String result = delegate.registerEmployee(vo1);
            System.out.println("Result: " + result);
            // forward to show_result.jsp
        } catch (InternalProblemException ipe) {
            System.out.println("Error: " + ipe.getMessage());
            // forward to error.jsp
        }

        System.out.println();

        // Scenario 2: Invalid input (VO to BO conversion fails)
        System.out.println("=== Scenario 2: Invalid Salary ===");
        EmployeeVO vo2 = new EmployeeVO();
        vo2.setEmpno("1002");
        vo2.setEname("Priya Singh");
        vo2.setEaddr("Mumbai");
        vo2.setSalary("not_a_number");  // invalid!
        vo2.setDesg("Manager");

        try {
            String result = delegate.registerEmployee(vo2);
            System.out.println("Result: " + result);
        } catch (InternalProblemException ipe) {
            System.out.println("Error: " + ipe.getMessage());
        }

        System.out.println();

        // Scenario 3: DB failure (simulated - table doesn't exist)
        System.out.println("=== Scenario 3: DB Failure ===");
        try {
            String result = delegate.registerEmployee(vo1);
            System.out.println("Result: " + result);
        } catch (InternalProblemException ipe) {
            // This is InternalProblemException, NOT SQLException
            // Controller doesn't know which DB technology is used
            System.out.println("Error (project-specific): " + ipe.getMessage());
        }
    }
}

Expected Output

=== Scenario 1: Successful Registration === Transaction committed. HR: 1, Finance: 1 Result: Employee registration successful === Scenario 2: Invalid Salary === Result: Invalid input format: For input string: "not_a_number" === Scenario 3: DB Failure === Transaction rolled back Error (project-specific): Employee registration failed: Table 'HR_Employee' doesn't exist

Key observations from the project

  1. VO class has ALL String properties. Presentation tier never changes when DB column types change. Only BusinessDelegate handles the conversion.
  2. Shared connection enables transaction. Both HREmployeeDAO and FinanceEmployeeDAO receive the same Connection object. con.commit() commits both inserts. con.rollback() rolls back both.
  3. Exception translation protects presentation tier. Controller catches InternalProblemException (project-specific), never SQLException or HibernateException. If DAO technology changes, only BusinessDelegate's catch block changes.
  4. Business logic lives in BusinessDelegate. Net salary calculation (75% of gross) is done here, not in DAO or Controller. DAO has only SQL.
  5. If FinanceEmployeeDAO fails, HREmployeeDAO insert is also rolled back. Do everything or nothing principle. Consistent state guaranteed.

7. DB Schema (from course notes)

-- HR Employee table
CREATE TABLE HR_Employee (
    empno   NUMBER(10) NOT NULL ENABLE,
    ename   VARCHAR2(20 BYTE),
    eaddr   VARCHAR2(20 BYTE),
    desg    VARCHAR2(20 BYTE),
    CONSTRAINT HR_Employee_PK PRIMARY KEY (empno)
);

-- Finance Employee table
CREATE TABLE Finance_Employee (
    empno      NUMBER(10) NOT NULL ENABLE,
    ename      VARCHAR2(20 BYTE),
    eaddr      VARCHAR2(20 BYTE),
    desg       VARCHAR2(20 BYTE),
    salary     FLOAT(126),
    CONSTRAINT Finance_Employee_PK PRIMARY KEY (empno)
);

-- Sequence for auto-generated employee IDs
CREATE SEQUENCE empno_seq MINVALUE 1 MAXVALUE 1000
    INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE;

8. When to Use and When NOT to Use

When to use Business Delegate

ScenarioWhy
Multi-tier web applicationDecouples presentation from business tier
Multiple DAO operations in one requestShared connection enables transaction management
Technology-agnostic error handlingConverts DB exceptions to project exceptions
Data format conversion neededConverts String VO to typed BO
Team separationPresentation and business teams work independently

When NOT to use

ScenarioAlternative
Spring Boot applicationSpring @Transactional handles transactions. @ControllerAdvice handles exceptions. ModelMapper handles VO-to-BO.
Single DAO per requestNo need for shared connection or transaction coordination
Microservice with REST APIService layer with DTOs replaces BusinessDelegate

9. Quick Reference Card

ResponsibilityWhat it doesWithout BDWith BD
VO to BOConverts all-String VO to typed BOVO changes when DB types changeOnly BD changes
Exception translationConverts tech exception to project exceptionController catches SQLExceptionController catches project exception
Transaction MgmtShared connection, atomic commit/rollbackEach DAO has own connectionAll DAOs share one connection