리플렉션 어노테이션 방식 (이전글 2.2 참조)로
mongoDB 필드를 암호화하면, mongoTemplate.save에는 적용되지만, mongoTemplate.update에는 반영되지 않는다.
이걸 우회하는 방법으로 새로운 암호화전용 mongoTemplate을 만들 수 있는데
@Component
public class EncryptingMongoTemplate {
private final MongoTemplate mongoTemplate;
private final IpoCreditMasterService cryptoService;
// 캐시: 엔티티별 암호화 필드명 목록
private final ConcurrentMap<Class<?>, Set<String>> encryptedFieldCache = new ConcurrentHashMap<>();
public EncryptingMongoTemplate(MongoTemplate mongoTemplate,
IpoCreditMasterService cryptoService) {
this.mongoTemplate = mongoTemplate;
this.cryptoService = cryptoService;
}
// === 외부에서 이 메서드들을 써서 update === //
public UpdateResult updateFirst(Query query, Update update, Class<?> entityClass) {
encryptUpdateFields(entityClass, update);
return mongoTemplate.updateFirst(query, update, entityClass);
}
public UpdateResult updateMulti(Query query, Update update, Class<?> entityClass) {
encryptUpdateFields(entityClass, update);
return mongoTemplate.updateMulti(query, update, entityClass);
}
public UpdateResult upsert(Query query, Update update, Class<?> entityClass) {
encryptUpdateFields(entityClass, update);
return mongoTemplate.upsert(query, update, entityClass);
}
// 필요하면 find, save 등도 래핑해서 여기에 모아줄 수 있음
// === 핵심: Update 안의 @EncryptedOrNotText 필드 값 암호화 === //
private void encryptUpdateFields(Class<?> entityClass, Update update) {
if (update == null) return;
Set<String> encryptedFields = getEncryptedFieldNames(entityClass);
if (encryptedFields.isEmpty()) return;
// Spring Data Mongo의 Update는 내부에 Document를 가지고 있음
Document root = update.getUpdateObject();
// 여기서는 .set()만 쓴다고 하셨으니 $set만 처리
Document setDoc = (Document) root.get("$set");
if (setDoc == null) {
return;
}
for (String fieldName : encryptedFields) {
if (!setDoc.containsKey(fieldName)) continue;
Object raw = setDoc.get(fieldName);
if (raw instanceof String str) {
setDoc.put(fieldName, cryptoService.encrypt(str));
}
}
}
private Set<String> getEncryptedFieldNames(Class<?> entityClass) {
return encryptedFieldCache.computeIfAbsent(entityClass, this::scanEncryptedFields);
}
private Set<String> scanEncryptedFields(Class<?> type) {
Set<String> result = new HashSet<>();
Class<?> current = type;
while (current != null && current != Object.class) {
for (Field field : current.getDeclaredFields()) {
if (field.isAnnotationPresent(EncryptedOrNotText.class)
&& field.getType() == String.class) {
// @Field("otherName") 고려 안 한 버전
result.add(field.getName());
}
}
current = current.getSuperclass();
}
return result;
}
}
사용예제:
public class UserService {
private final EncryptingMongoTemplate encryptMt;
public void updateUserPhone(String userId, String newPhone) {
Query q = Query.query(Criteria.where("_id").is(userId));
Update u = new Update()
.set("phone", newPhone); // phone이 @EncryptedOrNotText
encryptMt.updateFirst(q, u, UserEntity.class);
// → 호출 전에 EncryptingMongoTemplate이 newPhone을 encrypt해서 MongoDB에 저장
}
}



