카테고리 없음

spring jpa의 patch때 사용하는 유틸

머룽 2023. 4. 19. 09:55
회사에서 잉여짓만하는구만ㅠㅠ 스프링 jpa를 쓰다보면 뭐 form으로 전달하는 것은 상관이 없는데 API 를만들다보면 업데이트시 null로 오면 null을 업데이트 한다. 그래서 옛날객체의 새로운 객체를 넣어 주어 update 하는 방식으로 해왔다. 예를 들어 이런 방식이다.
if(account.getName() == null){
  oldAccount.setName(account.getName())
}
필드가 많다보면 이것도 일이다. notnull은 상관없는데 null을 허용하는 것들이 문제다. 그래서 유틸로 만들었다.
public static <T> void oldInstanceBynewInstance(T oldInstance, T newInstance) {
    Class<?> newInstanceClass = newInstance.getClass();
    Class<?> oldInstanceClass = oldInstance.getClass();

    if(!newInstanceClass.isAssignableFrom(oldInstanceClass)){
        return;
    }

    for (Field newField : newInstanceClass.getDeclaredFields()) {
        newField.setAccessible(true);
        Object obj;
        try {
            obj = newField.get(newInstance);
        } catch (IllegalAccessException e) {
            throw new ReflecationException("reflecation Exception get field");
        }
        Id id = newField.getAnnotation(Id.class);
        PatchIgnore patchIgnore = newField.getAnnotation(PatchIgnore.class);
        if (id == null) {
            if (obj != null && patchIgnore == null) {
                Field oldField;
                try {
                    oldField = oldInstanceClass.getDeclaredField(newField.getName());
                    oldField.setAccessible(true);
                    oldField.set(oldInstance, obj);
                } catch (NoSuchFieldException e) {
                    throw new ReflecationException("no such field");
                } catch (IllegalAccessException e) {
                    throw new ReflecationException("reflecation Exception set field");
                }
            }
        }
    }
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PatchIgnore {
}
새로운객체의 필드를 오래된 객체에 넣는것 뿐이다. 테스트를 해보자
public class ReflectionUtilsTest {

    Product oldProduct = new Product();

    @Before
    public void before() {
        oldProduct.setId(1L);
        oldProduct.setName("wonwoo");
        oldProduct.setEmail("email@test.com");
        oldProduct.setPassword("password");
    }

    @Test
    public void reflectionUtilTest() {
        Product product = new Product();
        product.setId(20L);
        product.setName("ooooo");
        product.setEmail("wonwoo@test.com");
        ReflectionUtils.oldInstanceBynewInstance(oldProduct, product);
        assertEquals(oldProduct.getId().longValue(), 1L);
        assertEquals(oldProduct.getName(), "ooooo");
        assertEquals(oldProduct.getEmail(), "wonwoo@test.com");
    }

    @Test
    public void reflectionUtilIgnoreTest() {
        Product product = new Product();
        product.setName("ooooo");
        product.setEmail("wonwoo@test.com");
        product.setPassword("repassword");
        ReflectionUtils.oldInstanceBynewInstance(oldProduct, product);
        assertEquals(oldProduct.getName(), "ooooo");
        assertEquals(oldProduct.getEmail(), "wonwoo@test.com");
        assertEquals(oldProduct.getPassword(), "password");
    }
}

@Data
class Product {

    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String email;

    @PatchIgnore
    private String password;
}
Id 어노테이션과 PatchIgnore은 무시한다. old객체의 name 에 wonwoo라는 String이 들어가 있다. 그리고 나서 새로운 객체에 ooooo 을 넣었다. 그러면 old객체에 ooooo이 들어간걸 확인 할 수 있다. 또한 PatchIgnore 어노테이션은 무시한다. 기존에 password 라고 넣고 새로운 객체에는 repassword라 넣었다. 하지만 무시당하고 password가 계속 담겨 있다. 실직적으로 사용은 이렇게 하면 된다.
public Account update(final Long id, final Account account) {
    Account oldAccount = findOne(id);
    if (oldAccount == null) {
        throw new IDNotFoundException("id " + id + " not found");
    }
    ReflectionUtils.oldInstanceBynewInstance(oldAccount, account);
    return oldAccount;
}
jpa 의 변경감지로 인해 굳이 save를 할 필요 없다 필자가 생각한방법이다. 물론 더 좋은 방법이 있을 수도 있지만 지금 현재 나는 모른다.ㅜㅜㅜ