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


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);