[Web_3] Spring 7 ๐Ÿ˜ด

08627ยท2022๋…„ 9์›” 23์ผ
1

Spring

๋ชฉ๋ก ๋ณด๊ธฐ
7/13
post-thumbnail


๐Ÿ“Œ JDBC
๐Ÿ“Œ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค MyBatis
๐Ÿ“Œ SpringBoot ํ”„๋กœ์ ํŠธ DB ์—ฐ๊ฒฐ
๐Ÿ“Œ Request ๋ฐ์ดํ„ฐ DB Insert : News POST
๐ŸฆŽ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค : useGeneratedKeys
๐ŸฆŽ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค : resultMap


๐Ÿ’ก JDBC (Java Database Connectivity)

์ž๋ฐ” ํ”„๋กœ๊ทธ๋žจ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์ž๋ฐ” ๋‚ด API

Connection ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด ์ž๋ฐ” ํ”„๋กœ๊ทธ๋žจ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐํ•œ๋‹ค.

๐Ÿ’ก SpringBoot JDBC

JdbcTemplate
์Šคํ”„๋ง์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•ด์ฃผ๋Š” JDBC ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
JdbcTemplate์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด @Autowired๋กœ bean ๋“ฑ๋ก์„ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

๐Ÿ’ก ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค MyBatis

์ง์ ‘ JDBC๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ๊ธฐ์กด ๋ฐฉ์‹ ... ๊ทธ๋Ÿฌ๋‚˜ SQL ์ฟผ๋ฆฌ๊ฐ€ ํ”„๋กœ๊ทธ๋žจ์— ๋ฐ”๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ์–ด ๋ณต์žกํ•˜๊ณ  ๋ฐ˜๋ณต๋˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋งŽ์Œ.

โžก๏ธ ๊ทธ๋ž˜์„œ ์ผ๋ฐ˜์ ์œผ๋กœ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
์ž๋ฐ” ๊ฐ์ฒด์™€ SQL ์ฟผ๋ฆฌ๋ฅผ ๋งคํ•‘ํ•˜์—ฌ ์ž๋ฐ” ํ”„๋กœ๊ทธ๋žจ๊ณผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์—ฐ๊ฒฐ์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ

SQL ์ฟผ๋ฆฌ๋ฅผ XML ํŒŒ์ผ ํ•œ ๊ณณ์—์„œ ์ž‘์„ฑํ•˜๋„๋ก ํ•˜์—ฌ ์„œ๋น„์Šค ๋กœ์ง ์ฝ”๋“œ์™€ ๋ถ„๋ฆฌํ•œ๋‹ค.

๐Ÿผ Spring - MyBatis Project ์ƒ์„ฑ

โ†’ spring.io
์••์ถ• ํ’€๊ณ  ์ธํ…”๋ฆฌ์ œ์ด ์—์„œ ํด๋” ์—ด์–ด์ฃผ๋ฉด ๋—

๐ŸฆŽ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค ์Šคํ”„๋ง ์„ค์ •
โ–ซ๏ธ application.yml

server:
  port: 8000

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb:// ...
    username: ...
    password: ...
    
๐Ÿ“
mybatis:
  mapper-locations:
    - /mappers/*.xml

๐Ÿ’ก Request ๋ฐ์ดํ„ฐ DB Insert ํ•˜๊ธฐ : News POST

๐Ÿ‘พ News : ๊ธฐ์‚ฌ์™€ ์ฒจ๋ถ€ํŒŒ์ผ์„ DB์— Insert ํ•˜๊ธฐ

>> java > com.boot.Project ํด๋”์—
> controller
> controller.api ( โ€ป ์„œ๋น„์Šค ์•ˆ ๋งŒ๋“ค๊ณ  ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐ”๋กœ ์„œ๋น„์Šค ๋กœ์ง ๊ตฌํ˜„ํ•จ )
> domain
> repository

>> resource ํด๋”์—
> mappers ๋งŒ๋“ค์–ด์คŒ (myBatis xml ํŒŒ์ผ ํด๋”)

โ–ซ๏ธ application.yml
file ๋ฐ›์•„์˜ฌ ๋•Œ โ†’ multipart , file.path

server:
  port: 8000

spring:
  mvc:
    static-path-pattern: /static/**

  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://...:3306/...
    username: ...
    password: ...
 
๐Ÿ“servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

mybatis:
  mapper-locations:
    - /mappers/*.xml

 
file:
๐Ÿ“path: C:/User/user/upload/

โ–ซ๏ธ PageController.java

@Controller
public class PageController {
    @GetMapping("/news/newpost")
    public String writeNews() {
        return "news/write"; // news ํด๋”์˜ write.html
    }
}

โ†’ http://localhost:8000/news/newpost

โ–ซ๏ธ NewsWriteReqDto.java

@Data
public class NewsWriteReqDto {
    private String title;
    private String writer;
    private String broadcastingName;
    private String content;
    private List<MultipartFile> files;

    public News toEntity(String writer) { // save ์‹œ ์ž‘์„ฑ์ž๋ฅผ ๋“ฑ๋ก
        return News.builder()
                .news_title(title)
                .news_writer(writer)
                .news_broadcasting(broadcastingName)
                .news_content(content)
                // file ์€ ๋”ฐ๋กœ ์ฒ˜๋ฆฌ
                .build();
    }
}

โ–ซ๏ธ NewsFile.java

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class NewsFile {
    private int file_id;
    private int news_id;
    private String file_origin_name;
    private String file_temp_name;
}

โ–ซ๏ธ News.java

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class News {
    private int news_id;
    private String news_title;
    private String news_writer;
    private String news_broadcasting;
    private String news_content;
    private List<NewsFile> news_file;

    private LocalDateTime create_date;
    private LocalDateTime update_date;

    public NewsWriteRespDto toNewsWriteRespDto(List<NewsFile> newsFileList) {
    // ์ฒจ๋ถ€ํŒŒ์ผ์„ ๋”ฐ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ, ์„œ๋ฒ„์—์„œ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด ์ค„ ๋•Œ ํ•ฉ์ณ์คŒ. 
        return NewsWriteRespDto.builder()
                .id(news_id)
                .title(news_title)
                .writer(news_writer)
                .broadcastingName(news_broadcasting)
                .content(news_content)
                .newsFileList(newsFileList)
                .build();
    }
}

โ–ซ๏ธ NewsWriteRespDto.java

@Builder
@Data
public class NewsWriteRespDto {
    private int id;
    private String title;
    private String writer;
    private String broadcastingName;
    private String content;
    private List<NewsFile> newsFileList;
}

(์š”์ฒญ) NewsWriteReqDto , NewsFile โ†’ News โ†’ (์‘๋‹ต) NewsWriteRespDto


โ–ซ๏ธ NewsRepository.java

@Mapper
public interface NewsRepository {
    public int save(News news);
    public int saveFiles(List<NewsFile> newsFileList);
    public News getNews(int news_id); 
    // ๋ฉ”์†Œ๋“œ๋“ค์€ xml ํŒŒ์ผ์—์„œ sql ๋ฌธ์œผ๋กœ ๊ตฌํ˜„๋จ. 
}

@Mapper ์–ด๋…ธํ…Œ์ด์…˜์ด myBatis ์™€ ์—ฐ๊ฒฐํ•ด์„œ ๊ตฌํ˜„๋œ xml ์„ ๊ฐ€์ง€๊ณ  ์Šคํ”„๋ง IoC ์ปจํ…Œ์ด๋„ˆ์˜ @์„œ๋น„์Šค, @๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋“ฑ์„ ์•Œ์•„์„œ ์ฒ˜๋ฆฌํ•ด์คŒ.

โ–ซ๏ธ news.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.boot.mybatisProject.repository.NewsRepository">

    <resultMap id="news_file" type="com.boot.mybatisProject.domain.NewsFile">
        <result property="file_origin_name" column="file_origin_name"></result>
        <result property="file_temp_name" column="file_temp_name"></result>
    </resultMap>
  
    <resultMap id="news" type="com.boot.mybatisProject.domain.News">
        <result property="news_id" column="news_id"></result>
        <result property="news_title" column="news_title"></result>
        <result property="news_writer" column="news_writer"></result>
        <result property="news_broadcasting" column="news_broadcasting"></result>
        <result property="news_content" column="news_content"></result>
        <result property="create_date" column="create_date"></result>
        <collection property="news_file" javaType="list" resultMap="news_file"></collection>
    </resultMap>

 
  <!-- id๋Š” ๋ฉ”์†Œ๋“œ๋ช…๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•จ โ— -->
  
    <insert
            id="save"
            parameterType="com.boot.mybatis20220923junil.domain.News"
            useGeneratedKeys="true"
            keyProperty="news_id"
    >
        insert into
            news_mst
        values (
            0,
            #{news_title},
            #{news_writer},
            #{news_broadcasting},
            #{news_content},
            now(),
            now()
        )
    </insert>

    <select id="getNews" resultMap="news">
        SELECT
            nm.news_id,
            nm.news_title,
            nm.news_writer,
            nm.news_broadcasting,
            nm.news_content,

            nf.file_origin_name,
            nf.file_temp_name,

            nm.create_date

        FROM
            news_mst nm
            left outer join news_file nf ON(nf.news_id = nm.news_id)
        WHERE
            nm.news_id = #{news_id};
    </select>

    <insert
            id="saveFiles"
            parameterType="java.util.List"
            useGeneratedKeys="true"
            keyProperty="file_id"
    >
        insert into
            news_file
        values
        <foreach item="newsFile" collection="newsFileList" separator=",">
            (
                0,
                #{newsFile.news_id},
                #{newsFile.file_origin_name},
                #{newsFile.file_temp_name},
                now(),
                now()
            )
        </foreach>


    </insert>
</mapper>

๐ŸฆŽ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค : useGeneratedKeys

์ž๋™ ์ƒ์„ฑ๋˜๋Š” ๊ฐ’๋“ค์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด useGeneratedKeys="true" ๋กœ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค. keyProperty๋กœ ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ๋ณ€์ˆ˜๋ฅผ ์ง€์ •ํ•จ.
โžก๏ธ ์ž๋™ ์ƒ์„ฑ๋œ news_id ๊ฐ’๋“ค์ด ๊ฐ€์ ธ์™€์ง.

๐ŸฆŽ ๋งˆ์ด๋ฐ”ํ‹ฐ์Šค : resultMap

result(์กฐํšŒ๊ฐ’) ๋ฅผ ๊ฐ์ฒด ๋ชจ๋ธ์— ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ์„ค์ •

DTO ๊ฐ์ฒด์˜ ํ•„๋“œ๋ช…๊ณผ SELECT ๊ตฌ๋ฌธ์˜ ์กฐํšŒ ์ปฌ๋Ÿผ๋ช…์ด ์ผ์น˜ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜, ํƒ€์ž…์ด ๋งค์นญ๋˜์ง€ ์•Š์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

โ–ซ๏ธ NewsController.java
๐Ÿผ ( 1 ) ๊ธฐ์‚ฌ ๋ฐ›์•„์˜ค๊ธฐ

@Slf4j
@RequestMapping("/api")
@RequiredArgsConstructor
@RestController
public class NewsController {
    
    private final NewsRepository newsRepository;

    @PostMapping("/news")
    public ResponseEntity<?> write(NewsWriteReqDto newsWriteReqDto) {
        log.info("{}", newsWriteReqDto);

        News news = newsWriteReqDto.toEntity("์งฑ๊ตฌ"); // ์ž‘์„ฑ์ž
        int result = newsRepository.save(news); // result ๋Š” ์˜ํ–ฅ๋ฐ›์€ ํ–‰ ์ˆ˜ 

        if(result == 0) {
            return ResponseEntity.internalServerError().body(new CMRespDto<>(-1, "์ƒˆ ๊ธ€ ์ž‘์„ฑ ์‹คํŒจ", news));
        }

        return ResponseEntity.ok(new CMRespDto<>(1, "์ƒˆ ๊ธ€ ์ž‘์„ฑ ์™„๋ฃŒ", newsWriteRespDto));
    }

๐Ÿผ ( 2 ) ์ฒจ๋ถ€ํŒŒ์ผ ๋ฐ›์•„์˜ค๊ธฐ

@Slf4j
@RequestMapping("/api")
@RequiredArgsConstructor
@RestController
public class NewsController {

    @Value("${file.path}") 
    // โžก๏ธ NewsController๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ๋ณ€์ˆ˜์— Value๊ฐ€ ์ฃผ์ž…๋จ. (application.yml์—์„œ ๊ฐ’์„ ๊ฐ€์ ธ์™€ ์ฃผ์ž…)
    // โœ”๏ธ application.yml ์—์„œ ๋ชจ๋“  ๊ฒฝ๋กœ๋ฅผ ๊ด€๋ฆฌํ•จ.
    private String filePath;
    private final NewsRepository newsRepository;

    @PostMapping("/news")
    public ResponseEntity<?> write(NewsWriteReqDto newsWriteReqDto) {
        log.info("{}", newsWriteReqDto);

		// โญ ์ฒจ๋ถ€ํŒŒ์ผ ๋ฐ›์•„์˜ค๊ธฐ 
        List<NewsFile> newsFileList = null;

		// (1) ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ณด๋‚ธ ์ฒซ๋ฒˆ์งธ[์ธ๋ฑ์Šค:0] ์ฒจ๋ถ€ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ด
        MultipartFile firstFile = newsWriteReqDto.getFiles().get(0);
        String firstFileName = firstFile.getOriginalFilename();
		
        // 0๋ฒˆ ์ธ๋ฑ์Šค ์ฒจ๋ถ€ํŒŒ์ผ์ด ๋นˆ ๊ฐ’์ด ์•„๋‹˜ โžก๏ธ POST ์š”์ฒญํ•œ ํŒŒ์ผ์ด ์กด์žฌ
        if(!firstFileName.isBlank()) {
            log.info("์ฒจ๋ถ€ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.");

            newsFileList = new ArrayList<NewsFile>();

            for(MultipartFile file : newsWriteReqDto.getFiles()){
                String originFileName = file.getOriginalFilename();
				
                // file RandomUUID ์ด๋ฆ„ ์ƒ์„ฑ (ํŒŒ์ผ๋ช…์ด ๊ฒน์น˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ) 
                // extension : ํŒŒ์ผ ํ™•์žฅ์ž๋ช…
                String uuid = UUID.randomUUID().toString();
                String extension = originFileName.substring(originFileName.lastIndexOf("."));
                String tempFileName = uuid + extension; 

                Path uploadPath = Paths.get(filePath, "news/" + tempFileName);
				
                // filePath ๊ฒฝ๋กœ์— news ํด๋”๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ๋งŒ๋“ค์–ด์คŒ. 
                File f = new File(filePath + "news");
                if(!f.exists()) {
                    f.mkdirs(); // make directory
                }

				// ๋ฏธ๋ฆฌ ์ง€์ •ํ•ด๋‘” ํŒŒ์ผ ๊ฒฝ๋กœ์— ์‹ค์ œ ํŒŒ์ผ ๋ฐ์ดํ„ฐ ๋ฎ์–ด์“ฐ๊ธฐํ•จโ—
                try {
                    Files.write(uploadPath, file.getBytes());
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

                NewsFile newsFile = NewsFile.builder()
                        .file_origin_name(originFileName)
                        .file_temp_name(tempFileName)
                        .build();

                newsFileList.add(newsFile);
            }
        }

		// โญ ๊ธฐ์‚ฌ ๋ฐ›์•„์˜ค๊ธฐ
        News news = newsWriteReqDto.toEntity("์งฑ๊ตฌ"); // ์ž‘์„ฑ์ž
        // ๊ธฐ์‚ฌ DB insert ํ•ด์คŒ. 
        int result = newsRepository.save(news); // result ๋Š” ์˜ํ–ฅ๋ฐ›์€ ํ–‰ ์ˆ˜

        if(result == 0) {
            return ResponseEntity.internalServerError().body(new CMRespDto<>(-1, "ํฌ์ŠคํŒ… ์‹คํŒจ", news));
        }

		// ์—…๋กœ๋“œํ•  ํŒŒ์ผ์ด ์กด์žฌํ•˜๋ฉด ๋”ฐ๋กœ DB Insert ํ•ด์คŒ. 
        if(newsFileList != null) {
            for(NewsFile newsFile : newsFileList) {
                newsFile.setNews_id(news.getNews_id()); // news.xml ์—์„œ useGenerate ๋กœ id ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Œ. 
                log.info("NewsFile ๊ฐ์ฒด: {}", newsFile);
            }
            result = newsRepository.saveFiles(newsFileList);

            if(result != newsFileList.size()) {
                return ResponseEntity.internalServerError().body(new CMRespDto<>(-1, "ํŒŒ์ผ ์—…๋กœ๋“œ ์‹คํŒจ", newsFileList));
            }
        }
        
        // โญ ๊ธฐ์‚ฌ + ์ฒจ๋ถ€ํŒŒ์ผ DB insert ์™„๋ฃŒ โžก๏ธ ์‘๋‹ต ๋ฐ์ดํ„ฐ ์ƒ์„ฑ

        NewsWriteRespDto newsWriteRespDto = news.toNewsWriteRespDto(newsFileList);

        return ResponseEntity.ok(new CMRespDto<>(1, "ํฌ์ŠคํŒ… ์™„๋ฃŒ", newsWriteRespDto));
    }

}

์„œ๋ฒ„์—์„œ์˜ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ

  • ํŒŒ์ผ์˜ ์œ ์ผํ•œ ์ด๋ฆ„์„ ์ƒ์„ฑํ•จ. (๊ฒน์น˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•จ)
  • ์ €์žฅ๊ณต๊ฐ„์— ํŒŒ์ผ ์ด๋ฆ„์œผ๋กœ ์—…๋กœ๋“œ ๊ฒฝ๋กœ๋ฅผ ์„ค์ •ํ•˜์—ฌ ๋นˆ ํŒŒ์ผ์œผ๋กœ ๋‘ .
    ( โ€ป ๊ฒฝ๋กœ์—๋Š” ํ™•์žฅ์ž๊นŒ์ง€ ํ•„์š”ํ•จ )
  • ๋ฏธ๋ฆฌ ์ง€์ •ํ•ด ๋‘” ํŒŒ์ผ ๊ฒฝ๋กœ(๋นˆ ํŒŒ์ผ)์— ํŒŒ์ผ ๋ฐ์ดํ„ฐ(byte)๋ฅผ ๋ฎ์–ด์”Œ์šฐ๋„๋ก ํ•จ.



๐Ÿ“ข ์†Œ๊ฐ ๐Ÿ˜ด
์ธ๊ฐ„์ด ๊ฐ€์žฅ ๋ถˆํ–‰ํ•˜๋‹ค๋Š” ์ผ์š”์ผ ์ €๋… 7์‹œ
๋‚˜๋Š” ๋„ˆ๋ฌด๋„ˆ๋ฌด ๋ถˆํ–‰ํ•ด์„œ ์•„์ด์Šคํฌ๋ฆผ์„ ๋จน์„ ์ˆ˜ ๋ฐ–์— ์—…์—ˆ๋‹ค
์š”์ฆ˜์—๋Š” ์น™์ด‰ ์•„์ด์Šคํฌ๋ฆผ์— ์™„์ „ ๋น ์ ธ์žˆ์”€

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