[Spring] 어노테이션 기반 프로젝트 진행 순서

2020. 8. 13. 23:13Spring/Spring

1. 공통 적용 내용

※ 이클립스 기반, STS, Java ORM Plugin 설치가 되어있다고 가정

1) 서버 설정

- Server 뷰 → Tomcat 설정

2) 프로젝트 생성

- Spring Leagcy Project

- Spring MVC Project

3) 불필요한 파일 제거 (옵션)

- (폴더) /src/main/resources/META-INF

- (폴더) /src/main/webapp/WEB-INF/spring

- (폴더) /src/main/webapp/WEB-INF/views

- (코드) /src/main/webapp/WEB-INF/web.xml

4) 스프링, Java 버전 변경 및 런타임 설정

- pom.xml (mvnrepository.com/artifact/org.springframework/spring-core)

- Properties → Project Facets → Java 1.8, Apache Tomcat

5) 참고할 내용

1. (폴더) /src/main/webapp/WEB-INF

웹에 필요한 코드 파일, 컴파일 파일, 환경설정 파일이 보관되는 곳으로 보안이 중요한 파일이다. 따라서 사용자가 외부에서 접근할 수 없다. 컨트롤러(핸들러)를 이용하여 내부적으로만 접근이 가능한 공간이다.

2. (폴더) /src/main/webapp/resources

웹에 필요한 다양한 자원들을 보관하는 곳이자 사용자가 직접 접근할 수 있는 공간이다. js, css, img 파일 등이 보관되어 있고 컨트롤러가 요청을 가로채지 않고 바로 접근할 수 있도록 설정해서 사용하는 곳이다.


 

 

 

2. JDBC 기반

1) 설정 파일 생성

설정 파일의 경우 체계적으로 나누어 사용하는게 좋다. 각 역할에 맞게 파일을 분리하고 최종적으로는 applicationContext.xml 파일에 통합하여 이용한다.

 

- Spring Bean Configuration File → applicationContext.xml 생성

2) 데이터베이스

- pom.xml → H2 라이브러리 추가

3) 주요 클래스

DAO 클래스, ServiceImpl 클래스는 @Component 어노테이션이 붙어있어야한다. 각 클래스의 용도에 맞게 @Repository, @Service 어노테이션을 적절하게 사용한다. applicationContext.xml에는 context 네임스페이스를 추가하고 <context:component-scan>을 통해서 스프링 컨테이너가 컴포넌트를 스캔하여 객체를 만들 수 있도록 한다.

 

클래스 설명
VO - 데이터 전달을 목적으로 사용하는 클래스
- 변수명은 테이블 컬럼명과 동일하며 private로 선언
- Getter / Setter 메서드 구현
JDBCUtil - 데이터베이스의 연결과 해제를 담당하는 클래스
DAO - 데이터베이스의 접근을 위한 클래스 (CRUD, SQL 명령어)
- 메서드명은 구현할 기능과 테이블명을 결합한 형태
Service - 인터페이스
ServiceImpl - Service 인터페이스를 구현
Log - 예외 로그, 수행 시간 로그 메서드 구현

4) AOP

AOP 어노테이션을 사용하기 위해서는 반드시 어드바이스 객체가 생성되어 있어야하므로 @Service 어노테이션을 사용하고 애스팩트 역할을 할 클래스임을 @Aspect 어노테이션으로 명시해준다. 공통의 포인트컷을 사용하기에 PointcutCommon 파일을 생성한다. Log 클래스에는 @Before, @After, @AfterReturning, @AfterThrowing, @Around 어노테이션으로 동작 시점을 지정한다.

 

- pom.xml → AspectJ 라이브러리 추가

- applicationContext.xml → aop 네임스페이스 추가 및 <aop:aspectj-autoproxy> 설정

 

용어 설명
포인트컷 - 필터링된 클라이언트가 호출하는 모든 비즈니스 메서드
- 특정 메서드에서만 공통 기능을 수행할 수 있도록 클래스, 패키지, 메서드 시그니처 지정 가능
어드바이스 - 횡단 관심에 해당하는 공통 코드
- 독립된 클래스의 메서드
- 메서드 동작 시점 지정
위빙 - 포인트컷으로 지정한 핵심 관리 메서드가 호출 시 어드바이스에 해당하는 횡단 관심 메서드가 삽입되는 과정
애스팩트 - 포인트컷과 어드바이스의 결합으로 포인트컷 메서드에 대해 어떤 어드바이스 메서드를 실행할지 결정

5) 트랜잭션

JDBC 기반으로 동작하기 때문에 별도의 라이브러리 추가가 필요하며, 프로퍼티파일을 이용한 DataSource 등록과 이를 이용한 트랜잭션 관리자를 등록한다. (모든 트랜잭션 관리자는 PlatformTransactionManager 인터페이스를 구현한 클래스이다.)

 

- pom.xml → JDBC, DBCP 라이브러리 추가

- applicationContext.xml → DataSource 지정(db.properties 파일 설정)

- applicationContext.xml → TransactionManager 지정

6) JSP 화면 구성

7) MVC 적용

Controller 클래스의 각 메서드가 받는 인자값이 Command 객체를 사용하도록 한다. DAO, ModelAndView 객체도 매개변수로 선언 시 스프링 컨테이너가 객체를 생성해주고 전달해주기 때문에 더 간결한 코드를 작성할 수 있다. (단, JSP 파일의 Form 태그 내의 Command 객체의 Setter 메서드 이름이 반드시 일치해야하며 '${...}' 구문을 이용해야 한다.)

 

일반적으로 Controller 클래스의 각 메서드 반환 타입은 통일해준다. ModelAndView를 사용했을 경우, 반환 타입은 String, 매개 변수는 Model로 수정하도록 한다.

 

Controller 클래스의 메서드가 다음 화면으로 넘어갈 문자열을 반환할때, 기본적으로 포워딩으로 동작하기 때문에 URL 변경이 되지 않기에 리다이렉트 설정을 해주어야 한다.

 

Controller는 DAO 객체를 직접 이용해서는 안되며, 비즈니스 컴포넌트(Service)를 이용해야한다.

 

- Spring Bean Configuration File → presentation-layer.xml 생성

- web.xml → Spring Servlet 등록, 설정 파일(presentation-layer.xml) 위치 및 이름 지정

- web.xml → CharacterEncoding, ContextLoaderListener(applicationContext.xml) 지정

- presentation-layer.xml → context 네임스페이스 추가 및 <context:component-scan> 설정

- Controller 클래스 → 어노테이션 지정

 

어노테이션 설명
@Controller - 해당 클래스가 Controller임을 나타내기 위한 어노테이션
@RequestMapping - 요청에 대해 어떤 Controller, 어떤 메소드가 처리할지를 맵핑하기 위한 어노테이션
@ModelAttribute - Controller 메소드의 파라미터나 리턴값을 Model 객체와 바인딩하기 위한 어노테이션
- Command 객체명과 클래스명이 동일하지 않을 경우 사용
- View에서 사용할 데이터를 설정하는 용도로 사용
- @RequestMapping 보다 먼저 호출이되며, 반환된 결과는 자동으로 Model에 저장
@RequestParam - Controller 메소드의 파라미터와 웹요청 파라미터와 맵핑하기 위한 어노테이션
- HttpSession의 getParameter()와 동일한 기능을 수행
- VO/Command 객체에 없는 정보일 때 사용
- 검색과 관련된 파라미터 정보를 추출
@SessionAttributes - Model 객체를 세션에 저장하고 사용하기 위한 어노테이션
- null 업데이트 방지

※ @RequestMapping 속성을 이용하여 동일한 URL 요청에 대해 메서드별로 동작하는 함수를 다르게 설정할 수 있다. (LoginController)

※ @ModelAttribute는 프로젝트내에서 검색 조건(제목, 내용)을 @RequestMapping 메서드가 수행하기 전에 View에 전달해준다.

8) 예외 처리

발생하는 예외의 종류에 따라 적절한 화면이 나오도록 CommonExceptionHandler 클래스를 생성하고, @ControllerAdvice와 @ExceptionHandler 어노테이션을 설정한다.

 

- presentation-layer.xml → mvc 네임스페이스 추가 및 <mvc:annotation-driven> 설정

- CommonExceptionHandler 클래스 작성 및 어노테이션 적용

- 예외 화면 구성

어노테이션 설명
@ControllerAdvice - 특정 위치에서 발생하는 예외에 대한 처리를 한다고 명시
@ExceptionHandler - 각 예외에 따라 호출되는 메서드를 지정

9) 다국어 설정

/src/main/resources/message 폴더 하위에 Locale 파일을 생성한다. 한글 파일의 경우 유니코드가 필요하니 메모장을 이용하여 유니코드로 변환시켜 저장한다.

 

- File → messageSource_en.properties

- File → messageSource_ko.properties

- presentation-layer.xml → MessageSource 지정

- presentation-layer.xml → mvc 네임스페이스 추가

- presentation-layer.xml → LocalResolver, LocalChangeInterceptor 지정

10) JSON 변환

Controller 클래스에는 @ResponseBody 어노테이션을 이용하여 HTTP 프로토콜의 몸체로 변환하고, 스프링 컨테이너는 설정 파일을 통해 변환기 객체를 생성하고 JSON 형태로 변환되어 HTTP 응답 바디에 전달된다.

 

- pom.xml → Jackson2 라이브러리 추가

- presentaion-layer.xml → mvc 네임스페이스 추가 및 <mvc:annotation-driven> 설정 (HttpMessageConvertor 변환기)

- Controller 클래스 변환 메서드 및 @ResponseBody 어노테이션 추가

- VO 클래스에 변환하지 않을 내용 @JsonIgnore 어노테이션 적용

11) 코드

지금까지 공부해온 내용 정리, 해당 프로젝트의 흐름이나 감을 잡기 위해 임의로 작성한 코드이기 때문에 동작하지 않을 가능성이 높다. applicationContext.xml, presentation.xml, web.xml, Controller 클래스를 주의 깊게 보도록 한다.


[참고] sarc.io/index.php/development/1139-requestmapping

[참고] github.com/ozofweird/Spring_BoardAJdbc


 

 

 

3. JdbcTemplate 기반

1) 설정 파일 생성

설정 파일의 경우 체계적으로 나누어 사용하는게 좋다. 각 역할에 맞게 파일을 분리하고 최종적으로는 applicationContext.xml 파일에 통합하여 이용한다.

 

- Spring Bean Configuration File → applicationContext.xml 생성

2) 데이터베이스

- pom.xml → H2 라이브러리 추가

3) 주요 클래스

DAO 클래스, ServiceImpl 클래스는 @Component 어노테이션이 붙어있어야한다. 각 클래스의 용도에 맞게 @Repository, @Service 어노테이션을 적절하게 사용한다. applicationContext.xml에는 context 네임스페이스를 추가하고 <context:component-scan>을 통해서 스프링 컨테이너가 컴포넌트를 스캔하여 객체를 만들 수 있도록 한다.

 

클래스 설명
VO - 데이터 전달을 목적으로 사용하는 클래스
- 변수명은 테이블 컬럼명과 동일하며 private로 선언
- Getter / Setter 메서드 구현
RowMapper - JdbcTemplate queryForObject() 메서드를 위한 클래스
- SELECT 쿼리문의 결과가 여러 개일 경우, 자바 객체로 매핑에 사용
DAO - 데이터베이스의 접근을 위한 클래스 (CRUD, SQL 명령어)
- 메서드명은 구현할 기능과 테이블명을 결합한 형태
Service - 인터페이스
ServiceImpl - Service 인터페이스를 구현
Log - 예외 로그, 수행 시간 로그 메서드 구현

4) AOP

AOP 어노테이션을 사용하기 위해서는 반드시 어드바이스 객체가 생성되어 있어야하므로 @Service 어노테이션을 사용하고 애스팩트 역할을 할 클래스임을 @Aspect 어노테이션으로 명시해준다. 공통의 포인트컷을 사용하기에 PointcutCommon 파일을 생성한다. Log 클래스에는 @Before, @After, @AfterReturning, @AfterThrowing, @Around 어노테이션으로 동작 시점을 지정한다.

 

- pom.xml → AspectJ 라이브러리 추가

- applicationContext.xml → aop 네임스페이스 추가 및 <aop:aspectj-autoproxy> 설정

 

용어 설명
포인트컷 - 필터링된 클라이언트가 호출하는 모든 비즈니스 메서드
- 특정 메서드에서만 공통 기능을 수행할 수 있도록 클래스, 패키지, 메서드 시그니처 지정 가능
어드바이스 - 횡단 관심에 해당하는 공통 코드
- 독립된 클래스의 메서드
- 메서드 동작 시점 지정
위빙 - 포인트컷으로 지정한 핵심 관리 메서드가 호출 시 어드바이스에 해당하는 횡단 관심 메서드가 삽입되는 과정
애스팩트 - 포인트컷과 어드바이스의 결합으로 포인트컷 메서드에 대해 어떤 어드바이스 메서드를 실행할지 결정

5) 트랜잭션

JDBC 기반으로 동작하기 때문에 별도의 라이브러리 추가가 필요하며, 프로퍼티파일을 이용한 DataSource 등록과 이를 이용한 트랜잭션 관리자를 등록한다. (모든 트랜잭션 관리자는 PlatformTransactionManager 인터페이스를 구현한 클래스이다.)

 

- pom.xml → JDBC, DBCP 라이브러리 추가

- applicationContext.xml → DataSource 지정(db.properties 파일 설정)

- applicationContext.xml → TransactionManager 지정

6) JSP 화면 구성

7) MVC 적용

Controller 클래스의 각 메서드가 받는 인자값이 Command 객체를 사용하도록 한다. DAO, ModelAndView 객체도 매개변수로 선언 시 스프링 컨테이너가 객체를 생성해주고 전달해주기 때문에 더 간결한 코드를 작성할 수 있다. (단, JSP 파일의 Form 태그 내의 Command 객체의 Setter 메서드 이름이 반드시 일치해야하며 '${...}' 구문을 이용해야 한다.)

 

일반적으로 Controller 클래스의 각 메서드 반환 타입은 통일해준다. ModelAndView를 사용했을 경우, 반환 타입은 String, 매개 변수는 Model로 수정하도록 한다.

 

Controller 클래스의 메서드가 다음 화면으로 넘어갈 문자열을 반환할때, 기본적으로 포워딩으로 동작하기 때문에 URL 변경이 되지 않기에 리다이렉트 설정을 해주어야 한다.

 

Controller는 DAO 객체를 직접 이용해서는 안되며, 비즈니스 컴포넌트(Service)를 이용해야한다.

 

- Spring Bean Configuration File → presentation-layer.xml 생성

- web.xml → Spring Servlet 등록, 설정 파일(presentation-layer.xml) 위치 및 이름 지정

- web.xml → CharacterEncoding, ContextLoaderListener(applicationContext.xml) 지정

- presentation-layer.xml → context 네임스페이스 추가 및 <context:component-scan> 설정

- Controller 클래스 → 어노테이션 지정

 

어노테이션 설명
@Controller - 해당 클래스가 Controller임을 나타내기 위한 어노테이션
@RequestMapping - 요청에 대해 어떤 Controller, 어떤 메소드가 처리할지를 맵핑하기 위한 어노테이션
@ModelAttribute - Controller 메소드의 파라미터나 리턴값을 Model 객체와 바인딩하기 위한 어노테이션
- Command 객체명과 클래스명이 동일하지 않을 경우 사용
- View에서 사용할 데이터를 설정하는 용도로 사용
- @RequestMapping 보다 먼저 호출이되며, 반환된 결과는 자동으로 Model에 저장
@RequestParam - Controller 메소드의 파라미터와 웹요청 파라미터와 맵핑하기 위한 어노테이션
- HttpSession의 getParameter()와 동일한 기능을 수행
- VO/Command 객체에 없는 정보일 때 사용
- 검색과 관련된 파라미터 정보를 추출
@SessionAttributes - Model 객체를 세션에 저장하고 사용하기 위한 어노테이션
- null 업데이트 방지

※ @RequestMapping 속성을 이용하여 동일한 URL 요청에 대해 메서드별로 동작하는 함수를 다르게 설정할 수 있다. (LoginController)

※ @ModelAttribute는 프로젝트내에서 검색 조건(제목, 내용)을 @RequestMapping 메서드가 수행하기 전에 View에 전달해준다.

8) 예외 처리

발생하는 예외의 종류에 따라 적절한 화면이 나오도록 CommonExceptionHandler 클래스를 생성하고, @ControllerAdvice와 @ExceptionHandler 어노테이션을 설정한다.

 

- presentation-layer.xml → mvc 네임스페이스 추가 및 <mvc:annotation-driven> 설정

- CommonExceptionHandler 클래스 작성 및 어노테이션 적용

- 예외 화면 구성

어노테이션 설명
@ControllerAdvice - 특정 위치에서 발생하는 예외에 대한 처리를 한다고 명시
@ExceptionHandler - 각 예외에 따라 호출되는 메서드를 지정

9) 다국어 설정

/src/main/resources/message 폴더 하위에 Locale 파일을 생성한다. 한글 파일의 경우 유니코드가 필요하니 메모장을 이용하여 유니코드로 변환시켜 저장한다.

 

- File → messageSource_en.properties

- File → messageSource_ko.properties

- presentation-layer.xml → MessageSource 지정

- presentation-layer.xml → mvc 네임스페이스 추가

- presentation-layer.xml → LocalResolver, LocalChangeInterceptor 지정

10) JSON 변환

Controller 클래스에는 @ResponseBody 어노테이션을 이용하여 HTTP 프로토콜의 몸체로 변환하고, 스프링 컨테이너는 설정 파일을 통해 변환기 객체를 생성하고 JSON 형태로 변환되어 HTTP 응답 바디에 전달된다.

 

- pom.xml → Jackson2 라이브러리 추가

- presentaion-layer.xml → mvc 네임스페이스 추가 및 <mvc:annotation-driven> 설정 (HttpMessageConvertor 변환기)

- Controller 클래스 변환 메서드 및 @ResponseBody 어노테이션 추가

- VO 클래스에 변환하지 않을 내용 @JsonIgnore 어노테이션 적용

11) 코드

지금까지 공부해온 내용 정리, 해당 프로젝트의 흐름이나 감을 잡기 위해 임의로 작성한 코드이기 때문에 동작하지 않을 가능성이 높다.

 

데이터베이스 연결, 해제를 직접 만들고 안만들고의 차이와, JdbcTemplate를 JdbcDaoSupport 클래스를 상속받을 경우와 <bean>으로 JdbcTemplate 등록 방법의 차이만 존재하기에, 앞서 진행한 프로젝트와 다른 BoardRowMapper 클래스, UserRowMapper 클래스, context-transaction.xml을 주의 깊게 보도록 한다.


[참고] github.com/ozofweird/Spring_BoardAJdbcTemplate


 

 

 

4. Mybatis 기반

1) 설정 파일 생성

설정 파일의 경우 체계적으로 나누어 사용하는게 좋다. 각 역할에 맞게 파일을 분리하고 최종적으로는 applicationContext.xml 파일에 통합하여 이용한다.

 

- Spring Bean Configuration File → applicationContext.xml 생성

2) 데이터베이스

- pom.xml → H2 라이브러리 추가

3) 주요 클래스

클래스 설명
VO - 데이터 전달을 목적으로 사용하는 클래스
- 변수명은 테이블 컬럼명과 동일하며 private로 선언
- Getter / Setter 메서드 구현
Service - 인터페이스
ServiceImpl - Service 인터페이스를 구현
Log - 예외 로그, 수행 시간 로그 메서드 구현

4) Mybatis

한두 줄의 자바 코드로 데이터베이스 연동을 처리하며 SQL 명령어를 자바 코드에서 분리하여 XML 파일을 따로 관리한다.

 

SqlSession 객체를 얻기 위해 SqlSessionFactoryBean 클래스를 <bean> 등록해야한다. SqlSessionDaoSupport 클래스를 상속받아서 구현하는 방식과 SqlSessionTemplate 클래스를 <bean> 등록하여 사용하는 방법이 있다. 후자의 방법 기준으로, SqlSessionTemplate 타입의 변수를 사용하는 DAO 클래스에는 @Autowired가 정의되어 있어야 한다.

 

Mybatis Mapper XML로 생성한 파일은 /src/main/resources/mappings 폴더 하위에 위치하고 Mybatis Configuration XML로 생성한 파일은 /src/main/resources/ 하위에 위치한다.(단, db.properties 파일의 경우 AOP 설정 및 트랜잭션에서 DataSource에서 이미 사용하고 있으므로 삭제한다.)

 

- pom.xml → Mybatis, Mybatis-spring 라이브러리 추가

- Mybatis Mapper XML → mapping.xml 생성

- Mybatis Configuration XML → map-config.xml, (db.properties) 생성

 

파일 설명
mapping.xml - 기존 DAO 클래스로 부터 분리하여 SQL 쿼리문 관리
map-config.xml - 별칭, DataSource, Mapper가 정의됨

5) AOP

AOP 어노테이션을 사용하기 위해서는 반드시 어드바이스 객체가 생성되어 있어야하므로 @Service 어노테이션을 사용하고 애스팩트 역할을 할 클래스임을 @Aspect 어노테이션으로 명시해준다. 공통의 포인트컷을 사용하기에 PointcutCommon 파일을 생성한다. Log 클래스에는 @Before, @After, @AfterReturning, @AfterThrowing, @Around 어노테이션으로 동작 시점을 지정한다.

 

- pom.xml → AspectJ 라이브러리 추가

- applicationContext.xml → aop 네임스페이스 추가 및 <aop:aspectj-autoproxy> 설정

 

용어 설명
포인트컷 - 필터링된 클라이언트가 호출하는 모든 비즈니스 메서드
- 특정 메서드에서만 공통 기능을 수행할 수 있도록 클래스, 패키지, 메서드 시그니처 지정 가능
어드바이스 - 횡단 관심에 해당하는 공통 코드
- 독립된 클래스의 메서드
- 메서드 동작 시점 지정
위빙 - 포인트컷으로 지정한 핵심 관리 메서드가 호출 시 어드바이스에 해당하는 횡단 관심 메서드가 삽입되는 과정
애스팩트 - 포인트컷과 어드바이스의 결합으로 포인트컷 메서드에 대해 어떤 어드바이스 메서드를 실행할지 결정

6) 트랜잭션

JDBC 기반으로 동작하기 때문에 별도의 라이브러리 추가가 필요하며, 프로퍼티파일을 이용한 DataSource 등록과 이를 이용한 트랜잭션 관리자를 등록한다. (모든 트랜잭션 관리자는 PlatformTransactionManager 인터페이스를 구현한 클래스이다.)

 

- pom.xml → JDBC, DBCP 라이브러리 추가

- applicationContext.xml → DataSource 지정(db.properties 파일 설정)

- applicationContext.xml → TransactionManager 지정

7) JSP 화면 구성

8) MVC 적용

Controller 클래스의 각 메서드가 받는 인자값이 Command 객체를 사용하도록 한다. DAO, ModelAndView 객체도 매개변수로 선언 시 스프링 컨테이너가 객체를 생성해주고 전달해주기 때문에 더 간결한 코드를 작성할 수 있다. (단, JSP 파일의 Form 태그 내의 Command 객체의 Setter 메서드 이름이 반드시 일치해야하며 '${...}' 구문을 이용해야 한다.)

 

일반적으로 Controller 클래스의 각 메서드 반환 타입은 통일해준다. ModelAndView를 사용했을 경우, 반환 타입은 String, 매개 변수는 Model로 수정하도록 한다.

 

Controller 클래스의 메서드가 다음 화면으로 넘어갈 문자열을 반환할때, 기본적으로 포워딩으로 동작하기 때문에 URL 변경이 되지 않기에 리다이렉트 설정을 해주어야 한다.

 

Controller는 DAO 객체를 직접 이용해서는 안되며, 비즈니스 컴포넌트(Service)를 이용해야한다.

 

- Spring Bean Configuration File → presentation-layer.xml 생성

- web.xml → Spring Servlet 등록, 설정 파일(presentation-layer.xml) 위치 및 이름 지정

- web.xml → CharacterEncoding, ContextLoaderListener(applicationContext.xml) 지정

- presentation-layer.xml → context 네임스페이스 추가 및 <context:component-scan> 설정

- Controller 클래스 → 어노테이션 지정

 

어노테이션 설명
@Controller - 해당 클래스가 Controller임을 나타내기 위한 어노테이션
@RequestMapping - 요청에 대해 어떤 Controller, 어떤 메소드가 처리할지를 맵핑하기 위한 어노테이션
@ModelAttribute - Controller 메소드의 파라미터나 리턴값을 Model 객체와 바인딩하기 위한 어노테이션
- Command 객체명과 클래스명이 동일하지 않을 경우 사용
- View에서 사용할 데이터를 설정하는 용도로 사용
- @RequestMapping 보다 먼저 호출이되며, 반환된 결과는 자동으로 Model에 저장
@RequestParam - Controller 메소드의 파라미터와 웹요청 파라미터와 맵핑하기 위한 어노테이션
- HttpSession의 getParameter()와 동일한 기능을 수행
- VO/Command 객체에 없는 정보일 때 사용
- 검색과 관련된 파라미터 정보를 추출
@SessionAttributes - Model 객체를 세션에 저장하고 사용하기 위한 어노테이션
- null 업데이트 방지

※ @RequestMapping 속성을 이용하여 동일한 URL 요청에 대해 메서드별로 동작하는 함수를 다르게 설정할 수 있다. (LoginController)

※ @ModelAttribute는 프로젝트내에서 검색 조건(제목, 내용)을 @RequestMapping 메서드가 수행하기 전에 View에 전달해준다.

9) 예외 처리

발생하는 예외의 종류에 따라 적절한 화면이 나오도록 CommonExceptionHandler 클래스를 생성하고, @ControllerAdvice와 @ExceptionHandler 어노테이션을 설정한다.

 

- presentation-layer.xml → mvc 네임스페이스 추가 및 <mvc:annotation-driven> 설정

- CommonExceptionHandler 클래스 작성 및 어노테이션 적용

- 예외 화면 구성

어노테이션 설명
@ControllerAdvice - 특정 위치에서 발생하는 예외에 대한 처리를 한다고 명시
@ExceptionHandler - 각 예외에 따라 호출되는 메서드를 지정

10) 다국어 설정

/src/main/resources/message 폴더 하위에 Locale 파일을 생성한다. 한글 파일의 경우 유니코드가 필요하니 메모장을 이용하여 유니코드로 변환시켜 저장한다.

 

- File → messageSource_en.properties

- File → messageSource_ko.properties

- presentation-layer.xml → MessageSource 지정

- presentation-layer.xml → mvc 네임스페이스 추가

- presentation-layer.xml → LocalResolver, LocalChangeInterceptor 지정

11) JSON 변환

Controller 클래스에는 @ResponseBody 어노테이션을 이용하여 HTTP 프로토콜의 몸체로 변환하고, 스프링 컨테이너는 설정 파일을 통해 변환기 객체를 생성하고 JSON 형태로 변환되어 HTTP 응답 바디에 전달된다.

 

- pom.xml → Jackson2 라이브러리 추가

- presentaion-layer.xml → mvc 네임스페이스 추가 및 <mvc:annotation-driven> 설정 (HttpMessageConvertor 변환기)

- Controller 클래스 변환 메서드 및 @ResponseBody 어노테이션 추가

- VO 클래스에 변환하지 않을 내용 @JsonIgnore 어노테이션 적용

12) 코드

지금까지 공부해온 내용 정리, 해당 프로젝트의 흐름이나 감을 잡기 위해 임의로 작성한 코드이기 때문에 동작하지 않을 가능성이 높다. Mybatis 설정 파일 sql-map-config.xml과 board-mappings.xml 파일을 주의 깊게 보도록 한다.


[참고] github.com/ozofweird/Spring_BoardAMybatis


 

 

 

5. JPA 기반

1) 설정 파일 생성

설정 파일의 경우 체계적으로 나누어 사용하는게 좋다. 각 역할에 맞게 파일을 분리하고 최종적으로는 applicationContext.xml 파일에 통합하여 이용한다.

 

- Spring Bean Configuration File → applicationContext.xml 생성

2) 데이터베이스

- pom.xml → H2 라이브러리 추가

3) 주요 클래스

클래스 설명
VO - 데이터 전달을 목적으로 사용하는 클래스
- 변수명은 테이블 컬럼명과 동일하며 private로 선언
- Getter / Setter 메서드 구현
Service - 인터페이스
ServiceImpl - Service 인터페이스를 구현
Log - 예외 로그, 수행 시간 로그 메서드 구현

4) JPA

JPA 프로젝트로 변경하게 되면 persistence.xml 파일이 생성이 된다. 생성된 파일에 하이버네이트를 설정한다.

 

- pom.xml → JPA, 하이버네이트 , JPA-spring 라이브러리 추가

- persistence.xml → 하이버네이트 설정

- VO 클래스 → 엔티티 매핑

- applicationContext.xml → HibernateJpaVendorAdaptor, LocalContainerEntityManagerFactoryBean 등록

- applicationContext.xml → 트랜잭션 JPA 관리자 변경

- EntityManager 객체를 이용한 DAO 클래스 작성

 

※ HibernateJpaVendorAdaptor, LocalContainerEntityManagerFactoryBean 등록할 때, 하이버네이트 설정도 동시에 직접 적용할 경우 persistence.xml 파일을 삭제해도 무관하다.

5) AOP

AOP 어노테이션을 사용하기 위해서는 반드시 어드바이스 객체가 생성되어 있어야하므로 @Service 어노테이션을 사용하고 애스팩트 역할을 할 클래스임을 @Aspect 어노테이션으로 명시해준다. 공통의 포인트컷을 사용하기에 PointcutCommon 파일을 생성한다. Log 클래스에는 @Before, @After, @AfterReturning, @AfterThrowing, @Around 어노테이션으로 동작 시점을 지정한다.

 

- pom.xml → AspectJ 라이브러리 추가

- applicationContext.xml → aop 네임스페이스 추가 및 <aop:aspectj-autoproxy> 설정

 

용어 설명
포인트컷 - 필터링된 클라이언트가 호출하는 모든 비즈니스 메서드
- 특정 메서드에서만 공통 기능을 수행할 수 있도록 클래스, 패키지, 메서드 시그니처 지정 가능
어드바이스 - 횡단 관심에 해당하는 공통 코드
- 독립된 클래스의 메서드
- 메서드 동작 시점 지정
위빙 - 포인트컷으로 지정한 핵심 관리 메서드가 호출 시 어드바이스에 해당하는 횡단 관심 메서드가 삽입되는 과정
애스팩트 - 포인트컷과 어드바이스의 결합으로 포인트컷 메서드에 대해 어떤 어드바이스 메서드를 실행할지 결정

6) 트랜잭션

JDBC 기반으로 동작하기 때문에 별도의 라이브러리 추가가 필요하며, 프로퍼티파일을 이용한 DataSource 등록과 이를 이용한 트랜잭션 관리자를 등록한다. (모든 트랜잭션 관리자는 PlatformTransactionManager 인터페이스를 구현한 클래스이다.)

 

- pom.xml → JDBC, DBCP 라이브러리 추가

- applicationContext.xml → DataSource 지정(db.properties 파일 설정)

- applicationContext.xml → TransactionManager 지정

7) JSP 화면 구성

8) MVC 적용

Controller 클래스의 각 메서드가 받는 인자값이 Command 객체를 사용하도록 한다. DAO, ModelAndView 객체도 매개변수로 선언 시 스프링 컨테이너가 객체를 생성해주고 전달해주기 때문에 더 간결한 코드를 작성할 수 있다. (단, JSP 파일의 Form 태그 내의 Command 객체의 Setter 메서드 이름이 반드시 일치해야하며 '${...}' 구문을 이용해야 한다.)

 

일반적으로 Controller 클래스의 각 메서드 반환 타입은 통일해준다. ModelAndView를 사용했을 경우, 반환 타입은 String, 매개 변수는 Model로 수정하도록 한다.

 

Controller 클래스의 메서드가 다음 화면으로 넘어갈 문자열을 반환할때, 기본적으로 포워딩으로 동작하기 때문에 URL 변경이 되지 않기에 리다이렉트 설정을 해주어야 한다.

 

Controller는 DAO 객체를 직접 이용해서는 안되며, 비즈니스 컴포넌트(Service)를 이용해야한다.

 

- Spring Bean Configuration File → presentation-layer.xml 생성

- web.xml → Spring Servlet 등록, 설정 파일(presentation-layer.xml) 위치 및 이름 지정

- web.xml → CharacterEncoding, ContextLoaderListener(applicationContext.xml) 지정

- presentation-layer.xml → context 네임스페이스 추가 및 <context:component-scan> 설정

- Controller 클래스 → 어노테이션 지정

 

어노테이션 설명
@Controller - 해당 클래스가 Controller임을 나타내기 위한 어노테이션
@RequestMapping - 요청에 대해 어떤 Controller, 어떤 메소드가 처리할지를 맵핑하기 위한 어노테이션
@ModelAttribute - Controller 메소드의 파라미터나 리턴값을 Model 객체와 바인딩하기 위한 어노테이션
- Command 객체명과 클래스명이 동일하지 않을 경우 사용
- View에서 사용할 데이터를 설정하는 용도로 사용
- @RequestMapping 보다 먼저 호출이되며, 반환된 결과는 자동으로 Model에 저장
@RequestParam - Controller 메소드의 파라미터와 웹요청 파라미터와 맵핑하기 위한 어노테이션
- HttpSession의 getParameter()와 동일한 기능을 수행
- VO/Command 객체에 없는 정보일 때 사용
- 검색과 관련된 파라미터 정보를 추출
@SessionAttributes - Model 객체를 세션에 저장하고 사용하기 위한 어노테이션
- null 업데이트 방지

※ @RequestMapping 속성을 이용하여 동일한 URL 요청에 대해 메서드별로 동작하는 함수를 다르게 설정할 수 있다. (LoginController)

※ @ModelAttribute는 프로젝트내에서 검색 조건(제목, 내용)을 @RequestMapping 메서드가 수행하기 전에 View에 전달해준다.

9) 예외 처리

발생하는 예외의 종류에 따라 적절한 화면이 나오도록 CommonExceptionHandler 클래스를 생성하고, @ControllerAdvice와 @ExceptionHandler 어노테이션을 설정한다.

 

- presentation-layer.xml → mvc 네임스페이스 추가 및 <mvc:annotation-driven> 설정

- CommonExceptionHandler 클래스 작성 및 어노테이션 적용

- 예외 화면 구성

어노테이션 설명
@ControllerAdvice - 특정 위치에서 발생하는 예외에 대한 처리를 한다고 명시
@ExceptionHandler - 각 예외에 따라 호출되는 메서드를 지정

10) 다국어 설정

/src/main/resources/message 폴더 하위에 Locale 파일을 생성한다. 한글 파일의 경우 유니코드가 필요하니 메모장을 이용하여 유니코드로 변환시켜 저장한다.

 

- File → messageSource_en.properties

- File → messageSource_ko.properties

- presentation-layer.xml → MessageSource 지정

- presentation-layer.xml → mvc 네임스페이스 추가

- presentation-layer.xml → LocalResolver, LocalChangeInterceptor 지정

11) JSON 변환

Controller 클래스에는 @ResponseBody 어노테이션을 이용하여 HTTP 프로토콜의 몸체로 변환하고, 스프링 컨테이너는 설정 파일을 통해 변환기 객체를 생성하고 JSON 형태로 변환되어 HTTP 응답 바디에 전달된다.

 

- pom.xml → Jackson2 라이브러리 추가

- presentaion-layer.xml → mvc 네임스페이스 추가 및 <mvc:annotation-driven> 설정 (HttpMessageConvertor 변환기)

- Controller 클래스 변환 메서드 및 @ResponseBody 어노테이션 추가

- VO 클래스에 변환하지 않을 내용 @JsonIgnore 어노테이션 적용

12) 코드

지금까지 공부해온 내용 정리, 해당 프로젝트의 흐름이나 감을 잡기 위해 임의로 작성한 코드이기 때문에 동작하지 않을 가능성이 높다.

 

※ 프로젝트를 진행하면서 DataSource 설정 부분에서 문제가 발생했다. 따라서 프로퍼티 파일을 이용한 DataSource 등록이 아닌 직접 값을 주어 해결하는 방식으로 진행했다.


[참고] github.com/ozofweird/Spring_BoardAJPA


 

 

 

6. 참고 내용

1) 어노테이션 설정

어노테이션 위치 의미
@Service OOOServiceImpl 비즈니스 로직을 처리하는 Service 클래스
@Repository OOODAO 데이터베이스 연동을 처리하는 DAO 클래스
@Controller OOOController 사용자 요청을 제어하는 Controller 클래스

- @Autowired의 경우 객체 타입과 메모리 객체 존재 여부를 확인 후 변수에 주입하도록 설정하는 어노테이션이다.

- @Qualifier의 경우 동일한 타입의 객체가 두개 이상일 경우 의존성 주입할 객체를 지정하기 위해 사용

/* 클래스.java */
@Component("클래스")
public class 클래스 {

}


/* 테스트.java */
@Component
public class 테스트 {
	
    @Autowired
    @Qualifier("클래스")
    private int seq;

}

2) 설정 파일 설정 (applicationContext.xml)

- context 네임스페이스 추가 후 어노테이션을 사용하기 위한 설정

<context:component-scan base-package="패키지명" />

- @Autowired로 의존성 주입할 객체 <bean> 등록

<bean class="클래스명" />

 

3) JdbcTemplate

- db.properties 파일 생성

jdbc.driver=org.h2.Driver
jdbc.url=jdbc:h2:~/test
jdbc.username=sa
jdbc.password=

- DataSource 설정

<context:property-placeholder location="classpath:db.properties" />

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	
    <property name="driverClassName" value="${jdbc.driver}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    
</bean>

4) Mybatis

- map-config.xml

<!-- Properties 설정 -->
<properties resource="db.properties" />

<!-- Alias 설정 -->
<typeAliases>
    <typeAlias type="VO 클래스명" alias="별칭"></typeAlias>
</typeAliases>

<!-- DataSource 설정은 이미 설정 파일에서 제공-->
	
<!-- Sql Mapper 설정 -->
<mappers>
    <mapper resource="mappings/mapping.xml" />
</mappers>

- SqlSessionFactoryBean / SqlSession 객체 등록

 

<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:map-config.xml" />
</bean>

- SqlSessionTemplate <bean>등록

 

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:map-config.xml" />
</bean>

<bean class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg ref="sqlSession />
</bean>

5) JPA

- persistence.xml

<persistence-unit name="유닛명">
		<class>VO 클래스명</class>
		<properties>
			<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
			
			<!-- 옵션 -->
			<property name="hibernate.show_sql" value="true"/>
			<property name="hibernate.format_sql" value="true"/>
			<property name="hibernate.use_sql_comments" value="false"/>
			<property name="hibernate.id.new_generator_mappings" value="true"/>
			<property name="hibernate.hbm2ddl.auto" value="create"/>
		</properties>
		
</persistence-unit>

- Hibernate JPA 구현체를 등록

<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="jpaVendorAdapter" ref="jpaVendorAdapter"></property>
</bean>

- DAO 클래스는 @PersistenceContext로 정의된 EntityManager 변수를 이용하여 구현한다.

6) AOP

- applicationContext.xml

<aop:aspectj-autoproxy />

- 어노테이션

 

@Service
@Aspeect
public class 클래스 {
	@Pointcut("execution(* com.springbook.biz..*Impl.*(..))")
	public void allPointcut() {
	
	}

	@Before("allPointcut")
	public void print() {
	
	}
}

7) 트랜잭션

<!-- 관리자 클래스 등록 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 어드바이스 설정 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
	
    <tx:attributes>
        <tx:method name="get" read-only="true" />
    </tx:attributes>
    
</tx:advice>

<!-- 트랜잭션 적용 -->
<aop:config>

    <aop:pointcut id="txPointcut" expression="execution(* com.springbook.biz..*Impl.*(..))" />
    <aop:advisor pointcut-ref="txPointcut" advice-ref"txAdvice" />
    
</aop:config>

8) 트랜잭션 JPA

DataSourceTransactionManager는 스프링 JDBC나 Mybatis를 이용할 때 사용하는 관리자이기에 JPA 전용 관리자로 변경해주어야한다.

<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

9) EL/JSTL

JSP 파일은 /src/main/webapp 하위에 생성하며 화면 구성을 하면서 작성될 자바 코드를 EL/JSTL로 제거하여 작성한다.

<%taglib uri="http://java.sun.com/jstl/core_r" prefix="c" %>

- EL 표현식

<%= session.getAttribute("userName") %>를 ${userName}으로 변경

- JSTL 표현식

<c:forEach items="${conditionMap }" var="option">
    <option value="${option.value }">${option.key }
</c:forEach>

10) MVC 기본 동작 과정

- DispatcherServlet에서 클라이언트의 요청을 받는다.

- DispatcherServlet에서는 HandlerMapping 객체를 통해 요청 처리할 Controller 검색한다.

- 검색된 Controller에서 handleRequest() 메서드 호출 시 로직을 수행한다.

- Controller에서 로직 수행 후 이동할 화면 정보를 DispatcherServlet에 전달한다.

- DispatcherServlet에서 ViewResolver 객체를 통해 파일명과 경로를 받는다.

- 최종적으로 받은 파일명과 경로를 실행하여 브라우저에 응답한다.

11) MVC 직접 구현 방식

- Servlet 파일을 생성하고 web.xml에 추가된 내용을 확인한다. (DispatcherServlet.java)

- 최상위 Controller 인터페이스를 생성하고 이를 상속받는 클래스를 생성한다.

- 생성한 클래스에 JSP 파일에서 처리되는 자바 코드를 옮긴다.

- 클라이언트 요청에 따라 처리될 Controller를 매핑해주는 HandlerMapping을 구현한다.

- Controller가 반환하는 값에 View 경로와 파일명을 결합해주는 ViewResolver를 구현한다.

- Servlet 파일에 HandlerMapping 변수, ViewResolver 변수와 이 변수들의 객체를 생성해주도록 init()메서드를 구현한다.

12) DispatcherServlet 설정 (web.xml)

 

<servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/config/presentation-layer.xml</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

<!-- Encoding -->
<filter>
    <filter-name>characterEncoding</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>EUC-KR</param-value>
    </init-param>
</filter>
  
<filter-mapping>
    <filter-name>characterEncoding</filter-name>
    <url-pattern>*.do</url-pattern>
</filter-mapping>

7) Controller 클래스 설정 및 어노테이션

 

 

- 컴포넌트 스캔 (presentation-layer.xml)

<context:component-scan base-package="컨트롤러 클래스가 존재하는 패키지" />

- @Controller 및 @RequestMapping 어노테이션

@Controller
public class 클래스 {

    @RequestMapping(value="/요청 URL", method=RequestMethod.POST)
    public String test(Command 객체, Model model) {
    
    	
        return "화면.jsp";
    }
}

- 화면에서 넘겨주는 데이터가 Command 객체에 없는 파라미터일 경우 @RequestParam을 이용한 정보 추출

public String test(@RequestParam(value="jsp에서 넘겨주는 값",
		defaultValue="기본값", required="생략여부") {

}

- null 방지

@Controller
@SessionAttribute("전달되어야 하는 값")
public class 클래스 {
	
    public void update(@ModelAttribute("전달되어야 하는 값") testVO vo) {
    
    }
}

8) ContextLoaderListener

<context-param>
	<param-name>contextConfigLocation></param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

9) 예외 처리

<mvc:annotation-driven />
@ControllerAdvice("com.springbook.view")
public class 클래스 {
	
    @ExceptionHandler(ArithmeticException.class)
    public ModelAndView handleArithmeticException(Exception e) {
    	
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.setViewName("보여줄 화면.jsp");
        return mav;
    }
}

10) 다국어 설정

- messageSource_en.properties, messageSource_ko.properties

# /src/main/resources/message/
# login.jsp
message.user.login.title=LOGIN
message.user.login.id=ID
message.user.login.password=PASSWORD
message.user.login.loginBtn=LOG-IN

message.user.login.language.en=English
message.user.login.language.ko=Korean

# getBoardList.jsp
message.board.list.mainTitle=BOARD LIST
message.board.list.welcomeMsg=! Welcome to my BOARD
message.board.list.search.condition.title=TITLE
message.board.list.search.condition.content=CONTENT
message.board.list.search.condition.btn=Search
message.board.list.table.head.seq=SEQ
message.board.list.table.head.title=TITLE
message.board.list.table.head.writer=WRITER
message.board.list.table.head.regDate=REGDATE
message.board.list.table.head.cnt=CNT
message.board.list.link.insertBoard=Insert Board
# /src/main/resources/message/
# login.jsp
message.user.login.title=\uB85C\uADF8\uC778
message.user.login.id=\uC544\uC774\uB514
message.user.login.password=\uBE44\uBC00\uBC88\uD638
message.user.login.loginBtn=\uB85C\uADF8\uC778

message.user.login.language.en=\uC601\uC5B4
message.user.login.language.ko=\uD55C\uAE00

# getBoardList.jsp
message.board.list.mainTitle=\uAC8C\uC2DC\uAE00 \uBAA9\uB85D
message.board.list.welcomeMsg=\uB2D8! \uAC8C\uC2DC\uD310\uC5D0 \uC624\uC2E0\uAC78 \uD658\uC601\uD569\uB2C8\uB2E4.
message.board.list.search.condition.title=\uC81C\uBAA9
message.board.list.search.condition.content=\uB0B4\uC6A9
message.board.list.search.condition.btn=\uAC80\uC0C9
message.board.list.table.head.seq=\uBC88\uD638
message.board.list.table.head.title=\uC81C\uBAA9
message.board.list.table.head.writer=\uC791\uC131\uC790
message.board.list.table.head.regDate=\uB4F1\uB85D\uC77C
message.board.list.table.head.cnt=\uC870\uD68C\uC218
message.board.list.link.insertBoard=\uC0C8\uAE00 \uB4F1\uB85D

- web.xml

<!-- 다국어 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>message.messageSource</value>
        </list>
    </property>
</bean>
	
<!-- LocalResolver 등록 -->
<bean id="localResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
	
<!-- LocalChangeInterceptor Locale 변경 -->
<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
        <property name="paramName" value="lang"/>
    </bean>
</mvc:interceptors>

11) JSP 파일 다국어 설정

<%@taglib uri="http://www.springframework.org/tags" prefix="spring" %>

<spring:message code="Locale에 등록된 정보" />

12) JSON 변환

Jackson2 라이브러리 추가 후 HttpMessageConvertor 빈 등록 후 @ResponseBody, @JsonIgnore 어노테이션을 사용하여 구현한다.

<mvc:annotation-driven />
728x90

'Spring > Spring' 카테고리의 다른 글

[Spring] 간단한 API 제작  (0) 2020.08.20
[Spring] IntelliJ Tomcat, MySQL, SpringMVC 설정  (0) 2020.08.20
[Spring] XML 기반 프로젝트 진행 과정  (0) 2020.08.13
[Spring] JPA 스프링 연동  (0) 2020.08.13