Search code examples
jpaspring-data-jpa

JPA how to call custom function on insert


I have a database function getRandomString() which generate a random string. I want to call this function on insert like this INSERT INTO user (username, email) VALUES (getRandomString(), ?)

I have below JPA code

@Table(name="user")
public class User {
   
   private String username;

   private String email;
}

public interface UserRepository extends JpaRepository<User, String> {}

I want to use JPA save method to insert.

User user = new User();
user.setEmail("some email");
userRepository.save(user);

How do I make it so that it will call getRandomString() to add the username value?

Note that I know we can use native query, but my real project has much more complex entity. I'm looking for a way that does not use native query.


Solution

  • In the case your field is an ID, and if it's ok not to call the getRandomString strictly into the same INSERT statement, you can tell Hibernate that the @Id annotated field is generated and declare how it's generated.

    First, you create a class that implements the IdentifierGenerator interface, like this:

    public class RandomStringIdGenerator implements IdentifierGenerator {
    
      @Override
      public Object generate(SharedSessionContractImplementor session, Object object) {
        return session.createNativeQuery("call getRandomString()", String.class).getSingleResult();
      }
    }
    

    Then you can create a custom annotation that uses the new Hibernate's @IdGeneratorType:

    @IdGeneratorType(RandomStringIdGenerator.class)
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.FIELD})
    public @interface RandomStringGeneratedId {}
    

    And then you annotate the User class id with this:

    public class User {
    
      @Id
      @RandomStringGeneratedId
      private String username;
    

    Using this approach, the id will be generated by calling the getRandomString function before each INSERT.

    On the other way, if the generated field is not ad ID, you can use a similar approach, but changing slightly the classes that have to be used.

    You can use a BeforeExecutionGenerator interface an implement the generator like this:

    public class RandomStringGenerator implements BeforeExecutionGenerator {
    
      @Override
      public EnumSet<EventType> getEventTypes() {
        return EnumSet.of(EventType.INSERT);
      }
    
      @Override
      public Object generate(
          SharedSessionContractImplementor session,
          Object owner,
          Object currentValue,
          EventType eventType) {
        return session.createNativeQuery("call getRandomString()", String.class).getSingleResult();
      }
    }
    

    And the custom annotation has to be of @ValueGenerationType:

    @ValueGenerationType(generatedBy = RandomStringGenerator.class)
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.FIELD})
    public @interface RandomStringGenerated {}
    

    Then you can annotate any attribute that you want to be generated on each INSERT of you entities like this:

    public class User {
    
      @RandomStringGenerated private String username;
    
      private String email;
    }