Manipulating Entity ID before it gets inserted

Introduction

In this tutorial I will explain how to manipulate an entity ID even before it gets inserted into the database table.

For that purpose I will use spring framework (spring -boot) and hibernate.

Use case

Assume that you have a column that you need to update based on the row ID; however the entity is not created yet; so the ID doesn't exist yet.

A basic use case consists of creating an entity Odd, that has besides its ID a property called isOdd to store whether the ID is odd or even.

GitHub repository

A working spring-boot project is available on this GitHub repository

Let's get hands dirty

Use a sequence generator for the ID

The first step is delegating the ID generation to Hibernate by using a sequence generation strategy and by defining a sequence generator for it, that way the ID will be generated and included in the insert query; that hibernate will send to the RDBMS for persistence.

Be careful not to use a database sequence; because in that case the RDBMS will generate the id on its own.

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
@SequenceGenerator(name = "sequenceGenerator")
@Column(name = "id")
private Long id;

Use a PreInsertEventListener

Luckily hibernate allows us to define a callback just before inserting a row into the database; and since the id is already generated, we will be able to use it in that event listener.

@Component
public class PreInsertListener implements PreInsertEventListener {

    @Override
    public boolean onPreInsert(PreInsertEvent event) {
        Odd entityCasted = (Odd) event.getEntity();
        if (entityCasted.getId() == null) {
            throw new UnknownId("Entity id is null");
        }
        Boolean isOdd = (entityCasted.getId() % 2 == 0);
        /*
            update the state of the entity
            this is enough for saving the right values in the database
        */
        event.getState()[ArrayUtils.indexOf(
                event.getPersister().getEntityMetamodel().getPropertyNames(),
                "isOdd")] = isOdd;

        /*
            Even if the right state is saved in the database
            hibernate will not update the entity field, we need to do this by ourselves
        */
        entityCasted.setIsOdd(isOdd);
        return false;
    }
}

Add the EventListenerIntegrator

To be able to use the PreInsertListener we just wrote, we need to ask hibernate to register it; so it will be aware of its existence.

@Component
@RequiredArgsConstructor
public class EventListenerIntegrator implements Integrator {

    private final PreInsertListener preInsertListener;

    @Override
    public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
        EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
        /*
            Register the event listener
         */
        eventListenerRegistry.appendListeners(EventType.PRE_INSERT, preInsertListener);
    }

    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
    }
}

Add the integrator to hibernate properties

The final step is making the previous integrator available for use by adding it to hibernate properties.

@Component
@RequiredArgsConstructor
public class HibernateConfig implements HibernatePropertiesCustomizer {

    private final EventListenerIntegrator eventListenerIntegrator;

    @Override
    public void customize(Map<String, Object> hibernateProperties) {
        hibernateProperties.put(
                "hibernate.integrator_provider",
                (IntegratorProvider) () -> Collections.singletonList(eventListenerIntegrator)
        );
    }
}

It's showtime

Now that we have all the code written, we can check what it gives.

Logging the SQL queries

In order to log the queries generated by hibernate we can add those two lines to the application.properties file

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

Logging SQL queries parameters

While logging queries we might also want to log the parameters used in them; so all we have to do is adding these two lines to the application.properties.

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type=trace

Running a test:

To tests the whole thing lets run this basic tests

@Test
public void saveTest(){
        Odd savedOdd1 = oddRepository.save(new Odd());
        assertFalse(savedOdd1.getIsOdd());
        Odd savedOdd2 = oddRepository.save(new Odd());
        assertTrue(savedOdd2.getIsOdd());
        }

Logs

If we look close to the logs we can see that hibernate generates IDs first then inserts the entity row into the database, the values inserted in the database are also logged.

2022-10-31 22:15:01.927 DEBUG 2079 --- [           main] org.hibernate.SQL                        : 
    call next value for sequence_generator
2022-10-31 22:15:01.953 DEBUG 2079 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        odd
        (is_odd, id) 
    values
        (?, ?)
2022-10-31 22:15:01.956 TRACE 2079 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BOOLEAN] - [false]
2022-10-31 22:15:01.957 TRACE 2079 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
2022-10-31 22:15:01.963 DEBUG 2079 --- [           main] org.hibernate.SQL                        : 
    call next value for sequence_generator
2022-10-31 22:15:01.963 DEBUG 2079 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        odd
        (is_odd, id) 
    values
        (?, ?)
2022-10-31 22:15:01.964 TRACE 2079 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BOOLEAN] - [true]
2022-10-31 22:15:01.964 TRACE 2079 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [2]

Commentaires

Posts les plus consultés de ce blog

Checking rows integrity by adding a signature column to tables