javaspring-boothibernateimportexport

Spring Boot 3.3: Import entities with Ids provided in a CSV file while having Id auto-generation for new entities?


When my Java application was running on Spring Boot 2.6.2, the entities for which Export/Import features applied were using the following custom id generator:

import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentityGenerator;

import java.io.Serializable;

public class CustomIdGenerator extends IdentityGenerator {

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object entity) {
        Serializable id = session.getEntityPersister(null, entity)
                .getClassMetadata().getIdentifier(entity, session);
        return id != null ? id : super.generate(session, entity);
    }
}

so that, when an Id was provided in the CSV file, the import was producing the very same entity in the database, with the very same Id.

In particular, I was able to save and restore the whole table by doing these actions in the following order:

  1. Export (to save as CSV)
  2. Import an empty CSV file (clears the table)
  3. Import the saved CSV file Result: after these 3 operations, the database content was exactly the same as before.

Now that I have migrated my application to Spring Boot 3.3.1, this doesn't work anymore. I was forced to dispose of CustomIdGenerator because this implementation is no more compatible with Spring Boot 3.3.1 (because of its Hibernate dependency). I tried to obtain a similar CustomIdGenerator the new way, but in vain after several attempts and a couple of evenings trying to achieve that...

To summarize: I need the Ids of my entity to be auto-generated (like with GenerationType.IDENTITY) when they are provided without Id, but I need the Ids to be strictly identical to those provided in the CSV file in case of an import action.

Any known way to do that with success?

--

For example, I tried to implement a CustomIdGenerator this way (Spring Boot 3.3.1):

public class NewCustomIdGenerator implements IdentifierGenerator {

@Override
public Object generate (SharedSessionContractImplementor session, Object entity) {
    var id = session.getEntityPersister(entity.getClass().toString(), entity).getIdentifier(entity, session);
    if (id != null) {
        return id;
    }

    //var res = super.generate(session, entity); // doesn't work
    var res = 0L; // doesn't work (here I'd like the auto-generation to take over)
    return res;
}

I also tried to extend IncrementGenerator or TableGenerator... in vain.

Any idea that works?


Solution

  • I finally found this solution based upon hybrid ID generation:

    import org.hibernate.engine.spi.SharedSessionContractImplementor;
    import org.hibernate.generator.BeforeExecutionGenerator;
    import org.hibernate.generator.EventType;
    import org.hibernate.id.IdentityGenerator;
    
    import static java.util.Objects.isNull;
    
    public class CustomIdGenerator extends IdentityGenerator implements BeforeExecutionGenerator {
    
        @Override
        public Object generate(SharedSessionContractImplementor session, Object entity, Object currentValue, EventType eventType) {
            return getId(entity, session);
        }
    
        private static Object getId(Object entity, SharedSessionContractImplementor session) {
            return session.getEntityPersister(null, entity)
                    .getIdentifier(entity, session);
        }
    
        /**
         * The generate() method above is called if no ID is provided!
         */
        @Override
        public boolean generatedOnExecution(Object entity, SharedSessionContractImplementor session) {
            Object id = getId(entity, session);
            return isNull(id);
        }
    
        @Override
        public boolean generatedOnExecution() {
            return true;
        }
    }
    

    The following post helped me a great deal:
    https://discourse.hibernate.org/t/hybrid-id-generation-in-hibernate-6-2-is-difficult/7666/7