Loading learning content...
The relational model revolutionized database management by providing a clean, mathematical foundation for data storage and retrieval. Tables, rows, and columns—combined with SQL—became the dominant paradigm for decades. But as software engineering evolved, a fundamental tension emerged: the impedance mismatch between how programmers think about data and how databases store it.
Programmers working in object-oriented languages like Java, C++, or Python naturally think in terms of objects—self-contained entities that bundle data (attributes) with behavior (methods). A Customer isn't just a row of values; it's an entity that can placeOrder(), updateAddress(), or calculateLoyaltyPoints(). The relational model, with its flat tables and value-based relationships, struggles to represent this richness directly.
The Object-Oriented Database Model (OODM) emerged to address this mismatch. Rather than forcing programmers to translate between their mental model and the database's storage model, OODM stores objects directly—preserving their identity, structure, and behavior exactly as they exist in application memory.
By the end of this page, you will understand how objects and classes serve as the foundational building blocks of object-oriented databases. You'll grasp object identity versus value equality, the anatomy of database objects, class hierarchies, and why these concepts represent a paradigm shift from relational thinking.
An object in the database context is a direct analog of an object in object-oriented programming. It is a self-contained unit that encapsulates:
This triad—state, behavior, identity—forms the conceptual foundation of object-oriented databases. Each element addresses limitations in the relational model.
Employee object might contain a nested Address object, a collection of Project references, and computed properties like yearsOfService. This complexity is stored directly, not decomposed into normalized tables.BankAccount object knows how to withdraw(), deposit(), and calculateInterest(). This co-location of data and behavior eliminates the artificial separation between database storage and application logic.Person objects with identical names and addresses remain distinct objects because they have different OIDs.This distinction is crucial. In relational databases, two rows are 'the same' if their primary key values match—a value-based identity. In object databases, identity is existence-based: an object is itself because it exists, not because of what values it holds. This mirrors how we think about real-world entities—you remain you even if you change your name, address, or every measurable attribute.
Object Identity (OID) is perhaps the most fundamental departure from relational thinking. In the relational model, entities are identified by their key values—a customer is identified by customer_id = 12345. If you need to reference that customer from another table, you copy this value as a foreign key.
This approach has consequences:
Object Identity eliminates these issues by introducing a system-managed, immutable identifier that:
| Aspect | Value-Based (Relational) | Object-Based (OODM) |
|---|---|---|
| What identifies an entity | Primary key value(s) | System-assigned OID |
| Can identity change? | Yes (key update) | Never |
| How are references stored? | Copy of key value (FK) | Direct OID reference |
| Reference resolution | Lookup via index | Direct pointer traversal |
| Two entities with same values | Cannot exist (key constraint) | Fully allowed (different OIDs) |
| Cross-database identity | Application-managed | System-guaranteed unique |
Implementation of OIDs:
Different OODBMS implementations use different strategies for OID generation:
Most modern systems prefer logical OIDs for their flexibility, accepting the minor indirection cost for the ability to reorganize storage without affecting application code.
12345678910111213141516171819202122232425262728
// In a relational database:customer_table: | customer_id (PK) | name | email | |------------------|-----------|-------------------| | 1001 | Alice | alice@example.com | | 1002 | Bob | bob@example.com | order_table: | order_id | customer_id (FK) | total | |----------|------------------|--------| | 5001 | 1001 | 250.00 | // To find Alice's orders: SELECT * FROM order_table WHERE customer_id = 1001// This requires an index lookup on customer_id // In an object-oriented database:Customer object (OID: 0x7F3A2B1C): - name: "Alice" - email: "alice@example.com" - orders: [direct reference to 0x8D4E5F2A, ...] Order object (OID: 0x8D4E5F2A): - total: 250.00 - customer: [direct reference to 0x7F3A2B1C] // Navigation is instant pointer traversal:// alice.orders → immediate access, no lookup// order.customer → immediate access, no lookupDirect OID references enable O(1) navigation between related objects—no indexes, no joins, no hash lookups. For applications that traverse complex object graphs (CAD systems, scientific simulations, AI knowledge bases), this direct navigation can provide orders-of-magnitude performance improvements over relational approaches.
The relational model enforces First Normal Form (1NF): all attribute values must be atomic (indivisible). This constraint, while mathematically elegant, forces artificial decomposition of naturally complex data.
Consider modeling an employee's work history:
Relational approach:
work_history tableemployee_idObject approach:
employee.workHistoryObject-oriented databases support complex state through several mechanisms:
Company object might have a headquarters attribute that is itself an Address object containing street, city, country, and postal code.Author object might have books: Set<Book> containing references to all their published works.Person can have phoneNumbers: List<String> directly.Employee object might have a yearsOfService attribute computed from hireDate and current date, stored or calculated on access.Order has customer: Customer that directly references the Customer object.| Scenario | Relational Representation | Object Representation |
|---|---|---|
| Employee with multiple skills | employee table + skill table + employee_skill junction table | Employee.skills: Set<Skill> |
| Order with line items | order table + order_item table + FK relationships | Order.items: List<OrderItem> |
| Person with nested address | person table + address table + FK or denormalized columns | Person.address: Address (embedded object) |
| Document with version history | document table + version table + complex queries | Document.versions: List<Version> |
| Graph of related entities | Multiple junction tables + recursive queries | Entity.relatedTo: Set<Entity> |
Complex state in objects resembles denormalization in relational databases—both avoid joins by storing related data together. The key difference: OODM provides principled ways to share objects (via OID references) rather than copying data. This preserves update consistency while enabling direct access.
Just as in object-oriented programming, a class in OODM serves as a template or blueprint that defines:
Every object in the database is an instance of exactly one class. The class defines what the object can be; the instance represents what a specific object actually is.
Class as Schema:
In relational databases, the schema (table definitions) is separate from the data (rows). In OODM, classes serve a similar schema role—they define structure—but with crucial differences:
123456789101112131415161718192021222324252627282930313233343536373839404142
// Class definition in Object Definition Language (ODL) styleclass Employee { // Attributes define state structure attribute string employeeId; attribute string firstName; attribute string lastName; attribute Date hireDate; attribute float salary; // Complex attribute: embedded object attribute Address homeAddress; // Collection attribute: set of project references relationship Set<Project> assignedProjects inverse Project::teamMembers; // Reference to another object relationship Department department inverse Department::employees; // Derived attribute: computed from other data attribute int yearsOfService = (currentDate - hireDate).years; // Methods define behavior void promote(float raisePercent) { this.salary = this.salary * (1 + raisePercent/100); } void assignToProject(Project p) { this.assignedProjects.add(p); p.teamMembers.add(this); } float calculateBonus() { return this.salary * 0.10 * this.yearsOfService; } // Constraints constraint salary > 0; constraint hireDate <= currentDate;}Class Extent:
The extent of a class is the set of all current instances of that class in the database. This is analogous to a relational table containing all rows, but with important differences:
When you create a new object, it automatically becomes part of its class's extent. When you delete an object, it's automatically removed. This automatic extent management simplifies application code.
In pure OODBMS implementations, class definitions are themselves objects stored in the database. This enables powerful metaprogramming: you can query the schema ('show me all classes with a salary attribute'), dynamically create new classes, or implement schema evolution as data operations rather than DDL commands.
Understanding the lifecycle of database objects is essential for working with OODBMS. Unlike transient programming objects that exist only in memory, database objects have persistence—they survive program termination and system restarts.
The object lifecycle encompasses:
makePersistent()). In others, reachability-based persistence means objects become persistent when referenced by other persistent objects.The ideal OODBMS provides transparent persistence: application code operates on objects without awareness of whether they're in memory or on disk. The system automatically loads objects on access (swizzling) and writes modifications on commit. This transparency dramatically simplifies application development compared to explicit data access layers.
Relationships between objects in OODM are fundamentally different from relational foreign keys. While relational systems represent relationships as value matches between columns, object databases represent relationships as direct references between objects.
Types of Object Relationships:
| Relationship Type | Description | Example |
|---|---|---|
| Association | General connection between objects. Can be unidirectional or bidirectional. | Employee works-in Department |
| Aggregation | Weak 'has-a' relationship. Component can exist independently. | Department has Employees |
| Composition | Strong 'contains' relationship. Component cannot exist without container. | Order contains OrderItems |
| Dependency | One object uses another without ownership. | ReportGenerator uses DataSource |
Relationship Cardinality:
Like relational models, object relationships have cardinality constraints:
Unlike relational systems that require junction tables for M:N relationships, OODM handles them naturally through collection-valued attributes on both sides.
123456789101112131415161718192021222324252627282930313233343536373839404142434445
// Bidirectional relationship with inverse specificationclass Department { attribute string name; attribute string building; // One-to-many: department has many employees relationship Set<Employee> employees inverse Employee::department; // One-to-one: department has one manager relationship Employee manager inverse Employee::managedDepartment;} class Employee { attribute string name; attribute float salary; // Many-to-one: employee belongs to one department relationship Department department inverse Department::employees; // One-to-one inverse (optional - not all employees manage) relationship Department managedDepartment inverse Department::manager; // Many-to-many: employee works on many projects relationship Set<Project> projects inverse Project::team;} class Project { attribute string title; attribute Date deadline; // Many-to-many inverse relationship Set<Employee> team inverse Employee::projects;} // Usage: Navigation is directEmployee emp = getEmployeeByName("Alice");Department dept = emp.department; // Direct traversal, no joinSet<Project> projects = emp.projects; // Direct access to collectionString building = emp.department.building; // Chained navigationWhen relationships are declared bidirectional with inverse specifications, the OODBMS automatically maintains referential integrity. Adding an employee to a department's employees set automatically sets the employee's department reference—and vice versa. This eliminates a major source of application bugs and data inconsistency.
To solidify understanding, let's directly compare how the same real-world scenario is modeled in relational versus object-oriented approaches.
Scenario: A library system tracking books, authors, and borrowing history.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
-- Relational Schema for LibraryCREATE TABLE Author ( author_id INT PRIMARY KEY, name VARCHAR(100), birth_date DATE, nationality VARCHAR(50)); CREATE TABLE Book ( book_id INT PRIMARY KEY, title VARCHAR(200), isbn VARCHAR(13), publication_year INT, page_count INT); -- Junction table for M:N relationshipCREATE TABLE Book_Author ( book_id INT REFERENCES Book(book_id), author_id INT REFERENCES Author(author_id), author_order INT, -- For co-author ordering PRIMARY KEY (book_id, author_id)); CREATE TABLE Member ( member_id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100), join_date DATE); CREATE TABLE Borrowing ( borrowing_id INT PRIMARY KEY, book_id INT REFERENCES Book(book_id), member_id INT REFERENCES Member(member_id), borrow_date DATE, due_date DATE, return_date DATE); -- Query: Find all books by an author with borrowing countSELECT b.title, COUNT(br.borrowing_id) as borrow_countFROM Book bJOIN Book_Author ba ON b.book_id = ba.book_idJOIN Author a ON ba.author_id = a.author_idLEFT JOIN Borrowing br ON b.book_id = br.book_idWHERE a.name = 'James Clear'GROUP BY b.book_id, b.title;Key observations:
No junction tables — The M:N relationship between Books and Authors is handled directly through bidirectional collection references.
Behavior with data — Methods like isAvailable(), isOverdue(), and getBorrowCount() live with their data, rather than in application code far from the schema.
Navigation vs. joins — The object model navigates directly (author.books), while relational requires joins across multiple tables.
Natural collections — Ordered lists (List<Author> for co-author ordering) are first-class features, not implemented via extra columns.
Query simplicity — Complex relational queries with GROUP BY become simple method calls on objects.
We've established the foundational concepts of object-oriented databases. Let's consolidate the key insights:
What's Next:
Now that we understand objects and classes, the next page explores inheritance—how class hierarchies enable code and structure reuse, polymorphism, and sophisticated type systems that dramatically reduce redundancy in complex domain models.
You now understand the fundamental building blocks of object-oriented databases. Objects and classes provide a richer, more natural representation of complex data than flat relational tables—at the cost of some mathematical elegance. Next, we'll see how inheritance extends this power.