๐ JDBC
๐ ๋ง์ด๋ฐํฐ์ค MyBatis
๐ SpringBoot ํ๋ก์ ํธ DB ์ฐ๊ฒฐ
๐ Request ๋ฐ์ดํฐ DB Insert : News POST
๐ฆ ๋ง์ด๋ฐํฐ์ค : useGeneratedKeys
๐ฆ ๋ง์ด๋ฐํฐ์ค : resultMap
์๋ฐ ํ๋ก๊ทธ๋จ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ํ ์ ์๋๋ก ํ๋ ์๋ฐ ๋ด API
Connection ๊ฐ์ฒด๋ฅผ ์์ฑํด ์๋ฐ ํ๋ก๊ทธ๋จ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐํ๋ค.
JdbcTemplate
์คํ๋ง์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํด์ฃผ๋ JDBC ๋ผ์ด๋ธ๋ฌ๋ฆฌ
JdbcTemplate์ ์ฌ์ฉํ๋ ค๋ฉด @Autowired๋ก bean ๋ฑ๋ก์ ํด์ฃผ๋ฉด ๋๋ค.
์ง์ 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
>> 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="true" ๋ก ์ค์ ํด์ผ ํ๋ค. keyProperty๋ก ๊ฐ์ ๊ฐ์ ธ์ฌ ๋ณ์๋ฅผ ์ง์ ํจ.
โก๏ธ ์๋ ์์ฑ๋ news_id ๊ฐ๋ค์ด ๊ฐ์ ธ์์ง.
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));
}
}
์๋ฒ์์์ ํ์ผ ๋ค์ด๋ก๋
๐ข ์๊ฐ ๐ด
์ธ๊ฐ์ด ๊ฐ์ฅ ๋ถํํ๋ค๋ ์ผ์์ผ ์ ๋
7์
๋๋ ๋๋ฌด๋๋ฌด ๋ถํํด์ ์์ด์คํฌ๋ฆผ์ ๋จน์ ์ ๋ฐ์ ์
์๋ค
์์ฆ์๋ ์น์ด ์์ด์คํฌ๋ฆผ์ ์์ ๋น ์ ธ์์