SoFunction
Updated on 2025-04-14

SpringData JPA entity mapping and relational mapping implementation

introduction

In enterprise-level Java application development, object relational mapping (ORM) technology has become an important bridge to connect object-oriented programming and relational databases. Spring Data JPA, as a core project in the Spring ecosystem, provides an elegant and powerful entity mapping and relationship processing mechanism through JPA specifications. This article discusses the entity mapping and relation mapping technology of Spring Data JPA in depth, helping developers master how to establish effective mapping relationships between Java objects and database tables, improve development efficiency and ensure maintainability of the data access layer.

1. Entity mapping basics

Entity mapping is the core function of JPA, which defines the mapping relationship between Java objects and database tables. In Spring Data JPA, this mapping can be easily accomplished through annotation-driven approach, without writing complex XML configurations.

1.1 Basic Entity Mapping

A basic entity class needs to be used@EntityAnnotation marks and pass@TableSpecify the corresponding database table name:

package ;

import .*;
import ;

/**
  * User Entity Class
  * Demonstrate basic entity mapping
  */
@Entity
@Table(name = "tb_user")
public class User {
    
    @Id  // Mark the primary key    @GeneratedValue(strategy = )  // Auto-increment primary key strategy    private Long id;
    
    @Column(name = "username", length = 50, nullable = false, unique = true)
    private String username;
    
    @Column(name = "email", length = 100)
    private String email;
    
    @Column(name = "password", length = 64)
    private String password;
    
    @Column(name = "age")
    private Integer age;
    
    @Column(name = "active")
    private Boolean active = true;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    // constructor, getter and setter methods are omitted}

1.2 Entity attribute mapping

JPA provides rich attribute mapping annotations that can accurately control the characteristics of columns:

package ;

import .*;
import ;
import ;

/**
  * Product Entity
  * Demonstrate more complex attribute mappings
  */
@Entity
@Table(name = "tb_product")
public class Product {
    
    @Id
    @GeneratedValue(strategy = , generator = "product_seq")
    @SequenceGenerator(name = "product_seq", sequenceName = "product_sequence", allocationSize = 1)
    private Long id;
    
    @Column(name = "product_name", length = 100, nullable = false)
    private String name;
    
    @Column(name = "description", columnDefinition = "TEXT")
    private String description;
    
    @Column(name = "price", precision = 10, scale = 2)
    private BigDecimal price;
    
    @Column(name = "stock_quantity")
    private Integer stockQuantity;
    
    @Enumerated()
    @Column(name = "status")
    private ProductStatus status;
    
    @Temporal()
    @Column(name = "release_date")
    private  releaseDate;
    
    @Transient  // Not mapped to the database    private BigDecimal discountPrice;
    
    @Lob  // Big object    @Column(name = "image_data")
    private byte[] imageData;
    
    // Product status enumeration    public enum ProductStatus {
        DRAFT, PUBLISHED, OUT_OF_STOCK, DISCONTINUED
    }
    
    // constructor, getter and setter methods are omitted}

1.3 Composite primary key mapping

Sometimes entities need to use composite primary keys, and JPA provides two ways to handle them:@IdClassand@EmbeddedId

package ;

import .*;
import ;
import ;
import ;

/**
  * Line item entity class
  * Demonstrate composite primary key mapping (using @IdClass)
  */
@Entity
@Table(name = "tb_order_item")
@IdClass()
public class OrderItem {
    
    @Id
    @Column(name = "order_id")
    private Long orderId;
    
    @Id
    @Column(name = "product_id")
    private Long productId;
    
    @Column(name = "quantity", nullable = false)
    private Integer quantity;
    
    @Column(name = "unit_price", nullable = false)
    private Double unitPrice;
    
    // constructor, getter and setter methods are omitted}

/**
  * Line item composite primary key class
  */
class OrderItemId implements Serializable {
    
    private Long orderId;
    private Long productId;
    
    // Default constructor    public OrderItemId() {}
    
    // Constructor with parameter    public OrderItemId(Long orderId, Long productId) {
         = orderId;
         = productId;
    }
    
    // Implementation of equals and hashCode methods    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != ()) return false;
        OrderItemId that = (OrderItemId) o;
        return (orderId, ) && 
               (productId, );
    }
    
    @Override
    public int hashCode() {
        return (orderId, productId);
    }
    
    // Getter and setter methods are omitted}

use@EmbeddedIdWays:

package ;

import .*;
import ;
import ;

/**
  * Student course grades entity category
  * Demonstrate composite primary key mapping (using @EmbeddedId)
  */
@Entity
@Table(name = "tb_student_course")
public class StudentCourse {
    
    @EmbeddedId
    private StudentCourseId id;
    
    @Column(name = "score")
    private Double score;
    
    @Column(name = "semester")
    private String semester;
    
    // constructor, getter and setter methods are omitted}

/**
  * Student course compound primary key class
  */
@Embeddable
class StudentCourseId implements Serializable {
    
    @Column(name = "student_id")
    private Long studentId;
    
    @Column(name = "course_id")
    private Long courseId;
    
    // Default constructor    public StudentCourseId() {}
    
    // Constructor with parameter    public StudentCourseId(Long studentId, Long courseId) {
         = studentId;
         = courseId;
    }
    
    // Implementation of equals and hashCode methods    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != ()) return false;
        StudentCourseId that = (StudentCourseId) o;
        return (studentId, ) && 
               (courseId, );
    }
    
    @Override
    public int hashCode() {
        return (studentId, courseId);
    }
    
    // Getter and setter methods are omitted}

2. Detailed explanation of relationship mapping

Relational mapping is another core function of JPA, which handles associations between entities, corresponding to foreign key relationships between database tables. JPA supports four relationship types: one-to-one, one-to-many, many-to-one and many-to-many.

2.1 One-to-one relationship (@OneToOne)

A one-to-one relationship is the simplest association between two entities, and each entity instance only associates one instance of another entity:

package ;

import .*;

/**
  * User details entity class
  * Demonstrate one-to-one relationship with User
  */
@Entity
@Table(name = "tb_user_profile")
public class UserProfile {
    
    @Id
    private Long id;
    
    @Column(name = "phone_number")
    private String phoneNumber;
    
    @Column(name = "address")
    private String address;
    
    @Column(name = "bio", columnDefinition = "TEXT")
    private String bio;
    
    @OneToOne
    @MapsId  // Use User ID as primary key    @JoinColumn(name = "user_id")
    private User user;
    
    // constructor, getter and setter methods are omitted}

// Add a reference to UserProfile in User class@Entity
@Table(name = "tb_user")
public class User {
    // Previous fields...    
    @OneToOne(mappedBy = "user", cascade = , fetch = , 
             orphanRemoval = true)
    private UserProfile profile;
    
    // constructor, getter and setter methods are omitted}

2.2 One-to-many relationship (@OneToMany) and many-to-one relationship (@ManyToOne)

One-to-many and many-to-one are mutually corresponding relationships, indicating that one entity instance can associate multiple instances of another entity:

package ;

import .*;
import ;
import ;
import ;

/**
  * Order entity class
  * Demonstrate one-to-many relationship with OrderItem
  */
@Entity
@Table(name = "tb_order")
public class Order {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @Column(name = "order_number", unique = true)
    private String orderNumber;
    
    @Column(name = "order_date")
    private LocalDateTime orderDate;
    
    @Column(name = "total_amount")
    private Double totalAmount;
    
    @Enumerated()
    @Column(name = "status")
    private OrderStatus status;
    
    @ManyToOne(fetch = )
    @JoinColumn(name = "user_id")
    private User user;
    
    @OneToMany(mappedBy = "order", cascade = , orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();
    
    // Order status enumeration    public enum OrderStatus {
        PENDING, PAID, SHIPPED, DELIVERED, CANCELLED
    }
    
    // Convenient way to add line items    public void addItem(OrderItem item) {
        (item);
        (this);
    }
    
    // Easy way to remove line items    public void removeItem(OrderItem item) {
        (item);
        (null);
    }
    
    // constructor, getter and setter methods are omitted}

/**
  * Line item entity class
  * Demonstrate a many-to-one relationship with Order
  */
@Entity
@Table(name = "tb_order_item")
public class OrderItem {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @ManyToOne(fetch = )
    @JoinColumn(name = "order_id")
    private Order order;
    
    @ManyToOne(fetch = )
    @JoinColumn(name = "product_id")
    private Product product;
    
    @Column(name = "quantity")
    private Integer quantity;
    
    @Column(name = "unit_price")
    private Double unitPrice;
    
    // constructor, getter and setter methods are omitted}

2.3 Many-to-many relationship (@ManyToMany)

A many-to-many relationship requires an intermediate table to store the association:

package ;

import .*;
import ;
import ;

/**
  * Student Entity Class
  * Demonstrate many-to-many relationship with Course
  */
@Entity
@Table(name = "tb_student")
public class Student {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @Column(name = "student_name", nullable = false)
    private String name;
    
    @Column(name = "student_number", unique = true)
    private String studentNumber;
    
    @ManyToMany(cascade = {, })
    @JoinTable(
        name = "tb_student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();
    
    // Easy way to add courses    public void addCourse(Course course) {
        (course);
        ().add(this);
    }
    
    // Easy way to remove courses    public void removeCourse(Course course) {
        (course);
        ().remove(this);
    }
    
    // constructor, getter and setter methods are omitted}

/**
  * Course Entity Class
  * Demonstrate many-to-many relationship with Student
  */
@Entity
@Table(name = "tb_course")
public class Course {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @Column(name = "course_name", nullable = false)
    private String name;
    
    @Column(name = "course_code", unique = true)
    private String code;
    
    @Column(name = "credits")
    private Integer credits;
    
    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();
    
    // constructor, getter and setter methods are omitted}

3. Inheritance mapping strategy

JPA provides three entity inheritance mapping strategies for handling the mapping of class inheritance hierarchy to database tables.

3.1 Single table policy (SINGLE_TABLE)

A single table policy is the default policy that maps the entire inheritance hierarchy to a single table:

package ;

import .*;
import ;

/**
  * Payment record abstract base class
  * Demonstrate single table inheritance strategy
  */
@Entity
@Table(name = "tb_payment")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type", discriminatorType = )
public abstract class Payment {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @Column(name = "amount")
    private BigDecimal amount;
    
    @Column(name = "payment_date")
    private  paymentDate;
    
    @ManyToOne(fetch = )
    @JoinColumn(name = "order_id")
    private Order order;
    
    // constructor, getter and setter methods are omitted}

/**
  * Credit card payment entity category
  */
@Entity
@DiscriminatorValue("CREDIT_CARD")
public class CreditCardPayment extends Payment {
    
    @Column(name = "card_number")
    private String cardNumber;
    
    @Column(name = "card_holder")
    private String cardHolder;
    
    @Column(name = "expiry_date")
    private String expiryDate;
    
    // constructor, getter and setter methods are omitted}

/**
  * Bank transfer payment entity category
  */
@Entity
@DiscriminatorValue("BANK_TRANSFER")
public class BankTransferPayment extends Payment {
    
    @Column(name = "bank_name")
    private String bankName;
    
    @Column(name = "account_number")
    private String accountNumber;
    
    @Column(name = "reference_number")
    private String referenceNumber;
    
    // constructor, getter and setter methods are omitted}

3.2 Joint table policy (JOINED)

The join table policy creates a table for each subclass and associates it with the parent class table through a foreign key:

package ;

import .*;

/**
  * Personnel abstract base class
  * Demonstrate the connection table inheritance policy
  */
@Entity
@Table(name = "tb_person")
@Inheritance(strategy = )
public abstract class Person {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @Column(name = "name")
    private String name;
    
    @Column(name = "email")
    private String email;
    
    @Column(name = "phone")
    private String phone;
    
    // constructor, getter and setter methods are omitted}

/**
  * Employee entity category
  */
@Entity
@Table(name = "tb_employee")
@PrimaryKeyJoinColumn(name = "person_id")
public class Employee extends Person {
    
    @Column(name = "employee_number")
    private String employeeNumber;
    
    @Column(name = "department")
    private String department;
    
    @Column(name = "position")
    private String position;
    
    @Column(name = "salary")
    private Double salary;
    
    // constructor, getter and setter methods are omitted}

/**
  * Customer Entity Class
  */
@Entity
@Table(name = "tb_customer")
@PrimaryKeyJoinColumn(name = "person_id")
public class Customer extends Person {
    
    @Column(name = "customer_number")
    private String customerNumber;
    
    @Column(name = "company")
    private String company;
    
    @Column(name = "industry")
    private String industry;
    
    // constructor, getter and setter methods are omitted}

3.3 Table per-category strategy (TABLE_PER_CLASS)

Table Each class policy creates an independent table for each specific class:

package ;

import .*;
import ;

/**
  * Notify abstract base class
  * Demonstrate tables for each type of inheritance strategy
  */
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Notification {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @Column(name = "title")
    private String title;
    
    @Column(name = "content")
    private String content;
    
    @Column(name = "sent_at")
    private LocalDateTime sentAt;
    
    @Column(name = "is_read")
    private boolean read;
    
    @ManyToOne(fetch = )
    @JoinColumn(name = "user_id")
    private User user;
    
    // constructor, getter and setter methods are omitted}

/**
  * Email notification entity class
  */
@Entity
@Table(name = "tb_email_notification")
public class EmailNotification extends Notification {
    
    @Column(name = "email_address")
    private String emailAddress;
    
    @Column(name = "subject")
    private String subject;
    
    @Column(name = "cc")
    private String cc;
    
    // constructor, getter and setter methods are omitted}

/**
  * SMS notification entity class
  */
@Entity
@Table(name = "tb_sms_notification")
public class SmsNotification extends Notification {
    
    @Column(name = "phone_number")
    private String phoneNumber;
    
    @Column(name = "sender")
    private String sender;
    
    // constructor, getter and setter methods are omitted}

4. Entity listener and callback

JPA provides an entity lifecycle event callback mechanism that allows custom logic to be executed when entity state changes. These callbacks can be used to implement audits, verifications, or other cross-cutting concerns.

package ;

import .*;
import ;

/**
  * Entity base class
  * Demonstrate entity listeners and callbacks
  */
@MappedSuperclass
@EntityListeners()
public abstract class BaseEntity {
    
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;
    
    @Column(name = "created_by", updatable = false)
    private String createdBy;
    
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @Column(name = "updated_by")
    private String updatedBy;
    
    @PrePersist
    public void prePersist() {
        createdAt = ();
        updatedAt = ();
        
        // You can get the current user from SecurityContext        String currentUser = getCurrentUser();
        createdBy = currentUser;
        updatedBy = currentUser;
    }
    
    @PreUpdate
    public void preUpdate() {
        updatedAt = ();
        updatedBy = getCurrentUser();
    }
    
    // Get the helper method of the current user    private String getCurrentUser() {
        // In actual applications, Spring Security can be integrated here to obtain the current user        return "system";  // Example returns the default value    }
    
    // Getter and setter methods are omitted}

/**
  * Custom audit listener
  */
public class AuditingEntityListener {
    
    @PrePersist
    public void touchForCreate(Object entity) {
        if (entity instanceof Auditable) {
            ((Auditable) entity).setCreatedAt(());
        }
    }
    
    @PreUpdate
    public void touchForUpdate(Object entity) {
        if (entity instanceof Auditable) {
            ((Auditable) entity).setUpdatedAt(());
        }
    }
}

/**
  * Auditable interface
  */
public interface Auditable {
    void setCreatedAt(LocalDateTime dateTime);
    void setUpdatedAt(LocalDateTime dateTime);
}

5. Entity mapping best practices

In SpringData JPA projects, following some best practices can improve code quality and performance:

5.1 Using the appropriate relationship loading strategy

Relational loading strategies have a significant impact on performance. Choose the appropriate loading method according to business needs:

package ;

import .*;
import ;
import ;

/**
  * Department entity category
  * Demonstrate the use of different loading strategies
  */
@Entity
@Table(name = "tb_department")
public class Department {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @Column(name = "name", nullable = false)
    private String name;
    
    // Department managers are frequently visited and loaded using EAGER    @OneToOne(fetch = )
    @JoinColumn(name = "manager_id")
    private Employee manager;
    
    // The department may have many employees, using LAZY loading to avoid loading large amounts of data at once    @OneToMany(mappedBy = "department", fetch = )
    private Set<Employee> employees = new HashSet<>();
    
    // Information of the company affiliated to the department often needs to be accessed, but it is loaded using LAZY and optimized through the association diagram    @ManyToOne(fetch = )
    @JoinColumn(name = "company_id")
    private Company company;
    
    // constructor, getter and setter methods are omitted}

5.2 Using indexes and constraints

Improve query performance and data integrity with database indexes and constraints:

package ;

import .*;

/**
  * Product inventory entity category
  * Demonstrate the use of indexes and constraints
  */
@Entity
@Table(
    name = "tb_product_inventory",
    indexes = {
        @Index(name = "idx_warehouse_product", columnList = "warehouse_id, product_id"),
        @Index(name = "idx_product_stock", columnList = "product_id, stock_quantity")
    },
    uniqueConstraints = {
        @UniqueConstraint(name = "uk_warehouse_product", columnNames = {"warehouse_id", "product_id"})
    }
)
public class ProductInventory {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "warehouse_id", nullable = false)
    private Warehouse warehouse;
    
    @ManyToOne
    @JoinColumn(name = "product_id", nullable = false)
    private Product product;
    
    @Column(name = "stock_quantity", nullable = false)
    private Integer stockQuantity;
    
    @Column(name = "min_stock_level")
    private Integer minStockLevel;
    
    @Column(name = "max_stock_level")
    private Integer maxStockLevel;
    
    // constructor, getter and setter methods are omitted}

5.3 Using Embed Types

For related properties that are often used together, you can use embedded types to improve code readability:

package ;

import .*;

/**
  * Address embed type
  */
@Embeddable
public class Address {
    
    @Column(name = "street")
    private String street;
    
    @Column(name = "city")
    private String city;
    
    @Column(name = "state")
    private String state;
    
    @Column(name = "postal_code")
    private String postalCode;
    
    @Column(name = "country")
    private String country;
    
    // constructor, getter and setter methods are omitted}

/**
  * Customer Entity Class
  * Demonstrate the use of embedded types
  */
@Entity
@Table(name = "tb_customer")
public class Customer {
    
    @Id
    @GeneratedValue(strategy = )
    private Long id;
    
    @Column(name = "name", nullable = false)
    private String name;
    
    // Embed billing address    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "billing_street")),
        @AttributeOverride(name = "city", column = @Column(name = "billing_city")),
        @AttributeOverride(name = "state", column = @Column(name = "billing_state")),
        @AttributeOverride(name = "postalCode", column = @Column(name = "billing_postal_code")),
        @AttributeOverride(name = "country", column = @Column(name = "billing_country"))
    })
    private Address billingAddress;
    
    // Embed delivery address    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "shipping_street")),
        @AttributeOverride(name = "city", column = @Column(name = "shipping_city")),
        @AttributeOverride(name = "state", column = @Column(name = "shipping_state")),
        @AttributeOverride(name = "postalCode", column = @Column(name = "shipping_postal_code")),
        @AttributeOverride(name = "country", column = @Column(name = "shipping_country"))
    })
    private Address shippingAddress;
    
    // constructor, getter and setter methods are omitted}

Summarize

Spring Data JPA's entity mapping and relationship mapping capabilities provide Java developers with powerful and flexible data persistence solutions. By rationally using entity mapping, relational mapping, inheritance strategies and life cycle callbacks, developers can build applications that conform to object-oriented principles and efficiently utilize relational databases. This article introduces in detail the implementation methods of basic entity mapping, entity attribute mapping, composite primary key mapping and four core relationship mapping types, and explores the three strategies of entity inheritance and the application of entity life cycle events. In real projects, choosing the right mapping strategy is crucial to improve application performance and maintainability. By following the best practices mentioned in this article, developers can build high-quality data access layers to lay the foundation for the entire application

This is the end of this article about the implementation of SpringData JPA entity mapping and relational mapping. For more related SpringData JPA entity mapping and relational mapping, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!