If you have access to JPA 2.1, I would advocate for the use of an @Convert annotation with an AttributeConverter implementation.
An AttributeConverter defines a contract between the state of an entity property when it is serialized to the datastore and when it is deserialized from the datastore.
public class CreditCard {
@Convert(converter = CreditCardNumberConverter.class)
private String creditCardNumber;
}
Your converter implementation might look like this
public class CreditCardNumberConverter implements AttributeConverter<String, String> {
@Override
public String convertToDatabaseColumn(String attribute) {
/* perform encryption here */
}
@Override
public String convertToEntityAttribute(String dbData) {
/* perform decryption here */
}
}
If you are not able to leverage JPA 2.1, an EntityListener or the use of @PrePersist, @PreUpdate, and @PostLoad may be used in order to perform similar logic for encrypting and decrypting the database value.
Just be sure that if you decide to use an EntityListener or any of the Pre/Post callback method annotations, store the decrypted result in a transient field and use that field as your business layer's usage, such as follows:
public class CreditCard {
// this field could have package private get/set methods
@Column(name = "card_number", length = 25, nullable = false)
private String encrpytedCardNumber;
// this is the public operated upon field
@Transient
private String cardNumber;
@PostLoad
public void decryptCardNumber() {
// decrypts card number during DATABASE READ
this.cardNumber = EncryptionUtils.decrypt(encryptedCardNumber);
}
@PrePersist
@PreUpdate
public void encryptCardNumber() {
// encrypts card number during INSERT/UPDATE
this.encryptedCardNumber = EncryptionUtils.encrypt(cardNumber);
}
}
Doing the above keeps entity state consistent in the object as to what exists in your database, without having Hibernate believing that the entity has changed immediately upon loading the database data.