// 에드센스


1. 객체 정렬?

class Person {
    String name;
    int weight;
    int height;

    public Person(String name, int height, int weight) {
        this.name = name;
        this.weight = weight;
        this.height = height;
    }

    public void print() {
        System.out.println(name + " " + height + " " + weight);
    }

    public int getWeight() {
        return weight;
    }

    public int getHeight() {
        return height;
    }
}

여기 Person 클래스가 있다.

 

 

List<Person> list = new ArrayList<>();

Person 클래스로 이루어진 어레이리스트도 있다.

 

이 리스트를 정렬하고하자한다.

name을 기준으로 정렬을 해야할까 weight를 기준으로 정렬을 해야할까?

우리는 이 문제를 Comparable이나 Comparator를 통해서 해결할 수 있다.

 

 

 

 


1. Comparable

정렬하고자 하는 객체의 클래스에 Comparable 인터페이스를 구현하는 방법이다.

물론 그 클래스에 Comparable 인터페이스를 구현할 수 있다면 말이다.

// 정렬 대상 클레스에 인터페이스를 구현할 수 있다면 Comparable 사용 가능
class Person implements Comparable<Person> {
    String name;
    int weight;
    int height;

    public Person(String name, int height, int weight) {
        this.name = name;
        this.weight = weight;
        this.height = height;
    }

    public void print() {
        System.out.println(name + " " + height + " " + weight);
    }

    public int getWeight() {
        return weight;
    }

    public int getHeight() {
        return height;
    }

    @Override
    public int compareTo(Person p) {
        // 오름차순: this 객체 - 인자로 넘어온 객체
        // 내림차순: 인자로 넘어온 객체 - this 객체
        return this.height - p.height; // 오름차순 정렬
    }
}
Collections.sort(list);

Comparable 인터페이스의 compareTo 메소드를 구현하면 된다.

위 코드는 height를 기준으로 오름차순 정렬의 예시다.

 

 

 

만약 1순위로 height를 기준으로 오름차순 정렬하고

height가 같은 객체가 있다면

2순위로 weight를 기준으로 내림차순 정렬하고 싶다면?

 

class Person implements Comparable<Person> {
    String name;
    int weight;
    int height;

    public Person(String name, int height, int weight) {
        this.name = name;
        this.weight = weight;
        this.height = height;
    }

    public void print() {
        System.out.println(name + " " + height + " " + weight);
    }

    public int getWeight() {
        return weight;
    }

    public int getHeight() {
        return height;
    }

    @Override
    public int compareTo(Person p) {
        // this 객체 > 인자로 넘어온 객체 => return 1이라는것은
        // this 객체 - 인자로 넘어온 객체 > 0 => 오름차순
        if (this.height > p.height) return 1;
        else if (this.height == p.height) { // height가 같다면
            // this 객체 < 인자로 넘어온 객체 => return 1이라면
            // this 객체 - 인자로 넘어온 객체 < 0 => 내림차순
            if (this.weight < p.weight) return 1; // weight를 내림차순으로
        }
        return -1;
    }
}

이렇게 하면 된다.

 

 

 

 


2. Comparator

정렬하고자 하는 객체의 클래스에 Comparable 인터페이스를 구현할 수 없을때 사용하는 방법이다.

혹은 Comprable 인터페이스를 통해 이미 정렬기준이 정해져있지만 다른 기준으로 정렬하고 싶을때 사용하는 방법이다.

 

// 정렬 대상 클레스에 인터페이스를 구현할 수 없다면
// 혹은 Comparable을 통해 이미 정해져있는 정렬 기준과 다르게 정렬하고 싶다면
class Person {
    String name;
    int weight;
    int height;

    public Person(String name, int height, int weight) {
        this.name = name;
        this.weight = weight;
        this.height = height;
    }

    public void print() {
        System.out.println(name + " " + height + " " + weight);
    }

    public int getWeight() {
        return weight;
    }

    public int getHeight() {
        return height;
    }
}

Person 클래스는 동일하다.

 

 

 

우리는 Comparator라는 일종의 정렬기준을 정의한 객체를 만들어서 Collections.sort()의 인자로 넣어주어야 한다.

 

    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }

 

Collections.sort 함수를 까보면 첫번째로 정렬 할 리스트와 두번째로 정렬 기준인 Comparator를 받는 것을 볼 수 있다.

 

 

 

Collections.sort(list, new Comparator<Person>() {
    @Override
    public int compare(Person o1, Person o2) {
    	// 오름차순
        return o1.height - o2.height;
    }
});

 

Comparator객체를 한번쓰고 버릴것 이니 익명클래스로 넣어주었다.

저 익명클래스의 객체는 Comparator 인터페이스를 구현받고있기에 compare 함수를 오버라이드하고있다.

 

 

 

정렬 기준이 2개 이상일 때도 Comparable과 동일하게 해주면 된다.

Collections.sort(list, new Comparator<Person>() {
    @Override
    public int compare(Person o1, Person o2) {
    	// height 기준 오름차순
        if (o1.height > o2.height) return 1;
        else if (o1.height == o2.height) {
        	// height가 같다면 weight 기준으로 내림차순
            if (o1.weight < o2.weight) return 1;
        }
        return -1;
    }
});

 

 

 

Comparator를 람다함수로 조금더 간단하게 표현할 수 있다.

Collections.sort(list, (a, b) -> a.getHeight() - b.getHeight());

 

 

 

stream을 쓰면 더더더 간단하게 표현할 수 있다.

list.sort(Comparator.comparing(Person::getHeight));

 

 

 

stream을 사용했을때 2개 이상의 기준을 적용하고 싶다면

list.sort(Comparator.comparing(Person::getHeight).thenComparing(Person::getWeight));

 

이렇게 thenComparing을 통해 이어주면 된다.

하나의 조건은 역순으로 하고싶다면 .reversed()를 붙혀주면 되는데 주의 할 것이

 

 

 

list.sort(Comparator.comparing(Person::getHeight).thenComparing(Person::getWeight).reversed());

 

이렇게 뒤에 reversed()를 바로 붙이면 height에 대한 정렬도 reversed()에 영향을 받아서 두가지 기준 다 내림차순 정렬이 돼 버린다.

 

 

 

두번째 기준만 내림차순으로 하고 싶다면

Comparator<Person> reverseCompare = Comparator.comparing(Person::getWeight).reversed();
list.sort(Comparator.comparing(Person::getHeight).thenComparing(reverseCompare));

 

이렇게 reverse용 Comparator를 따로 만들어서 넣어주어야 한다.

 

 

 

 

참고:

 

'Java' 카테고리의 다른 글

[Java] 해시/해시테이블이란?  (0) 2021.07.12
[Java] 제네릭(Generic)  (0) 2021.07.11
[Java] Garbage Collection  (4) 2021.07.01
[Java] Thread/MultiThread 4 - 동시성 문제  (0) 2021.06.29
[Java] Static 키워드  (0) 2021.06.28

spring data jpa에서

 

No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer

 

에러가 발생했다.

 

간단하게 해석해보자면 serializer가 없다는 것인데,

serialize, 즉 직렬화란 간단히 말해서 Object를 연속된 String 데이터나 연속된 Bytes 데이터로 바꾸는 것을 의미한다.

역직렬화는 그 반대겠지?

 

Object는 메모리상에서 존재하고 추상적이다.

String 데이터나 Bytes 데이터는 드라이브에 저장될 수 있고 통신선을 통해 전송될 수 있다.

https://youtu.be/qrQZOPZmt0w

 

 

아무튼 다시 나는 스프링부트 + 코틀린을 사용하여 개발을 하고있었고,

JPA로 table을 findAll을 통해서 목록을 뽑아오는 기능을 구현하고있었다.

에러는 이 부분에서 발생했다.

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    var noticeCategory: NoticeCategory,

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "board_id")
    var noticeBoard: NoticeBoard

 

JPA로 조회한 데이터는 메모리 상에 존재하는 Object다

이 Object를 Json 형태로 반환하기위해 직렬화를 해주어야하는데 

NoticeCategory와 NoticeBoard 컬럼이 LAZY로딩으로 설정되어있다.

 

이렇게 되면 조회한 Object를 serialize하려는 순간 실제 Object가 아닌

프록시로 감싸져있는 hibernateLazyInitializer를 serialize하게되므로 에러가 발생한다

 

 

 

해결

3가지 방법정도가 있다. 난 2번 방법을 사용했다.

1. @JsonIgnore로 반환시키지 않는다

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    @JsonIgnore
    var noticeCategory: NoticeCategory,

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "board_id")
    @JsonIgnore
    var noticeBoard: NoticeBoard

 

 

2. LAZY로딩 대신 EAGER로딩으로 설정해주면 된다

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "category_id")
    var noticeCategory: NoticeCategory,

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "board_id")
    var noticeBoard: NoticeBoard

 

3. application 파일에 spring.jackson.serialization.fail-on-empty-beans=false 설정해주면 된다

 

 

 

 

+

220328 추가사항

프록시 컬럼이 있는 엔티티를 그대로 반환하면 위에 보이듯 hibernateLazyInitializer를 직렬화하려다가 에러가났는데

ObjectMapper로 Entity -> dto로 변환을 시켜주니 LAZY로딩이 정상적으로 동작했다.

 

참고:

 

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring()
    	.antMatchers("/")
        .antMatchers("/ping");
}

@Override
public void configure(HttpSecurity http) throws Exception {
     http.csrf().disable()
        .authorizeRequests()
        .antMatchers("/v1/**").authenticated();
}

개발하며 위처럼 파라미터가 다른 두 configure를 재정의해서 사용할 일이 있었다.

 

 

HttpSecurity 패턴은 보안처리

WebSecurity 패턴은 보안예외처리 (정적리소스나 health check)

 

WebSecurity 패턴은 HttpSecurity패턴이 정의되어야 사용할 수 있다.

 

 

 

configure (WebSecurity)

  • antMatchers에 파라미터로 넘겨주는 endpoints는 Spring Security Filter Chain을 거치지 않기 때문에 '인증' , '인가' 서비스가 모두 적용되지 않는다.
  • 또한 Security Context를 설정하지 않고, Security Features(Secure headers, CSRF protecting 등)가 사용되지 않는다.
  • Cross-Site-Scripting(XSS), content-sniffing에 대한 endpoints 보호가 제공되지 않는다.
  • 일반적으로 로그인 페이지, public 페이지 등 인증, 인가 서비스가 필요하지 않은 endpoint에 사용한다.

 

configure (HttpSecurity)

  • antMatchers에 있는 endpoint에 대한 '인증'을 무시한다.
  • Security Filter Chain에서 요청에 접근할 수 있기 때문에(요청이 security filter chain 거침) 인증, 인가 서비스와 Secure headers, CSRF protection 등 같은 Security Features 또한 사용된다.
  • 취약점에 대한 보안이 필요할 경우 HttpSecurity 설정을 사용해야 한다.

 

 

 

 

참고:

더보기

+ Recent posts