Search code examples
hibernatejakarta-eehibernate-validator

Correct way to use Hibernate Persistence and Validation together


I have a situation using hibernate persistence with hibernate validations and although I've thought of a few hack-ish approaches, I don't know the best way to solve this problem using hibernate persistence and validation.

Simply put, I have a User object and I want to persist it. But before I persist it I want to validate it. Pretty standard stuff.

Now, a User object has a password and we have rules about valid passwords. Eg: minimum of 8 characters, include at least 1 number, etc...I want to validate these things.

But when I persist I need to encrypt/salt/hash the password. But after I do the salting/hashing there is obviously no reasonably way to do the above validations on the password.

So, I thought I could use the @PrePersist and @PreUpdate annotations for this. My thought was in the User class I have a method called onCreate(). I tagged it with @PrePersist and I do something like this (I have something similar for onUpdate()):

@PrePersist
protected void onCreate() {
    encryptPassword();
}

I thought that when I call entityManager.persist() it would first call the validations, then call onCreate() and then persist. So the validations would validate the original, non-salted/hashed password. And the salting/hashing would happen later.

When I ran my tests and debugged though, I found that methods tagged with @PrePersist get called before validations run meaning I can't validate my password anymore.

How do I hook the salting/hashing of the password correctly into the entityManager.persist() lifecycle so that I can validate correctly and after that salt and hash and finally persist?

Thanks.


Solution

  • Use two bean properties, one for persistence and the other for validation / conversion. Something like this:

    @Entity
    @MyCustomConstraint(...)
    public class User implements Serializable {
    
        // place persistence annotations here, for example
        @Lob @Column(...)
        private byte[] hashedPassword;
        // place validation constraints here, for example
        @Size(min = 8, max = 16)
        @Transient
        private String password;
    
        public byte[] getHashedPassword() {
            return this.hashedPassword;
        }
    
        protected void setHashedPassword(byte[] hashedPassword) {
            this.hashedPassword = hashedPassword;
        }
    
        public void setPassword(String password) {
            this.password = password;
            this.setHashedPassword(this.hashAndSaltMyPassword(this.password));
        }
    
        protected String getPassword() {
            return this.password;
        }
    
        protected byte[] hashAndSaltMyPassword(String password) {
            ...
        }
    }
    

    Done.