Data access object
The Data Access Object (DAO) is a structural design pattern in software engineering that provides an abstraction layer between an application's business logic and its underlying data persistence mechanisms, such as databases or file systems.[1] By encapsulating all data access operations—including creation, reading, updating, and deletion (CRUD)—within dedicated DAO classes, the pattern ensures that changes to the data source do not propagate to the core application code.[2] This separation promotes modularity, making it easier to switch persistence technologies, such as from a relational database like MySQL to a NoSQL store like MongoDB, without refactoring business components.[3]
Originating as part of the Core J2EE Patterns for enterprise Java applications, the DAO pattern abstracts specific data access APIs into a generic interface, allowing implementations to vary independently of client code.[1] Key components include a DAO interface that defines standard methods (e.g., findById(), save(), delete()), concrete DAO classes that implement these methods using technologies like JDBC, JPA, or even in-memory storage, and optionally a factory class for instantiating the appropriate DAO based on configuration.[2] For instance, in a Java-based system, a UserDAO interface might be implemented separately for cloud-based or local databases, ensuring the business layer interacts solely through the abstract API.[1]
The pattern's benefits extend to improved testability, as mock DAOs can simulate data access during unit testing, and enhanced maintainability by localizing persistence-specific code.[3] However, it requires careful design to avoid over-abstraction, particularly in modern frameworks where tools like Spring Data or Hibernate often provide built-in DAO-like functionality.[2] Overall, DAO remains a foundational approach for building scalable, layered architectures in object-oriented programming.[1]
Introduction
Definition and Purpose
The Data Access Object (DAO) is a structural design pattern that provides an abstract interface to data sources, such as databases, files, or web services, while encapsulating all associated data access logic.[1][2] This abstraction separates the client interface of a data resource from its underlying access mechanisms, adapting specific data access APIs—such as those for relational databases or XML files—to a standardized, generic interface.[1]
The primary purpose of the DAO pattern is to decouple business logic from persistence details, allowing changes in data access mechanisms without affecting the code that utilizes the data.[1][2] By promoting separation of concerns, it simplifies unit testing of business components in isolation from actual data stores and enhances portability by enabling seamless switching between different persistence technologies, such as from a SQL database to an in-memory store.[2][3]
Key characteristics of the DAO pattern include the use of interfaces to abstract data operations, typically providing uniform methods for CRUD (Create, Read, Update, Delete) functionalities without exposing implementation specifics.[1][2] It hides vendor-specific elements, such as SQL dialects, connection pooling, or exception handling, ensuring that the business layer interacts solely with a clean, portable API.[1] The pattern draws from the roots of the Adapter pattern but focuses distinctly on persistence by bridging incompatible data resource interfaces to support flexible, pluggable implementations.[1]
Historical Development
The Data Access Object (DAO) pattern emerged in the late 1990s during the rise of enterprise Java and multi-tier application architectures, driven by the need to manage relational database interactions in distributed systems.[4] As Java 2 Platform, Enterprise Edition (J2EE) gained traction around 1999, developers sought ways to decouple business logic from data persistence to handle the complexities of scalable, database-centric applications.
The pattern was formally documented in 2001 in the book Core J2EE Patterns: Best Practices and Design Strategies by Deepak Alur, Dan Malks, and John Crupi, published as part of Sun Microsystems' J2EE best practices initiative.[5][6] This work positioned DAO as a key strategy for abstracting data source access, aligning with the release of Enterprise JavaBeans (EJB) 2.0 on August 22, 2001, where it was integrated to simplify entity bean interactions with underlying data stores. The pattern drew influence from earlier concepts like the Table Data Gateway, outlined in Martin Fowler's Patterns of Enterprise Application Architecture (2003), which emphasized encapsulating SQL operations for a single table or view, though DAO extended this to broader persistence abstraction.[7][8]
In the 2000s, DAO adoption expanded beyond pure J2EE environments through frameworks like Spring, which originated in 2002 under Rod Johnson and provided DAO support in its initial releases starting in 2003 to facilitate consistent data access across JDBC, Hibernate, and other technologies.[9][10] By the 2010s, the pattern evolved to accommodate NoSQL databases and cloud-native data sources, with object-NoSQL mappers emerging to maintain abstraction layers similar to traditional DAOs while handling schema-less structures in systems like MongoDB.[11] Into the 2020s, DAO remains relevant in microservices architectures, where it enables isolated data access per service, supporting polyglot persistence in distributed environments.[2]
Architectural Role
Position in Layered Architecture
In layered software architectures, such as the Presentation-Domain-Data model or n-tier designs, the Data Access Object (DAO) occupies the Data Access Layer (DAL), situated between the Business Logic Layer (often encompassing domain logic and services) and the underlying data sources, including databases or file systems. This positioning centralizes all persistence-related responsibilities, shielding higher layers from the specifics of data storage and retrieval mechanisms.[1][12]
The DAO enforces separation of concerns by serving as a clear boundary that prohibits direct data source calls from business code, thereby isolating persistence logic and facilitating modular development in architectures like Model-View-Controller (MVC) or multi-tier systems. This isolation allows independent evolution of the data layer without impacting the presentation or domain components, promoting cleaner code organization and easier maintenance.[1][12][2]
In terms of data flow, business objects or service components invoke operations through DAO interfaces, which abstract and translate these requests into implementation-specific actions, such as executing SQL queries via JDBC for relational databases. This process ensures unidirectional communication from upper layers to the data source, maintaining encapsulation and consistency across the application.[1][2]
The DAO's architectural placement enhances adaptability by allowing interchangeable implementations for different data sources— for example, transitioning from a relational database management system (RDBMS) to a NoSQL store—without requiring alterations to the business logic or presentation layers, thus supporting long-term scalability and technology migrations.[1][2][12]
Interaction with Business Logic
In software applications employing the Data Access Object (DAO) pattern, business logic components, often implemented as service classes, interact with DAOs through well-defined interfaces that expose methods for data operations such as findById() or save(). These invocations typically occur via dependency injection mechanisms or factory patterns, which allow business services to request and receive domain objects or Data Transfer Objects (DTOs) without direct knowledge of the underlying data source implementation. This setup ensures that business logic remains decoupled from persistence details, enabling seamless integration across different tiers of the application.[13][14]
A key benefit of this interaction is the encapsulation provided by DAOs, which shields business logic from low-level data access concerns. For instance, DAOs intercept and translate database-specific exceptions, such as SQLException, into application-level errors that are more meaningful to business services, thereby preventing propagation of technical details upward. Additionally, DAOs often coordinate transaction management across multiple operations—such as a series of saves or updates—ensuring atomicity and consistency without requiring business logic to handle connection pooling or rollback logic directly. This abstraction promotes maintainability and portability, as changes to the data source do not necessitate alterations in the business layer.[13][14]
The flow of data between DAOs and business logic is bidirectional, with DAOs performing preliminary data validation prior to persistence to ensure basic integrity constraints are met, and fetching related entities through mechanisms like lazy loading to populate domain objects on demand. However, DAOs deliberately avoid incorporating complex business rules, such as policy enforcement or workflow orchestration, which are deferred to the service layer to maintain clear separation of concerns. This approach allows business services to orchestrate DAO calls as needed, composing data operations into higher-level processes while leveraging the DAO's role in the data access layer for efficient retrieval and storage.[13]
A common pitfall in DAO-business logic interactions arises when developers overload DAOs with business-specific rules, blurring layer boundaries and potentially contributing to an "anemic domain model" where domain objects become mere data containers lacking behavioral richness. To mitigate this, best practices emphasize keeping DAO interactions purely data-oriented: limit DAO methods to CRUD operations and simple validations, use interfaces to enforce contracts, and route all substantive logic through dedicated service components. This discipline preserves the pattern's intent of modularity and testability, avoiding tightly coupled systems that hinder scalability.[14][15]
Implementation
Core Components
The Data Access Object (DAO) pattern relies on a structured set of core components to abstract data persistence operations from the business logic layer. At its foundation is an abstract interface that defines a standard set of methods for common data operations, such as creating, reading, updating, deleting, and querying entities. For instance, a typical DAO interface for a customer entity might include methods like public Customer findCustomer(int customerID) or public void updateCustomer(Customer customer), ensuring a consistent API regardless of the underlying data source.[13]
The concrete implementation class realizes this interface by encapsulating the specifics of interacting with a particular data source, such as a relational database or a file system. This class handles low-level details like establishing connections, executing SQL queries, managing transactions, and utilizing connection pooling to optimize performance. By isolating these implementation concerns within the concrete DAO, the pattern allows for interchangeable data access strategies without affecting dependent components.[13]
To enable flexibility across different data sources or environments, the DAO pattern integrates a factory mechanism, often through a DAOFactory class or abstract factory pattern. This factory instantiates the appropriate concrete DAO implementations based on runtime configuration, such as selecting a JDBC-based DAO for an Oracle database or a JPA-based one for another persistence provider. Such integration supports seamless switching between databases or even between SQL and non-relational stores, promoting maintainability in multi-environment deployments.[13]
Transfer objects, also known as Data Transfer Objects (DTOs) or value objects, serve as lightweight carriers for data exchanged between the DAO and business components. These serializable objects map database rows or query results to domain-specific representations, encapsulating attributes without exposing the full entity or including behavior. By using DTOs, the DAO avoids direct exposure of persistent entities to clients, reducing coupling and minimizing network overhead in distributed systems, while allowing selective data transfer to match client needs.[13][16]
Basic Example Structure
The Data Access Object (DAO) pattern structures data access through an interface that abstracts persistence operations, allowing implementations to vary without affecting higher layers. This basic example illustrates a simple DAO for a User entity, using pseudocode to maintain language-agnostic clarity. The structure typically includes an interface defining CRUD (Create, Read, Update, Delete) methods and a concrete implementation handling data source interactions, such as via a JDBC-like API.[17][1]
Interface Definition
The DAO interface declares standard methods for data operations, promoting loose coupling by hiding implementation details from business logic. A representative interface for a User DAO might include:
[interface](/page/Interface) UserDAO {
User create(User user);
User findById(int id);
void update(User user);
void delete(int id);
}
[interface](/page/Interface) UserDAO {
User create(User user);
User findById(int id);
void update(User user);
void delete(int id);
}
This interface encapsulates the contract for user persistence, where User is a simple domain object with attributes like id, name, and email.[17][2]
Concrete Implementation
The concrete DAO class implements the interface, performing actual data access logic, such as SQL queries via a database connection API. Exceptions from the data source are typically wrapped in custom exceptions for cleaner error handling. Below is a pseudocode example using mock database calls to simulate JDBC operations:
class UserDAOImpl implements UserDAO {
private DataSource dataSource; // Connection factory, e.g., JDBC DataSource
UserDAOImpl(DataSource ds) {
this.dataSource = ds;
}
User create(User user) {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("INSERT INTO users (name, email) VALUES (?, ?)", Statement.RETURN_GENERATED_KEYS);
stmt.setString(1, user.getName());
stmt.setString(2, user.getEmail());
stmt.executeUpdate();
ResultSet rs = stmt.getGeneratedKeys();
if (rs.next()) {
user.setId(rs.getInt(1));
}
closeResources(conn, stmt, rs);
return user;
} catch (SQLException e) {
throw new DAOException("Failed to create user: " + e.getMessage(), e);
}
}
User findById(int id) {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT id, name, email FROM users WHERE id = ?");
stmt.setInt(1, id);
ResultSet rs = stmt.executeQuery();
User user = null;
if (rs.next()) {
user = new User(rs.getInt("id"), rs.getString("name"), rs.getString("email"));
}
closeResources(conn, stmt, rs);
return user;
} catch (SQLException e) {
throw new DAOException("Failed to find user by ID: " + e.getMessage(), e);
}
}
void update(User user) {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("UPDATE users SET name = ?, email = ? WHERE id = ?");
stmt.setString(1, user.getName());
stmt.setString(2, user.getEmail());
stmt.setInt(3, user.getId());
stmt.executeUpdate();
closeResources(conn, stmt);
} catch (SQLException e) {
throw new DAOException("Failed to update user: " + e.getMessage(), e);
}
}
void delete(int id) {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("DELETE FROM users WHERE id = ?");
stmt.setInt(1, id);
stmt.executeUpdate();
closeResources(conn, stmt);
} catch (SQLException e) {
throw new DAOException("Failed to delete user: " + e.getMessage(), e);
}
}
private void closeResources(AutoCloseable... resources) {
for (AutoCloseable resource : resources) {
try {
if (resource != null) resource.close();
} catch (Exception e) {
// Log silently or handle as needed
}
}
}
}
class UserDAOImpl implements UserDAO {
private DataSource dataSource; // Connection factory, e.g., JDBC DataSource
UserDAOImpl(DataSource ds) {
this.dataSource = ds;
}
User create(User user) {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("INSERT INTO users (name, email) VALUES (?, ?)", Statement.RETURN_GENERATED_KEYS);
stmt.setString(1, user.getName());
stmt.setString(2, user.getEmail());
stmt.executeUpdate();
ResultSet rs = stmt.getGeneratedKeys();
if (rs.next()) {
user.setId(rs.getInt(1));
}
closeResources(conn, stmt, rs);
return user;
} catch (SQLException e) {
throw new DAOException("Failed to create user: " + e.getMessage(), e);
}
}
User findById(int id) {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT id, name, email FROM users WHERE id = ?");
stmt.setInt(1, id);
ResultSet rs = stmt.executeQuery();
User user = null;
if (rs.next()) {
user = new User(rs.getInt("id"), rs.getString("name"), rs.getString("email"));
}
closeResources(conn, stmt, rs);
return user;
} catch (SQLException e) {
throw new DAOException("Failed to find user by ID: " + e.getMessage(), e);
}
}
void update(User user) {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("UPDATE users SET name = ?, email = ? WHERE id = ?");
stmt.setString(1, user.getName());
stmt.setString(2, user.getEmail());
stmt.setInt(3, user.getId());
stmt.executeUpdate();
closeResources(conn, stmt);
} catch (SQLException e) {
throw new DAOException("Failed to update user: " + e.getMessage(), e);
}
}
void delete(int id) {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("DELETE FROM users WHERE id = ?");
stmt.setInt(1, id);
stmt.executeUpdate();
closeResources(conn, stmt);
} catch (SQLException e) {
throw new DAOException("Failed to delete user: " + e.getMessage(), e);
}
}
private void closeResources(AutoCloseable... resources) {
for (AutoCloseable resource : resources) {
try {
if (resource != null) resource.close();
} catch (Exception e) {
// Log silently or handle as needed
}
}
}
}
This implementation uses prepared statements to prevent SQL injection and ensures resources are closed to avoid leaks. The DAOException wraps low-level exceptions like SQLException for domain-specific error management.[1][2]
Step-by-Step Integration
To integrate the DAO, first define the interface as shown. Next, implement the concrete class with data source logic, injecting the connection factory (e.g., via constructor) for configurability across environments. Handle exceptions by propagating wrapped errors to callers, enabling graceful degradation. Finally, instantiate via a factory pattern for runtime selection of implementations, such as:
class DAOFactory {
static UserDAO getUserDAO() {
// Return JDBC impl in production, mock in tests
return new UserDAOImpl(getProductionDataSource());
}
}
class DAOFactory {
static UserDAO getUserDAO() {
// Return JDBC impl in production, mock in tests
return new UserDAOImpl(getProductionDataSource());
}
}
This factory decouples creation from usage, allowing swaps like in-memory storage for development.[17]
Variations
DAOs often extend beyond basic CRUD to support collections and custom queries. For lists, add a method like:
List<User> findAll() {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT id, name, email FROM users");
ResultSet rs = stmt.executeQuery();
List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(new User(rs.getInt("id"), rs.getString("name"), rs.getString("email")));
}
closeResources(conn, stmt, rs);
return users;
} catch (SQLException e) {
throw new DAOException("Failed to find all users: " + e.getMessage(), e);
}
}
List<User> findAll() {
try {
Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT id, name, email FROM users");
ResultSet rs = stmt.executeQuery();
List<User> users = new ArrayList<>();
while (rs.next()) {
users.add(new User(rs.getInt("id"), rs.getString("name"), rs.getString("email")));
}
closeResources(conn, stmt, rs);
return users;
} catch (SQLException e) {
throw new DAOException("Failed to find all users: " + e.getMessage(), e);
}
}
For custom queries, include parameterized methods such as List<User> findByName(String name), using SQL like "SELECT * FROM users WHERE name LIKE ?" to filter results efficiently. These variations maintain the interface's abstraction while accommodating common persistence needs.[2]
Testing Considerations
Unit testing DAOs benefits from mocking to isolate logic from real data sources, ensuring fast, repeatable tests without database dependencies. Implement mock DAOs by creating test doubles that return predefined data or simulate failures, often using libraries like Mockito to verify interactions. For instance, inject a mock UserDAO into service classes and assert method calls without executing SQL. This approach tests business rules independently while integration tests validate the concrete DAO against a test database.[18][19]
Advantages and Drawbacks
Key Benefits
The Data Access Object (DAO) pattern offers several key benefits in application development by providing a structured abstraction for data persistence operations. One primary advantage is enhanced maintainability, as the centralized location of data access code in DAOs simplifies updates and modifications when underlying data sources evolve. For instance, migrating from one database system like MySQL to another such as PostgreSQL requires changes only within the DAO layer, without affecting the broader application codebase, thereby reducing the risk of introducing errors elsewhere.[1] This centralization also minimizes code redundancy and complexity in business objects, allowing developers to focus on core logic rather than scattered data-handling routines.[1] Studies on DAO implementations, such as WebDAO variants, further demonstrate that this pattern can reduce the need for source code modifications by over two times compared to non-DAO approaches, directly improving long-term maintenance efficiency.[20]
Another significant benefit is improved testability, which stems from the DAO's role in isolating data access from business logic. By implementing DAOs with interfaces, developers can easily create mock implementations to simulate database interactions during unit testing, eliminating the need for connections to live databases and enabling faster, more reliable tests of application behavior.[21] This separation promotes single-responsibility principles, allowing external testing scripts to control DAO functionality without altering the core application, which reduces the overall number of test scripts required and enhances coverage of business rules in isolation.[21]
The pattern also excels in portability, by abstracting vendor-specific details of persistence mechanisms behind a consistent interface. This decoupling ensures that changes to the underlying storage technology—such as switching between relational databases, NoSQL systems, or even file-based storage—can occur seamlessly without necessitating alterations to the business logic or presentation layers.[1] Consequently, applications become more adaptable to diverse environments, supporting easier deployment across different platforms or integration with evolving data infrastructures.[1] Advanced DAO designs, like dispatcher variants, further bolster this by enabling runtime replacement of implementations, enhancing flexibility without recompilation.[21]
Finally, reusability is a core strength of the DAO pattern, as it encapsulates data access logic into modular, interchangeable components that can be shared across multiple modules, services, or even entire applications within an enterprise. This modularity not only promotes code reuse but also scales effectively in large systems, where common data operations can be standardized to avoid duplication and facilitate consistent handling of persistence concerns.[1] Empirical evidence from DAO-augmented projects indicates improved code reuse through fewer modifications and better encapsulation, contributing to higher overall productivity in development teams.[20] In reusable DAO frameworks, such as those supporting generic functionality, this benefit extends to project-independent modules that lower development costs across initiatives.[21]
Potential Limitations
The Data Access Object (DAO) pattern introduces additional layers of abstraction, including interfaces and implementation classes, which can add unnecessary complexity and boilerplate code for simple applications performing basic CRUD operations.[22] This overhead often outweighs the benefits in small-scale projects, potentially leading to over-engineering where direct data access would suffice.[23]
In traditional JDBC-based DAOs, boilerplate for handling connections, statements, and exceptions further amplifies this overhead, risking resource leaks if not managed properly.[14] The pattern may also incur slight performance overhead due to additional method calls.[22]
The pattern demands familiarity with concepts like interfaces, factories, and strict separation of data access from business logic, creating a steep learning curve for junior developers.[22]
In large systems, maintaining numerous entity-specific DAO classes can lead to code duplication and maintenance challenges.[22] Issues with connection and transaction scoping, such as multiple connections per operation or leaked resource management to higher layers, compound these maintenance challenges.[24]
ORM-Supported DAOs
Object-Relational Mapping (ORM) tools extend the DAO pattern by providing built-in mechanisms for database interactions, often through session factories, entity managers, or context objects that serve as DAO implementations. Hibernate, first released in 2001, integrates the DAO pattern via its Session interface, which acts as a central DAO component for managing entity lifecycles, executing queries, and handling persistence operations in Java applications.[25] Similarly, Microsoft's Entity Framework, introduced in 2008 as part of .NET Framework 3.5 SP1, uses the DbContext class as a repository-like DAO that coordinates data persistence, enabling developers to abstract SQL details while maintaining a unit-of-work pattern for transactions.[26][27]
Key features of ORM-supported DAOs include automatic object-to-table mapping, which eliminates manual SQL for basic CRUD operations; lazy loading, where related entities are fetched only when accessed; and caching mechanisms to reduce database roundtrips. In Hibernate, the Session wraps these capabilities, allowing DAO interfaces to delegate to it for entity persistence and retrieval, while Entity Framework's DbContext automatically maps .NET objects to relational schemas and supports lazy loading proxies for on-demand data fetching.[28] These features streamline development by handling impedance mismatch between object-oriented code and relational databases.
A prominent example is Spring Data JPA, which reached general availability in 2011 and builds on JPA providers like Hibernate to auto-generate DAO methods from simple repository interfaces. Developers define interfaces extending Repository or CrudRepository, and Spring automatically implements methods for queries, pagination, and updates using declarative annotations, while managing transactions via @Transactional.[29][30] This approach allows declarative handling of complex queries through method name conventions or @Query annotations, reducing custom SQL needs.
Despite these benefits, ORM-supported DAOs introduce trade-offs, such as reduced boilerplate code at the cost of potential performance pitfalls like the N+1 query problem, where an initial query for a collection triggers additional queries for each related entity due to lazy loading. In Hibernate and Spring Data JPA, this can be mitigated with JOIN FETCH or @EntityGraph for eager loading, but unaddressed cases lead to inefficient multiple database calls.[31][32] Entity Framework similarly warns against heavy lazy loading to avoid N+1 issues, recommending explicit Include() for related data.[28] Overall, these DAOs excel in scenarios with complex relational data, where mapping and caching provide significant productivity gains over raw SQL.
Standalone DAO Libraries
Standalone DAO libraries provide frameworks for implementing custom Data Access Objects (DAOs) without relying on full Object-Relational Mapping (ORM) systems, emphasizing direct control over database queries and connections across various programming languages. These tools bridge the gap between application code and data storage by offering abstractions for SQL execution, result mapping, and transaction management while keeping developers close to the underlying database operations.
In Java, Spring JDBC, introduced as part of the Spring Framework 1.0 release in March 2004, simplifies DAO development through its JdbcTemplate class, which handles boilerplate JDBC code like connection acquisition, statement creation, and exception translation for raw SQL queries, thereby reducing ORM overhead.[33] This approach allows developers to write SQL directly while benefiting from Spring's resource management and dependency injection.
MyBatis, originally released as iBATIS in July 2002, supports DAO implementations in Java by mapping SQL statements defined in XML files or annotations to methods in interface-based DAOs, enabling precise control over dynamic queries and result set handling without automatic object-relational mapping.[34] For Python, SQLAlchemy, first released in February 2006, includes a Core engine that facilitates DAO patterns through a SQL expression language for composing queries and managing connections, with its ORM layer available only optionally for higher-level abstractions.[35][36]
Adaptations for NoSQL databases include Mongoose, released in 2010 for Node.js applications interacting with MongoDB, which enables schema-based DAOs by defining document structures and providing methods for CRUD operations, validation, and querying without imposing relational mapping constraints.[37][38]
These libraries excel in lightweight applications or environments demanding fine-grained SQL or query control, such as direct connection pooling and bespoke query builders, where the simplicity of raw data access outweighs the automation of ORMs.[36]