Java Persistence API (JPA)
0. Reference
The best way to map a @OneToMany relationship with JPA and Hibernate
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
Hibernate @EmbeddedId + join (@MapsId)
https://stackoverflow.com/a/16775756/1323562
Spring Data JPA @Query
https://www.baeldung.com/spring-data-jpa-query
- Guidelines
By default, let's only use @ManyToOne with FetchType.LAZY as per vladmihalcea blog.
Let's avoid @OneToMany because of performance and, for retrieving children, use Spring Data repositories programmatically.
2. jakarta vs javax
Spring Boot >= 3 requires the jakarta ones.
Inject:
jakarta.inject.Inject javax.inject.Inject
In pom.xml replace:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
by:
<!-- JSR-330 annotations -->
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<version>2.0.1</version>
</dependency>
Persistence:
jakarta.persistence.CascadeType javax.persistence.CascadeType
jakarta.persistence.Column javax.persistence.Column
jakarta.persistence.EntityManager javax.persistence.EntityManager
jakarta.persistence.PersistenceContext javax.persistence.PersistenceContext
jakarta.persistence.GeneratedValue javax.persistence.GeneratedValue
jakarta.persistence.GenerationType javax.persistence.GenerationType
jakarta.persistence.JoinColumns javax.persistence.JoinColumns
jakarta.persistence.Embeddable javax.persistence.Embeddable
jakarta.persistence.Entity javax.persistence.Entity
jakarta.persistence.GeneratedValue javax.persistence.GeneratedValue
jakarta.persistence.GenerationType javax.persistence.GenerationType
jakarta.persistence.Id javax.persistence.Id
jakarta.persistence.JoinColumn javax.persistence.JoinColumn
jakarta.persistence.Lob javax.persistence.Lob
jakarta.persistence.OneToOne javax.persistence.OneToOne
jakarta.persistence.PrePersist javax.persistence.PrePersist
jakarta.persistence.PreUpdate javax.persistence.PreUpdate
jakarta.persistence.Table javax.persistence.Table
jakarta.persistence.Temporal javax.persistence.Temporal
jakarta.persistence.TemporalType javax.persistence.TemporalType
jakarta.persistence.Version javax.persistence.Version
3. Entities examples
3.0. Technical fields
By default, the columns annotated with @CreatedBy and @LastModifiedBy are populated with the name of the principal that created or last modified the entity. The information comes from SecurityContext‘s Authentication instance.
For reference see sections:
4.3. Tracking Created and Last Modified Dates
4.4. Auditing the Author of Changes With Spring Security
at:
https://www.baeldung.com/database-auditing-jpa
Warning: Your project, eg: App.java, might also need for the annotations to work:
@SpringBootApplication
@EnableJpaAuditing
Note 1: When using Tests you have to enable auditing as well on the test
@DataJpaTest
@RunWith(SpringRunner.class)
@EnableJpaAuditing
public class EntityListenerTest {
Note 2: The data type of attributes for @CreatedDate and @LastModifiedData can be Instant according to:
https://github.com/spring-projects/spring-data-commons/issues/880#issuecomment-807053488
although AnnotationAuditingMetadata class only declares Long, long and Date.
Liquibase technical fields creation:
TBD
Base class w/ all fields that the entities will extends.
@MappedSuperclass
public class BaseEntity {
/*-
* Do never set this value, managed by JPA.
*/
@Column(name = "version", nullable = false)
@Nullable
@Version
private Long version;
/*-
* Do never set this value, implicitly assigned.<br>
* Nullable only before it's persisted
*/
@Column(name = "created_instant", nullable = false)
@Nullable
@CreatedDate
private Instant createdInstant;
/*-
* Technical field. Created by.
*/
@Column(name = "created_by", nullable = true)
@Nullable
@CreatedBy
private Long createdBy;
/*-
* Do never set this value, implicitly assigned.<br>
* Nullable only before it's persisted
*/
@Column(name = "modified_instant", nullable = false)
@Nullable
private Instant modifiedInstant;
/*-
* Technical field. Modified by.
*/
@Column(name = "modified_by", nullable = true)
@Nullable
@LastModifiedBy
private Long modifiedBy;
}
Entity:
@EntityListeners(AuditingEntityListener.class)
@Entity
public class Employee extends BaseEntity{
@Id long empId;
String name;
...
}
TBD
Camp
desc
Camp
name
Tipus
Altres
JPA version
version
bigint
not null default 0. JPA optimistic locking, at entity: @Version private Long version
Created instant
created_instant
datetime
not null default current_timestamp. At entity: Column updatable = false i @CreationTimestamp private Instant createdInstant;
Created by
created_by
bigint
nullable. IdP of the authenticated user (set on @PrePersit method)
Modified instant
modified_instant
datetime
not null default current_timestamp. At entity: @UpdateTimestamp private Instant modifiedInstant;
Modified by
modified_by
bigint
nullable. IdP of the authenticated user (set on @PrePersit and @PreUpdate methods)
3.1. @Column nullable w/ size
@Column(name = "learning_object_name")
@Size(max = 1024)
@Nullable
private String learningObjectName;
3.1. @Column w/ size, clob liquibase type
Oracle batch limit for CLOB is 4000 character.
Liquibase type 'clob' should generate PostgreSQL type accordingly ('text' is preferred on PostgreSQL).
If issues arise, try Column attribute columnDefinition="LONGTEXT"
@Column(name = "learning_object_explanatory_text", nullable = false)
@JdbcTypeCode(java.sql.Types.LONGVARCHAR) /*(hibernate<6) @Type(type = "text")*/
@Lob
@Size(max = 4000)
private String learningObjectExplanatoryText;
3.9. @ManyToOne w/ @EmbeddedId
Designates a ManyToOne or OneToOne relationship attribute that provides the mapping for an EmbeddedId primary key, an attribute within an EmbeddedId primary key, or a simple primary key of the parent entity. The value element specifies the attribute within a composite key to which the relationship attribute corresponds. If the entity's primary key is of the same Java type as the primary key of the entity referenced by the relationship, the value attribute is not specified.
Example:
/** Join lazy to parent NotificationConfig */
@ToString.Exclude
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("notificationId") //Attribute this entity id (NotifEnrollmentId)
@JoinColumn(name = "notificationId") //Attribute of parent NotificationConfig
private NotificationConfig notificationConfig;
Another example:
// parent entity has simple primary key
@Entity
public class Employee {
@Id long empId;
String name;
...
}
// dependent entity uses EmbeddedId for composite key
@Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
@Entity
public class Dependent {
@EmbeddedId DependentId id;
...
@MapsId("empid") // maps the empid attribute of embedded id
@ManyToOne Employee emp;
}
3.10. @MappedSuperclass
Entity inheritance. Eg for the created, modified, etc. fields
https://www.baeldung.com/hibernate-inheritance
JPQL
The Java Persistence Query Language is defined in the JPA specification.
DISTINC with scalar value and order
Example in an Spring Boot repository:
@Query(nativeQuery = false, value = """
SELECT DISTINCT(ctf.calendarCode)
FROM CalendarTFEstructura ctf
ORDER BY ctf.calendarCode DESC""")
List<String> getCalendarsOrderedDesc();
Pageable & Slice
@Query(value = """
SELECT app FROM Application app
WHERE app.institutionCode = :iCode
AND app.learningUnitCode = :sCode
AND (app.calendarCode = :cCode OR app.calendarEndCode = :cCode)
AND app.statusCode != 9300""")
@NotNull
Slice<Application> findByInstitutionSubjectCalendar(@Param("iCode") Integer instituionCode,
@Param("sCode") String subjectCode, @Param("cCode") String calendarCode, Pageable pageable);