Optimizing Data Fetching in J2EE Applications Using Lazy Loading, Fetch Strategies, and AspectJ

This article examines the challenges of lazy loading in J2EE ORM tools, compares eager fetching and pre-fetching, and proposes a modular solution using Aspect‑Oriented Programming to inject custom fetching strategies at the repository level, reducing SQL queries, connection usage, and code bloat.

Art of Distributed System Architecture Design
Art of Distributed System Architecture Design
Art of Distributed System Architecture Design
Optimizing Data Fetching in J2EE Applications Using Lazy Loading, Fetch Strategies, and AspectJ

Typical J2EE applications that use O/R mapping tools encounter the problem of obtaining required data with the most concise SQL queries; lazy loading, which loads dependent data only when it is actually requested, helps avoid creating unnecessary objects.

However, when many SELECT statements are executed, lazy loading can degrade database performance because the needed data is not retrieved in a single query, creating scalability bottlenecks for high‑traffic applications.

For example, fetching a Person together with its Address using lazy loading requires two separate SQL queries (first Person, then Address), increasing communication overhead. A single query that joins both entities solves this issue.

Creating operation‑specific fetching APIs in DAO/Repository layers leads to code bloat, maintenance nightmares, and duplicated logic, especially when the same domain object must be fetched with different data sets.

Lazy loading also forces database connections to stay open until all required data is retrieved, otherwise a lazy‑loading exception is thrown.

Using pre‑fetching to load data from the second‑level cache does not solve the problem for Hibernate, because it still queries the database even when the data exists in the cache.

When an O/R mapping tool can adjust the query to obtain cached objects, the connection‑open issue is avoided because data is fetched in a single step rather than on demand.

The article presents a concrete example with three entities— Employee, Department, and Dependent —and shows the drawbacks of lazy loading and eager fetching for three different operations (fetching employee details, employee with dependents, employee with department).

To avoid code explosion, the fetching strategy is moved out of the Repository/DAO layer and placed in an Aspect. All fetching‑strategy classes implement a common interface, allowing the Aspect to inject the appropriate strategy based on the business use‑case.

The Repository method that uses the fetching strategy is shown below:

public Employee findEmployeeById(int employeeId) {
    List employee = hibernateTemplate.find(fetchingStrategy.queryEmployeeById(),
        new Integer(employeeId));
    if (employee.size() == 0)
        return null;
    return (Employee) employee.get(0);
}

AspectJ is used to change the fetching strategy before the Repository method is invoked. The following pointcuts and advice illustrate how the strategy is swapped for different service calls:

/**
 * Identify the getEmployeeWithDepartmentDetails flow where you need to change the fetching 
 * strategy at repository level 
 */
pointcut empWithDepartmentDetail(): call(* EmployeeRepository.findEmployeeById(int))
    && cflow(execution(* EmployeeDetailsService.getEmployeeWithDepartmentDetails(int)));

/**
 * Before proceeding, update the fetchingStrategy in EmployeeRepositoryImpl to EmployeeWithDepartmentFetchingStrategy
 */
before(EmployeeRepositoryImpl r): empWithDepartmentDetail() && target(r) { 
    r.fetchingStrategy = new EmployeeWithDepartmentFetchingStrategy(); 
}

/** Identify the getEmployeeWithDependentDetails flow */
pointcut empWithDependentDetail(): call(* EmployeeRepository.findEmployeeById(int))
    && cflow(execution(* EmployeeDetailsService.getEmployeeWithDependentDetails(int)));

/** Before proceeding, update the fetchingStrategy to EmployeeWithDependentFetchingStrategy */
before(EmployeeRepositoryImpl r): empWithDependentDetail() && target(r) { 
    r.fetchingStrategy = new EmployeeWithDependentFetchingStrategy(); 
}

Another AspectJ snippet shows how to inject a strategy that fetches both department and dependent information in a single query:

pointcut empWithDependentAndDepartmentDetail(): call(* EmployeeRepository.findEmployeeById(int))
    && cflow(execution(* EmployeeDetailsService.getEmployeeWithDepartmentAndDependentsDetails(int)));

before(EmployeeRepositoryImpl r): empWithDependentAndDepartmentDetail() && target(r) { 
    r.fetchingStrategy = new EmployeeWithDepartmentAndDependentFetchingStaratergy(); 
}

By moving the decision of which query to execute into an Aspect, new business requirements can be satisfied without modifying Service or Repository APIs, keeping the core layers thin and maintainable.

The article also discusses second‑level cache query optimization, noting that pre‑fetching cached entities still triggers database queries in Hibernate, and that only the O/R mapping tool can provide a mechanism to fetch cached data without extra queries.

Finally, the article provides instructions for running the sample project (including setting up the database, importing the code into Eclipse with AJDT, and executing Main.java), and concludes that modular fetching strategies enable efficient data retrieval without inflating repository or service code.

Conclusion: By applying modular fetching strategies via Aspect‑Oriented Programming, backend systems can retrieve data efficiently while avoiding code bloat, excessive SQL queries, and prolonged database connections.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceORMaspectjlazy loadingFetching Strategy
Art of Distributed System Architecture Design
Written by

Art of Distributed System Architecture Design

Introductions to large-scale distributed system architectures; insights and knowledge sharing on large-scale internet system architecture; front-end web architecture overviews; practical tips and experiences with PHP, JavaScript, Erlang, C/C++ and other languages in large-scale internet system development.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.