본문 바로가기

TIL

TIL) @field:, getBy vs findby, jwt

1. @field: (annotation use-site target)

코드중 에너테이션 앞에 이런 접두어가 붙는 것을 보았다.

 

@JsonProperty 같은 에너테이션은 런타임시 리플렉션으로 이 에너테이션이 붙은 필드값을 찾는다.

따라서 필드에 붙어야한다. 그런데.

 

코틀린의 primary 생성자에 프로퍼티를 선언하고 사용하는 패턴을 많이들 쓴다.

그런데 이 위에 어떤 에너테이션을 붙이면, 예를들어

 

class Dto(
    @JsonProperty
    val name:String
)

 

이러게 되면, 컴파일된 자바코드로는 생성자의 파라미터에 에너테이션이 붙는다. 즉,

 

public final class Dto {

    @NotNull
    private final String name;

    public Dto(@JsonProperty("name") @NotNull String name) {
        Intrinsics.checkNotNullParameter(name, "name");
        super();
        this.name = name;
    }
}

 

하지만 이건 우리가 의도했던 바가 아니고,

우린 필드에 붙길 바란다.

그러려면 코드작성시 아래와 같이 @field: 를 붙이면 된다.

 

class Dto(
    @field:JsonProperty
    val name:String
)

 

public final class Dto {

    @JsonProperty("name")
    @NotNull
    private final String name;

    public Dto(@NotNull String name) {
        Intrinsics.checkNotNullParameter(name, "name");
        super();
        this.name = name;
    }
}

 

 

https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets

 

Annotations | Kotlin

 

kotlinlang.org

 

 

 

 

2. getBy vs findBy

Jpa 의 select 하는 방법에는 위 두가지가 있다.

 

2.1 getById, findById

id 대상으로 한 경우는 특이하다.

getById 는 결과를 proxy로 가져오고 Lazy Loading 을 해서 가져온다.

findById 같은 경우 Eager 한 로딩을 한다.

그런데 Id 대상이 아닌 getByName 같은경우는 get 이라도 proxy 안쓴다.

 

 

 

2.2 select 한 결과가 하나도 없는 경우

 - id 를 대상 즉 ~ById

getById -> EntityNotFoundException

findById -> Optional 반환

 

 - id 대상 아닌 경우 즉 ~ByName .. && 단건조회인 경우 (반환형이 List<Object> 가 아닌 Object 인 경우)

getByName, findByName 둘다 -> NoResultException 발생 (리포지토리단 예외)

                                                    -> 서비스 로직에서 위 에러를 잡아서 EmptyResultDataAccessException 로 다시 던짐.

 

추가로, 단건조회인데 2건 이상 발견되면, NonUniqueResultException 가 발생.

 

- id 대상 아닌 경우 즉 ~ByName .. && 여러건 조회인 경우 (반환형이 List)

getByName, findByName 둘다 -> Empty 컬렉션 반환

 

 

위는 자바기준이고

코틀린에서 Nullable 하게 ? 붙이면 에러 안나고 null 반환.

근데 ? 안붙이면 EmptyResultDataAccessException 뜬다.

 

 - All (리스트 반환하게 한 경우)

findAllByName(bookName: String): MutableList<Book>?

getAllByName(bookName: String): MutableList<Book>?

findAllByName(bookName: String): MutableList<Book>

getAllByName(bookName: String): MutableList<Book>

위의 모든 경우 size 가 0 인 리스트 반환

null 을 반환하진 않는다는것.

 

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.nullability.annotations

 

Spring Data JPA - Reference Documentation

Example 109. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

 

 

get, find 는 서로 호환성이 있어서 바꿔써도 결과에는 문제가 없다.

 

또, 서비스 로직에서의 경우는 의미적으로 나눌 수 있는데,

get 은 확실히 얻어온다는 느낌의 가져오는 로직 따라서 없으면 Exception 낼거같은?

find 는 null 일 수 있는 찾는 로직.

https://szymonkrajewski.pl/the-practical-difference-between-findby-and-getby-in-repositories/

 

The practical difference between "findBy" and "getBy" in repositories

Repositories are a special example of a class. They usually have a lot of methods designed to retrieve data from the database or the other storage. To mark this operation in the name of the method, we can use one of the common words: find, get, search. Ar

szymonkrajewski.pl

 

 

 

3-3. JPA 가 던져주는 예외 정리

 

롤백되는 예외

javax.persistence.EntityExistsException EntityManager.persist() 호출 시 같은 엔티티가 있으면 발생
javax.persistence.EntityNotFoundException EntityManager.getReference(..) 호출하고 실제 사용 시 엔티티가 존재하지 않으면 발생. refresh(..), lock(..) 에서도 발생
javax.persistence.OptimisticLockException 낙관적 락 충돌
javax.persistence.PessimisticLockException 비관적 락 충돌
javax.persistence.RollbackException EntityTransaction.commit() 실패
롤백이 표시되어 있는 트랜잭션 커밋시에 발생
javax.persistence.TransactionRequiredException 트랜잭션이 필요할 떄 트랜잭션이 없으면 발생. 트랜잭션 없이 엔티티를 변경할 떄 주로 발생

 

 

 

롤백되지 않는 예외

javax.persistence.NoResultException Query.getSingleResult() 호출 시 결과가 하나도 없을 때 발생
javax.persistence.NonUniqueResultException Query.getSingleResult() 호출시 결과가 둘 이상일 떄 발생
javax.persistence.LockTimeoutException 비관적 락에서 시간 초과
javax.persistence.QueryTimeException 쿼리 실행 시간 초과

 

 

JPA 는 결국 Spring 에서 DB 접근을 쉽게 코드적으로 할 수 있게 도와주는 프레임워크다.

위의 예외들을 서비스에서 사용하는 것은, JPA 에 의존하는 것이므로

의존성을 떨어뜨리기 위해 스프링 예외로 잡아서 바꿔서 던져주기 위해서는,

PersistenceExceptionTranslationPostProcessor

를 빈으로 등록해야 한다.

 

편리하게도, 스프링 부트를 사용하면 이 빈이 자동으로 등록되기에, 바꿔서 던져지는 것을 볼 수 있다.

 

 

https://joont92.github.io/jpa/JPA-%EC%98%88%EC%99%B8/

 

 

 

 

 

3. JWT

멘토링중 Java 에서 jjwt 를 사용해서 JWT 를 만드는 경우의 이슈가 있었다.

 

JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey)

을 이용해서 만든 JWT 를 암호화하고 jwt.io 사이트에서 키로 복호화하면 Invalid 가 발생한다.

 

인자를 보면 알겠지만 key 를 base64 인코딩한 값으로 받는다.

base64 인코딩 방식은 아스키 128개중에 64개만 사용한 특정문자열로 바꾸는 인코딩방식이다.

 

그래서 저 코드를 까보면, 키값이 base64 인코딩된 값이라 가정하고 진행한다.

더 자세하게는, 키 string 값에서 base64 값이 아니면 빼는 로직이 있다.

 

따라서 키값을 "+123+" 로 설정하여

signWith(알고리즘, "+123+") 라고 썼다면,

실제적으로 키값은 "123" 으로 적용되며, 123 은 인코딩이 된 값으로 쓴다는 것이다.

 

base64 가 아닌 아스키코드를 쓰려면

즉, "+123+" 을 그대로 쓰려면

JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKey);

를 쓰면 된다. 즉, byte[] 로 바꾸고 넣으면 된다.