JPA에 관한 세부내용은 이전에 정리해둔 JPA란 무엇인가?블로그를 참고하길 바람.
이전 시간 실습에 이어 실습을 진행함
추가 후 코끼리 이모티콘 눌러 최신화 꼭 하기
(1)
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/likelion-db
username: root
password: password
(2)
jpa:
show-sql: true
database-platform: org.hibernate.dialect.MySQL8Dialect
database : mysql
hibernate.ddl-auto : update
(1) DB에 해당하는 주소와 연동하는데 필요한 정보를 설정함
(2) show-sql: true → jpa가 자동으로 만들어주는 query문을 console에서 볼 것인지 여부
database-platform → 사용할 DB의 Dialect설정
database : mysql → 사용할 DB종류
hibernate.ddl-auto —> update 가장 중요한 옵션 초보자일 때 create쓰지 말 것. 인생망함
❗ create, update 사용시 매우 주의!!!
비교적 덜 위험한 validate 사용 권장!!
@Entity
@Table(name = "article2")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
public Article(String title,String content) {
this.title = title;
this.content = content;
}
}
public interface ArticleRepository extends JpaRepository<Article,Long> {
}
@Getter
@ToString
public class ArticleDto {
private Long id;
private String title;
private String content;
public ArticleDto(Long id, String title, String content) {
this.id = id;
this.title = title;
this.content = content;
}
public Article toEntity(){
return new Article(title,content);
}
}
@Controller
@Slf4j
@RequestMapping("/articles")
public class ArticleController {
@Autowired
private final ArticleRepository articleRepository;
public ArticleController(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
@GetMapping("/new")
public String newArticleForm(){
return "articles/new";
}
@PostMapping("/posts")
public String createArticle(ArticleDto form){
log.info(form.toString());
Article article = form.toEntity(); // form 데이터를 Entity 형식으로 변경(JPA로 DB에 데이터를 보내기 위해 JPA형식으로 가공한 셈)
articleRepository.save(article); // Repository를 통해 DB에 값 저장
return "";
}
}
이전 코드에서 추가되고 수정된 코드만 기재하도록 하겠다.
@Controller
@RequestMapping("/articles")
@Slf4j
public class ArticleController {
/* Spring이 DI하는 구간
ArticleRepository는 interface지만 그 구현제(Article)를 SpringBoot가 넣어준다.
*/
private final ArticleRepository articleRepository;
public ArticleController(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
// 데이터 입력 페이지 이동
@GetMapping("/new")
public String newArticleForm(){
return "new";
}
// DB 전체 출력
@GetMapping("/list")
public String list(Model model){
List<Article> articleList = articleRepository.findAll();
model.addAttribute("articles", articleList);
return "list";
}
// 아무것도 입력받지 않으면 /articles/list로 이동
@GetMapping("")
public String index(){
return "redirect:/articles/list";
}
// id를 통한 데이터 출력 페이지
@GetMapping("/{id}")
public String selectSingle(@PathVariable Long id, Model model){
Optional<Article> optArticle = articleRepository.findById(id); // id를 통해 데이터를 가져옴
// Optional를 사용해 null 체크함
if(!optArticle.isEmpty()) { // 데이터가 있다면
model.addAttribute("article", optArticle.get()); // model에 id에 해당하는 title, content 데이터를 저장함
return "show";
}else { // 데이터가 null이라면
return "error";
}
}
// 사용자에게 입력받은 데이터 DB에 저장 후 /articles/%d url로 이동
@PostMapping("")
public String addAriticle(ArticleDto articledto){
log.info(articledto.toString());
Article saveArticle = articleRepository.save(articledto.toEntity());
log.info("generatedId:{}", saveArticle.getId());
return String.format("redirect:/articles/%d", saveArticle.getId());
}
}
Redirect?
- Re(다시) + direct(지시하다) 다시 지시하는것을 의미한다.
예를들어 유저는 A라는 페이지에 요청을 보냈으나 서버는 권한이 없어서 B라는 페이지로 우회를 시킬때 사용함
즉, 다른 웹페이지로 우회시킬때 사용함
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
</body>
</html>
<div>
에러 페이지
</div>
{{>layouts/header}}
<table class="table">
<thead>
<tr>
<th scope="col">Id</th>
<th scope="col">Title</th>
<th scope="col">Content</th>
</tr>
</thead>
<tbody class="table-group-divider">
{{#articles}}
<tr>
<th>{{id}}</th>
<td><a href="/articles/{{id}}">{{title}}</a></td>
<td>{{content}}</td>
</tr>
{{/articles}}
</tbody>
</table>
<a href="/articles/new">new article</a>
<a href="/articles">articles List</a>
{{>layouts/footer}}
{{>layouts/header}}
<form class="container" action="/articles" method="post">
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Title</label>
<input type="text" class="form-control" name="title" id="exampleInputEmail1" aria-describedby="title">
<div id="emailHelp" class="form-text">제목을 입력하세요.</div>
<br>
<label for="exampleInputEmail1" class="form-label">Content</label>
<input type="text" class="form-control" name="content" id="exampleInputEmail1" aria-describedby="content">
<div id="emailHelp" class="form-text">내용을 입력하세요.</div>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
{{>layouts/footer}}
{{>layouts/header}}
<div class="card" style="width: 18rem;">
{{#article}}
<div class="card-body">
<h5 class="card-title">{{title}}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{id}}</h6>
<p class="card-text">{{content}} </p>
<a href="/articles/new">new articles</a>
<a href="/articles">list articles</a>
</div>
{{/article}}
</div>
{{>layouts/footer}}
DB에 없는 값 찾기
DB 모든 값 출력
@Controller
@RequestMapping("/articles")
@Slf4j
public class ArticleController {
/* Spring이 DI하는 구간
ArticleRepository는 interface지만 그 구현제(Article)를 SpringBoot가 넣어준다.
*/
private final ArticleRepository articleRepository;
public ArticleController(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
// 데이터 입력 페이지 이동
@GetMapping("/new")
public String newArticleForm(){
return "new";
}
// 사용자에게 입력받은 데이터를 articledto를 통해 DTO에 저장하고 난후 toEntity를 통해 entity클래스에
// 저장하고난 후 DB에 저장 한다. 그리고 나서 redirect로 /articles/%d url 이동
@PostMapping("")
public String addAriticle(ArticleDto articledto){
log.info(articledto.toString());
Article saveArticle = articleRepository.save(articledto.toEntity());
log.info("generatedId:{}", saveArticle.getId());
return String.format("redirect:/articles/%d", saveArticle.getId());
}
// DB 전체 출력
@GetMapping("/list")
public String list(Model model){
List<Article> articleList = articleRepository.findAll();
model.addAttribute("articles", articleList);
return "list";
}
// 아무것도 입력받지 않으면 /articles/list로 이동
@GetMapping("")
public String index(){
return "redirect:/articles/list";
}
// id를 통한 데이터 세부 출력 페이지
@GetMapping("/{id}")
public String selectSingle(@PathVariable Long id, Model model){
Optional<Article> optArticle = articleRepository.findById(id); // id를 통해 데이터를 가져옴
// Optional를 사용해 null 체크함
if(!optArticle.isEmpty()) { // 데이터가 있다면
model.addAttribute("article", optArticle.get()); // model에 id에 해당하는 title, content 데이터를 저장함
return "show";
}else { // 데이터가 null이라면
return "error";
}
}
// edit(수정) 페이지 이동
// 기존의 데이터를 출력하기 위해 id를 통해 데이터를 모두 호출함
@GetMapping("/{id}/edit")
public String edit(@PathVariable Long id, Model model){
Optional<Article> optionArticle = articleRepository.findById(id);
if(!optionArticle.isEmpty()){
model.addAttribute("article",optionArticle.get());
log.info(String.valueOf(optionArticle));
return "edit";
}else{
model.addAttribute("errormessage",String.format("%d가 없습니다",id));
return "error";
}
}
// 수정 url
// 프론트에서 수정한 값을 Form을 통해 articleDto에 저장하고 그값을 toEntity, JPA를 통해 DB에 저장한다.
// 그 후 모델에 담아 수정된 상세 페이지에 데이터를 보내 수정된 데이터를 출력시킨다.
@PostMapping("/{id}/update")
public String update(@PathVariable Long id, ArticleDto articleDto,Model model){
Article article = articleRepository.save(articleDto.toEntity()); // DB에 수정한 데이터 저장함
model.addAttribute("article",article); // model에 article 데이터 저장
return String.format("redirect:/articles/%d",article.getId()); // 수정한 상세 페이지 이동
}
// 삭제 url
@GetMapping("/{id}/delete")
public String Delete(@PathVariable Long id,Model model){
articleRepository.deleteById(id);
return "redirect:/articles";
}
}
Duplicated Entry에러가 안나는 이유
- .save( )를 할 때 id가 있다면 insert대신 update가 실행 되기 때문에 오류가 발생하지 않고 JPA에서 자동으로 신규 작성과 수정을 구분하여 DB에 저장할때 새로운 Column의 추가여부를 결정한다.
id있는 경우
Hibernate: update article2 set content=?, title=? where id=?
id없는 경우
Hibernate: insert into article2 (content, title) values (?, ?)
@Getter
@Setter
@ToString
@AllArgsConstructor
public class ArticleDto {
private Long id;
private String title;
private String content;
public Article toEntity(){
return new Article(this.id,this.title,this.content);
}
}
@Entity
@Table(name = "article2")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
public Article(String title,String content) {
this.title = title;
this.content = content;
}
}
<div>
edit page 입니다.
{{#article}}
<form action="/articles/{{id}}/update" method="post">
<!-- update 컨트롤러에 id,title,content값을 넘겨줘야 하기 때문에 id는 hidden으로 따로 저장함
만약 프론트 말고 백에서 처리하고 싶다면 setter를 통해 dto의 id값을 초기화 시켜주면 된다. -->
<input type="hidden" name="id" value="{{id}}">
<input type="text" name="title" value="{{title}}"/>
<textarea rows="3" name="content" >{{content}}</textarea>
<input type="submit"/>
</form>
<a href="/articles/{{id}}"> 상세페이지</a>
<a href="/articles/list"> 메인화면</a>
{{/article}}
</div>
{{>layouts/header}}
{{#article}}
<div class="card" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title">{{title}}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{id}}</h6>
<p class="card-text">{{content}} </p>
<button type="button" class="btn btn-outline-success"><a href="/articles/new">새로만들기</a></button>
<button type="button" class="btn btn-outline-info"><a href="/articles">리스트 출력</a></button>
</div>
</div>
<button type="button" class="btn btn-danger"><a href="/articles/{{id}}/edit">수정</a></button>
<button type="button" class="btn btn-danger"><a href="/articles/{{id}}/delete">삭제</a></button>
{{/article}}
{{>layouts/footer}}