동등성과 동일성

이번 시간에는 자바의 동등성(equals) 동일성(==)에 대해 알아보자
일단 뭐 부터 해야될지 모르겠다.

간단하게 String 으로 먼저 알아보고 밑에선 hashcode 도 알아보자
우리는 String을 두가지 방법으로 초기화 할 수 있다.
다들 아시다시피

String temp = "hello";

String str = "hello";
String str1 = new String("hello");

위와 같은 방법으로 가능하다.
그럼 무엇이 다른가.

System.out.println(str == temp);
System.out.println(str1 == temp);

무엇이 나올까 고민해보자. true? false?
일단 저것을 알기전에 자바에선 기본자료형 참조자료형이 있다. 자세한건 구글링
말그대로 기본자료형 같은 경우에는 실제 값을 저장 하는 반면에 참조형은 실제 객체가 가르키는 주소이다.
첫번째 str은 기본 자료형으로 선언했고 str1은 참조형으로 선언 했다.
그럼 대충 감이 오지 않나.
첫번째는 true 이고 두번째는 false가 나온다.

그럼 int는 어떨까?

Integer temp = 123;

Integer i = 123;
Integer i1 = new Integer(123);

위와 같이 선언 했다고 가정해보자

System.out.println(i == temp);
System.out.println(i1 == temp);

예상 했던거와 같이 true와 false가 나온다.
그런데 재미있는 사실은 다음과 같다.

Integer temp = 128;
Integer i = 128;
Integer i1 = new Integer(128);

값을 128이상으로 바꾸어 보자. 혹은 -129이하로 그럼 원하던 값이 나오지 않는다.
Integer는 다음과 같이 캐싱을 한다.

 private static class IntegerCache {
        static final int low = -128;
        static {
            // high value may be configured by property
            int h = 127;
           ...
        }
    }

아무튼 그렇다.
이번에 hashcode와 equals에 대해 알아보자

class Product{

    private Long id;
    private String name;
    private Long price;

    public Product(Long id, String name, Long price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getPrice() {
        return price;
    }

    public void setPrice(Long price) {
        this.price = price;
    }
}

아주 평범한 자바빈즈 형태의 객체이다.

Product product1 = new Product(1L, "iphone", 99L);
Product product2 = new Product(1L, "iphone", 99L);

System.out.println(product1 == product2);
System.out.println(product1.equals(product2));

위와 같이 해보면 첫번째는 당연히 false가 나올테고 두번째는 기대 했던거와 달리 false가 나온다. (아닌가 ㅎㅎ)
어쨋든 우리는 true의 값을 기대 할려면 객체의 equals를 오버라이딩 해야된다.
오버라이딩 하지 않은 객체는 첫번째와 같이 레퍼런스를 비교한다.

public boolean equals(Object obj) {
    return (this == obj);
}

위는 Object의 equals이다.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Product product = (Product) o;
    return Objects.equals(id, product.id) &&
        Objects.equals(name, product.name) &&
        Objects.equals(price, product.price);
}

다음과 같이 오버라이딩을 하면 우리가 원하던 값을 얻을 수 있다.
equals은 IDE에서 생성해주는것으로 만들었다.

Set은 중복을 허용하지 않는다.
만약 Set에 product1, product2를 넣는 다면 어떻게 될까? 넣어보자

Set<Product> products = new HashSet<>();
products.add(product1);
products.add(product2);
System.out.println(products.size());

중복 객체를 허용 하지 않는데 동등한 객체인대도 불구하고 size는 2가 나온다.
Set은 내부적으로 hashCode를 호출하여 비교를 한다.

@Override
public int hashCode() {
    return Objects.hash(id, name, price);
}

우리는 위와 같이 hashCode를 오버라이딩 해야된다. 물론 HashSet은 HashMap으로 참조하여 구현 되었다.
아마두 컬렉션 대부분 hashCode를 호출하는 거 같다 (확인은 하지 않음)
다시 실행 해보면 우리가 원하는 결과인 size는 1이 나온다.
버그를 없애기 위해선 가능한 hashCode와 equals를 구현해주자

hashCode는 다음과 같은 규약이 있다.
만약 equals가 같다면 hashCode의 값도 같아야 하고
equals가 다르다면 hashCode는 다를수도 같을 수도 있지만 성능상 다른게 낫다.
마지막으로 hashCode가 같다고 해서 equals를 항상 true가 나오는 것은 아니다.
같은 해싱값이 나올 수 도 있다.

이렇게 자바의 동일성과 동등성에 대해 알아 봤다.

javax.validation 빈 검증 (JSR 303)

javax.validation 빈 검증 (JSR 303)

@NotNull, @NotEmpty, @NotBlank

  • @Notnull 일 경우

    null 허용 하지 않는다.
    “” 허용한다.

  • @NotEmpty 일 경우

    null 허용 하지 않는다.
    “” 허용하지 않는다.
    ” “(space) 허용한다.

  • @NotBlank 일 경우

    셋다 허용 하지 않는다.

@Notnull @NotEmpty @NotBlank
null 허용하지 않음 허용하지 않음 허용하지 않음
“” 허용 허용하지 않음 허용하지 않음
” “(space) 허용 허용 허용하지 않음

실제 디비에는 모두 not null로 들어 간다.

  • @Size

    size 를 지정 할 수 있다.
    속성은 max, min, message
    데이터 베이스엔 max 값 으로 지정 된다.

  • @Max, @Min

    max와 min을 지정 할 수 있다.
    Min(10) 으로 설정하면 10보다 작은 숫자를 걸러낸다

  • @Email

    email 검증을 할 수는 있는데. 영 별루다.
    @(골뱅이) 앞뒤로 문자나 숫자만 있으면 통과한다.
    속성중 regexp 와 같이 사용하자

  • @creditCardNumber

    신용카드 번호를 검증한다.

자주쓰는건 이정도 이지 않나 싶다.

spring data jpa 의 @jsonview

spring data jpa 의 jsonview

바로 쓴다.ㅎㅎㅎ 왜냐면 쉬워서 바로 했다.
아무튼 이번엔 @jsonView를 사용해보자
이것도 간단하지만 어노테이션을 많이 추가 해야된다.
그때 그때 맞게 잘 쓰면 될거 같다.

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {

    @Id
    @GeneratedValue
    @Column(name = "account_id")
    @JsonView(View.Accounts.class)
    private Long id;

    @NotNull
    @JsonView(View.Accounts.class)
    private String name;


    @OneToMany(mappedBy = "account")
    private List<Ordered> ordered;
}

class View {
    interface Accounts {}
}

뷰에 뿌려줄 데이타에 @JsonView 어노테이션을 붙이면 된다. 쉽다.
이것도 마찬가지로 한가지 더 해야 될 것이 있다.

    @RequestMapping(value = "/accounts", method = RequestMethod.GET, headers = "Accept=application/json")
    @JsonView(View.Accounts.class)
    public List<Account> getAccounts() throws JsonProcessingException {
        List<Account> accounts = accountRepository.findAll();
        return accounts;
    }

위와 같이 controller 메소드에도 똑같이 넣어주자 그럼 끝.
이거 역시 매우 간단하다.

이번엔 다중으로 해보자 어떤곳은 id만 어떤곳은 id와 name 둘다.

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {

    @Id
    @GeneratedValue
    @Column(name = "account_id")
    @JsonView({
            View.Accounts.class,
            View.Account.class
    })
    private Long id;

    @NotNull
    @JsonView(View.Accounts.class)
    private String name;


    @OneToMany(mappedBy = "account")
    private List<Ordered> ordered;
}

class View {
    interface Accounts {}
    interface Account {}
}

위와 같이 배열 형태로 넣으면 된다.

    @RequestMapping(value = "/account/{id}", method = RequestMethod.GET, headers = "Accept=application/json")
    @JsonView(View.Account.class)
    public Account getAccount(@PathVariable Long id) throws IOException {
        Account account = accountRepository.findOne(id);
        return account;
    }

그런다음 account에 지정하여 보자.
그러면 id만 json 으로 나올 것이다.
이것으로 jpa의 json 순환할 경우에 대해서 알아 봤다.