Visual Paradigm Desktop VP Online

Mapping Class Diagrams to Entity-Relationship Diagrams (ERD): A Beginner's Step-by-Step Guide

Introduction

When designing software systems, developers often start with object-oriented class diagrams and then need to translate those designs into database schemas. Understanding how to map class diagrams to Entity-Relationship Diagrams (ERDs) is crucial for building efficient, well-structured databases. This guide will walk you through the process step by step, with practical examples using PlantUML.

Key Concepts

What is a Class Diagram?

A class diagram represents the structure of a system using classes, their attributes, methods, and relationships (associations, inheritance, composition).

What is an ERD?

An Entity-Relationship Diagram represents the logical structure of a database using entities (tables), attributes (columns), and relationships between entities.

Why Map Between Them?

  • Object-Relational Impedance Mismatch: Objects and relational databases have different structures

  • Persistence Layer Design: Ensures your database supports your application's object model

  • Data Integrity: Proper mapping maintains relationships and constraints

Step-by-Step Mapping Process

Step 1: Identify Classes That Become Entities

Rule: Not every class becomes a table. Generally:

  • ✅ Regular classes → Entities/Tables

  • ❌ Utility/helper classes → No table needed

  • ❌ Abstract classes (usually) → No direct table

  • ⚠️ Interface implementations → Depends on strategy

 

 

@startuml
' Original Class Diagram
class User {
    +userId: int
    +username: string
    +email: string
    +createdAt: datetime
}

class Order {
    +orderId: int
    +orderDate: datetime
    +totalAmount: decimal
}

class Product {
    +productId: int
    +name: string
    +price: decimal
}

class EmailService {
    +sendEmail(to: string, body: string)
}

User "1" --> "*" Order : places
Order "*" --> "*" Product : contains
@enduml

Mapping ResultUserOrder, and Product become tables. EmailService does not.

Step 2: Map Attributes to Columns

Rules:

  • Simple data types (int, string, date) → Direct column mapping

  • Complex objects → May need separate tables or serialization

  • Collections → Usually require separate relationship tables

 

@startuml
' ERD Representation
entity User {
    * userId : int <<PK>>
    --
    username : varchar(50)
    email : varchar(100)
    createdAt : datetime
}

entity Order {
    * orderId : int <<PK>>
    --
    orderDate : datetime
    totalAmount : decimal(10,2)
    userId : int <<FK>>
}

entity Product {
    * productId : int <<PK>>
    --
    name : varchar(100)
    price : decimal(10,2)
}
@enduml

 

Step 3: Handle Relationships

One-to-Many (1:N)

Class Diagram: One class has a collection of another

@startuml
' Class Diagram - One-to-Many
class Customer {
    +customerId: int
    +name: string
}

class Order {
    +orderId: int
    +orderDate: datetime
}

Customer "1" --> "*" Order : places
@enduml

ERD Mapping: Add foreign key to the "many" side

@startuml
' ERD - One-to-Many
entity Customer {
    * customerId : int <<PK>>
    --
    name : varchar(100)
}

entity Order {
    * orderId : int <<PK>>
    --
    orderDate : datetime
    customerId : int <<FK>>
}

Customer ||--o{ Order : places
@enduml

 

Many-to-Many (M:N)

Class Diagram: Both classes have collections of each other

@startuml
' Class Diagram - Many-to-Many
class Student {
    +studentId: int
    +name: string
}

class Course {
    +courseId: int
    +title: string
}

Student "*" --> "*" Course : enrolls_in
@enduml

ERD Mapping: Create a junction/join table

@startuml
' ERD - Many-to-Many with Junction Table
entity Student {
    * studentId : int <<PK>>
    --
    name : varchar(100)
}

entity Course {
    * courseId : int <<PK>>
    --
    title : varchar(200)
}

entity Enrollment {
    * studentId : int <<PK, FK>>
    * courseId : int <<PK, FK>>
    --
    enrollmentDate : datetime
    grade : char(2)
}

Student }|--|| Enrollment : has
Course }|--|| Enrollment : has
@enduml

 

One-to-One (1:1)

Class Diagram: Each instance relates to exactly one instance

@startuml
' Class Diagram - One-to-One
class User {
    +userId: int
    +username: string
}

class UserProfile {
    +profileId: int
    +bio: string
    +avatar: string
}

User "1" --> "1" UserProfile : has
@enduml

ERD Mapping Options:

Option A: Foreign key in either table

@startuml
' ERD - One-to-One (FK approach)
entity User {
    * userId : int <<PK>>
    --
    username : varchar(50)
    profileId : int <<FK, unique>>
}

entity UserProfile {
    * profileId : int <<PK>>
    --
    bio : text
    avatar : varchar(255)
}

User ||--|| UserProfile : has
@enduml

Option B: Merge into single table (if tightly coupled)

Step 4: Handle Inheritance

Three common strategies:

Strategy 1: Single Table Inheritance

All classes in hierarchy share one table with a discriminator column

 

@startuml
' Class Diagram - Inheritance
class Payment {
    +paymentId: int
    +amount: decimal
    +date: datetime
}

class CreditCardPayment {
    +cardNumber: string
    +expiryDate: string
}

class BankTransferPayment {
    +accountNumber: string
    +routingNumber: string
}

Payment <|-- CreditCardPayment
Payment <|-- BankTransferPayment
@enduml

@startuml
' ERD - Single Table Inheritance
entity Payment {
    * paymentId : int <<PK>>
    --
    amount : decimal(10,2)
    date : datetime
    paymentType : varchar(20) <<discriminator>>
    cardNumber : varchar(20)
    expiryDate : varchar(7)
    accountNumber : varchar(20)
    routingNumber : varchar(9)
}
@enduml

 

Strategy 2: Class Table Inheritance

Each class gets its own table

@startuml
' ERD - Class Table Inheritance
entity Payment {
    * paymentId : int <<PK>>
    --
    amount : decimal(10,2)
    date : datetime
}

entity CreditCardPayment {
    * paymentId : int <<PK, FK>>
    --
    cardNumber : varchar(20)
    expiryDate : varchar(7)
}

entity BankTransferPayment {
    * paymentId : int <<PK, FK>>
    --
    accountNumber : varchar(20)
    routingNumber : varchar(9)
}

Payment ||--|| CreditCardPayment : extends
Payment ||--|| BankTransferPayment : extends
@enduml

 

Strategy 3: Concrete Table Inheritance

Only concrete classes get tables (no parent table)

@startuml
' ERD - Concrete Table Inheritance
entity CreditCardPayment {
    * paymentId : int <<PK>>
    --
    amount : decimal(10,2)
    date : datetime
    cardNumber : varchar(20)
    expiryDate : varchar(7)
}

entity BankTransferPayment {
    * paymentId : int <<PK>>
    --
    amount : decimal(10,2)
    date : datetime
    accountNumber : varchar(20)
    routingNumber : varchar(9)
}
@enduml

 

Step 5: Handle Composition and Aggregation

Composition (Strong Ownership)

The contained object cannot exist without the container

@startuml
' Class Diagram - Composition
class Invoice {
    +invoiceId: int
    +invoiceDate: datetime
}

class LineItem {
    +lineItemId: int
    +quantity: int
    +unitPrice: decimal
}

Invoice *-- LineItem : contains
@enduml

 

@startuml
' ERD - Composition
entity Invoice {
* invoiceId : int <<PK>>
--
invoiceDate : datetime
}

entity LineItem {
* lineItemId : int <<PK>>
--
quantity : int
unitPrice : decimal(10,2)
invoiceId : int <<FK>>
}

Invoice ||--|{ LineItem : contains
@enduml

Step 6: Handle Associations with Attributes

When associations themselves have attributes, create a separate entity

@startuml
' Class Diagram - Association with Attributes
class Employee {
    +employeeId: int
    +name: string
}

class Project {
    +projectId: int
    +projectName: string
}

Employee "*" --> "*" Project : works_on
note right: Association has attributes:\n- startDate\n- role\n- hoursPerWeek
@enduml

 

 

@startuml
' ERD - Association as Entity
entity Employee {
    * employeeId : int <<PK>>
    --
    name : varchar(100)
}

entity Project {
    * projectId : int <<PK>>
    --
    projectName : varchar(200)
}

entity Assignment {
    * employeeId : int <<PK, FK>>
    * projectId : int <<PK, FK>>
    --
    startDate : date
    role : varchar(50)
    hoursPerWeek : int
}

Employee }|--|| Assignment : assigned_to
Project }|--|| Assignment : includes
@enduml

 

Complete Example: E-Commerce System

Let's map a complete class diagram to an ERD:

 

@startuml
' Complete Class Diagram
class Customer {
    +customerId: int
    +firstName: string
    +lastName: string
    +email: string
    +phone: string
}

class Address {
    +addressId: int
    +street: string
    +city: string
    +state: string
    +zipCode: string
}

class Order {
    +orderId: int
    +orderDate: datetime
    +status: string
    +shippingAddress: Address
}

class OrderItem {
    +orderItemId: int
    +quantity: int
    +unitPrice: decimal
}

class Product {
    +productId: int
    +name: string
    +description: string
    +price: decimal
    +stockQuantity: int
}

class Category {
    +categoryId: int
    +categoryName: string
}

Customer "1" --> "*" Address : has
Customer "1" --> "*" Order : places
Order "1" *-- "*" OrderItem : contains
OrderItem "*" --> "1" Product : refers_to
Product "*" --> "*" Category : belongs_to
Category "1" --> "*" Product : categorizes
@enduml

 

 

@startuml
' Complete ERD
entity Customer {
* customerId : int <<PK>>
--
firstName : varchar(50)
lastName : varchar(50)
email : varchar(100)
phone : varchar(20)
}

entity Address {
* addressId : int <<PK>>
--
street : varchar(200)
city : varchar(100)
state : varchar(50)
zipCode : varchar(10)
customerId : int <<FK>>
}

entity Order {
* orderId : int <<PK>>
--
orderDate : datetime
status : varchar(20)
customerId : int <<FK>>
shippingAddressId : int <<FK>>
}

entity OrderItem {
* orderItemId : int <<PK>>
--
quantity : int
unitPrice : decimal(10,2)
orderId : int <<FK>>
productId : int <<FK>>
}

entity Product {
* productId : int <<PK>>
--
name : varchar(200)
description : text
price : decimal(10,2)
stockQuantity : int
}

entity Category {
* categoryId : int <<PK>>
--
categoryName : varchar(100)
}

entity ProductCategory {
* productId : int <<PK, FK>>
* categoryId : int <<PK, FK>>
}

Customer ||--o{ Address : has
Customer ||--o{ Order : places
Order ||--|{ OrderItem : contains
Product ||--o{ OrderItem : referenced_by
OrderItem }|--|| Order : part_of
OrderItem }|--|| Product : refers_to
Product }|--|| ProductCategory : categorized_in
Category }|--|| ProductCategory : contains
@enduml

 

Guidelines and Best Practices

1. Naming Conventions

  • Tables: Use plural nouns (e.g., usersorders)

  • Columns: Use snake_case (e.g., user_idcreated_at)

  • Foreign Keys: Follow pattern [table_name]_id (e.g., customer_id)

2. Data Type Mapping

Class Type Database Type Notes
int/Integer INT Consider BIGINT for large datasets
long/Long BIGINT For very large numbers
String VARCHAR(n) Choose appropriate length
Text/long string TEXT For unlimited length
boolean/Boolean BOOLEAN/TINYINT Database dependent
Date DATE Date only
DateTime DATETIME/TIMESTAMP Date and time
Decimal/BigDecimal DECIMAL(p,s) Specify precision and scale
Double/Float FLOAT/DOUBLE Be aware of precision issues

3. Indexing Strategy

  • Always index primary keys (automatic)

  • Index foreign keys for join performance

  • Index columns used in WHERE clauses frequently

  • Consider composite indexes for multi-column queries

4. Normalization

  • 1NF: Atomic values, no repeating groups

  • 2NF: Remove partial dependencies

  • 3NF: Remove transitive dependencies

  • Balance normalization with performance needs

5. Common Pitfalls to Avoid

❌ Creating tables for every class

  • Utility classes, services, and interfaces typically don't need tables

❌ Ignoring cardinality

  • Misunderstanding 1:1 vs 1:N vs M:N leads to incorrect schema

❌ Forgetting about referential integrity

  • Always define foreign key constraints

❌ Over-normalizing or under-normalizing

  • Find the right balance for your use case

❌ Not considering query patterns

  • Design with actual usage in mind

Quick Reference Checklist

When mapping class diagrams to ERDs:

  1. ✅ Identify which classes become entities

  2. ✅ Map simple attributes to columns

  3. ✅ Determine relationship cardinalities

  4. ✅ Add foreign keys for 1:N relationships

  5. ✅ Create junction tables for M:N relationships

  6. ✅ Choose inheritance strategy

  7. ✅ Handle composition/aggregation appropriately

  8. ✅ Create entities for associations with attributes

  9. ✅ Define primary and foreign keys

  10. ✅ Add appropriate indexes

  11. ✅ Validate against query requirements

  12. ✅ Review for normalization

Conclusion

Mapping class diagrams to ERDs is a systematic process that requires understanding both object-oriented design and relational database principles. By following these steps and guidelines, you can create database schemas that accurately represent your application's domain model while maintaining data integrity and performance.

Remember:

  • Start with clear class diagrams

  • Understand relationship cardinalities

  • Choose appropriate inheritance strategies

  • Balance normalization with practical needs

  • Always consider your actual query patterns

With practice, this mapping process becomes intuitive, enabling you to design robust database schemas that support your application's object model effectively.


This guide provides foundational knowledge for beginners. As you gain experience, you'll encounter more complex scenarios requiring advanced techniques like polymorphic associations, soft deletes, and denormalization for performance optimization.

Turn every software project into a successful one.

We use cookies to offer you a better experience. By visiting our website, you agree to the use of cookies as described in our Cookie Policy.

OK