Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Use Hibernate Natural IDs in Spring Boot

DZone 's Guide to

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.

· Database Zone ·
Free Resource

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.

Topics:
hibernate ,springdata ,database ,spring boot ,tutorial ,hibernate natural ids

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}