Add Hibernate 7 batches 2-6: mapping styles, JPA annotations, natural IDs, @Immutable, stored procedures, in-memory test databases, JNDI mocking, proxies, associations, temporal mapping, named queries, HQL, Criteria API and EntityManager bootstrapping (Hibernate 7.4.5.Final + Spring Boot 4.1.1 + JDK 25)
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EQNA6DJ9VgCtW6zhCE8Xud
This commit is contained in:
+54
@@ -0,0 +1,54 @@
|
||||
package com.ankurm.hibernatedemo.naturalid;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.CacheConcurrencyStrategy;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
import org.hibernate.annotations.NaturalIdCache;
|
||||
|
||||
/**
|
||||
* Same shape as {@link NaturalIdProduct}, but with {@code @Cacheable} + {@code @Cache} (entity
|
||||
* L2 cache) AND {@code @NaturalIdCache} (the SEPARATE natural-id-to-PK resolution L2 cache
|
||||
* region -- these are two different cache regions, not one).
|
||||
*
|
||||
* <p>Docs: docs/06-natural-ids.md, Topic 3.
|
||||
*/
|
||||
@Entity
|
||||
@jakarta.persistence.Cacheable
|
||||
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
|
||||
@NaturalIdCache
|
||||
public class CachedNaturalIdProduct {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@NaturalId
|
||||
@Column(nullable = false, unique = true, updatable = false)
|
||||
private String sku;
|
||||
|
||||
private String name;
|
||||
|
||||
public CachedNaturalIdProduct() {
|
||||
}
|
||||
|
||||
public CachedNaturalIdProduct(String sku, String name) {
|
||||
this.sku = sku;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getSku() {
|
||||
return sku;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ankurm.hibernatedemo.naturalid;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
/**
|
||||
* Parent side of the composite natural id demo -- see {@link Department}.
|
||||
*
|
||||
* <p>Docs: docs/06-natural-ids.md, Topic 3.
|
||||
*/
|
||||
@Entity
|
||||
public class Company {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
public Company() {
|
||||
}
|
||||
|
||||
public Company(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.ankurm.hibernatedemo.naturalid;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
|
||||
/**
|
||||
* A COMPOSITE natural id: (company, deptCode) together must be unique, not either field alone.
|
||||
* Matches the article's example -- verified to actually load via
|
||||
* {@code session.byNaturalId(Department.class).using(...).using(...).load()}.
|
||||
*
|
||||
* <p>Docs: docs/06-natural-ids.md, Topic 3.
|
||||
*/
|
||||
@Entity
|
||||
public class Department {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@NaturalId
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "company_id")
|
||||
private Company company;
|
||||
|
||||
@NaturalId
|
||||
private String deptCode;
|
||||
|
||||
private String name;
|
||||
|
||||
public Department() {
|
||||
}
|
||||
|
||||
public Department(Company company, String deptCode, String name) {
|
||||
this.company = company;
|
||||
this.deptCode = deptCode;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Company getCompany() {
|
||||
return company;
|
||||
}
|
||||
|
||||
public String getDeptCode() {
|
||||
return deptCode;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.ankurm.hibernatedemo.naturalid;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
|
||||
/**
|
||||
* {@code @NaturalId} with NO {@code mutable} attribute -- defaults to {@code mutable = false}
|
||||
* (immutable). Deliberately does NOT add {@code @Column(updatable = false)}, so if Hibernate
|
||||
* lets an UPDATE through at the SQL level, that is Hibernate's own natural-id immutability
|
||||
* enforcement failing to stop it, not a JPA column-level guard doing the stopping.
|
||||
*
|
||||
* <p>Docs: docs/06-natural-ids.md, Topic 3.
|
||||
*/
|
||||
@Entity
|
||||
public class ImmutableNaturalIdEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@NaturalId // mutable defaults to false
|
||||
private String code;
|
||||
|
||||
public ImmutableNaturalIdEntity() {
|
||||
}
|
||||
|
||||
public ImmutableNaturalIdEntity(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.ankurm.hibernatedemo.naturalid;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
|
||||
/**
|
||||
* {@code @NaturalId(mutable = true)} -- the explicit opt-in for a natural id that IS allowed
|
||||
* to change (e.g. an email address).
|
||||
*
|
||||
* <p>Docs: docs/06-natural-ids.md, Topic 3.
|
||||
*/
|
||||
@Entity
|
||||
public class MutableNaturalIdEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@NaturalId(mutable = true)
|
||||
private String code;
|
||||
|
||||
public MutableNaturalIdEntity() {
|
||||
}
|
||||
|
||||
public MutableNaturalIdEntity(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.ankurm.hibernatedemo.naturalid;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import java.util.Objects;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
|
||||
/**
|
||||
* equals()/hashCode() based on the IMMUTABLE natural id (sku), assigned in the constructor --
|
||||
* exactly the pattern posts 4864/4865 recommend. Since sku is set before the object ever enters
|
||||
* a Set/Map (unlike a surrogate id, which is null until flush), the hash code should be STABLE
|
||||
* across persist(). This entity exists to test whether that advice actually holds up.
|
||||
*
|
||||
* <p>Docs: docs/06-natural-ids.md, Topic 3.
|
||||
*/
|
||||
@Entity
|
||||
public class NaturalIdEqualsEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@NaturalId
|
||||
@Column(nullable = false, unique = true, updatable = false)
|
||||
private final String sku;
|
||||
|
||||
private String name;
|
||||
|
||||
protected NaturalIdEqualsEntity() {
|
||||
this.sku = null; // JPA no-arg constructor requirement
|
||||
}
|
||||
|
||||
public NaturalIdEqualsEntity(String sku, String name) {
|
||||
this.sku = sku;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getSku() {
|
||||
return sku;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
||||
NaturalIdEqualsEntity that = (NaturalIdEqualsEntity) o;
|
||||
return Objects.equals(sku, that.sku);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(sku);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.ankurm.hibernatedemo.naturalid;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.annotations.NaturalId;
|
||||
|
||||
/**
|
||||
* A single-field, IMMUTABLE natural id (the JPA/Hibernate default for {@code @NaturalId}).
|
||||
* No {@code @Cache}/{@code @NaturalIdCache} here -- this is the baseline entity for the
|
||||
* "does bySimpleNaturalId save a query without L2 cache" experiment.
|
||||
*
|
||||
* <p>Docs: docs/06-natural-ids.md, Topic 3.
|
||||
*/
|
||||
@Entity
|
||||
public class NaturalIdProduct {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@NaturalId
|
||||
@Column(nullable = false, unique = true, updatable = false)
|
||||
private String sku;
|
||||
|
||||
private String name;
|
||||
|
||||
public NaturalIdProduct() {
|
||||
}
|
||||
|
||||
public NaturalIdProduct(String sku, String name) {
|
||||
this.sku = sku;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getSku() {
|
||||
return sku;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user