Java hibernate условно применяет аннотацию к полю?
Сценарий: объект данных, который сохраняется в таблице БД. В таблице есть несколько старых записей. Теперь мне нужно применить шифрование к новым записям в таблице. Поэтому я добавляю новый столбец, в котором для поля encrypted по умолчанию установлено значение False, чтобы проверить, зашифрованы ли значения.
Проблема: я хочу написать аннотацию для шифрования полей в модели данных (POJO) перед сохранением и расшифровкой при вызовах getter (), только если она зашифрована.
Контекст:
Пользовательская модель.
public class UserData {
@Id
@Column(name = "ID", length = 36)
private String id;
@Column(name = "IS_ENCRYPTED")
private boolean isEncrypted;
@Column(name = "NAME")
@Convert(converter = EncryptionConverter.class)
private String name;
// more fields ....
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
// more similar getter and setters
}
Написанный мною класс шифрования.
@Converter
public class EncryptionConverter implements AttributeConverter<String, String>{
private final String secretKey= "someSecret";
UserData Data = new UserData();
@Override
public String convertToDatabaseColumn(String str) {
if(!isNullOrBlank(str))
return AesEncrypt.encrypt(str, secretKey);
return str;
}
@Override
public String convertToEntityAttribute(String encrypedStr) {
if(!isNullOrBlank(encrypedStr) && Data.isEncrypted)
return AesEncrypt.decrypt(encrypedStr, secretKey);
return encrypedStr;
}
}
Этот класс находится внутри класса модели. (можно выйти наружу, но как передать в аннотацию флаг isencrypted)
Как я могу это сделать, мой подход правильный?
Изменить: есть несколько полей, которые должны быть зашифрованы / дешифрованы, а не только имя.
Ответы
Вы можете создать поведение шифрования в другом классе конфигурации, скажем EncryptedPropertyConfig, в этом вы можете создать bean-компонент EncryptablePropertyResolver из jasypt-spring-boot
@EnableAutoConfiguration
public class EncryptedPropertyConfig {
public EncryptedPropertyConfig() {
}
@Bean
public EncryptablePropertyResolver encryptablePropertyResolver() {
EncryptablePropertyResolver r = new MyPropertyPlaceholderConfigurer();
return r;
}
}
public final class MyPropertyPlaceholderConfigurer implements EncryptablePropertyResolver {
private StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
private EnvironmentStringPBEConfig envConfig = new EnvironmentStringPBEConfig();
public MyPropertyPlaceholderConfigurer() {
// set the encryption key and config
}
public String resolvePropertyValue(String passedValue) {
if (!PropertyValueEncryptionUtils.isEncryptedValue(passedValue)) {
return passedValue;
} else {
String returnValue = "";
try {
returnValue = PropertyValueEncryptionUtils.decrypt(passedValue, this.encryptor);
return returnValue;
} catch (Exception var4) {
throw new RuntimeException("Error in decryption of property value:" + passedValue, var4);
}
}
}
}
Я предлагаю альтернативное решение с использованием Entity Listeners
import javax.persistence.PostLoad;
import javax.persistence.PreUpdate;
public class UserData {
private final String secretKey= "someSecret";
// ...
@PreUpdate
private void onUpdate() {
// triggered before saving entity to DB (both create & update)
if(!isNullOrBlank(name)) {
name = AesEncrypt.encrypt(name, secretKey);
}
}
@PostLoad
private void onLoad() {
// triggered after entity is fetched from Entity Provider
if (!isNullOrBlank(name) && isEncrypted) {
name = AesEncrypt.decrypt(name, secretKey);
}
}
}
Вместо использования JPA AttributeConverter вы можете реализовать тип пользователя гибернации следующим образом:
import java.util.Objects;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.StringType;
import org.hibernate.usertype.UserType;
public class CustomNameType implements UserType
{
private String secretKey = "someSecret";
public CustomNameType()
{
}
@Override
public Object deepCopy(Object value) throws HibernateException
{
if (null == value) return null;
return ((CustomName) value).clone();
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException
{
return cached;
}
@Override
public Serializable disassemble(Object value) throws HibernateException
{
return (Serializable) value;
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException
{
return original;
}
@Override
public boolean equals(Object one, Object two) throws HibernateException
{
return Objects.equals(one, two);
}
@Override
public int hashCode(Object obj) throws HibernateException
{
return Objects.hashCode(obj);
}
@Override
public boolean isMutable()
{
return true;
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException
{
boolean isEncrypted = rs.getBoolean(0); // IS_ENCRYPTED
String name = rs.getString(1); // NAME
if (isEncrypted) {
name = AesEncrypt.decrypt(name, secretKey);
}
return new CustomName(isEncrypted, name);
}
@Override
public void nullSafeSet(PreparedStatement statement, Object value, int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException
{
CustomName customName = (CustomName) value;
String name = customName.getName();
if (customName.isEncrypted()) {
name = AesEncrypt.encrypt(name, secretKey);
}
statement.setBoolean(0, customName.isEncrypted());
statement.setString(1, name);
}
@Override
public Class<?> returnedClass()
{
return CustomName.class;
}
@Override
public int[] sqlTypes()
{
// I do not know the types of your IS_ENCRYPTED and NAME fields
// So, this place maybe require correction
int[] types = {BooleanType.INSTANCE.sqlType(), StringType.INSTANCE.sqlType()};
return types;
}
}
где CustomNameнаходится:
public class CustomName implements Serializable, Cloneable
{
private boolean isEncrypted;
private String name;
public CustomName(boolean isEncrypted, String name)
{
this.isEncrypted = isEncrypted;
this.name = name;
}
// getters , equals, hashCode ...
@Override
public CustomName clone()
{
return new CustomName(isEncrypted, name);
}
}
а затем используйте его:
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Columns;
@Entity
public class UserData {
@Type(type = "com.your.CustomNameType")
@Columns(columns = {
@Column(name = "IS_ENCRYPTED"),
@Column(name = "NAME")
})
private CustomName name;
}