Skip to content

Commit b83d97c

Browse files
committed
Association referencing natural id
1 parent bafe3a9 commit b83d97c

File tree

14 files changed

+414
-6
lines changed

14 files changed

+414
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
**[How To Use Hibernate `@NaturalId` in SpringBoot](https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootNaturalId)**
3+
4+
**Description:** This is a SpringBoot application that maps a natural business key using Hibernate `@NaturalId`.
5+
6+
**Key points:**
7+
- in the entity (e.g., `Book`), mark the fields (business keys) that should act as natural IDs with `@NaturalId`; commonly, there is a single such property, but multiple are suppored as well as [here](https://docs.jboss.org/hibernate/orm/5.0/mappingGuide/en-US/html/ch07.html).
8+
- for non-mutable ids, mark the columns as `@NaturalId(mutable = false)` and `@Column(nullable = false, updatable = false, unique = true, ...)`
9+
- for mutable ids, mark the columns as `@NaturalId(mutable = true)` and `@Column(nullable = false, updatable = true, unique = true, ...)`
10+
- override the `equals()` and `hashCode()` using the natural id(s)\
11+
- define a `@NoRepositoryBean` interface (`NaturalRepository`) to define two methods, named `findBySimpleNaturalId()` and `findByNaturalId()`
12+
- provide an implementation for this interface (`NaturalRepositoryImpl`) relying on Hibernate, `Session`, `bySimpleNaturalId()` and `byNaturalId()` methods
13+
- for the entity, write a repository class (e.g., for the `Product` entity write `ProductNaturalRepository`) that extends the `NaturalRepositoryImpl` and use it for setting the entity class type and the natural id type (when an entity uses more than one natural ID, the type is not relevant anymore, simply set it to `Serializable`)
14+
- inject this class in your services and call `findBySimpleNaturalId()` or `findByNaturalId()`
15+
16+
-------------------------------
17+
18+
**You may like to try as well:**
19+
<a href="https://leanpub.com/java-persistence-performance-illustrated-guide"><p align="center"><img src="https://github.com/AnghelLeonard/Hibernate-SpringBoot/blob/master/Java%20Persistence%20Performance%20Illustrated%20Guide.jpg" height="410" width="350"/></p></a>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>com.jpa</groupId>
7+
<artifactId>HibernateSpringBootReferenceNaturalId</artifactId>
8+
<version>1.0</version>
9+
<packaging>jar</packaging>
10+
11+
<name>HibernateSpringBootReferenceNaturalId</name>
12+
<description>JPA project for Spring Boot</description>
13+
14+
<parent>
15+
<groupId>org.springframework.boot</groupId>
16+
<artifactId>spring-boot-starter-parent</artifactId>
17+
<version>2.1.4.RELEASE</version>
18+
<relativePath/> <!-- lookup parent from repository -->
19+
</parent>
20+
21+
<properties>
22+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
24+
<java.version>1.8</java.version>
25+
</properties>
26+
27+
<dependencies>
28+
<dependency>
29+
<groupId>org.springframework.boot</groupId>
30+
<artifactId>spring-boot-starter-data-jpa</artifactId>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-starter-jdbc</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.springframework.boot</groupId>
38+
<artifactId>spring-boot-starter-web</artifactId>
39+
</dependency>
40+
<dependency>
41+
<groupId>mysql</groupId>
42+
<artifactId>mysql-connector-java</artifactId>
43+
<scope>runtime</scope>
44+
</dependency>
45+
<dependency>
46+
<groupId>org.springframework.boot</groupId>
47+
<artifactId>spring-boot-starter-test</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
</dependencies>
51+
52+
<build>
53+
<plugins>
54+
<plugin>
55+
<groupId>org.springframework.boot</groupId>
56+
<artifactId>spring-boot-maven-plugin</artifactId>
57+
</plugin>
58+
</plugins>
59+
</build>
60+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.bookstore;
2+
3+
import com.bookstore.entity.Book;
4+
import org.springframework.boot.ApplicationRunner;
5+
import org.springframework.boot.SpringApplication;
6+
import org.springframework.boot.autoconfigure.SpringBootApplication;
7+
import org.springframework.context.annotation.Bean;
8+
import com.bookstore.service.BookstoreService;
9+
10+
@SpringBootApplication
11+
public class MainApplication {
12+
13+
private final BookstoreService bookstoreService;
14+
15+
public MainApplication(BookstoreService bookstoreService) {
16+
this.bookstoreService = bookstoreService;
17+
}
18+
19+
public static void main(String[] args) {
20+
SpringApplication.run(MainApplication.class, args);
21+
}
22+
23+
@Bean
24+
public ApplicationRunner init() {
25+
return args -> {
26+
27+
bookstoreService.persistAuthorsAndBooks();
28+
bookstoreService.fetchBookWithAuthor();
29+
};
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.bookstore.entity;
2+
3+
import java.io.Serializable;
4+
import javax.persistence.Column;
5+
import javax.persistence.Entity;
6+
import javax.persistence.GeneratedValue;
7+
import javax.persistence.GenerationType;
8+
import javax.persistence.Id;
9+
import org.hibernate.annotations.NaturalId;
10+
11+
@Entity
12+
public class Author implements Serializable {
13+
14+
private static final long serialVersionUID = 1L;
15+
16+
@Id
17+
@GeneratedValue(strategy = GenerationType.IDENTITY)
18+
private Long id;
19+
20+
private int age;
21+
private String name;
22+
private String genre;
23+
24+
@NaturalId(mutable = false)
25+
@Column(nullable = false, updatable = false, unique = true, length = 50)
26+
private String email;
27+
28+
public Long getId() {
29+
return id;
30+
}
31+
32+
public void setId(Long id) {
33+
this.id = id;
34+
}
35+
36+
public String getName() {
37+
return name;
38+
}
39+
40+
public void setName(String name) {
41+
this.name = name;
42+
}
43+
44+
public String getGenre() {
45+
return genre;
46+
}
47+
48+
public void setGenre(String genre) {
49+
this.genre = genre;
50+
}
51+
52+
public int getAge() {
53+
return age;
54+
}
55+
56+
public void setAge(int age) {
57+
this.age = age;
58+
}
59+
60+
public String getEmail() {
61+
return email;
62+
}
63+
64+
public void setEmail(String email) {
65+
this.email = email;
66+
}
67+
68+
@Override
69+
public String toString() {
70+
return "Author{" + "id=" + id + ", age=" + age
71+
+ ", name=" + name + ", genre=" + genre + ", email=" + email + '}';
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.bookstore.entity;
2+
3+
import java.io.Serializable;
4+
import javax.persistence.Entity;
5+
import javax.persistence.FetchType;
6+
import javax.persistence.GeneratedValue;
7+
import javax.persistence.GenerationType;
8+
import javax.persistence.Id;
9+
import javax.persistence.JoinColumn;
10+
import javax.persistence.ManyToOne;
11+
12+
@Entity
13+
public class Book implements Serializable {
14+
15+
private static final long serialVersionUID = 1L;
16+
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.IDENTITY)
19+
private Long id;
20+
21+
private String title;
22+
private String isbn;
23+
24+
@ManyToOne(fetch = FetchType.LAZY)
25+
@JoinColumn(referencedColumnName = "email")
26+
private Author author;
27+
28+
public Long getId() {
29+
return id;
30+
}
31+
32+
public void setId(Long id) {
33+
this.id = id;
34+
}
35+
36+
public String getTitle() {
37+
return title;
38+
}
39+
40+
public void setTitle(String title) {
41+
this.title = title;
42+
}
43+
44+
public String getIsbn() {
45+
return isbn;
46+
}
47+
48+
public void setIsbn(String isbn) {
49+
this.isbn = isbn;
50+
}
51+
52+
public Author getAuthor() {
53+
return author;
54+
}
55+
56+
public void setAuthor(Author author) {
57+
this.author = author;
58+
}
59+
60+
@Override
61+
public String toString() {
62+
return "Book{" + "id=" + id + ", title=" + title + ", isbn=" + isbn + '}';
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.bookstore.naturalid;
2+
3+
import java.io.Serializable;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import org.springframework.data.repository.NoRepositoryBean;
7+
8+
@NoRepositoryBean
9+
public interface NaturalRepository<T, NID extends Serializable> {
10+
11+
// use this method when your entity has a single field annotated with @NaturalId
12+
Optional<T> findBySimpleNaturalId(NID naturalId);
13+
14+
// use this method when your entity has more than one field annotated with @NaturalId
15+
Optional<T> findByNaturalId(Map<String, Object> naturalIds);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.bookstore.naturalid;
2+
3+
import java.io.Serializable;
4+
import java.util.Map;
5+
import java.util.Optional;
6+
import javax.persistence.EntityManager;
7+
import javax.persistence.PersistenceContext;
8+
import org.hibernate.NaturalIdLoadAccess;
9+
import org.hibernate.Session;
10+
import org.springframework.stereotype.Repository;
11+
import org.springframework.transaction.annotation.Transactional;
12+
13+
@Repository
14+
@Transactional(readOnly = true)
15+
public abstract class NaturalRepositoryImpl<T, NID extends Serializable>
16+
implements NaturalRepository<T, NID> {
17+
18+
@PersistenceContext
19+
private EntityManager entityManager;
20+
21+
private final Class<T> entityClass;
22+
23+
public NaturalRepositoryImpl(Class<T> entityClass) {
24+
this.entityClass = entityClass;
25+
}
26+
27+
@Override
28+
public Optional<T> findBySimpleNaturalId(NID naturalId) {
29+
30+
Optional<T> entity = entityManager.unwrap(Session.class)
31+
.bySimpleNaturalId(entityClass)
32+
.loadOptional(naturalId);
33+
34+
return entity;
35+
}
36+
37+
@Override
38+
public Optional<T> findByNaturalId(Map<String, Object> naturalIds) {
39+
40+
NaturalIdLoadAccess<T> loadAccess
41+
= entityManager.unwrap(Session.class).byNaturalId(entityClass);
42+
naturalIds.forEach(loadAccess::using);
43+
44+
return loadAccess.loadOptional();
45+
}
46+
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.bookstore.repository;
2+
3+
import com.bookstore.entity.Author;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface AuthorRepository<T, ID> extends JpaRepository<Author, Long> {
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.bookstore.repository;
2+
3+
import com.bookstore.entity.Book;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface BookRepository<T, ID> extends JpaRepository<Book, Long> {
9+
10+
public Book findByTitle(String title);
11+
}

0 commit comments

Comments
 (0)