반응형

1. 개요

 Table 생성 후 셀마다 셀 테두리를 설정하였으나, 셀 테두리 사이 간격이 생기는 현상이 발생하고 있음.

셀 테두리가 벌어짐


2. 해결

 table 태그에 대한 css로 border-collapse : collapse 설정을 적용하면 표의 테두리 및 셀간 테두리 간격을 없애고, 겹치는 부분은 한줄로 표시.

1
2
3
4
5
6
7
8
9
10
table{
    border : 1px solid black;
    border-collapse : collapse;
}
 
td,th{
    border : 1px solid black;
    width : 100px;
    height : 30px;
}
cs

3. 결과

border-collapse : collapse 적용

반응형

'프론트엔드 > CSS' 카테고리의 다른 글

[CSS] !important 란?  (1) 2019.07.26
CSS의 등장배경  (0) 2019.06.19
반응형
반응형

1. 개요

 - 업무를 하던 도중 특정 로직에서 Enhancer, MethodInterceptor이라는 객체를 사용하고 있었다. 로직을 분석해봐도 이해가 잘 가지 않았던 구조였기 때문에 공부의 필요성을 느껴 공부 후 내용을 정리한다.


2. Enhancer란?

 - Enhancer의 사전적 의미는 기능을 높이는 것, 증진시키는 것을 의미한다. 이를 프록시에 대입하여 생각해보니 프록시 객체는 다양한 객체를 호출할 수 있기때문에 기능이 증진된다는 것과 비슷한 맥락을 갖는 것 같다.

 - 로직적으로 이 객체는 프록시 객체를 생성하는 역할을 한다.


3. Enhancer를 사용한 프록시 객체 생성

 - Enhancer 객체를 사용하여 프록시 객체를 생성하는 예제이다.

 

3.1) EnhancerTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ssk.simpany.test.methodInterceptorTest;
 
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.NoOp;
 
public class EnhancerTest {
 
    public static void main(String[] args) {
        
        Enhancer enhancer = new Enhancer(); //Enhancer 객체 생성
        
        enhancer.setSuperclass(MyService.class); // 타켓 클래스 지정
        enhancer.setCallback(NoOp.INSTANCE);      // 옵션  (NoOp, MethodIntetceptor 등)
        Object targetObj = enhancer.create();     // 프록시 객체 생성
        
        if(targetObj instanceof MyService){           // targetObj 객체가 MyService로 형 변환이 가능한지
            //형변환 가능.
            MyService myService = (MyService)targetObj; //형변환
            myService.myServiceMethod("test");
        }
    }
}
 
cs

 

3.2) MyService.java

1
2
3
4
5
6
7
8
9
package com.ssk.simpany.test.methodInterceptorTest;
 
public class MyService {
 
    public String myServiceMethod(String a){
        System.out.println("call my ServiceMethod is "+a);
        return a;
    }
}
cs

 

- EnhancerTest 클래스에서 프록시 객체를 생성할 수 있는 Enhancer 객체를 생성하고 setSuperclass 메서드를 사용하여 타겟 클래스를 지정한다.

- setCallback 메서드에는 NoOp.INSTANCE(NoOption)을 입력했는데, 말 그대로 옵션이 없는 프록시객체로 설정하기 위함이다.

- NoOp로 설정하면 단순히 프록시 객체를 통해 타겟 클래스만 호출하는 것이고, MethodInterceptor로 설정하면 타겟 클래스 호출 전 후로 로직을 넣거나 매개변수를 변경하는 등의 작업이 가능하다. 후자의 경우도 다뤄보도록 할 예정이다.

- enhancer.create() 메서드를 호출하면 마침내 프록시 객체를 생성하게 된다.

- Object 형으로 반환되게 되지만 내부적으로는 앞서 설정한 타켓 클래스이기 때문에 instanceof를 사용하여 형 변환 또한 가능함을 확인할 수 있다. 결과적으로 enhance 에서 생성한 프록시 객체를 통해 MyService객체를 호출한다.

 

3.3) 결과

프록시 객체를 통한 MyService 호출 결과

 

  사실 위 예제를 직접 작성했을땐 '그냥 객체 생성하고 호출하면 될것이지 왜 프록시까지 굳이 생성해서 호출하는걸까?' 라는 생각과 함께 비지니스 로직에 의해 동적으로 호출되어야 할 객체가 있다면 이를 사용했을 때 어느정도 효과를 볼 수 있지 않을까라는 생각도 들었다. 타겟 객체를 생성하는 게 아닌 호출하는 것이므로 메모리적으로도 좋지않을까? 혹시 누군가 알고계시다면 댓글로 달아주시길 부탁드리와요..


4. MethodInterceptor란?

 프록시 객체의 콜백 기능 중 하나로, 프록시 객체에서 타겟 객체를 호출하기 전, 후로 비지니스 로직을 추가하거나, 타겟 클래스가 아닌 다른 객체의 메소드를 호출하거나, 인자 값을 변경할 수 있다.

 구현 방법은 커스텀한 클래스에 MethodInterceptor 인터페이스를 상속받은 후 intercept 메서드를 오버라이드 한다.

 

4.1) MethodInterceptorTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.ssk.simpany.test.methodInterceptorTest;
 
import java.lang.reflect.Method;
 
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
 
public class MethodInterceptorTest implements MethodInterceptor{
 
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("before target Proxy");
        Object returnValue = methodProxy.invokeSuper(object, args);
        System.out.println("after target Proxy");
        return returnValue;
    }
}
cs

 

4.2) Main.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.ssk.simpany.test.methodInterceptorTest;
 
import org.springframework.cglib.proxy.Enhancer;
 
public class Main {
 
    public static void main(String[] args) {
        
        Enhancer enhancer = new Enhancer();     // enhancer 객체 생성
        enhancer.setSuperclass(MyService.class);// 타겟 클래스 지정
        enhancer.setCallback(new MethodInterceptorTest()); //콜백으로 MethodInterceptorTest 객체 설정
        Object targetObj = enhancer.create();    //프록시 생성
        MyService myService = (MyService)targetObj; //형변환
        myService.myServiceMethod("test"); //프록시 객체로 메서드 호출
    }
}
 
cs

- MethodInterceptor를 사용하여 아주 간단한 프록시 객체를 구현했다. 기존과 다른 건 MethodInterceptorTest 클래스를 구현한 후 callback 으로 설정했다는 점이다.

- 이 설정으로 인해 프록시 객체로 메서드를 호출하면 MethodInterceptorTest 클래스의 intercept 메서드가 호출되게 되어 before target Proxy 문자열 호출 후 타겟 클래스가 실행됨을 확인할 수 있다.

참고로 methodProxy.invokeSuper의 리턴 값은 타겟 객체의 리턴 값이다.

 

4.3) 결과

MethodInterceptor를 적용한 프록시 객체 호출 결과


5. 마치며

 지금은 단순히 enhancer와 MethodInterceptor를 이용해 프록시를 구현하는 것만 해보아서 그런지 느낌만 알 뿐, 아직도 머릿속에서 정리가 되지 않은 느낌이다. 또한 어떻게 활용되는지도 크게 와닿지 않는다. 다음 게시글로 이 방식을 활용하여 다양한 예제를 만들어보도록 하겠다.

반응형
반응형

1. 개요

 여름이 내 옆으로 바짝 다가온 그날. 때아닌 열대야가 찾아와 방안에 있는 내 몸을 달구고있었다. 얼른 에어컨을 켜 25도를 맞추고 시원한 마음으로 책상에 앉아 msi 노트북의 입을 열어 젖힌다.

 입 안에 반짝이는 이클립스가 보인다. 이녀석... 바로 이클립스를 클릭하여 개인 프로젝트를 재개한다.

 그런데...나는 아무것도 만진게 없는데. web.xml에서 빨간 X 표시가 뜨고있었다. 

에러


2. 해결방안

 간단했다. 구글링한 결과 아래 빨간색 java 스펠링을 대문자로 바꿔주면 된다.

에러

 

끄읕

반응형
반응형

1. 개요

 - 클래스에 접근을 제어하는 접근제어자에 대해 복습하였다.


2. 접근제어자

 - 클래스의 멤버변수와 메서드는 접근제어자를 통해 외부에서의 접근을 제어할 수 있다.

 - 접근제어자의 종류와 특징은 다음과 같으며, public과 private가 자주 사용된다.

 

  > public : 클래스의 외부에서 접근이 가능.

  > private : 클래스 내부에서만 접근 가능.

  > default : 동일 패키지에 있는 다른 클래스에서 접근 가능.

  > protected : 동일 패키지의 다른 클래스와 다른 패키지의 하위클래스에서 접근 가능 

 


3. public

 - MyClass 멤버변수의 접근제어자를 public으로 설정하면 외부 클래스인 AccessMain 클래스에서 접근이 가능하다.

 - 멤버변수가 public으로 되어있기 때문에 위 객체의 값을 어디서든 수정, 조회할 수 있다.

MyClass
AccessMain


4. private

 - MyClass 멤버변수의 접근제어자를 private로 설정하면 외부 클래스인 AccessMain 클래스에서 접근이 불가능하다.

 - 예제의 AccessMain 클래스를 보면 7번째 줄에서 컴파일 에러가 발생함을 확인할 수 있다.

 - 접근제어자가 public인 getter, setter 메서드를 생성하면 메서드를 통한 접근이 가능해진다. (AccessMain : 9 line)

 - 만약 특정 객체에 대해 값을 조회하는 기능만 넣고싶다면 public 형식의 getter만 생성하면 된다.

MyClass


5. default

 - MyClass 멤버변수의 접근제어자 설정하지 않으면 default로 설정되며 같은 패키지 내의 클래스에서만 접근할 수 있다.

 - MyClass와 AccessMain 클래스는 같이 패키지에 위치하므로 접근이 가능하다.

 - AccessMain2 클래스는 MyClass의 하위 패키지에 위치하므로 접근이 불가능하다.

MyClass

 

AccessMain

 

AccessMain2


5. protected

 - MyClass 멤버변수의 접근제어자를 protected로 설정하면 같은 패키지 내의 클래스와 자식클래스에서 접근할 수 있다.

 - MyClass와 AccessMain 클래스는 같이 패키지에 위치하므로 접근이 가능하다.

 - AccessMain2 클래스는 MyClass의 하위 패키지에 위치하지만 MyClass를 상속받은 자식클래스이므로 접근할 수 있다.

MyClass

 

AccessMain

 

AccessMain2


6. 마치며

 접근제어자에 대한 복습을 하니, 머릿속에 한번 더 정리되는 느낌이 들어 좋다. default는 있는지도 몰랐는데... 신기했으나 앞으로 쓸일은 없을것같다!

반응형
반응형

1. 개요

 스프링 서버에서 DB 조회 결과를 내가 커스텀한 객체(DAO)로 가져오고 싶은 경우가 있다. 그럴 때 아래 예제처럼 resultType을 풀패키지 경로로 입력해야한다. 만약 입력한 BoardDAO 객체가 다른 쿼리에서도 사용된다면, 해당 쿼리의 resultType 또한 풀 패키지경로로 입력해야할 것이다. 추가로 패키지 경로가 바뀌거나 클래스 명이 바꿔버린다면? 모든 쿼리 설정파일을 바꿔줘야할 것이다.

 이러한 문제를 미연에 방지할 수 있는 방법이 바로 Mybatis의 typeAlias(별명) 설정이다.


2. typeAlias

 typeAlias는 패키지에 대한 별명을 지정할 수 있다. Mybatis 설정 파일에서 설정 가능하며, 아래 예제와 같이 typeAlias 태그의 type 값에  풀 패키지명을 입력하고, alias에는 별명을 지정한다.

typeAlias 설정

 


3. Mybatis 설정 파일 로드

 Mybatis 설정파일을 로드하는 방법은 스프링의 context 설정 중 sqlSessionFactory를 설정하는 부분에 configLocation 값으로 mybatis 설정파일 경로를 넣어주면 된다. 내 설정파일은 클래스파일 경로에 포함되어있기 때문에 다음과 같이 classpath 로 경로설정을 하였다.

 


4. 적용

 설정이 적용되면 resultType에 Alias로 값을 넣고 테스트해보자. 정상적으로 조회됨을 확인할 수 있을것이다.

 mybatis 설정 에러가 발생하는 경우가 있는데, 그럴때는 seetings라는 태그가 configuration태그 내 첫번째로 위치하는지 확인하자.

반응형
반응형

1. 개요

 스터디로 File 업로드, 다운로드 로직을 작성하던 도중, 특정 부분에 빨간줄이 등장했다. 자연스럽게 마우스 가져가보니 Exception!. 별생각 없이 add thrwos declaration 클릭. 상황종료.

이처럼 예외 처리에 큰 의미나 생각을 부여하지 않았다.

두둥등장한 에러.

 그러던 어느날, 고객사측에서 에러 문의가 들어와 로그를 확인해보니 딱 봐도 DB 예외로 보이는 ERDBException이 발생하고 있었다.(커스텀 익셉션인 것!) 그래서 DB 예외라는 필터를 머릿속에 장착한 뒤 로직을 확인해보니... 이게웬걸.. 모든 예외에 대한 처리를 ERDBException으로 던지고있었다.

 그리고 깨달았다. 예외처리를 제대로 하지 않으면 유지보수 시 혼란을 초래할 수 있다는 것을...


2. 예외? 에러?

 예외에 대한 기본 개념을 정립하기 위해 구글링을 하니 "예외와 에러의 차이" 가 눈에 들어왔다. 사실 이 두 개념은 비슷하다고 생각했으나, 막상 확인해보니 예외 공부에 아주 중요한 키포인트가 됐던 내용이었다.

 정리하면 에러는 시스템 레벨에서 발생하는 아주 심각한 수준의 문제이다. 예를들면 서버의 과부하가 걸려 발생하는 OutOfMemory 같은 것이다. 이러한 에러는 프로그래머가 미리 예측하지 못하며 로직으로 처리할 수 없다.

 이에반해 예외는 프로그래머가 작성한 로직으로 인해 발생하는 문제이다.  미리 예측하여 처리할 수 있기 때문에 올바른 처리방법을 통해 핸들링하는 것이 중요하다.

예외와 에러의 계층구조 (출처 : https://velog.io/@new_wisdom/Exception)


3. 예외 코드 예제

 예외 코드에 대한 예제 코드를 txt 파일을 조회하는 것으로 작성해보도록 하겠다.

예외 처리를 안한 코드

 위 코드에 빨간줄이 아주 많다; 빨간줄이 나타나는 이유는 예외처리를 하지 않아서인데, 이처럼 예외처리를 꼭 해줘야하는 것들이 있다. 이러한 예외들을 CheckedException이라고 한다. 말 그대로 컴파일 시점에서 체크해주는 예외이다.

 런타임 시점에 발생하는 예외들도 있는데 이는 UnCheckedException이라고도 하고 RuntimException이라고 한다. 이러한 예외는 컴파일 시점에 체크되지 않아 위 소스처럼 에러가 뜨지 않는다.


4. try, catch, finally

 예외를 처리하기 위해서 사용하는 기본적인 문법이 있다. 바로 try, catch, finally이다.

 try는 예외 발생 가능성이 있는 로직이 포함된 블럭, catch는 예외가 발생했을 때 처리되는 로직이 포함된 블럭, finally는 예외가 발생하던 안하던 최종적으로 처리되는 로직이 포함된 블럭이다.

간단한 예제를 통해 실습해보자.

 

예외 멈춰!

 

예외 멈춰! 결과

현재 File 객체의 매개변수 값으로 텍스트파일의 경로를 넣어놨는데, 이 경로는 실제로 존재하지 않는 파일의 경로이다. 이로인해 try 문 안의 fis = new FileInputStream 부분에서 FileNotFoundException (FileNotFoundException 예외는 Exception을 상속하고 있음)  이 발생하게 되는데, 이때 catch 블록으로 이동하여 예외 처리 후, 최종적으로 finally문에서 스트림을 닫게 된다.

 

그럼 이런 의문이 들 수 있다. 

 

 Q : catch의 매개변수에 Exception을 넣으면 결론적으로 try 문에서 발생한 모든 예외들은 저 하나의 catch문에서 처리되는게 아닌가? 그럼 다양한 예외에 대한 핸들링하지 못하잖아?

 

맞다. 저렇게 무작정 Exception으로만 넣게되면, 모든 예외를 한곳에서 핸드링하는 격이기 때문에 상황에 맞게 예외를 분기해서 처리해야 한다. 한곳에서 처리하는 것이 효율적인 예외도 있겠지만, 예를 들어 로직에 SQLException과 FileNotFoundException 예외가 발생할 수 있는 상황이라면 각각의 예외에 대한 세부 로그를 남기는 등의 예외처리를 하면 좋지 않을까 싶다. (참고로 모든 예외를 Exception으로 통일한 프로젝트도 있었다.)


5. throw, throws

 예외란 프로그래머가 작성한 로직에 의해 발생된다. 그렇다면 프로그래머가 예외를 발생시킬수도 있을까? 있다. 추가적으로 예외에 대한 책임을 전가할수도 있다. 이게 바로 throw와 throws이다.

 

 - throw는 예외를 강제로 발생시킨 후, 상위 블럭이나 catch문으로 예외를 던진다.

throw

위 예제는 main 메서드에서 myException메서드를 호출하고, 여기서 throw를 통해 Exception을 강제로 발생시키고 있다.

때문에 catch 블럭으로 처리가 위임되고, 여기서 예외를 처리하고 있다. 이는 콘솔 로그를 통해 알수있다.

 

 - throws는 예외가 발생하면 상위메서드로 예외를 던진다.

throws

일반적으로 throws를 사용하면 try, catch 구문이 생성되지 않는 것을 확인할 수 있는데, 이 이유는 throws구문에 의해 예외에 대한 처리를 호출부로 위임하기 때문이다.

throw를 통해 예외를 발생시키고 throws는 이 예외를 밖으로 던져버리고 있다.

추가적으로 이 두가지를 합친 방식도 있다.

 

throw + throws는 예외처리를 catch문에서도 하고, 호출부로 예외를 던진다.

throw + throws

22줄에 throw e를 통해 예외를 발생시키면 throws에 의해 상위 메서드로 예외를 던지게 된다.

예외처리를 예외 발생지와 호출부에서 하고싶다면 이와 같은 방법을 사용하면 된다.

반응형
반응형

1. 개요

 - Argument Resolver 란?

 - 예제

 

2. Argument Resolver 란?

 - Controller로 들어온 파라미터를 가공하거나 수정 기능을 제공하는 객체이다. 교재에서는 이를 사용해 Controller로 들어온 특정 파라미터에 세션 정보를 가공하여 넣어주었다.

 - Argument Resolver를 Controller 단에서 사용하면 중복 코드(HttpSession에서 세션 로드, HttpServletRequest에서 요청 url 및 ip 정보 로드 등)를 깔끔하게 처리할 수 있다. > 예제를 보면 이 말의 의미를 알 수 있다.

 - 예제에서는 LoginUser 어노테이션이 붙은 SessionUser 객체에 대해 Argument Resolver를 설정해주었다.

 

3. 예제

 3.1. @LoginUser.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.ssk.springboot.config.auth;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
 
import java.lang.annotation.RetentionPolicy;
 
/*
 * Target : 어노테이션이 생성될 수 있는 위치 지정. PARAMETER면 메소드의 파라미터로 선언된 객체에서만 사용 가능
 * Retention : 어노테이션의 메모리 생명 주기 지정. 런타임에도 해당 어노테이션 객체는 메모리에 올라가있음
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
 
}
cs

 - 파라미터 단위에서 사용 가능하며, 런타임 시 메모리에 적재되는 LoginUser 어노테이션을 생성한다.

 - 어노테이션을 만들어 주는 이유는 이 어노테이션이 붙은 파라미터에 대해 Argument Resolver 를 설정하기 위함이다.

 

 3.2. LoginUserArgumentResolver.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.ssk.springboot.config.auth;
 
import javax.servlet.http.HttpSession;
 
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
 
import com.ssk.springboot.config.auth.dto.SessionUser;
 
import lombok.RequiredArgsConstructor;
 
@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver{
 
    private final HttpSession httpSession;
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class!= null;
        boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());
        return isLoginUserAnnotation && isUserClass;
    }
 
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        // TODO Auto-generated method stub
        return httpSession.getAttribute("user");
    }
}
cs

 - HandlerMethodArgumentResolver 인터페이스를 상속받는 구현체 클래스를 생성한다.

 - 이 인터페이스는 개발자가 커스텀하는 Argument Resolver에 대한 여러 메서드를 지원한다.

 - supportsParameter 메서드는 들어온 파라미터에 대해 resolveArgument 메서드를 실행할지 말지 판단한다. 리턴 값이 true면 결과적으로 resolveArgument 메서드를 실행하게 되는데, 이 메서드는 파라미터를 가공하는 역할을 한다.

 

 - parameter.getParameterAnnotation(LoginUser.class) != null, 즉, 해당 파라미터의 어노테이션이 LoginUser이고,

   parameter.getParameterType이 SessionUser일 경우 true를 반환하고, resolveArgument 메서드는 HttpSession. getAttribute("user") 를 반환한다.

 

 - 정리하면, 컨트롤러의 파라미터 중 @LoginUser 어노테이션이 붙어있는 SessionUser 객체가 있을 경우 해당 파라미터에 user 세션 정보를 리턴한다.

 

 3.3. Controller.java

1
2
3
4
5
6
7
8
9
10
    @GetMapping("/")
    public String index(Model model, @LoginUser SessionUser user) {
        
        model.addAttribute("posts",postsService.findAllDesc());
        if(user != null) {
            System.out.println(user.getName());
            model.addAttribute("userName", user.getName());
        }
        return "index";
    }
cs

 - 파라미터로 들어온 user 객체에 HttpSession.getAttributes("user")에 해당하는 값이 들어있음을 확인할 수 있다.

반응형
반응형

* 이동욱 저자의 스프링 부트와 AWS로 혼자 구현하는 웹서비스 교재 참고

1. 개요

 - JPA란?

 - H2란?

 - JPA 및 H2 설정 및 연동

 - Junit을 사용한 JPA CRUD 테스트

 

2. JPA란?

 - DB 처리를 쿼리 매핑(ex: ibatis, mybatis)이 아닌 객체 매핑으로 처리할 수 있도록 하는 기술

 - ORM (Object Relational Mapping) 기반

 - 기본적인 CRUD(Create, Read, Update, Delete) 메서드 자동 생성

 

3. H2란?

 - 인메모리 관계형 데이터베이스

 - 별도의 설치가 필요 없이 프로젝트 의존성만으로 관리가 가능

 - 메모리에서 실행되기 때문에 애플리케이션을 재시작할 때마다 초기화됨 (테스트용으로 많이 사용)

 

4. JPA 및 H2 의존성 설정 (build.gradle)

1
2
compile('org.springframework.boot:spring-boot-starter-data-jpa'//스프링 부트용 Spring Data Jpa 라이브러리
compile('com.h2database:h2'//h2 라이브러리
cs

 

5. JPA 설정 및 예제

 5.1. 도메인 설정 (Posts.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Getter
@NoArgsConstructor
@Entity
public class Posts{
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(length = 500, nullable = false)
    private String title;
    
    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;
    
    private String author;
    
    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }
}
cs

- @NoArgsConstructor : 클래스에 대한 기본 생성자 메서드 생성

- @Entity : 테이블과 링크될 클래스를 지정하는 어노테이션이며, 언더스코어 네이밍으로 테이블 이름을 매칭

  (ex : PostsManager > posts_manager table)

 

- @Id : PK 필드

 

- @GenerateValue(strategy = GenerationType.IDENTITY) : 자동 인덱스 생성 설정

 

- @Column : 테이블의 컬럼을 나타내며 필요 옵션 추가 시 length, nullable, columnDefinition 등을 사용

  > null을 허용하지 않고 길이가 500인 title 컬럼을 생성 

  > null을 허용하지 않고 TEXT 타입인 content 컬럼을 생성

  > 필드에 Column 어노테이션을 붙이지 않아도 기본적으로 컬럼이 생성됨

 

- @Builder : 해당 클래스의 빌더 패턴 클래스 생성

  > build 패턴 사용시 보다 명시적인 객체 생성이 가능 (PostTestRepository.java 코드의 21번째 줄 참고)

 

 5.2. JpaRepository 설정 (PostsRepository.java)

1
2
3
public interface PostsRepository extends JpaRepository<Posts, Long>//Entity 클래스, PK 타입
 
}
cs

- extends JpaRepository<Entity.class, PK Type> : 기본적인 CRUD 메서드를 지원하는 Repository 인터페이스 생성

 > JpaRepository : DB Layer 접근자를 의미

 > Entity 클래스와 해당 Entity Repository는 같은 패키지에 위치해야함.

 

6. H2 설정 (src/main/resources/application.properties)

1
2
3
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.h2.console.enabled=true
cs

- spring.jpa.show-sql=true : 콘솔 내에서 쿼리 로그를 확인할 수 있도록 하는 설정

- spring.jpa.properties.hibernate.dialect : H2 형태의 쿼리 로그를 MySQL 버전으로 변경

- spring.h2.console.enabled=true : h2 웹 콘솔 사용 허용 (localhost:port/h2-console 으로 접속 가능)

 

7. Junit 테스트 (PostsRepositoryTest.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@RunWith(SpringRunner.class)
@SpringBootTest //별다른 설정없이 SpringBootTest를 사용할 경우 H2 데이터 베이스를 자동으로 실행
public class PostsRepositoryTest {
 
    @Autowired
    PostsRepository postsRepository;
    
    //After : 테스트 단위가 끝날때마다 수행되는 메서드를 지정하는 어노테이션
    @After
    public void cleanup() {
        postsRepository.deleteAll();
    }
    
    @Test
    public void 게시글저장_불러오기() {
        
        String title = "테스트 게시글";
        String content = "테스트 본문";
        
        //builder 클래스를 통해 생성자 생성 후 save (insert/update)
        postsRepository.save(Posts.builder()
                                .title(title)
                                .content(content)
                                .author("sksim@gmail.com")
                                .build());
        
        List<Posts> postsList = postsRepository.findAll();
        Posts posts = postsList.get(0);
        assertThat(posts.getTitle()).isEqualTo(title);
        assertThat(posts.getContent()).isEqualTo(content);
    }
}
cs

- @SpringBootTest : H2 데이터 베이스 자동 실행 

 

- postsRepository.save() : posts 테이블에 insert/update 쿼리 실행

  > id 값이 있다면 update, 없다면 insert

 

- postsRepository.findAll() : posts 테이블에 있는 모든 데이터를 조회

 

- assertThat, isEqualTo : assertJ에서 지원하는 테스트 메서드

 

8. H2 콘솔 접속 방법

 8.1. localhost:port/h2-console 을 입력하여 H2 웹 콘솔 접속 및 JDBC URL 입력 후 Connect

H2 웹 콘솔 접속 정보 입력

 

 8.2. DB 접속 및 Entity 객체와 매핑된 테이블 확인

H2 웹 콘솔 접속 성공

  * Junit 테스트 시 11번째 줄의 deleteAll()을 주석처리하면 posts 테이블에 데이터가 들어가는 것을 확인할 수 있음

반응형
반응형

* 이동욱 저자의 스프링 부트와 AWS로 혼자 구현하는 웹서비스 교재 참고

 

1. 개요

 - 롬복 설치 및 롬복을 사용하여 Dto 객체를 생성한다.

 - 테스트용 controller를 생성한다.

 - Junit을 통해 테스트한다.

 

2. 롬복 설치 및 이클립스 연동

 2.1. 롬복 설치 (gradle)

org.projectlombok:lombok 추가

  - gradle에 롬복 의존성을 추가하여 라이브러리를 다운받는다.

 

 2.2. 롬복 실행

  - 라이브러리가 다운받아졌으면 롬복이 설치된 경로(gradle Home)로 이동한다.

  - 그 후 관리자 권한으로 cmd를 실행한 후 jar파일을 실행한다.

롬복 설치 경로

 2.3. STS 연동

  - 롬복 install 창이 뜨면 Specify location 을 선택하여 STS 실행파일을 선택 후 install /Update 를 클릭한다.

   * 여기서 access 에러가 뜬다면 cmd를 관리자 권한으로 실행하면 된다.

롬복 연동

 2.4. STS 연동 완료

  - 다음과 같은 창이 출력되면 설치 및 연동이 완료된 것이다.

  - STS 및 이클립스를 실행중이라면 재시작 시 적용된다.

연동 완료

3. HelloController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ssk.springboot.web;
 
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
 
import com.ssk.springboot.web.dto.HelloResponseDto;
 
@RestController
public class HelloController {
 
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
    
    @GetMapping("/hello/dto")
    public HelloResponseDto helloDto(@RequestParam("name"String name, @RequestParam("amount"int amount){
        return new HelloResponseDto(name,amount);
    }
}
 
 
cs

 - @RestController : Json을 반환하는 컨트롤러로 만든다. @ResponseBody를 메서드마다 선언했던 것과 동일하다.

 - @GetMapping : Get 방식을 지원한다.

 - @RequestParam : request 파라미터를 가져온다.

 

4. HelloResponseDto.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.ssk.springboot.web.dto;
 
import lombok.Getter;
import lombok.RequiredArgsConstructor;
 
@Getter
@RequiredArgsConstructor
public class HelloResponseDto {
 
    private final String name;
    private final int amount;
}
 
cs

 - @Getter : 선언된 필드에 대해 Getter를 자동 생성한다.

 - @RequiredArgsConstructor : final로 선언된 필드가 포함된 생성자를 생성한다.

   > HelloResponseDto(String name, String amount) 라는 생성자가 내부적으로 생성된 것이다.

 

5. HelloControllerTest 클래스 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RunWith(SpringRunner.class//스프링 실행자를 JUnit 내에 저장된 실행자와 함께 실행
@WebMvcTest(controllers = HelloController.class// Mvc Test에 사용하는 어노테이션, 테스트 컨트롤러 리스트에 HelloController를 추가
public class HelloControllerTest {
    
    @Autowired
    private MockMvc mvc; //mvc 테스트용 객체
    
    @Test
    public void hello가_리턴된다() throws Exception{
        String hello = "hello";
        mvc.perform(get("/hello")) //hello로 get 통신
            .andExpect(status().isOk()) //200인지 검사
            .andExpect(content().string(hello)); //response 값이 hello 인지 검사
        
    }
    
    @Test
    public void helloDto가_리턴된다() throws Exception{
        String name = "hello";
        int amount = 1000;
        
        mvc.perform(get("/hello/dto")
                .param("name", name)
                .param("amount"String.valueOf(amount)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name", is(name)))
                .andExpect(jsonPath("$.amount", is(amount)));
    }
}
cs

 - @RunWith(SpringRunner.class) : 스프링 테스트에 필요한 스프링 실행자인 SpringRunner를 실행시킨다.

 - @WebMvcTest(controllers = HelloController.class) : 스프링 MVC 테스트에 사용하는 어노테이션이다. 테스트 controller로 HelloController를 지정했다.

 - mvc.perform(get("/hello")) : hello로 get요청을 보낸다. perform 메서드는 메서드 체이닝이 지원되며 andExpect 같은 검증 메서드와 함께 사용한다.

 - param("name",name) : 요청 파라미터 값을 추가한다. 단 String 형태만 허용되기 때문에 amount 값을 변환했다.

 - jsonPath : Json 응답값을 필드별로 검증할 수 있는 메서드이다. $를 기준으로 필드명을 명시한다. 

 

6. Junit 실행을 통한 테스트

Junit 실행

 - @Test 어노테이션이 붙은 메서드의 테스트가 정상적으로 실행됨을 확인할 수 있다.

 

 

반응형
반응형

1. 개요

 - gradle 프로젝트를 생성한다.

 - build.gradle을 작성하여 의존성을 주입한다.

 

2. Gradle 프로젝트 생성

 2.1. new / Spring Starter Project 선택

Spring Starter Project

 2.2. 프로젝트 초기 설정 입력

프로젝트 초기 설정 입력

 - Type을 Gradle, Java Version을 8로 선택 및 프로젝트 초기 설정 입력 후 Next를 선택한다.

 

2.3. 스프링 부트 프로젝트 설정

스프링 부트 프로젝트 설정

 - Web / Spring Web을 선택하고, Spring Boot Version은 임의로 2.4.4를 선택한다. 스프링 부트 버전은 build.gradle에서 수정이 가능하다.

 

 2.4. gradle 프로젝트 생성 완료

프로젝트 구조

 - 다음과 같은 구조로 프로젝트가 생성되었다.

 

3. build.gradle 작성

 

 3.1. build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
buildscript{
    ext{
        springBootVersion = '2.1.7.RELEASE'
    }
    repositories{
        mavenCentral()
        jcenter()
    }
    dependencies{
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
 
group = 'com.ssk'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
 
 
repositories{
    mavenCentral()
}
 
dependencies{
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}
cs

 - build.gradle 파일은 말 그대로 gradle을 통해 빌드될 수 있도록 하는 설정파일이다.

 - 프로젝트 기본 설정, 의존성 주입, 플러그인, 레포지토리 설정 등의 정보가 들어있다.

 - ext는 전역변수를 설정하겠다는 의미이며, 10번째 라인을 보면 스프링 부트 그레들 플로그인의 2.1.7.RELEASE를 의존성으로 받겠다는 의미이다.

 - repositories는 저장소를 의미하며, mavenCentral은 메이븐 중앙 레포지토리, jcenter는 mavenCentral 저장소의 문제점을 개선한 레포지토리이다. 일반적으로 위처럼 이 두가지 저장소 정보를 넣어준다.

 - dependencies는 의존성을 추가하는 부분이다.

 - 13 ~ 16 라인의 플러그인들은 자바와 스프링을 사용하기 위한 필수 플러그인들이므로 필히 추가해야 한다.

 

 3.2. build.gradle 실행

build.gradle 실행

 - build.gradle 우클릭 / Gradle / Refresh Gradle Project를 선택하여 build.gradle을 실행한다.

 - 정상적으로 실행되었다면 프로젝트 라이브러리에 의존성이 추가된 것을 확인할 수 있다.

 

4. 에러 리포트

 4.1. could not run phased build action using connection to gradle distribution 에러

 

 첫번째 솔루션. project / properties / Gradle / 설정 변경

 - Gradle user home 및 java home 경로를 명시적으로 기입해준다.

 

 두번째 솔루션. build.gradle 소스 문제

  - 위 문제로도 해결이 되지 않아 코드 확인 중 plugin 관련 코드를 sourceCompatibility 아래에 적어놨던 것을 뒤늦게 확인했다. plugin 코드들을 sourceCompatibility 하위에 적어뒀다면 위 예제처럼 위로 올리면 해결된다.

반응형

+ Recent posts