Introduction
In the previous blog, we introduced Spring Data JPA and its benefits. Now, we will explore the core concept of JPA entities, which are Java classes that represent data in a relational database. This blog will delve into defining JPA entities, using primary keys, mapping fields, establishing relationships between entities, and understanding the various annotations available in JPA.
Define JPA Entities
A JPA entity is a lightweight, persistent domain object that typically maps to a database table. To define an entity, you need to annotate the class with @Entity which makes it eligible for mapping database table.
Here’s an example of a Student entity:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private String email;
// Constructors, Getters, and Setters
}
Explaination:
- @Entity: Marks the class as a JPA entity
- @Table: Specifies the table in database that this entity maps to. If not specified, the class name will be used as the table name.
- @Id: Indicates the primary key of the entity
- @GeneratedValue” Configures the strategy for generating primary key values.
Primary keys and Auto-Generation Strategies
Every JPA entity requires a primary key to uniquely identify each record. JPA provides several strategies for generating primary keys:
- GenerationType.IDENTITY: The database generates the primary key value automatically, typically using an auto-increment column.
- GenerationType.SEQUENCE: JPA uses a database sequence to generate unique values. This strategy is commonly used with Oracle databases.
- GenerationType.TABLE: A separate table is used to generate unique values. This is less commonly used due to performance concerns.
- GenerationType.AUTO: JPA automatically chooses the most appropriate strategy based on the database dialect.
Example: Using GenerationType.SEQUENCE
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_seq")
@SequenceGenerator(name = "student_seq", sequenceName = "student_sequence", allocationSize = 1)
private Long id;
@SequenceGenerator: Defines a sequence generator with a specified name and sequence name in the database. The allocationSize attribute controls how many values are allocated at once, which can improve performance.
Field Mapping: Basic, Embedded, and Id
Basic Field Mapping
In JPA, basic fields are mapped to corresponding database columns. The @Column annotation can be used to customize this mapping.
@Column(name = "first_name", nullable = false, length = 50)
private String firstName;
@Column: Specifies the column name in the database, whether the column can be null, and the maximum length of the field
Embedded Field Mapping
You can embed a value object within an entity using @Embedded. This is useful when you want to group fields that logically belong together.
@Embeddable
public class Address {
private String street;
private String city;
private String state;
private String zipCode;
// Constructors, Getters, and Setters
}
@Entity
public class Student {
@Embedded
private Address address;
}
- @Embeddable: Marks a class as embeddable, meaning it can be embedded within another entity
- @Embedded: Indicates that an instance of the embeddable class should be embedded within the entity
Id Field Mapping
The @Id annotation is used to designate a field as the primary key. The @GeneratdValue annotation is often used alongside @Id to specify how the primary key value should be generated.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
Mapping Relationships: One-to-One, One-to-Many, Many-to-One, Many-to-Many
JPA provides annotations to define relationships between entities, allowing you to model complex data structures.
One-to-One Relationship
A one-to-one relationship exists when one entity is associated with exactly one other entity.
@Entity
public class Passport {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String number;
@OneToOne(mappedBy = "passport", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Student student;
}
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
@OneToOne
@JoinColumn(name = "passport_id")
private Passport passport;
}
Explanation:
- @OneToOne: Define a one-to-one relationship between two entities.
- mappedBy: Indicates the field in other entity that owns the relationship
- @JoinColumn: Specifies the foreign key column.
- cascade: Defines the cascade operations (CascadeType.ALL)
- fetch: Specify the fetching strategy (FetchType.LAZY or FetchType.EAGER)
One-to-Many Relationship
A one-to-many relationship means that one entity is associated with multiple instances of another entity.
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String courseName;
@OneToMany(mappedBy = "course", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Student> students;
}
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
@ManyToOne
@JoinColumn(name = "course_id")
private Course course;
}
Explanation:
- @OneToMany: Define a one-to-many relationship.
- @ManyToOne: Defines the inverse many-to-one relationship
- @JoinColumn: specifies the foreign key column in the Student table that refers to the Course
- cascade: Allows operations to cascade from parent to child (e.g., CascadeType.PERSIST).
- fetch: Determines how data is fetched (FetchType.LAZY delays loading until needed, FetchType.EAGER loads immediately)
Many-to-Many Relationship
A many-to-many relationship exists when multiple instances of one entity are associated with multiple instances of another entity
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private List<Course> courses;
}
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String courseName;
@ManyToMany(mappedBy = "courses", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Student> students;
}
Explanation:
- @ManyToMany: Defines a many-to-may relationship
- @JoinTable: Specifies the join table that maps the relationship
- @JoinColumn: Specifies the foreign key columns in the join table
- cascade: Manages cascading operations across the relationship.
- fetch: Controls the fetching strategy.
FetchType and Cascade Types
FetchType
The FetchType attribute determines how data is loaded from the database/
- FetchType.LAZY: Data is loaded only when it is accessed. This is the default for collections (e.g @OneToMany and @ManyToMany)
- FetchType.EAGERL Data is loaded immediately. This is the default for single valued relationships (e.g @OneToOne and @ManyToOne)
@ManyToOne(fetch = FetchType.EAGER)
private Course course;
CascadeType
The CascadeType attribute controls the propagation of operations from the parent entity to related entities
- CascadeType.PERSIST: Propagates the persist operation.
- CascadeType.MERGE: Propagates the merge operation.
- CascadeType.REMOVE: Propagates the remove operation.
- CascadeType.REFRESH: Propagates the refresh operation.
- CascadeType.DETACH: Propagates the detach operation.
- CascadeType.ALL: Propagates all operations (equivalent to all the above types).
Other Important JPA Annotaions
@Transient
The @Transient annotation is used to mark a field that should not be persisted to the database
@Transient
private int age;
The age field will not be saved in the database, but it can still be used within your application
@Enumerated
The @Enumerated annotation is used to persist an enum type as either an ordinal(Integer) or a string
public enum Status {
ACTIVE,
INACTIVE
}
@Entity
public class Student {
@Enumerated(EnumType.STRING)
private Status status;
}
@Lob
This annotation is used to indicate that a field should be stored as a large object (BLOB or CLOB) in the database
@Lob
private byte[] profilePicture;
@Temporal
The @Temporal annotation is used to specify the date/time type to be stored in the database
@Temporal(TemporalType.DATE)
private Date birthDate;
@Version
This is used for optimistic locking, helping to prevent concurrent updates from overwriting each other
@Version
private int version;
Explanation: The version field will be used to track the version of the entity, and it will automatically increment each time the entity is updated.
Adding Validation to JPA Entities
By using annotation from the javax.validation.contraints package, your can define various constraints on entity fields. These contraints are automatically checked before the entity is persisted to the database, ensuring data integrity.
Common Validation Annotations
Here’s a list of commonly used validation annotations with examples:
- @NotNull ensure that annotated field is not null
- @Size specifies the size constraints for a String, Collection, Map, Array (@Size(min = 2, max =6 ))
- @Min and @Max Enforces minimum and maximum values for numeric fields
- @Pattern specifies a regular expression that the annotated String must match, @Pattern(regexp = “^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,6}$”, message = “Invalid email address”)
- @Email Validates that the annotated field is a valid email address.
- @Past, @PostOrPresent, @Future and @FutureOrPresent ensure that a Date or LocalDate is in the past, present or future
- @Positive and @Negative ensure that annotated field is a positive or negative number
- @Digits Specifies that allowed number of integer and fractional digits , @Digits(integer = 6, fraction = 2)
- @AssertTrue and @AssertFalse ensure that the annotated field is true or false
Custom Validation Contraints
In addition to the built-in validation constraints, you can create custom validation annotations. This involves creating a new annotation and a corresponding validator class that implements ConstraintValidator
Let’s say you want to create a custom validation to ensure that a student’s age is valid according to certain business rules
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AgeValidator.class)
@Documented
public @interface ValidAge {
String message() default "Invalid age";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator Implementation
public class AgeValidator implements ConstraintValidator<ValidAge, Integer> {
@Override
public boolean isValid(Integer age, ConstraintValidatorContext context) {
return age != null && age >= 18 && age <= 65;
}
}
Applying the Custom Validator
@ValidAge
private int age;
Validating Associations
When working with entity relationships, you might want to validate associated entities as well. You can use the @Valid annotation to ensure that the associated entity or collection is validated
@OneToMany(cascade = CascadeType.ALL)
@Valid
private List<Course> courses;
Conclusion
In this blog, we explored the essential annotations and attributes used in JPA for defining and mapping entities. Understanding these concepts is crucial for building a robust data model that accurately reflects your application’s domain. As you progress in your Spring Data JPA journey, these mappings will become the foundation for efficient data retrieval and manipulation.