Search code examples
spring-boothibernatejpaspring-data-jpa

Need Help Generating ID in Spring Boot JPA Entity Class Before Saving to Database


I have an entity class in my Spring Boot application where I've implemented a custom ID generator for the ID field. However, the ID isn't generated upon constructing the entity using the default constructor. I need the ID to be auto-generated when I create a new instance of the entity, like this: TransactionsEntity myEntity = new TransactionsEntity(); and then call myEntity.getId() to retrieve the generated ID.

Here's my Entity Class:

@Entity
@Table(name = "transactions")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class TransactionsEntity {

    @Id
    @NonNull
    @Column(name = "transactionNumber", columnDefinition = "varchar(18)", length = 18)
    @GeneratedValue(generator = SequenceGenerator.generatorName)
    @GenericGenerator(name = SequenceGenerator.generatorName, strategy = "com.vendify.Dependencies.Tools.Spring.Generators.SequenceGenerator")
    private String transactionNumber;
}

And this is my Generator:

public class SequenceGenerator implements IdentifierGenerator {
    public static final String generatorName = "6CharGenerator";

    @Override
    public Serializable generate(SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws HibernateException {
        return RandomGenerator.GenerateRandomStringUpperCaseOnlyNoSimilarChars(6);
    }
}

Despite this setup, the constructor doesn't generate an ID as intended. I would greatly appreciate any guidance or help in resolving this issue!


Solution

  • When you create the entity via constructor, Jpa does not know anything about it. It gets to know your new entity when you call p EntityManager.persist().

    This is the moment when your entity becomes "managed" and gets a key assigned. The key generation strategy determines if the the record in the database is created immediately or at some time later (somewhere before transaction commit, see Flush Strategies).

    If you use Strategy @GeneratedValue(strategy = GenerationType.IDENTITY) the generated primary key of the database is used and thus an immediate INSERT is necessary. If you use sequence or a custom strategy, no database action is required for key generation and therefore insert can be deferred to a later point, which can have big performance plus.

    Bottomline: Just calling the constructor of an entity triggers nothing with JPA. If you want the transactionNumber assigned before the call to EntityManager.persist() you have to do it in your constructor.

    However, since it is marked as @Id this might trigger unexpexted results with the EntityManager thinking the entity is already persisted (as it has a persistent Id), but I am not so sure about that. You have to try...

    Response to Comment

    I think you have three choices:

    1. If you want to generate the value with @GeneratedValue, then you have to persist the entity before accessing it. Benefit of @GeneratedValue is, that the EntityManager ensures it is always created. Even when the entity is created without explicit persist (e.g. @Cascade)
    2. You can have @Id without @GeneratedValue. In this case, your business logic must ensure creation (as with any other value).
    3. It sounds like the transactionNumber is some kind of domain key. Using domain keys as primary key might not be the best idea (but that is another topic). So you could use a conventional @Id with a sequence generator and define the transactionNumber as a regular entity property with a unique constraint with @Column(unique=true)