[Spring] ๐Ÿ”ฅ ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ๊ฐœ๋… ์ •๋ฆฌ

ํ—Œ์น˜ยท2022๋…„ 6์›” 24์ผ
30

Spring

๋ชฉ๋ก ๋ณด๊ธฐ
2/13

๋…ธ์…˜ ๋งํฌ
๊นƒํ—ˆ๋ธŒ ํ•™์Šตํ…Œ์ŠคํŠธ

1. ์–ด๋…ธํ…Œ์ด์…˜(Annotation) ์ด๋ž€?

1-1 ์„ค๋ช…

์ฝ”๋“œ ์‚ฌ์ด์— ์ฃผ์„์ฒ˜๋Ÿผ ์“ฐ์ด๋ฉฐ ํŠน๋ณ„ํ•œ ์˜๋ฏธ, ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•˜๋Š” ๊ธฐ์ˆ ์ด๋‹ค.

  • ์ฆ‰, ํ”„๋กœ๊ทธ๋žจ์—๊ฒŒ ์ถ”๊ฐ€์ ์ธ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•ด์ฃผ๋Š” meta data๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
  • meta dataย : ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ

1-2 ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉ ์ˆœ์„œ

  1. ์–ด๋…ธํ…Œ์ด์…˜ ์ •์˜

    @Target({ElementType.[์ ์šฉ๋Œ€์ƒ-ํŒจํ‚ค์ง€, ์ƒ์„ฑ์ž, ๋ฉ”์„œ๋“œ, ํ•„๋“œ...]})
    // ElementType.TYPE ์€ ํด๋ž˜์Šค,์ธํ„ฐํŽ˜์ด์Šค,์—ด๊ฑฐํƒ€์ž…์— ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ
    @Retention(RetentionPolicy.[์ •๋ณด ์œ ์ง€๋˜๋Š” ๋Œ€์ƒ-์†Œ์Šค, ๋Ÿฐํƒ€์ž„(๋ณดํ†ต ๋Ÿฐํƒ€์ž„)...])
    public @interface [์–ด๋…ธํ…Œ์ด์…˜๋ช…]{
    	public ํƒ€์ž… elementName() [default ๊ฐ’]
        ...
    }
  2. ํด๋ž˜์Šค์— ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ฐฐ์น˜

  3. ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ์ค‘์— Reflection์„ ์ด์šฉํ•˜์—ฌ ์ถ”๊ฐ€์ •๋ณด๋ฅผ ํš๋“ํ•˜์—ฌ ๊ธฐ๋Šฅ ์‹ค์‹œ

2. ์Šคํ”„๋ง ๊ฐœ๋…

2-1 ์Šคํ”„๋ง MVC

1) @Controller , @RestController

@Controller

  • ์ฝ”๋“œ
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Controller {
    	@AliasFor(annotation = Component.class)
    	String value() default "";
    }
  • @Component ํด๋ž˜์Šค์˜ ํŠน์ˆ˜ํ™”
  • ํด๋ž˜์Šค ๊ฒฝ๋กœ ์Šค์บ๋‹์„ ํ†ตํ•ด, ๊ตฌํ˜„ ํด๋ž˜์Šค(@Component)๋ฅผ ์ž๋™ ๊ฐ์ง€(auto-detection)
  • ๋ณดํ†ต @RequestMapping ์„ ํ•จ๊ป˜ ์”€

@RestController

  • ์ฝ”๋“œ
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody // ์ด๊ฑฐ ํ•˜๋‚˜ ๋นผ๊ณค Controller์™€ ๋™์ผ
    public @interface RestController {
    	@AliasFor(annotation = Controller.class)
    	String value() default "";
    }
  • Spring 4.0์— ๋„์ž…
  • @Controller + @ResponseBody ๊ฒฐํ•ฉ
  • ๋ฆฌํ€˜์ŠคํŠธ ํ•ธ๋“ค๋ง ๋ฉ”์†Œ๋“œ์—์„œ @ResponseBody annotation์„ ์“ธ ํ•„์š”๊ฐ€ ์—†์–ด์ง!

2) @RequestMapping

  • ์ฝ”๋“œ
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Mapping
    public @interface RequestMapping {
    	String name() default "";
    	@AliasFor("path")//๋ณ„์นญ ์„ ์–ธ
    	String[] value() default {}
    	@AliasFor("value")
    	String[] path() default {};
    	RequestMethod[] method() default {};
    	String[] params() default {};
    	String[] headers() default {};
    	String[] consumes() default {};
    	String[] produces() default {};
    }
    
    //...
    
    @RestController
    @RequestMapping(value = "/api", headers = "Accept=application/json", method = GET)
    // == @GetMapping(value = "/api", headers = "Accept=application/json")
    public class PathController {...}
  • @PostMapping(value = "/users", consumes = APPLICATION_JSON_VALUE)
  • ๋งคํ•‘ ์ข…๋ฅ˜
    • @GetMapping : ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์š”์ฒญํ•  ๋•Œ ์‚ฌ์šฉ
    • @PostMapping : ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์ˆ˜์ • ๋˜๋Š” ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉ
    • ๊ธฐํƒ€ ๋“ฑ๋“ฑโ€ฆ(HTTP ๋ฉ”์†Œ๋“œ๋ณ„๋กœ ์กด์žฌ)
  • ์†์„ฑ
    • value : ํŒจ์Šค ์ฃผ์†Œ
    • path : ํŒจ์Šค ์ฃผ์†Œ (@GetMapping(path = "/api") == @GetMapping("/api"))
    • params, headers :ย ๋งคํ•‘ํ•  ํŒŒ๋ผ๋ฏธํ„ฐ/ ํ—ค๋” ์†์„ฑ ์ง€์ •
    • consumes: ์š”์ฒญ ๋ฐ์ดํ„ฐ ํ˜•์‹! HTTP ์š”์ฒญ์˜ Content-Type ํ—ค๋”์™€ ๋Œ€์‘
    • produces: ์‘๋‹ต ๋ฐ์ดํ„ฐ ํ˜•์‹! HTTP ์š”์ฒญ์˜ Accept ํ—ค๋”์™€ ๋Œ€์‘

3) @ModelAttribute , @RequestBody ,@RequestParam , @PathVariable

  • @ModelAttribute
    • ์ฝ”๋“œ
      @Target({ElementType.PARAMETER, ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface ModelAttribute {
      	@AliasFor("name")
      	String value() default "";
      	@AliasFor("value")
      	String name() default "";
      	boolean binding() default true;
      }
      //...
      
      //์š”์ฒญ : http://localhost:8080/api/1
      @GetMapping
      public ResponseEntity<PathResponse> showPaths(@ModelAttribute PathsRequest pathsRequest) {
          return ResponseEntity
      				.ok(pathService.showPaths(pathsRequest));
      }
    • ๋ทฐ์—์„œ ์ „๋‹ฌ๋œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ JAVA ๊ฐ์ฒด๋กœ ๋ฐ”์ธ๋”ฉ
      • ์ฟผ๋ฆฌ(์ŠคํŠธ๋ง)/ํผ(ํŒŒ๋ผ๋ฏธํ„ฐ)โ†’ DTO
    • ์š”์ฒญ ๋ฐ์ดํ„ฐ์™€ Controller์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ 1:n(๊ฐ์ฒด) ๋งคํ•‘
    • ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„์„ ๋ช…์‹œ ์•ˆํ•˜๋ฉด โ†’ ํ•ด๋‹น ํ•„๋“œ๋ฉด์˜ Setter ํ˜ธ์ถœํ•ด ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ
  • @RequestBody
    • ์ฝ”๋“œ
      @Target(ElementType.PARAMETER)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface RequestBody {
      	boolean required() default true;
      }
      
      //...
      
      // ์š”์ฒญ : ๋ฐ”๋””๊ฐ’
      @PostMapping
      public ResponseEntity<LineResponse> createLine(@RequestBody LineRequest lineRequest) {
          return ResponseEntity
                  .created(URI.create("/lines"))
                  .body(lineService.create(lineRequest));
      }
    • JSON ํ˜•ํƒœ์˜ body๋ฅผ JAVA ๊ฐ์ฒด๋กœ ๋งคํ•‘
      • json ๋ฐ”๋””โ†’ DTO/์ด์™ธ ๊ฐ์ฒด(Stringโ€ฆ)
      • GetMapping ๋ถˆ๊ฐ€(๋ฐ”๋”” ์—†์Œ)
    • ๊ธฐ๋ณธ ์ƒ์„ฑ์ž ํ•„์ˆ˜ : ์ง๋ ฌํ™”
    • getter๋‚˜ setter ์ค‘ 1๊ฐ€์ง€๋Š” ์ •์˜ : ๋ฐ์ดํ„ฐ๋ฐ”์ธ๋”ฉ ์šฉ ํ•„๋“œ๋ช…์„ ์•Œ์•„๋‚ด๊ธฐ ์œ„ํ•ด
      • GET ๋งคํ•‘: DTO์— setter๊ฐ€ ํ•„์š”
      • POST ๋งคํ•‘ : DTO์— ์ƒ์„ฑ์ž ํ•„์š” (setter๋Š” ํ•„์š” X)
  • @RequestParam
    • ์ฝ”๋“œ
      @Target(ElementType.PARAMETER)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface RequestParam {
      	@AliasFor("name")
      	String value() default "";
      	@AliasFor("value")
      	String name() default "";
      	boolean required() default true;
      	String defaultValue() default ValueConstants.DEFAULT_NONE;
      }
      //...
      
      // ์š”์ฒญ : http://localhost:8080/api?stationId=1
      @DeleteMapping("/sections")
      public void deleteSection(@RequestParam long stationId) {
          lineService.deleteSection(lineId, stationId);
      }
    • HTTP ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์˜ด
    • ์š”์ฒญ ๋ฐ์ดํ„ฐ์™€ Controller์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ 1:1 ๋งคํ•‘
    • required : ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๋ณ€์ˆ˜์ธ์ง€ ์—ฌ๋ถ€
    • defaultValue : ํ•ด๋‹น ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์„ ์‹œ ๋””ํดํŠธ ๊ฐ’
  • @PathVariable
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface PathVariable 
    	@AliasFor("name")
    	String value() default "";
    	@AliasFor("value")
    	String name() default "";
    	boolean required() default true;
    
    //...
    
    @DeleteMapping("/{id}")
    public void deleteLineById(@PathVariable long id) {
        lineService.removeLineById(id);
    }
    • ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ ์•ž์— ์‚ฌ์šฉ
    • ํ•ด๋‹น URL์—์„œ ํŠน์ •๊ฐ’์„ ๋ณ€์ˆ˜๋กœ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
    • required : ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•œ ๋ณ€์ˆ˜์ธ์ง€ ์—ฌ๋ถ€

4) @ResponseBody, ResponseEntity

  • @ResponseBody
    • ์ฝ”๋“œ
      @Target({ElementType.TYPE, ElementType.METHOD})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface ResponseBody {}
      
      //...
      
      @ResponseBody
      @ResponseStatus(HttpStatus.OK)
      public MoveResponseDto move(@RequestBody MoveDto moveDto) {
        return moveResponseDto;
      }
    • HTTP ๊ทœ๊ฒฉ์— ๋งž๋Š” ์‘๋‹ต์„ ๋งŒ๋“ค์–ด์ฃผ๊ธฐ ์œ„ํ•œย Annotation
    • HTTPย ์š”์ฒญ์„ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜, ๊ฐ์ฒด๋ฅผ ์‘๋‹ต์œผ๋กœ ๋ณ€ํ™˜
    • @RestController๊ฐ€ ๋ถ™์œผ๋ฉดย `@ResponseBody` ์ƒ๋žต
  • ResponseEntity
    • ์ฝ”๋“œ
      public class ResponseEntity extends HttpEntity {
        private final Object status;
      }
      
      //...
      
      @GetMapping
      public ResponseEntity<PathResponse> showPaths(...) {
          return ResponseEntity.ok()
              .headers(...)
              .body(...);
      }
    • ์‘๋‹ต์œผ๋กœ ๋ณ€ํ™˜๋  ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๋‹ด์€ ์š”์†Œ๋“ค์„ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์„œ ๋ฐ˜ํ™˜
    • HTTP ์‘๋‹ต์— ํ•„์š”ํ•œ ์š”์†Œ๋“ค ์ค‘ ๋Œ€ํ‘œ์ ์ธย Status,ย Header,ย Body๋ฅผ ์ง€์ •
    • Builder ํŒจํ„ด์„ ํ™œ์šฉ

5) @ResponseStatus

@ResponseStatus(HttpStatus.BAD_REQUEST, reason = "Some parameters are invalid")
  • ์›ํ•˜๋Š” HttpStatus์™€ reason ์„ค์ •
  • 404 NOT FOUND, 200 OK โ€ฆ

6) @ExceptionHandler , @ControllerAdvice

  • ์ฝ”๋“œ
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ExceptionHandler {
    	Class<? extends Throwable>[] value() default {};
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface ControllerAdvice {
    	@AliasFor("basePackages")
    	String[] value() default {};
    }
    
    //...
    
    @ControllerAdvice
    public class SubwayAdvice 
        @ExceptionHandler(BadRequestException.class)
        public ResponseEntity<ErrorResponse> badRequestExceptionHandler(BadRequestException e) {
             return ResponseEntity.badRequest()
    							.body(e.getMessage());
        }
    }
  • @ExceptionHandler
    • ๋ฉ”์†Œ๋“œ ๋‹จ์— ๋ถ™์ž„
    • ํŠน์ • ์˜ˆ์™ธ(IllegalArgumentException.class ๋“ฑ)๋ฅผ ์บ์น˜ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • @ControllerAdvice
    • ํด๋ž˜์Šค ๋‹จ์— ๋ถ™์ž„
    • ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ๋„์ค‘ ์˜ˆ์™ธ๋“ค์„ Adviceํด๋ž˜์Šค์—์„œ ์บ์น˜
    • ์–ด๋–ค ์˜ˆ์™ธ๋ฅผ ์žก์•„๋‚ผ ๊ฒƒ์ธ์ง€๋Š” Adviceํด๋ž˜์Šค ์† ๋ฉ”์†Œ๋“œ ์ƒ๋‹จ์— @ExceptionHandler(์˜ˆ์™ธํด๋ž˜์Šค๋ช….class)๋ฅผ ๋ถ™์—ฌ์„œ ๊ธฐ์ˆ 
  • ์‹คํ–‰์ˆœ์„œ
    1. ์˜ˆ์™ธ ํ„ฐ์ง„ ํด๋ž˜์Šค์˜ @ExceptionHandler
    2. @ControllerAdvice์˜ @ExceptionHandler
    • ์œ„์˜ ๋ฉ”์†Œ๋“œ๋ถ€ํ„ฐ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰

2-2 ์Šคํ”„๋ง JDBC

1) @Repository

  • ์ฝ”๋“œ
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component
    public @interface Repository {
    	@AliasFor(annotation = Component.class)
    	String value() default "";
    }
  • @Component ํด๋ž˜์Šค์˜ ํŠน์ˆ˜ํ™”
  • ์™ธ๋ถ€I/O ์ฒ˜๋ฆฌ
  • DB ์ ‘๊ทผ์‹œ ์‚ฌ์šฉ

2) NamedParameterJdbcTemplate

  • ๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ JdbcTemplate โ†’ ๊ฐ€๋…์„ฑ ๋‚ฎ์Œ
  • NamedParameterJdbcTemplate : ? ๋Œ€์‹  :๋ณ€์ˆ˜๋ช… ์„ ์ด์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌ โ†’ ์ˆœ์„œ ์ƒ๊ด€์—†์–ด์ง
  • ๋ฉ”์†Œ๋“œ
    1. queryForObject()
      • ์ฝ”๋“œ
        public Long findIdByUserName(final String username) {
        		final String sql = "SELECT id FROM customer WHERE username = :username";
        		return namedParameterJdbcTemplate.queryForObject(sql, Map.of("username", username), Long.class);
        }
      1. queryForObject(SQL์ฟผ๋ฆฌ, ๋งคํ•‘๋  ํŒŒ๋ผ๋ฏธํ„ฐ, ๋ฆฌํ„ด๋  ํด๋ž˜์Šค(๋กœ์šฐ๋งคํผ))
      2. ๋งคํ•‘๋  ํŒŒ๋ผ๋ฏธํ„ฐ : Map<String, Object> ํ˜•ํƒœ
    2. query()
      • ์ฝ”๋“œ
        public List<Long> findIdsByCountry(final String country) {
        		final String sql = "SELECT id FROM customer WHERE country = :country";
        		return namedParameterJdbcTemplate.query(sql, Map.of("country", country), Long.class);
        }
      1. query(SQL์ฟผ๋ฆฌ, ๋งคํ•‘๋  ํŒŒ๋ผ๋ฏธํ„ฐ, ๋ฆฌํ„ด๋  ํด๋ž˜์Šค(๋กœ์šฐ๋งคํผ))
      2. ๋งคํ•‘๋  ํŒŒ๋ผ๋ฏธํ„ฐ : Map<String, Object> ํ˜•ํƒœ
    3. update()
      • ์ฝ”๋“œ
        public void updatePassword(final Long id, final String oldPassword, final String newPassword) {
                String query = "update customer set password = :newPassword"
                        + " where id = :id and password = :oldPassword";
                namedParameterJdbcTemplate.update(query, 
        						Map.of("id", id, "newPassword", newPassword, "oldPassword", oldPassword)
        				);
        }
      1. update(SQL์ฟผ๋ฆฌ, ๋งคํ•‘๋  ํŒŒ๋ผ๋ฏธํ„ฐ)
    4. batchUpdate()
      • ์ฝ”๋“œ
        public int[] deleteByIds(final List<Long> ids) {
            final String query = "DELETE FROM cart_item WHERE id = :id";
            List<Map<String, Long>> updateMaps = ids.stream()
                    .map(id -> Map.of("id", id))
                    .collect(Collectors.toList());
            SqlParameterSource[] batchArgs = SqlParameterSourceUtils.createBatch(updateMaps);
            return jdbcTemplate.batchUpdate(query, batchArgs);
        }
      1. batchUpdate(SQL์ฟผ๋ฆฌ, ๋งคํ•‘๋  ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฆฌ์ŠคํŠธ)
      2. ๋งคํ•‘๋  ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฆฌ์ŠคํŠธ : SqlParameterSource[] ํ˜•ํƒœ
        1. SqlParameterSourceUtils.createBatch() ๋ฅผ ํ†ตํ•ด ์ƒ์„ฑ
  • GeneratedKeyHolder
    • ์ฝ”๋“œ
      public Long create(final Long customerId, final Long productId) {
          final String query = "INSERT INTO cart_item(customer_id, product_id, quantity) VALUES (:customer_id, :product_id, :quantity)";
      
          Map<String, Object> params = new HashMap<>();
          params.put("customer_id", customerId);
          params.put("product_id", productId);
          params.put("quantity", 1);
      
          SqlParameterSource source = new MapSqlParameterSource(params);
      
          final KeyHolder keyHolder = new GeneratedKeyHolder();
          jdbcTemplate.update(query, source, keyHolder);
          return keyHolder.getKey().longValue();
      }
    • ์ž๋™์œผ๋กœ auto increment์— ์˜ํ•ด ์ƒ์„ฑ๋œ id๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•จ
  • ROW_MAPPER
    • ์ฝ”๋“œ
      RowMapper<User> ROW_MAPPER = (resultSet, rowNum) -> new User(
                           resultSet.getString("name"),
                           resultSet.getString("description")
                         ));
    • ํ–‰ ๋ณ„๋กœย JdbcTemplate ๋งคํ•‘ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค
    • RowMapper<GuestBook> rowMapper = BeanPropertyMapper.newInstance(GuestBook.class);
      ์˜ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

2) @Transactional

  • ์ฝ”๋“œ
    //๊ฒŒ์‹œํŒ์˜ ๊ฒŒ์‹œ๊ธ€์„ ์‚ญ์ œํ•˜๋Š” ๋ฉ”์„œ๋“œ 
    @Transactional
    public void removeBoard(Long id) throws Exception { 
    		replyDAO.removeAll(id); //์‚ญ์ œํ•  ๊ฒŒ์‹œ๊ธ€์˜ ๋‹ต๊ธ€ ์‚ญ์ œ 
    		boardDAO.deleteBoard(id); //๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ 
    }
  • DB ํŠธ๋žœ์žญ์…˜์„ ์„ค์ •ํ•˜๊ณ  ์‹ถ์€ ํด๋ž˜์Šค๋‚˜ ๋ฉ”์†Œ๋“œ์— ์ ์šฉ
  • ํด๋ž˜์Šค, ๋ฉ”์„œ๋“œ ๋ชจ๋‘์—ย ์ ์šฉ??
    • ๋ฉ”์„œ๋“œ ๋ ˆ๋ฒจ์˜ย @Transactional์„ ์–ธ์ด ์šฐ์„  ์ ์šฉ!
  • method ๋‚ด๋ถ€์—์„œ ์ผ์–ด๋‚˜๋Š” DB ๋กœ์ง์ดย ์ „๋ถ€ ์„ฑ๊ณตํ•˜๊ฑฐ๋‚˜, DB ์ ‘๊ทผ์ค‘ ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด ์ „์ฒด ๋กค๋ฐฑ
  • ์ผ๋ฐ˜์ ์œผ๋กœ DB ๋ฐ์ดํ„ฐ๋ฅผ ๋“ฑ๋ก/์ˆ˜์ •/์‚ญ์ œ ํ•˜๋Š” Service ๋ฉ”์†Œ๋“œ๋Š” @Transactional๋ฅผ ํ•„์ˆ˜์ ์œผ๋กœ ๊ฐ€์ ธ๊ฐ„๋‹ค.
  • @Transaction(readOnly=true, rollbackFor=Exception.class)
    • readOnly : ์ฝ๊ธฐ ์ „์šฉ
    • rollbackFor : ํ•ด๋‹น Exception์ด ์ƒ๊ธฐ๋ฉด ๋กค๋ฐฑ
  • ์ฃผ์˜์‚ฌํ•ญ
    • ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜ ๋ถ™์—ฌ๋„ ๋กค๋ฐฑ ์•ˆ๋จ!
      • WebEnvironment์˜ย RANDOM_PORT,ย DEFINED_PORT
      • DB์˜ id (Auto Incrementย ์˜ต์…˜ ์‚ฌ์šฉ ์‹œ)
    • ์ด๋ฏธ @Transaction์ ์šฉ๋จ!
      • @JdbcTest

2-3 ์Šคํ”„๋ง CORE

๊ฐœ๋…) DI/IoC

[10๋ถ„ ํ…Œ์ฝ”ํ†ก] ์˜ค์ฐŒ, ์•ผํ˜ธ์˜ DI์™€ IoC

  • ์ฝ”๋“œ
    @Service
    public class LineService {
        private final LineRepository repository;
    		//์ž๋™์œผ๋กœ DI(์ƒ์„ฑ์ž ์ฃผ์ž…)๋จ!!
        public LineService(LineRepository repository) {
            this.repository = repository;
        }
    }
  • IoC ์›์น™์ด๋ž€?
    • ์ œ์–ด์˜ ์—ญ์ „
    • ํ• ๋ฆฌ์šฐ๋“œ ์›์น™ : ์ฃผ๋„๊ถŒ์„ ๋นผ์•—๊ธฐ๊ณ , ํ˜ธ์ถœ ๋‹นํ•˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋ชจ์Šตโ€ฆ
    • ๊ธฐ์กด์—” ๋‚ด๋ถ€์—์„œ ์˜์กด์„ ๊ด€๋ฆฌํ–ˆ์ง€๋งŒ, ์ด์ œ ์™ธ๋ถ€์—์„œ ์˜์กด์„ ์ฃผ์ž…๋ฐ›๋„๋ก ๋ณ€๊ฒฝ
    • DIP (Dependency Inversion Principle, ์˜์กด์„ฑ ์—ญ์ „ ์›์น™) ์›์น™๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉ๋˜๊ธธ ๊ถŒ์žฅ๋จ
  • DI๋ž€?(Dependency Injection)
    • ์˜์กด์„ฑ ์ฃผ์ž… : ๊ฐ์ฒด ๊ฐ„ ์˜์กด์„ฑ์„ ์™ธ๋ถ€์—์„œ ์ฃผ์ž… ํ›„ ๊ด€๋ฆฌ
    • ์žฅ์ 
      • ๊น”๋”ํ•œ ์ฝ”๋“œ
      • ํด๋ž˜์Šค๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์›Œ์ง
      • ๊ฐ์ฒด ๊ฐ„ ์˜์กด์„ฑ์„ ์ค„์—ฌ์คŒ
      • ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ ์ฆ๊ฐ€
  • DI์˜ ์„ธ ๊ฐ€์ง€ ๋ฐฉ์‹
    • ์ƒ์„ฑ์ž ์ฃผ์ž…
      • ์ƒ์„ฑ์ž๋กœ ์˜์กด์„ฑ ์ฃผ์ž…
      • ํ•„๋“œ ์ฃผ์ž…์— ๋น„ํ•ด, ๊ฐ์ฒด์ง€ํ–ฅ ์›์น™์ค‘ ๋‹จ์ผ์ฑ…์ž„ ์›์น™์„ ์œ„๋ฐ˜ํ•  ์†Œ์ง€๊ฐ€ ๋œํ•จ
      • DI Container์— ๋œ ์˜์กด
      • ํ•„๋“œ๋ฅผ final๋กœ ์„ ์–ธโ†’ ๋ถˆ๋ณ€ ๊ฐ์ฒด
      • ์ˆœํ™˜์˜์กด ๋ฐฉ์ง€
    • setter ์ฃผ์ž…
    • ํ•„๋“œ ์ฃผ์ž…(@Autowired)

1) @Bean(@Configufation), @Component

  • @Bean
    • ์ฝ”๋“œ
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Component //@Configuration์€ @Component์„ ํฌํ•จํ•œ๋‹ค!!!
      public @interface Configuration {...}
      
      @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface Bean {...}
    • ๋ฉ”์†Œ๋“œ ๋ฆฌํ„ด ๊ฐ์ฒด์„ Bean์œผ๋กœ ๋“ฑ๋ก
    • @Configuration ์„ ํด๋ž˜์Šค์— ๋ถ™์ธ ํ›„ ์‚ฌ์šฉ!
    • ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์ œ์–ด๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•œ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ฑ์„ Bean์œผ๋กœ ๋งŒ๋“ค๋ คํ•  ๋•Œ ์‚ฌ์šฉ
      • ์˜ˆ) ObjectMapper Class์— @Component๋ฅผ ์„ ์–ธํ• ์ˆ˜๋Š” ์—†์œผ๋‹ˆ
        • ObjectMapper์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ๋งŒ๋“ค๊ณ 
        • ํ•ด๋‹น ๋ฉ”์†Œ๋“œ์— @Bean์„ ์„ ์–ธํ•˜์—ฌ Bean์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค.
  • @Component
    • ์ฝ”๋“œ
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Indexed
      public @interface Component {...}
    • ํด๋ž˜์Šค ๊ฐ์ฒด๋ฅผ Bean์œผ๋กœ ๋“ฑ๋ก
    • ๊ฐœ๋ฐœ์ž๊ฐ€ย ์ง์ ‘ ์ปจํŠธ๋กค์ด ๊ฐ€๋Šฅํ•œ ํด๋ž˜์Šค์˜ ๊ฒฝ์šฐ ์‚ฌ์šฉ

2) @ComponentScan

  • ์ฝ”๋“œ
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {...}
  • @Component, @Service, @Repository, @Controller, @Configuration์ด ๋ถ™์€ ๋นˆ๋“ค์„ ์ฐพ์•„์„œ Context์— ๋“ฑ๋ก
  • @Bean ๋Œ€์‹  @Controller, @Service, @Repository๋“ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 
    • ๊ฐ€๋…์„ฑ
    • @Repository ์—์„œ๋Š” unchecked exception๋“ค์„ ์Šคํ”„๋ง์˜ DataAccessException์œผ๋กœ ์ฒ˜๋ฆฌโ€ฆ

3) @SpringBootApplication

  • ์ฝ”๋“œ

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    //------์ค‘์š”------
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { 
    		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) 
    })
    //---------------
    public @interface SpringBootApplication {
    	...
    }

    [10แ„‡แ…ฎแ†ซ ํ…Œ์ฝ”ํ†ก] ๐Ÿฆ†แ„…แ…ฅแ„‚แ…ฅแ„ƒแ…ฅแ†จแ„‹แ…ด Springboot autoConfiguration : ๋งํฌ

  • @SpringBootApplicationย ์† 3๊ฐ€์ง€ ์–ด๋…ธํ…Œ์ด์…˜

    1. @SpringBootConfiguration

    • @Configuration๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ. bean์„ ๋งŒ๋“ค์–ด์คŒ

      2. @EnableAutoConfiguration

    • ์˜ˆ์‹œ ์‚ฌ์ง„

    • ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์† spring.factoriesย ์† Configuration๋“ค(๋ฆฌ์†Œ์Šค ์•ˆ์— ์šฐ๋ฆฌ๊ฐ€ ๋“ฑ๋กํ•ด๋‘” ๋นˆ๋“ค ํฌํ•จ)์„ ์ž๋™ ๋“ฑ๋ก

      • ์ด๋•Œ, AutoConfiguration ์˜ ๋“ฑ๋ก ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ๋“ฑ๋ก!
    • -debugย ์˜ต์…˜์œผ๋กœ jarํŒŒ์ผ์„ ์‹คํ–‰ : ์กฐ๊ฑด ๋งŒ์กฑ ์—ฌ๋ถ€, Bean ๋“ฑ๋ก๋ฆฌ์ŠคํŠธ ํ™•์ธ

    • IntelliJ์—์„œ๋Š” ๋ณ„๋„์˜ ์„ค์ • ์—†์ด Actuator ์˜ ๊ธฐ๋Šฅ์„ ์ œ๊ณต โ†’ ๋“ฑ๋ก๋œ Bean ํ™•์ธ

      3. @ComponentScan

    • (base-package๊ฐ€ ์ •์˜๋˜์ง€ ์•Š์œผ๋ฉด)

    • ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ classpath ํ•˜์œ„์˜ย @Componentย ๋“ค์„ ์Šค์บ”, Bean์œผ๋กœ ๋“ฑ๋ก

2-4 ์Šคํ”„๋ง MVC Config

๊ฐœ๋…) AOP

  • ์ธํ”„๋ผ ๋กœ์ง(๋ถ€๊ฐ€๊ธฐ๋Šฅ)์€ ์ค‘๋ณต๋กœ์งโ€ฆ!
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ๋ถ„๋ฆฌํ•˜์ž~~!!!
  • Interceptor๋„ ์ผ์ข…์˜ AOP?->๋” ์•Œ์•„๋ณด๊ธฐ

1) Interceptor

  • ์ฝ”๋“œ
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(createTokenInterceptor())
                    .addPathPatterns("/auth/**");
        }
    }
    
    //...
    
    public class TokenInterceptor implements AsyncHandlerInterceptor {
    
        private final JwtTokenProvider jwtTokenProvider;
    
        public TokenInterceptor(final JwtTokenProvider jwtTokenProvider) {
            this.jwtTokenProvider = jwtTokenProvider;
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            String accessToken = AuthorizationExtractor.extract(request);
            if (!jwtTokenProvider.validateToken(accessToken)) {
                throw new AuthorizationException("ํ† ํฐ์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
            }
            return true;
        }
    }
  • Dispatcher์ด ์‹คํ–‰๋œ ํ›„, ํ•ธ๋“ค๋Ÿฌ์˜ ์‹คํ–‰์„ ๊ฐ€๋กœ์ฑˆ๋‹ค
  • ์ปจํŠธ๋กค๋Ÿฌ์˜ ์š”์ฒญ, ์‘๋‹ต์„ ๊ฐ€๋กœ์ฑ„๋Š” ์—ญํ• 
  • preHandle : ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ ์ „ ์‹คํ–‰ / ๋ฆฌํ„ด๊ฐ’์ด false์ผ ์‹œ ์ž‘์—…์ค‘๋‹จ
  • postHandle : ์ปจํŠธ๋กค๋Ÿฌ ์‹คํ–‰ ํ›„ ํ˜ธ์ถœ/ view ์ƒ์„ฑ ์ „
  • afterCompletion : ์ตœ์ข… view ์‹คํ–‰ ํ›„ ์‹คํ–‰๋จ

2) ArgumentResolver

  • ์ฝ”๋“œ
    //1. ์„ค์ • ํด๋ž˜์Šค์—์„œ ArgumentResolver ์ถ”๊ฐ€ ๊ตฌํ˜„(WebMvcConfigurer) ํ™œ์šฉ
    @Configuration
    public class AuthenticationPrincipalConfig implements WebMvcConfigurer {
        @Override
        public void addArgumentResolvers(List argumentResolvers) {
            argumentResolvers.add(createAuthenticationPrincipalArgumentResolver());
        }
    
        @Bean
        public AuthenticationPrincipalArgumentResolver createAuthenticationPrincipalArgumentResolver() {
            return new AuthenticationPrincipalArgumentResolver(jwtTokenProvider);
        }
    }
    
    //2. ๋งคํ•‘ํ•  ๋ฉ”์†Œ๋“œ ์ธ์ž์— ๋ถ™์ผ ์–ด๋…ธํ…Œ์ด์…˜ ๋งŒ๋“ค๊ธฐ
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AuthenticationPrincipal {
    }
    
    //3. ํด๋ž˜์Šค ์•ˆ์—์„œ resolveArgument๋ฉ”์†Œ๋“œ๋กœ ๊ฐ์ฒด ๋งคํ•‘
    public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
    
        private final JwtTokenProvider jwtTokenProvider;
    
        public AuthenticationPrincipalArgumentResolver(final JwtTokenProvider jwtTokenProvider) {
            this.jwtTokenProvider = jwtTokenProvider;
        }
    
        @Override
    		//๋งคํ•‘์„ ์œ„ํ•ด ์‚ฌ์šฉํ•  ํด๋ž˜์Šค ์ž…๋ ฅ
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(AuthenticationPrincipal.class);
        }
    
        @Override
    		//๊ฐ์ฒด ๋งคํ•‘
        public Object resolveArgument(MethodParameter parameter,
                                      ModelAndViewContainer mavContainer,
                                      NativeWebRequest webRequest,
                                      WebDataBinderFactory binderFactory) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) webRequest.getNativeRequest();
            String payload = jwtTokenProvider.getPayload(AuthorizationExtractor.extract(httpServletRequest));
            return new TokenRequest(Long.parseLong(payload));
        }
    }
  • ๋ฆฌํ€˜์ŠคํŠธ ๊ฐ’โ†’๊ฐ์ฒด ๋งคํ•‘์„ ๋‹ด๋‹น
  • HandlerMethodArgumentResolver ์ธํ„ฐํŽ˜์ด์Šค
    • supportsParameter : ๋ฆฌํ€˜์ŠคํŠธ ๋ฉ”์†Œ๋“œ์˜ ์ธ์ž์— ์›ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด์žˆ๋Š”์ง€ ํ™•์ธ(๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ)
    • resolveArgument : ํŠน์ • ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด์žˆ๋Š” ๋ฉ”์„œ๋“œ์˜ parameter๋ฅผ ์›ํ•˜๋Š” ํ˜•ํƒœ๋กœ ์ •๋ณด๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜์—ฌ ๋ฐ˜ํ™˜

2-5 ์Šคํ”„๋ง Auth

  • ๋‚ด์ผ ์ž‘์„ฑโ€ฆ
  • ์•„๋‹ˆ๋ฉด 27์ผ ์ž‘์„ฑโ€ฆ

1) sessionLogin โ€”

2) tokenLogin โ€”

profile
๐ŸŒฑ ํ•จ๊ป˜ ์ž๋ผ๋Š” ์ค‘์ž…๋‹ˆ๋‹ค ๐Ÿš€ rerub0831@gmail.com

4๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2022๋…„ 7์›” 4์ผ

Your posting was really beneficial to me, and I look forward to reading more of your work in the future. You can join me in playing the amazing game smash karts if you have the time as well.

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ
comment-user-thumbnail
2022๋…„ 7์›” 4์ผ

I love reading useful information, thank you for sharing, i always love this and moto x3m

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ
comment-user-thumbnail
2022๋…„ 8์›” 11์ผ

Wow, that's a lot of technical information you have shared. Why don't you hire blog writer services uk and ask them to create a blog on your behalf which can be easily understandable by everyone Plus their services are also very affordable So do try them out

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ
comment-user-thumbnail
2023๋…„ 3์›” 8์ผ

Hey thanks for sharing it with us

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ