DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Upgrade Guide To Spring Boot 3.0 for Spring Data JPA and Querydsl
  • Querydsl vs. JPA Criteria, Part 6: Upgrade Guide To Spring Boot 3.2 for Spring Data JPA and Querydsl Project
  • Auditing Spring Boot Using JPA, Hibernate, and Spring Data JPA
  • Spring Boot MapStruct Example of Mapping JPA and Hibernate Entity to DTO | Spring Boot Tutorial

Trending

  • How Rule Engines Transform Business Agility and Code Simplicity
  • The Vector Database Lie
  • The Prompt Isn't Hiding Inside the Image
  • Comparing Top Gen AI Frameworks for Java in 2026
  1. DZone
  2. Coding
  3. Frameworks
  4. How to Use Hibernate Natural IDs in Spring Boot

How to Use Hibernate Natural IDs in Spring Boot

This article is a quick practical guide for using Hibernate Natural IDs (@NaturalId) in Spring Data style.

By 
Anghel Leonard user avatar
Anghel Leonard
DZone Core CORE ·
Feb. 12, 19 · Tutorial
Likes (10)
Comment
Save
Tweet
Share
28.8K Views

Join the DZone community and get the full member experience.

Join For Free

This article is a quick practical guide for using Hibernate Natural IDs (@NaturalId) in Spring Data style. Mainly, we want to expose the Hibernate,bySimpleNaturalId()and byNaturalId()methods via a typical Spring Data repository, and to call them exactly as we call the well-known, findAll(),findOne(), and so on.

Implementation

First, let's focus on the implementation of the needed classes. Having all this in place, we will be able to provide repositories for our entities with natural IDs.

Writing an Entity With Natural ID

Let's consider the following entity that has an auto-generated ID and a natural ID (thecodecolumn). This is just a typical entity using one natural ID via @NaturalId:

@Entity
public class Product implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @NaturalId(mutable = false)
    @Column(nullable = false, updatable = false, unique = true, length = 50)
    private String code;

    // getters and setters

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Product)) {
            return false;
        }
        Product naturalIdProduct = (Product) o;
        return Objects.equals(getCode(), naturalIdProduct.getCode());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getCode());
    }

    @Override
    public String toString() {
        return "Product{" + "id=" + id + ", name=" + name + ", code=" + code + '}';
    }
}

Writing the NaturalRepository Contract

We begin by writing an interface named, NaturalRepository. Basically, when we want to fine-tune a repository, we can rely on @NoRepositoryBeanannotation. In our case, we want to enrich the Spring Data methods arsenal with two more, findBySimpleNaturalId()andfindByNaturalId() :

@NoRepositoryBean
public interface NaturalRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {

    // use this method when your entity has a single field annotated with @NaturalId
    Optional<T> findBySimpleNaturalId(ID naturalId);

    // use this method when your entity has more than one field annotated with @NaturalId
    Optional<T> findByNaturalId(Map<String, Object> naturalIds);        
}

Writing the NaturalRepository Implementation

Further, we extend theSimpleJpaRepository class and implement the NaturalRepository. By extending theSimpleJpaRepositorywe can customize the base repository by adding our needed methods. Mainly, we extend the persistence technology-specific repository base class and use this extension as the custom base class for the repository proxies:

@Transactional(readOnly = true)
public class NaturalRepositoryImpl<T, ID extends Serializable>
        extends SimpleJpaRepository<T, ID> implements NaturalRepository<T, ID> {

    private final EntityManager entityManager;

    public NaturalRepositoryImpl(JpaEntityInformation entityInformation,
            EntityManager entityManager) {
        super(entityInformation, entityManager);

        this.entityManager = entityManager;
    }

    @Override
    public Optional<T> findBySimpleNaturalId(ID naturalId) {

        Optional<T> entity = entityManager.unwrap(Session.class)
                .bySimpleNaturalId(this.getDomainClass())
                .loadOptional(naturalId);

        return entity;
    }

    @Override
    public Optional<T> findByNaturalId(Map<String, Object> naturalIds) {

        NaturalIdLoadAccess<T> loadAccess
                = entityManager.unwrap(Session.class).byNaturalId(this.getDomainClass());
        naturalIds.forEach(loadAccess::using);

        return loadAccess.loadOptional();
    }

}

Setting the NaturalRepositoryImpl as the Base Class

Next, we have to instruct Spring Data to rely on our customized repository base class. In Java configuration, you can do so by using the repositoryBaseClassattribute via the @EnableJpaRepositoriesannotation:

@SpringBootApplication
@EnableJpaRepositories(repositoryBaseClass = NaturalRepositoryImpl.class)
public class NaturalIdApplication {
    ...
}

Let's See How It Works

Now, let's see if it works as expected. First, let's define the ProductRepository:

@Repository
public interface ProductRepository<T, ID> extends NaturalRepository<Product, Long>{    
}

One Natural ID

Further, let's save two products with unique codes (natural ids):

Product tshirt = new Product();
tshirt.setName("T-Shirt");
tshirt.setCode("014-tshirt-2019");

Product socks = new Product();
socks.setName("Socks");
socks.setCode("012-socks-2018");

productRepository.save(tshirt);
productRepository.save(socks);

Finally, let's find the T-Shirt product by its natural ID:

// this should return the t-shirt product wrapped in an Optional
Optional<Product> tshirt = productRepository.findBySimpleNaturalId("014-tshirt-2019");

Note: You can still use productRepository.findById(Object id)if you want to search by the auto-generated id.

More Natural IDs

An entity can use more than one natural ID. For example, let's assume that next to code we have skuas natural ID as well:

@NaturalId(mutable = false)
@Column(nullable = false, updatable = false, unique = true)
private Long sku;

Now, when we persist our product they will look like follows:

Product tshirt = new Product();
tshirt.setName("T-Shirt");
tshirt.setCode("014-tshirt-2019");
tshirt.setSku(1L);

Product socks = new Product();
socks.setName("Socks");
socks.setCode("012-socks-2018");
socks.setSku(2L);

productRepository.save(tshirt);
productRepository.save(socks);

Finding the T-Shirt product can be done like this:

Map<String, Object> ids = new HashMap<>();
ids.put("code", "014-tshirt-2019");
ids.put("sku", 1L);

Optional<Product> tshirt = productRepository.findByNaturalId(ids);

Thanks for reading. Let me know your thoughts in the comments. The source code can be found on GitHub.

Spring Framework Spring Boot Spring Data Hibernate

Opinions expressed by DZone contributors are their own.

Related

  • Upgrade Guide To Spring Boot 3.0 for Spring Data JPA and Querydsl
  • Querydsl vs. JPA Criteria, Part 6: Upgrade Guide To Spring Boot 3.2 for Spring Data JPA and Querydsl Project
  • Auditing Spring Boot Using JPA, Hibernate, and Spring Data JPA
  • Spring Boot MapStruct Example of Mapping JPA and Hibernate Entity to DTO | Spring Boot Tutorial

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook