스프링

dawn·2021년 6월 18일
0

스프링

목록 보기
1/2

코드로 배우는 스프링 웹 프로젝트 책을 정리한 내용입니다.

Mybatis

namespace와 id 속성 이름 동일하게 맞추기

1. 파라미터의 수집과 변환

1-1. @RequestParam

  • 전달되는 파라미터와 변수명이 다를 때
    파라미터로 사용된 변수의 이름과 전달되는 파라미터의 이름이 다른 경우에 유용하게 사용된다. 동일할 경우에는 @RequestParam만 쓰면 되나?
public String ex02(@RequestParam("name") String name, @RequestParam("age") int age)
  • 리스트, 배열 처리
    스프링은 파라미터의 타입을 보고 객체를 생성하므로 파리미터의 타입은 List<>와 같이 인터페이스 타입이 아닌 실제적인 클래스 타입으로 지정해야 한다.
@GetMapping("/ex02List")
public String ex02List(@RequestParam("ids") ArrayList<String> ids)

배열의 경우도 동일하게 처리할 수 있다.

@GetMapping("/ex02List")
public String ex02List(@RequestParam("ids") String[] ids)
  • 객체 리스트
    예를 들어 SampleDTO를 여러 개 전달받아서 처리하고 싶다면 다음과 같이 SampleDTO의 리스트를 포함하는 SampleDTOList 클래스를 설계한다.
@Data
public class SampleDTOList {
	private List<SampleDTO> list;
    
    public SampleDTOList() {
    	list = new ArrayList<>();
        }
    }
    
@GetMapping("/ex02Bean")
public String ex02Bean(SampleDTOList list) 

파라미터는 '[인덱스]'와 같은 형식으로 전달해 처리할 수 있다.
프로젝트 경로/sample/ex02Bean?list[0].name=aaa&list[2].name=bbb
톰캣 버전에 따라 위와 같은 문자열에서 '[]'문자를 특수문자로 허용하지 않을 수 있다. JS 사용하는 경우에는 encodeURIComponent()로 해결가능

1-2. @DateTimeFormat

@InitBinder를 이용해서 날짜를 변환할 수도 있지만, 파라미터로 사용되는 인스턴스 변수에 @DateTimeFormat을 적용햐도 변환이 가능하다.

@Data
public String TodoDTO {
	private String title;
    
    @DateTimeFormat(pattern = "yyyy/mm/dd")
    private Date dueDate;
}

프로젝트 경로?title=test&dueDate=2018/01/01와 같이 형식만 맞다면 자동으로 날짜 타입으로 변환이 된다.

1-3. @ModelAttribute

Java Beans 규칙에 맞는 객체는 다시 화면으로 객체를 전달한다. 기본생성자를 가져야 하며 getter/setter를 가진 클래스의 객체들을 의미한다.
반면 기본 자료형의 경우는 파라미터로 선언하더라도 기본적으로 화면까지 전달되지 않기때문에 @ModelAttribute("page") int page 를 써줘야 한다.

1-4. RedirectAttributes

글을 등록했는데 사용자에게 글 등록이 잘 됐는지 확실히 확인시켜주고 싶을때 사용
redirect를 할때 파라미터에 넘겨줄 데이터가 있을때?

@RequiredArgsConstructor
@RequestMapping("/basic/items")
public class BasicItemController {

	private final ItemRepository itemRepository;
    
    @GetMapping("/{ItemId}")
    public String item(@PathVariable long itemId, Model model) {
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "basic/item";
    }

    @PostMapping("/add")
    public String addItem(Item item, RedirectAttributes redirectAttributes) {
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getid());
        redirectAttributes.addAttribute("status", "true");
        return "redirect:/basic/items/{itemId}"; //위의 컨드롤러로 이동
    }
}

itemId는 {itemId}로 들어가고 경로에 안 적힌 "status"는 파라미터로 들어간다. /basic/items/3?status=true
그래서 View단에서 파라미터의 status가 true일 경우 "저장 완료"를 표시하든가 하면 된다.
앗. 원래는 model에 담아서 전달해줘야 하는데 타임리프가 파라미터에서 바로 가져올 수 있도록 지원하기 때문에 저렇게 쓴다는데. jsp도 가능하대 ${param.name}


2. Controller의 리턴 타입

  • String
  • void
  • VO, DTO : 주로 JSON 타입의 데이터를 만들어서 반환하는 용도
  • ResponseEntity : response 할 때 Http 헤더 정보와 내용을 가공하는 용도로 사용
  • HttpHeaders : 응답에 내용 없이 Http 헤더 메시지만 전달하는 용도
    @GetMapping("/ex07")
    public ResponseEntity<String> ex07() {
        
        // {"name" : "홍길동"}
        String msg = "{\"name\" : \"홍길동\"}";

        HttpHeaders header = new HttpHeaders();
        header.add("Content-Type", "application/json;charset=UTF-8");

        return new ResponseEntity<>(msg, header, HttpStatus.OK);
    }

3. 첨부파일 등록

requestHeader 에 accept-Encoding이 뭐지?

Accept-Encoding 요청 HTTP 헤더는, 보통 압축 알고리즘인, 클라이언트가 이해 가능한 컨텐츠 인코딩이 무엇인지를 알려줍니다. 컨텐츠 협상을 사용하여, 서버는 제안된 내용 중 하나를 선택하고 사용하며 Content-Encoding 응답 헤더를 이용해 선택된 것을 클라이언트에게 알려줍니다.

    @PostMapping("/exUploadPost")
    public void exUploadPost(ArrayList<MultipartFile> files) {
        files.forEach(file -> {
            log.info("---------------------------");
            log.info("name: " + file.getOriginalFilename());
            log.info("size: " + file.getSize());

        });
<form action="/sample/exUploadPost" method="post" enctype="multipart/form-data">
        <div>
          <input type="file" name='files'>
        </div>
        <div>
          <input type="file" name='files'>
        </div>
        <div>
          <input type="file" name='files'>
        </div>
        <div>
          <input type="file" name='files'>
        </div>
        <div>
          <input type="file" name='files'>
        </div>
        <div>
          <input type="submit">
        </div>
      </form>

최종 업로드를 하려면 byte[]를 처리해야 한대!! 이게 무슨뜻??


4. @Controller의 Exception 처리

스프링 MVC에서는 이러한 작업을 다음과 같은 방식으로 처리할 수 있다.

  • @ExceptionHandler와 @ControllerAdvice를 이용한 처리
  • @ResponseEntity를 이용하는 예외 메시지 구성

4-1. @ExceptionHandler와 @ControllerAdvice를 이용한 처리
@ControllerAdvice는 AOP를 이용하며, 예외처리를 분리하는데 사용가능
CommonExceptionAdvice는 @ControllerAdvice 어노테이션을 적용하지만 예외 처리를 목적으로 생성하는 클래스이므로 별도의 로직을 처리하지는 않는다.

//CommonExceptionAdvice
@ControllerAdvice
@Log4j2
public class CommonExceptionAdvice {

    @ExceptionHandler(Exception.class)
    public String except(Exception ex, Model model) {

        log.error("Exception ... {}", ex.getMessage());
        model.addAttribute("exception", ex);
        log.error(model);
        return "error_page";
    }

}
  • @ControllerAdvice는 해당 객체가 스프링의 컨트롤러에서 발생하는 예외를 처리하는 존재임을 명시하는데 사용
  • @ExceptionHandler는 해당 메서드가 () 들어가는 예외 타입을 처리하는 것을 의미한다.
    만일 특정한 타입의 예외를 다루고 싶다면 Exception.class 대신에 구체적인 예외의 클래스르 지정해야 한다. JSP 화면에서도 구체적인 메시지를 보고 싶다면 Model을 이용해서 전달하는 것이 좋습니다.

4-2. 404에러 처리
404에러는 가장 흔하게 사용되기에 조금 다르게 처리할 수도 있다.

 @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handle404(NoHandlerFoundException exception) {
        return "custom404";
    }

5. Controller 테스트

@SpringBootTest
@AutoConfigureMockMvc
class SampleControllerTest {

    //애노테이션을 이용한 자동 주입 이외에도, 스프링 프레임워크에서 사용하는 것 처럼 MockMvc를 직접 생성하는 방법도 사용 가능합니다.
    //직접 생성하게 되면 반복되는 코드를 설정할 수 있습니다. (예를들어 모든 예상 응답 status를 OK(200)으로 설정)
    @Autowired
    private MockMvc mockMvc;

    @Test
    void testList() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/board/list"))
                .andExpect(MockMvcResultMatchers.status().isOk());
    }

    @Test
    void postTest() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.post("/board/register")
                .param("title", "테스트 새글 제목")
                .param("content", "테스트 새글 내용")
                .param("writer", "user00")
        ).andExpect(MockMvcResultMatchers.status().isOk());
    }

6. 한글 문제와 UTF-8 필터 처리

새로운 게시물을 등록했을 때 한글 입력에 문제가 있다는 것을 발견했다면
1) 브라우저에서 한글이 깨져서 전송되는지 확인(F12)
2) 문제가 없다면 스프링 MVC 쪽에서 한글을 처리하는 필터를 등록해야 한다.(LOG를 통해 확인)

chain.do인가 그거 전후로 필터 전에 실행되어야 할것, 응답할 때 필터를 거치면서 실행되어야 할 것을 쓰면 된대.


7. REST 방식의 데이터 처리를 위한 여러 종류의 어노테이션과 기능들

어노테이션기능
@RestControllerController가 REST 방식을 처리하기 위한 것임을 명시
@ResponseBodt일반적인 JSP와 같은 뷰로 전달되는 게 아니라 데이터 자체를 전달하기 위한 용도
@PathVariableURL 경로에 있는 값을 파라미터로 추출하려고 할 때 사용
@CrossOriginAjax의 코로스 도메인 문제를 해결해주는 어노테이션
@RequestBodyJSON 테이터를 원하는 타입으로 바인딩 처리

7-1) @RestController
메서트의 리턴 타입으로 사용자가 정의한 클래스 타입을 사용할 수 있고, 이를 JSON이나 XML로 자동으로 처리한다.

  • 단순 문자열 반환
    @GetMapping(value = "/getText", produces = "text/plain; charset=UTF-8")
    public String getText() {
        log.info("MIME TYPE: " + MediaType.TEXT_PLAIN_VALUE);
        return "안녕하세요";
    }

produces은 해당 메소드가 응답하는 MIME 타입. 메소드에 produces를 쓰면 클라이언트의 accept에 맞춰서 보낼 수 있는건가보다.(맞는지 알아봐야 함!) produces 속성은 반드시 지정해야 하는 것은 아니므로 생략하는 것도 가능하다.

  • 객체의 반환
    객체를 반환하는 작업은 JSON이나 XML을 이용한다.

  • SampleVO

  • List

  • Map<String, SampleVO>

  • ResponseEntity 타입
    ResponseEntity는 테이터와 함께 HTTP 헤더의 상태 메시지 등을 같이 전달하는 용도로 사용한다. HTTP의 상태 코드와 에러 메시지 등을 함계 데이터를 전달할 수 있기 때문에 받는 입장에서는 확실하게 결과를 알 수 있다.

    @GetMapping(value = "/check", params = {"height", "weight" })
    public ResponseEntity<SampleVO> check(Double height, Double weight) {

        SampleVO vo = new SampleVO(0, "" + height, "" + weight);

        ResponseEntity<SampleVO> result = null;
        
        if (height < 150) {
            result = ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(vo);
        } else {
            result = ResponseEntity.status(HttpStatus.OK).body(vo);
        }
        return result;
    }

8. @RestController에서 파라미터

  • @PathVariable: 일반 컨트롤러에서도 사용이 가능하지만 REST 방식에서 자주 사용된다. URL 경로의 일부를 파라미터로 사용할 때 이용
  • @RequestBody: JSON 데이터를 원하는 타이브이 객체로 변환해야 하는 경우에 주로 사용

8-1) @PathVariable
REST 방식에서는 URL 내에 최대한 많은 정보를 담으려고 노력한다. @PathVariable 어노테이션을 이용해서 URL 상에 경로의 일부를 파라미터로 사용할 수 있다.
값을 얻을 떄에는 @PathVariable("pid") Integer pid와 같이 기본 자료형이 아닌 Wapper클래스를 사용해야 한다.

**8-2) @RequestBody
클라이언트에서 post방식으로 JSON데이터를 보낼때 객체로 변환하기 위한 어노테이션이다.


9. @Param

MyBatis는 두 개 이상이 데이터를 파라미터로 전달하기 위해서는 1) 별도의 객체로 구성하거나, 2) Map을 이용하는 방식, 3) @Param을 이용해서 이름을 사용하는 방식이 있다.

여러 방식 중에 가장 간단하게 사용할 수 있는 방식이 @Param을 이용하는 방식이다.
@Param의 속성값은 MyBatis에서 SQL을 이용할 때 '#{}'의 이름으로 사용이 가능하다.

@Mapper
public interface ReplyMapper {
  public List<ReplyVO> getListWithPaging(
    @Param("cri") Criteria cri,
    @Param("bno") Long bno);
}

XML로 처리할 때에는 지정된 'cri'와 'bno'를 모두 사용할 수 있다.

XML에서 '#{bno}'가 @Param("bno")와 매칭되어서 사용된다.

Mapper메소드를 호출할 때 파라미터로 전해줘서 사용하면 되겠다.


10. ReplyController의 설계

작업URLHTTP 전송방식
등록/replies/newPOST
조회/replies/:rnoGET
삭제/replies:/rnoDELETE
수정/replies/:rnoPUT or PATCH
페이지/replies/pages/:bno/:pageGET

REST 방식으로 동작하는 URL을 설계할 때는 PK를 기준으로 작성하는 것이 좋다. PK만으로 조회, 수정, 삭제가 가능하기 때문이다. 다만 댓글의 목록은 PK를 사용할 수 없기 때문에 파라미터로 필요한 게시물의 번호(bno)와 페이지 번호(page) 정보들을 URL에서 표현하는 방식을 사용한다.

  • REST 방식으로 처리할 때 주의해야 하는 점
    브라우저나 외부에서 서버를 호출할 때 데이터의 포맷과 서버에서 보내주는 데이터의 타입을 명확히 설계해야 하는 것이다. 예를 들어 댓글 등록의 경우 브라우저에서는 JSON 타입으로 된 댓글 데이터를 전송하고, 서버에서는 댓글의 처리 결과가 정상적으로 되었는지 문자열로 결과를 알려 주도록 한다.
   @PostMapping(value = "/new",
    consumes = "application/json",
    produces = {MediaType.TEXT_PLAIN_VALUE})
    public ResponseEntity<String> create(@RequestBody ReplyVO vo) {
        return insertCount == 1
                ? new ResponseEntity<>("success", HttpStatus.OK)
                : new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

참고

  • SQL문에서 UPDATE할 때 regdate는 수정하지 말것. 그냥 아예 넣지말기
  • BoardService 메서드를 설계할 때 메서드 이름은 현실적인 로직의 이름을 붙이는 것이 관례ㅣ다. (register, get, modify, remove, getList)
  • 객체가 있는지 확인할때는 assertNotNull 사용
  • register할 떄 생성된 게시물의 번호를 가져오려면 board.getNum을 사용하면 된다.
  • 삭제, 수정을 더 엄격하게 처리하게 위해서 리턴타입을 Boolean타입으로 처리할 수 있다.
//BoardServiceImpl의 일부
    @Override
    public boolean modify(BoardVO board) {
        return mapper.update(board) == 1;
    }
  • Junit5에서는 @RunWith를 쓰지 않는다.

    정리하자면, 최근 스프링 부트는 JUnit 5를 사용하기 때문에 더이상 JUnit 4에서 제공하던 @RunWith를 쓸 필요가 없고 (쓰고 싶으면 쓸 수는 있지만), @ExtendWith를 사용해야 하지만, 이미 스프링 부트가 제공하는 모든 테스트용 애노테이션에 메타 애노테이션으로 적용되어 있기 때문에 @ExtendWith(SpringExtension.class)를 생략할 수 있다.
    https://www.whiteship.me/springboot-no-more-runwith/

  • 로깅

    로그 라이브러리는 Logback, Log4J, Log4J2 등등 수 많은 라이브러리가 있는데, 그것을 통합해서 인터페이스로 제공하는 것이 바로 SLF4J 라이브러리다.
    SLF4J는 인터페이스이고, 그 구현체로 Logback 같은 로그 라이브러리를 선택하면 된다.
    //로그를 사용하지 않아도 a+b 계산 로직이 먼저 실행됨, 이런 방식으로 사용하면 X
    log.debug("String concat log=" + name);

    LEVEL: TRACE > DEBUG > INFO > WARN > ERROR
    개발 서버는 debug 출력
    운영 서버는 info 출력

  • 순서
    테이블 설계 -> BoardVO클래스 작성 -> Mapper 인터페이스(@Select("select * from tbl_board where bno > 0") -> BoardMapperTest 작성 ->BoardMapper.xml 작성(폴더는 하나씩 만들것) -> select 기능 먼저 mapper까지 되는지 확인하고 getList, insert, insertSelectKey, read 모두 작성 -> BoardService 선언(register, get, modify, remove, getList) -> BoardServiceImpl 작성 -> BoardServiceTest 작성 -> 구현,테스트, 구현, 테스트 반복할 것 -> Controller 작성 -> 화면 처리(includes로 공통 부분 빼기)
  • 이클립스 상에서 실행될 때는 "/" 경로로 설정되지 않고 "/contrller" 경로를 가지게 되므로 'Web Settings'를 이용해서 수정할 것

  • CSS가 작동하지 않는다면 경로가 어디로 설정되어 있는지 확인, resources아래 폴더에 css가 있는지 확인

  • includes를 사용할 경우 footer.jsp에서 jquery를 사용한다면 header에서 google을 통해 가져오도록 설정해야 한다???

  • html의 data 속성
    "data-"를 통해 자바스크립트에서 처리할 수 있도록 할 수 있다.
    <button data-oper='modify'>Modify</button>라고 쓰면
    자바스크립트에서 다음과 같이 쓸 수 있다.

$("button[data-oper='modify']").on("click", function(e) {

});

또는 "data-oper"값을 가져올 수 있다.
var operation = $(this).data("oper");


알아볼것

  • redirectAttribute였나? 어디에 쓰는건지 알아보기
  • AllArgsConstructor과 RequiredArgsConstructor 차이. 뭐가 더 쓰면 좋은지.
  • '뒤로가기'나 '앞으로 가기'를 해결하기 위한 window의 history를 쓴다는데 알아보기

SelectKey

SelectKey는 주로 PK값을 미리(before) SQL을 통해서 처리해 두고 특정한 이름으로 결과를 보관하는 방식이다.

SELECT MAX(num) + 1 FROM board; 하면 구해진다!!!!!

selectKey

profile
안녕하세요

0개의 댓글