10월 17, 2023

[Spring] VO 객체 복사하는 법 - BeanUtil.copyProperty + BeanUtil의 두 가지 library

Spring으로 코드를 짤 때 꼭 알아야 하는 것은 VO와 Bean의 개념이다. Spring으로 코드를 짜는 사람이면 모를 수 없는 두 개념이지만 개념에 대해 잘 익히지 않고 무작정 코드를 짜는 Spring 개발자들도 꽤나 있을 것이라고 생각한다.


1. VO의 개념

0 Spring에서 VO란 "Value Object"의 약자이다.

0 비즈니스 로직과 관련이 없는 데이터를 담는 데 사용하는 것이 중요한 개념이다.

0 VO는 주로 데이터를 전송하거나 저장하기 위해 사용되며, 그렇기 때문에 불변의, Read-Only, immutable 한 속성을 가진다.

0 VO의 주요 목적은 데이터를 묶어서 캡슐화하는 것이고, 그래서 자바라는 언어에서 더더욱 중요하게 활용되는 개념이다.


그렇다면 Bean의 정확한 개념은 무엇일까?

2. Bean의 개념

0 가장 쉽게 표현하면 Spring이 관리하는 객체를 의미한다.

0 Spring 컨테이너에 의해 instance화 되고 Application에서 사용되는 component, service, data access object 등과 같은 구성 요소를 의미한다.


3. Spring에서 VO 객체 복사하는 방법


0 Spring의 BeanUtils Class를 사용하면 VO 객체 간 속성을 복사할 수 있다.

0 주의할 사항으로는 BeanUtils Class가 어느 라이브러리를 import하냐에 따라 source와 target의 위치가 바뀐다는 것이다.

0 먼저 Spring Framework에서 사용되는 BeanUtils Class를 살펴보자


  1. 1. org.springframework.beans.BeanUtils (Spring Framework):

Spring Framework의 BeanUtils 클래스는 주로 Spring IoC(Inversion of Control) 컨테이너와 연계되어 사용되고 위에서 설명된 개념인 Bean과도 관련이 있다. 예시를 살펴보자


import org.springframework.beans.BeanUtils; 
public class BeanUtilsInSpringFramework
public static void main(String[] args)
// Spring BeanUtils 
Source source = new Source(); 
Target target = new Target(); 
 BeanUtils.copyProperties(source, target); 
// Spring Framework의 BeanUtils를 사용하여 빈(Bean) 간 속성 복사 
 } 
}


Spring에서는 위와 같이

BeanUtils.copyProperties(source, target); 

위 형식으로 source의 원본 객체를 target의 객체로 옮긴다.


이렇게 copyProperties 한 줄만 써주면 각각의 instance마다 set~ 형식으로 하나씩 옮기지 않아도 되서 코드의 가독성도 좋아진다.


2. org.apache.commons.beanutils.BeanUtils (Apache Commons BeanUtils)

반면 SpringFramework의 BeanUtils과는 헷갈려서는 안되는 것이 Apache Commons의 BeanUtils이다.

Apache Commons BeanUtils의 BeanUtils는 일반적인 Java 객체 간 속성 복사 및 변환을 수행하지만, Spring과는 별개의 Library이다. 따라서 Spring과 무관한 환경에서도 언제든 사용할 수 있다는 것이 장점이다.

import org.apache.commons.beanutils.BeanUtils; public class ApacheCommonsBeanUtils { public static void main(String[] args) { // Apache Commons 의 BeanUtils Source source = new Source(); Target target = new Target(); try { BeanUtils.copyProperties(target, source); } catch (Exception e) { e.printStackTrace(); } } }


Apache의 BeanUtils를 사용하면

BeanUtils.copyProperties(target, source);

SpringFramework의 BeanUtils를 사용할 때와 달리 target과 source의 위치가 달라진다는 것을 명심해야 한다.


따라서 BeanUtils를 사용할 때는 import된 library를 정확히 파악하고 target와 source의 위치를 정확하게 사용하는 것이 중요하다.


9월 04, 2023

StringUtils.equals 사용이유, string equals() 메서드와 다른점

자바에서는 String의 동일 여부를 체크할 때 

String의 equals() 메서드를 쓴다는 사실은 자바를 처음 배운 사람들도 모두 아는 사실일 것이다. 


다만 가끔 코드를 보다 보면 StringUtils.equals(str1, str2) 의 형태로 String의 동일여부를 체크하는 것인데도 String의 equals() 메서드를 사용하지 않고 StringUtils.equals() 를 사용하는 것을 볼 수 있을 것이다. 


이유는 바로 NullPointerException를 방지하는 데 있다고 할 수 있다. 

Null Pointer의 개념을 처음 만든 Tony Hoare의 "10억 달러 짜리 실수" 라는 유명한 발언만 보더라도 얼마나 개발자들이 NullPointerException을 막기 위해서 노력하고 있는지를 알 수 있을 것이다. 


다시 본론으로 돌아가서 


String str1과 String str2를 비교할 때 

str1.equals(str2) 라는 자바 line 중 str1이 null이면 NullPointerException이 발생한다. 반면 str1이 null이 아니고 str2가 null이면 NullPointerException이 발생하지 않는다. 


따라서 null 값이 확실히 아닌 str2를 인자 자리에 가급적 쓰라고 말을 하지만 그러다가 확실하다고 생각했던 str1의 값이 null로 들어오면 바로 NullPointerException이 발생하는 것이다. 


따라서 이러한 위험을 막기 위해 개발자들은 if 문 분기를 통해 null 이 아닐 때만 비교를 하기도 하는데, 또 다른 방법으로는 아예 StringUtils.equals를 사용하는 것이다. 


StringUtils를 사용하기 위해서는 

 import org.apache.commons.lang.StringUtils ; 

를 통해 먼저 import를 해주고 

 StringUtils.equals(str1, str2)  이런 식으로 사용해주면 비교가 가능하다. 


둘 중 하나가 null이어도 Exception을 뱉지 않아 더욱 안전하다고 볼 수 있다. 



9월 04, 2023

Java Logging Levels (log4j - 로그레벨) 총정리

 java debugging을 할 때 꼭 필요한 것이 log 이다. 

특히 운영 서비스에서 무엇인가 오류가 생겼을 때 필히 확인해야 하는 것이 로그라서 로그를 어느 레벨까지 찍을 것 인지에 대한 고민이 생긴다.


로그 레벨은 크게 7단계로 분류된다. 


1. ALL

2. DEBUG

3. INFO

4. WARN

5. ERROR

6. FATAL

7. OFF

1-> 7 로 갈수록 점점 높은 레벨의 로그 단계를 의미한다. 


즉, 

ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF


이 레벨로 분류가 된다고 할 수 있다. 


흔히 우리가 logger.debug, logger.info 이런 식으로 사용하는데 로그 레벨의 차이라고 볼 수 있다. 


log의 경우 

org.apache.log4j library에서 import해서 사용하고 원하는 로그 레벨을 먼저 지정하면 그 상위 로그 레벨이 로그로 출력되는 구조이다. 


그래서 ALL 이 가장 낮은 log level이고 OFF가 가장 높은 log level이라고 할 수 있다. 

ALL로 log level을 세팅하면 ALL 위의 모든 로그가 출력되니 ALL, 

OFF로 log level을 세팅하면 OFF 위의 모든 로그가 출력되니 출력되는 것이 없어 OFF라고 생각하면 이해하기 편하다. 

import org.apache.log4j.*; public class LogClass { private static org.apache.log4j.Logger log = Logger.getLogger(LogClass.class); public static void main(String[] args) { log.setLevel(Level.INFO);// INFO 레벨 위에 있는 메세지를 로그로 출력 log.trace("Log Level 1: TRACE"); log.debug("Log Level 2: DEBUG");
log.info("Log Level 3: INFO"); log.warn("Log Level 4: WARN"); log.error("Log Level 5: ERROR"); log.fatal("Log Level 6: FATAL"); } }

결과값은 어떻게 될까!


log.setLevel(Level.INFO)를 통해

INFO 레벨 위에 있는 (INFO 레벨 포함) 메세지를 로그로 출력하니 info, warn, error, fatal 단계가 출력될 것이다. 결과값은 아래와 같다.


Log Level 3: INFO Log Level 4: WARN Log Level 5: ERROR
Log Level 6: FATAL


적절한 로그 레벨을 설정하여 과도하게 로그가 쌓이지 않고 꼭 필요한 로그들을 적절히 볼 수 있도록 메세지를 적자.


9월 04, 2023

SQL distinct 사용법 (멀티, 단일 칼럼의 중복값 제거하기)

만약 name, department, age라는 세 개의 칼럼을 가지고 있는 worker 테이블이 있다고 가정해보자. 그리고 해당 테이블에 아래와 같은 DB 정보가 존재한다고 가정해보자. 


만약 여기서


 select * from worker;  


이라고 하면 회사의 모든 직원이 출력될 것이다. (위 예시의 경우 총 6명) 


하지만 만약 회사의 동명이인을 제외한 unique 한 이름을 가진 사람이 몇 명 있는지 알고 싶다면 어떻게 하면 될까? 


바로 

 SELECT DISTINCT name FROM worker; 


이렇게 작성되면 될 것이다.


이렇게 하고 실행을 시켜보면 James, Kate, John 이렇게 세 명의 unique한 이름이 출력 될 것이다. 

하지만 만약 관리자가 팀 구분자까지 하여 unique한 이름을 알고 싶다고 하면 어떻게 하면 될까? 즉 A 팀과 B 팀에 동명이인이 있더라도 이것은 다른 한 쌍으로 간주하겠다는 것이다.


 SELECT DISTINCT name, department FROM worker;  


이런 식으로 작성하면 된다. 


즉, "DISTINCT"가 name과 department에 모두 걸려있다는 것이다. 


위 sql을 실행시켜보면 name과 department가 모두 중복되는 건은 Kate와 Management밖에 없으니 한 건의 중복만 삭제되어 총 5건이 출력 될 것이다. 

만약 중복이 제거된 workers의 개수를 알고 싶다면 distinct 위에 count를 씌울 수도 있다. 


 SELECT COUNT(DISTINCT(name)) FROM worker ; 


이렇게 하면 직접 우리가 결과 값을 count 하지 않고도 3이라는 숫자가 바로 출력되어 unique한 이름이 몇 개인지 알 수 있다. 






9월 03, 2023

Spring 기초 - API 방식이란? 개념 + 예제

오늘은 Spring에서 사용하고 있는 API 방식에 대해 포스팅 해보려고 한다.





먼저 URL이 전달이 되면 API 방식에서는 Spring Controller의 @ResponseBody라는 것을 사용한다.  (위 그림 참고)

이후에 MVC 방식에서 viewResolver를 사용하였는데 API 방식에서는 HttpMessageConverter를 사용한다. 

@ResponseBody로 return값을 받으면 
기본 문자처리를 할 때는 StringHttpMessageConverter가 작동을 하고, 
객체 처리를 할 시에는 MappingJackson2HttpMessageConverter이 동작을 한다. 

예시로 TestController.java 파일을 하나 만들고
TestController.java에는 아래와 같이 적어주면 된다. 


package test.testspring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestController {

     static class Test {
        private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
@GetMapping("test-string")
@ResponseBody
public String testString(@RequestParam("name")String name){

return "test "+name;
}

@GetMapping("test-api")
@ResponseBody
public Test testApi(@RequestParam("name")String name){
Test test=
new Test();
test.setName(name);
return test;
}

}

이런 식으로 작성해주면 

http://localhost:8080/test-spring?name=spring

이라고 url이 오면 name 인자에 spring이 들어가서 

test spring이 html 로 반환되는 것이다. 이것은 string으로 반환이 되었으니 StringConverter를 통해 웹브라우저로 반환이 되었을 것이다. 

반면, 

url이
http://localhost:8080/test-api?name=spring
로 들어오게 되면 
@GetMapping("test-api")
이것과 연결되어 test 라는 객체가 반환이 된다. 

여기서 test 라는 객체의 setName 메서드를 통해 name attribute가 spring으로 지정이 된 것이고 이번에는 객체이므로 JsonConverter를 통해 

{name: spring}이라는 형태로 json이 반환되는 것을 알 수 있다.