model에 enum type을 넣고 

mybatis에서 아래 테이블의 data를 받아오려고 한다.

 

 

enum은 여러 형태를 가질 수 있다.

 

아래와 같이 그냥 단건의 데이터만 지정하여 만드는 경우가 있을 수 있고

 

 

db에 쌓이는 데이터유형이 숫자( 1_VERSION, 2_VERSION )

특수문자로 시작할 경우 enum을 아래와 같이 인자를 2개씩 갖게 지정될 수 있다.

 

 

 

그럴경우 여러 형태의 enum 데이터를 바인딩 하지 못해

아래와 같은 오류가 발생 할 수 있다.

 

Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column '' from result set.

Caused by: java.lang.IllegalArgumentException: No enum constant

 

 

 

위소스를 보면 상품정보 rest controller에서 service를 거쳐 mapper에 해당하는 쿼리결과를 lists 객체에

담겨야 하는데 데이터를 못받아와 catch절 NOT_FOUND로 빠져  

swagger에서해당 data를 알지 못해 못받아오는 경우도 생긴다.

 

 

 

이럴경우 TypeHandler를 생성하여 해결할 수 있다.

 

 

 

 

그리고 생성한 type handler 위치의 패키지명을 아래와 같이 지정해준다.

type-handlers-package: com.shlee.toy1.common.handler.type

 

 

 

그리고 api를 조회해보면 아래와 같이 enum으로 정의한 키값으로 나온다.

 

 

 

이제 이 반환된 키 값으로 서비스 로직에서 데이터를 가공해서 반환해주면 된다.

 

 

 

 

이제 여러개의 enum type에 대응가능한 mybatis type handler에 대해 작성할 수 있게 되었다. 

component로 등록된 것은 @SpringBootApplication 어노테이션을 통해 componentscan을 한다고 한다.

 

docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-using-springbootapplication-annotation

 

Using Spring Boot

This section goes into more detail about how you should use Spring Boot. It covers topics such as build systems, auto-configuration, and how to run your applications. We also cover some Spring Boot best practices. Although there is nothing particularly spe

docs.spring.io

intellij에서는 component로 등록된 것을 @Autowired하여 사용하려면

Could not autowire. No beans of '' type found. 라는 error가 발생한다.

컴파일 및 서버 실행에 문제는 없지만 해당 오류를 수정 할 수 있다.

 

컴포넌트를 사용하려는 클래스 위에 컴포넌트를 스캔할 패키지명을 적어준다.

@ComponentScan(basePackages={"com.shlee.toy1.common.components"}) 

 

참조 

stackoverflow.com/questions/26889970/intellij-incorrectly-saying-no-beans-of-type-found-for-autowired-repository

spring boot 테스트 프로젝트로 swagger를 적용해보았다.

 

1. gradle에 해당 springfox-swagger를 명시해준다.

 

 

 

swagger2, swagger-ui, swagger-annotations, swagger-models를 받아주는데

해당 정보는 mvn repository에서 확인할 수 있다.

 

mvnrepository.com/artifact/io.springfox/springfox-swagger2/2.9.2

mvnrepository.com/artifact/io.springfox/springfox-swagger-ui/2.9.2

mvnrepository.com/artifact/io.swagger/swagger-annotations/1.5.21

mvnrepository.com/artifact/io.swagger/swagger-models/1.5.21

 

gradle 설정이 완료되면

 

ToySwaggerConfig 파일을 하나 생성해준다.

 

 

먼저 WebMvcConfigurer를 implements 받고 addResourceHandlers를 구현해준다.

swagger-ui가 보여질 화면의 리소스 위치를 설정해주는 것이다.

 

다음은 swagger를 만들 Docket을 bean으로 등록하고 생성할 Docket을 만들어준다.

 

.apis는 api를 탐색할 위치(com.shlee.toy1,controller)

.path 경로로 시작되는 위치(controller에서 @RequestMapping("/v1")으로 시작되는 uri)

.directModelSubstitute로 날짜형식을 맞춰주고

.globalResponseMessage로 요청을 받았을때 어떻게 처리할지를 정해준다.

 

.additionalModels의 typeResolver.resolve(ResponseEntity.class)는 ModelRef에서 지정한.

ex) CommonError, CommonResponse, string, ResponseEntity 같은 사용자 지정 모델값을 넣어주면 된다.

필요 하지 않다면 다음과 같이 작성해도 된다.

 

 

마지막으로 .apiInfo를 통해 api정보를 지정해준다.

 

 

그리고 서버를 실행후 http://localhost:8080/swagger-ui.html 에 접속하면

controller와 연동된 api 목록이 나타나게 된다.

 

 

test 시도 시 response데이터가 잘 오는 것을 확인 할 수 있었다.

 

 

 

최근 스프링 부트 기본 설정을 테스트로 잡아보면서 작업하는데

DB 비밀번호를 간단하게 설정(숫자 0882) 하여 application.yml 파일을 작성하였더니 해당 오류가 발생하였다.

 

 

 

패스워드는 대문자, 소문자, 숫자, 특수문자를 포함한 암호 길이 8자 이상으로 설정해야 한다고 한다.

그래서 아래와 같이 패스워드를 조합하여 설정하였더니 해당 오류가 발생하지 않았다. 

 

 

 

또한 스프링 2.3.x 버전에서는 

application-local.yml profile을 local

application-dev.yml profile은 dev

application-prd.yml profile을 prd로 

파일단위별로 관리하여

 

 

 

해당하는 active profiles가 무엇인지 설정한 뒤 실행하였다.

 

2.4.x 버전 부터 profile을 설정할 시 deprecated 오류가 발생하지만

이전 설정을 spring.config.use-legacy-processing=true 값을 통해

2.3.x의 동일한 설정을 사용할 수 있다.

  

 


1. 프로젝트 구조 생성



- model 하위에 entity package를 생성하고 User 클래스를 만든다.

- repository package를 생성하고 하위에 UserRepository 인터페이스를 생성한다.

- test 패키지 하위에 repository package아래에 UserRepositoryTest라는 test클래스를 만든다.



2. user 테이블 생성


1
2
3
4
5
6
7
8
9
10
CREATE TABLE `test`.`user` (
  `id` BIGINT(20NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45NULL,
  `email` VARCHAR(45NULL DEFAULT NULL,
  `phone_number` VARCHAR(45NULL DEFAULT NULL,
  `reg_dt` VARCHAR(45NOT NULL,
  `reg_user` VARCHAR(45NOT NULL,
  `mod_dt` VARCHAR(45NULL DEFAULT NULL,
  `mod_user` VARCHAR(45NULL DEFAULT NULL,
  PRIMARY KEY (`id`));
cs


MySQL Tool (workbench, heidiSQL) 을 통해 test 데이터베이스에 user테이블을 생성한다.

생성할때 컬럼은 snake_case를 사용하여 각 단어와 단어 사이에  _언더바로 표시한다.



3. User 모델 생성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.test.model.entity;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
 
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
    private String phoneNumber;
    private LocalDateTime regDt;
    private String regUser;
    private LocalDateTime modDt;
    private String modUser;
}
cs



자바에선 user테이블의 컬럼명에 맞춰 

camel case를 사용하여 모델 User클래스를 만든다.



4. UserRepository 인터페이스 생성


1
2
3
4
5
6
7
8
9
10
package com.example.test.repository;
 
import com.example.test.model.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
 
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
 
}
cs


JpaRepository를 상속받아 생성한다.



5. UserRepositoryTest 클래스 생성


5.1 create


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
package com.example.test.repository;
 
import com.example.test.TestApplicationTests;
import com.example.test.model.entity.User;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
 
import java.time.LocalDateTime;
 
public class UserRepositoryTest extends TestApplicationTests {
 
    @Autowired
    private UserRepository userRepository;
 
    @Test
    public void create(){
        User user = new User();
        user.setName("user1");
        user.setEmail("user1@gmail.com");
        user.setPhoneNumber("010-1111-2222");
        user.setRegDt(LocalDateTime.now());
        user.setRegUser("shlee0882");
 
        User newUser = userRepository.save(user);
        System.out.println(newUser);
    }
}
 
cs


User 모델을 객체로 가져와 값을 set하고 

DI (의존성주입)를 사용해 UserRepository로 만든 객체에 save한다.


Test를 실행하면 데이터가 잘 들어간것을 확인할 수 있다.




5.2 read


1
2
3
4
5
6
7
8
    @Test
    public void read(){
        Optional<User> user = userRepository.findById(1L);
 
        user.ifPresent(selectUser ->{
            System.out.println("user: "+selectUser);
        });
    }
cs


read 메소드를 run하면 

id는 모델에서 long으로 선언 되어 있으므로 L을 붙여 id가 1번인 데이터를 찾는다.

데이터를 찾아 user객체에 담아주고 출력한다.


출력결과


1
user: User(id=1, name=user1, email=user1@gmail.com, phoneNumber=010-1111-2222, regDt=2019-08-09T09:25:28.653, regUser=shlee0882, modDt=2019-08-09T09:27:51.381, modUser=shlee0882)
cs


5.3 update


1
2
3
4
5
6
7
8
9
10
11
12
13
    @Test
    public void update(){
        Optional<User> user = userRepository.findById(1L);
 
        user.ifPresent(selectUser ->{
            selectUser.setUserAccount("modUser1");
            selectUser.setEmail("modUser1@gmail.com");
            selectUser.setModDt(LocalDateTime.now());
            selectUser.setModUser("shlee0882");
            User newUser = userRepository.save(selectUser);
            System.out.println("user: "+newUser);
        });
    }
cs


update 메소드를 run하면

id가 1번인 데이터를 찾아 user객체에 담고

담은 user의 객체의 값을 새로 set해주고 save하면 

기존의 데이터가 있다면 update가 일어난다.


출력결과


1
user: User(id=1, name=modUser1, email=modUser1@gmail.com, phoneNumber=010-1111-2222, regDt=2019-08-09T09:25:28.653, regUser=shlee0882, modDt=2019-08-09T11:20:17.179, modUser=shlee0882)
cs


5.4 delete


1
2
3
4
5
6
7
8
9
10
11
12
13
    @Test
    public void delete(){
        Optional<User> user = userRepository.findById(1L);
 
        Assert.assertTrue(user.isPresent());    // true
        user.ifPresent(selectUser ->{
            userRepository.delete(selectUser);
        });
 
        Optional<User> deleteUser = userRepository.findById(1L);
 
        Assert.assertFalse(deleteUser.isPresent());    // false
    }
cs


delete메소드는 id를 찾아 있다면 true, 

데이터 삭제 후 id가 있는지 확인 후 없다면 false를 통과한다.



5.5 Transactional



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
    @Test
    @Transactional
    public void update(){
        Optional<User> user = userRepository.findById(1L);
 
        user.ifPresent(selectUser ->{
            selectUser.setName("modUser1");
            selectUser.setEmail("modUser1@gmail.com");
            selectUser.setModDt(LocalDateTime.now());
            selectUser.setModUser("shlee0882");
            User newUser = userRepository.save(selectUser);
            System.out.println("user: "+newUser);
        });
    }
 
    @Test
    @Transactional
    public void delete(){
        Optional<User> user = userRepository.findById(1L);
 
        Assert.assertTrue(user.isPresent());    // true
        user.ifPresent(selectUser ->{
            userRepository.delete(selectUser);
        });
 
        Optional<User> deleteUser = userRepository.findById(1L);
 
        Assert.assertFalse(deleteUser.isPresent());    // false
    }
cs


transactional 어노테이션을 넣으면

update와 delete 시 RollBack 되는것을 확인할 수 있다. 


1
Rolled back transaction for test: [DefaultTestContext@576dad90 testClass = UserRepositoryTest, testInstance = com.example.test.repository.UserRepositoryTest@97bd416, testMethod = delete@UserRepositoryTest, testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@5baaa549 testClass = UserRepositoryTest, locations = '{}', classes = '{class com.example.test.TestApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@7097fbf1, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7ed6156f, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@2c5d456b, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@6b04043b], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]
cs





1. MySQL 설치를 완료한다.


최초 설치 시 root계정으로 비밀번호 설정을 하게 될 것이고

MySQL WorkBench로 root계정으로 로그인 되는지 테스트한다.



MySQL WorkBench로 들어와 test라는 스키마를 만든다.




2. IntelliJ 에서 build.gradle파일과 application.properties 파일을 수정한다.




build.gradle파일의 dependencies를 다음과 같이 작성한다.


1
2
3
4
5
6
7
dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('mysql:mysql-connector-java')
    compile('org.projectlombok:lombok')
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
cs


application.properties의 파일은 다음과 같이 작성한다.


1
2
3
4
5
6
7
8
9
10
11
# db url
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul
 
# db name
spring.datasource.username=root
 
# db password
spring.datasource.password=0000
 
# query print
spring.jpa.show-sql=true
cs


아까전에 MySQL WorkBench로 테스트한 root 계정과 비밀번호를 넣어주고 

DB접속정보 포트(3306)와 연결할 스키마명(test)을 넣어준다.


만약 Illegal char <:>  mysql:mysql-con과 같은 오류가 발생하면 build > build project를 시도해본다.


아래와 같이 프로젝트를 실행시켰을 때 오류가 나지 않는다면 성공이다.





우리가 객체를 만들때 변수를 선언하고 기본생성자부터 Getter, Setter까지 메소드를 다 작성해줘야 하지만

Lombok 플러그인은 기본생성자부터, Getter, Setter 생성을 한번에 해결해준다.

개발자는 객체에 사용될 변수만 선언해주고 클래스명 위에 @Data라는 어노테이션만 선언해주면 된다.


1. File -> Settings 로 진입한다.




2. Plugins > Marketplace > lombok 검색한다.



install 버튼을 누르고 설치를 진행하고 intellij를 restart한다.


3. 설치가 완료되면 build.gradle의 dependencies를 수정한다.


 


1
2
3
4
5
6
dependencies {
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
cs




build.gradle 마우스 오른쪽 버튼 > Import Gradle Project 선택



build.gradle 에서 dependencies 부분 Run build, Rebuild 한다.



4. 모델로 돌아와 @Data를 선언해주고 lombok을 import한다.


1
2
3
4
5
6
7
8
9
10
package com.example.test.model;
 
import lombok.Data;
 
@Data
public class SearchVO {
    private String id;
    private String email;
    private int page;
}
cs


Structure를 보면 생성자, 메소드가 정의된 것을 확인 할 수 있다.



5. File -> Settings -> Annotation Processors 로 진입한다.



Enable annotation processing을 체크해준다.

컴파일이 오류없이 잘 되는지 확인한다.


lombok 설치 및 설정이 잘 된 것을 확인했다.


1. POST Method 만들기



controller 패키지에 PostAPIController.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.example.test.controller;
 
import com.example.test.model.SearchVO;
import org.springframework.web.bind.annotation.*;
 
@RestController
@RequestMapping("/api")
public class PostAPIController {
 
    // http통신할때 post는 body에다 data를 집어넣어서 받아오겠다.
    // @RequestBody에 SearchVO에 있는 값을 매칭해서 보내줘라.
    
    @RequestMapping(method = RequestMethod.POST, path = "/postRequest")
    public SearchVO postRequest(@RequestBody SearchVO searchVo){
        return searchVo;
    }
 
    @PostMapping(value = "/postMapping")
    public SearchVO postMapping(@RequestBody SearchVO searchVo){
        return searchVo;
    }
 
}
cs


위와 같이 POST 메소드를 작성한다.


GET 메소드와는 다르게 POST 메소드는 @RequestBody 어노테이션을 사용한다.

프론트에서 http 통신 시 post 메소드는 data를 body안에 담아서 가져온다.

body에 담아올때 메소드에 진입하면 모델이 가지고 있는 data만 json형태로 받아온다.


2. 테스트 해보기


크롬 웹스토어로 가서 REST API 확장 프로그램을 설치한다.

https://chrome.google.com/webstore/category/extensions?hl=ko



설치가 완료되면 POST 메소드로 json형태의 BODY를 작성하여 Send한다.



Response 200 으로 정상적으로 POST 된 것을 확인했다.



모델(SearchVO)에 있는 data만 json형태로 가져온것을 확인 할 수 있다.

모델에 존재하지 않은 param값을 프론트에서 보내면 @RequestBody에서 받아오지 못한다.


POST 메소드 구현 및 테스트를 완료했다.

+ Recent posts