📅 공부 기간 : 08. 23(금)
<!-- 생략 -->
<html lang="ko" xmlns:th="http://thymeleaf.org">
<!-- 생략 -->
<body>
<ul>
<li><a th:href="@{/insertFriend}">정보 저장(화면 요청)</a></li>
<li><a th:href="@{/listFriend}">친구 목록</a></li>
</ul>
</body>
<!-- 생략 -->
<div>
<a th:href="@{/}"><img th:src="@{/images/home.png}" alt="첫화면으로"></a>
</div>
<form th:action="@{/insertFriend}" method="POST" th:object="${friend}">
<label for="fname">이름: </label>
<input type="text" id="fname" name="fname" placeholder="이름 입력" th:field="*{fname}">
<span class="error" th:errors="*{fname}"></span>
<br>
<label for="age">나이:</label>
<input type="number" id="age" name="age" placeholder="나이 입력" th:field="*{age}">
<span class="error" th:errors="*{age}"></span>
<br>
<label for="phone">전화번호:</label>
<input type="text" id="phone" name="phone" placeholder="전화번호 입력" th:field="*{phone}">
<span class="error" th:errors="*{phone}"></span>
<br>
<label for="birthday">생년월일:</label>
<input type="date" id="birthday" name="birthday" th:value="*{birthday}">
<span class="error" th:errors="*{birthday}"></span>
<br>
<label> 성향:
<input type="radio" name="active" value="1" checked>외향적
<input type="radio" name="active" value="0">내향적
</label>
<br><br>
<div>
<input type="submit" value="저장">
<input type="reset" value="초기화">
</div>
</form>
th:if="${#lists.isEmpty(list)}" : 객체가 비어있는지 확인(thymeleaf의 표현식 언어)<div>
<a th:href="@{/}"><img th:src="@{/images/home.png}" alt="첫화면으로"></a>
</div>
<p th:if="${#lists.isEmpty(list)}">친구 목록이 없습니다.</p>
<div th:unless="${#lists.isEmpty(list)}">
<table border="1">
<tr>
<th>번호</th>
<th>이름</th>
<th>나이</th>
<th>전화번호</th>
<th>생년월일</th>
<th>성향</th>
<th></th>
</tr>
<tr th:each="friend, status : ${list}">
<td th:text="${status.count}">번호</td>
<td th:text="${friend.fname}">이름</td>
<td th:text="${friend.age}">나이</td>
<td th:text="${friend.phone}">전화번호</td>
<td th:text="${friend.birthday}">생년월일</td>
<td th:text="${friend.active} ? '외향적' : '내성적'">성향</td>
<td>
<!-- PK값을 알아야 삭제나 수정을 할 수 있다. 사용자 정의한 속성 data-seq -->
<input type="button" class="deleteOne" th:data-seq="${friend.fseq}" value="삭제">
<input type="button" class="updateOne" th:data-seq="${friend.fseq}" value="수정">
</td>
</tr>
</table>
</div>
<form id="sendOne" th:action="@{/deleteOne}" method="GET">
<input type="hidden" id="fseq" name="fseq" value="" >
</form>
<script>
let delBtn = document.getElementsByClassName("deleteOne"); // 배열로 반환함
let updateBtn = document.getElementsByClassName("updateOne");
for(let i=0; i<delBtn.length; ++i ) {
delBtn[i].addEventListener('click', delFriend);
updateBtn[i].addEventListener('click', updateFriend);
}
// 정보 삭제 함수
function delFriend() {
let seq = this.getAttribute("data-seq")
document.getElementById("fseq").value=seq;
document.getElementById("sendOne").action = 'deleteOne';
document.getElementById("sendOne").submit();
}
// 정보 수정 함수
function updateFriend() {
let seq = this.getAttribute("data-seq")
document.getElementById("fseq").value=seq;
document.getElementById("sendOne").action = 'updateOne';
document.getElementById("sendOne").submit();
}
</script>
<div>
<a th:href="@{/}"><img th:src="@{/images/home.png}" alt="첫화면으로"></a>
</div>
<form th:action="@{/updateOne}" method="POST">
<!-- update 쿼리문에서 where절에서 사용해야함 -->
<input type="hidden" name="fseq" th:value="${friend.fseq}" >
<label for="fname">이름: </label>
<input type="text" id="fname" name="fname" th:value="${friend.fname}" readonly >
<br>
<label for="age">나이:</label>
<input type="number" id="age" name="age" th:value="${friend.age}">
<br>
<label for="phone">전화번호:</label>
<input type="text" id="phone" name="phone" th:value="${friend.phone}">
<br>
<label for="birthday">생년월일:</label>
<input type="date" id="birthday" name="birthday" th:value="${friend.birthday}">
<br>
<label>
성향:
<input type="radio" name="active" value="1" th:checked="${friend.active}">외향적
<input type="radio" name="active" value="0" th:checked="not ${friend.active}">내성적
</label>
<br><br>
<div>
<input type="submit" value="수정">
<input type="reset" value="초기화">
</div>
</form>
@Slf4j : 로그(log) 객체 생성log.info()@RequiredArgsConstructor : final 필드나 @NonNull이 붙은 필드를 인자로 받는 생성자 생성@Valid : 객체의 유효성 검사 수행return "redirect:/"; : 사용자를 root 경로로 리다이렉트(즉, 메인 페이지로 이동)@Controller
@Slf4j
@RequiredArgsConstructor
public class FriendController {
final FriendService service;
/**
* 화면을 요청
* @return
*/
@GetMapping("/insertFriend")
public String insertFriend(Model model) {
// (error 처리를 위해)비어있는 객체의 데이터를 추가
model.addAttribute("friend", new Friend());
return "insertFriend";
}
/**
* 저장 요청
* @param Friend
* @return
*/
@PostMapping("/insertFriend")
public String insertFriend(
@Valid
@ModelAttribute Friend friend,
BindingResult bindingResult // 객체 유효성 검사 후의 결과를 담는 객체. 유효성 검사에 실패하면 bindingResult.hasErrors()가 true
) {
log.info("friend 객체: {}", friend.toString());
log.info("bindingResult: {}", bindingResult);
if(bindingResult.hasErrors()) {
log.info("validation 시 오류 발생");
return "insertFriend";
}
service.insert(friend);
return "redirect:/";
}
/**
* 친구 목록 요청 ==> DB에서 데이터 목록을 가져와야 함
*/
@GetMapping("/listFriend")
public String listFriend(Model model) {
List<Friend> list = service.list();
model.addAttribute("list", list);
return "listFriend";
}
/**
* 파라미터로 전송받은 fseq값을 이용해 DB에서 데이터를 삭제하도록
* 전달함
* @param fseq
* @return
*/
@GetMapping("/deleteOne")
public String deleteFriend(@RequestParam(name="fseq") Integer fseq) {
log.info("전달된 번호: {} ==> ", fseq);
service.deleteOne(fseq);
return "redirect:/listFriend"; // redirect는 get 요청
}
/**
* 데이터를 수정하기 전 수정할 데이터를 DB에서 조회하는 기능
* @param fseq
* @return
*/
@GetMapping("/updateOne")
public String updateFriend(@RequestParam(name="fseq") Integer fseq, Model model) {
Friend friend = service.selectOne(fseq);
model.addAttribute("friend", friend);
return "updateFriend";
}
/**
* 전달받은 데이터를 수정하기 위한 요청(DB 까지 전달해야함)
* @param fseq
* @param model
* @return
*/
@PostMapping("/updateOne")
public String updateFriend(@ModelAttribute Friend friend) {
service.updateOne(friend); //
return "redirect:/listFriend"; // 브라우저에게 목록을 다시 요청하도록 지시
}
}
@Builder : Builder 패턴을 쉽게 사용하도록 도움.// lombok 라이브러리 등 모두 import
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
@Builder
public class Friend {
private Integer fseq;
// 이름입력 : 2~10길이로 제어
@Size(min=2, max=10, message="이름은 2~10자 이내로 입력해 주세요.")
private String fname;
// 나이는 15세 이상만
@Min(value=15, message="나이는 15세 이상 입력해 주세요.")
private Integer age;
// 정규표현식 :
@Pattern(regexp="01[016789]\\d{4}\\d{4}", message="-없이 숫자로 입력해주세요")
private String phone;
// 미래날짜는 입력하지 못하도록 제어
@PastOrPresent(message="과거의 날짜를 선택해주세요")
private LocalDate birthday;
private boolean active;
// Entity를 받아서 DTO 반환 (조회할 때)
public static Friend toDTO(FriendEntity friendEntity) {
return Friend.builder()
.fseq(friendEntity.getFseq())
.fname(friendEntity.getFname())
.age(friendEntity.getAge())
.phone(friendEntity.getPhone())
.birthday(friendEntity.getBirthday())
.active(friendEntity.isActive())
.build();
}
}
@GeneratedValue : 기본키 자동 생성(보통 @Id 애너테이션과 함께 사용)strategy 속성 : GenerationType.IDENTITY-> AUTO_INCREMENT 기능 추가@Column : JPA의 엔티티 클래스 필드를 데이터베이스 테이블의 컬럼과 매핑@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
@Builder
@Entity
@Table(name="friend")
public class FriendEntity {
@Id
@Column(name="fseq")
@GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment 설정
private Integer fseq;
@Column(name="fname", nullable=false) // 테이블의 Not Null 제약조건이 걸렸을 때
private String fname;
@Column(name="age")
private Integer age;
@Column(name="phone")
private String phone;
@Column(name="birthday")
private LocalDate birthday;
@Column(name="active")
private boolean active;
// DTO를 받아서 Entity로 반환하는 Builder를 생성 ==> INSERT, UPDATE
public static FriendEntity toEntity(Friend friend) {
return FriendEntity.builder()
.fseq(friend.getFseq())
.fname(friend.getFname())
.age(friend.getAge())
.phone(friend.getPhone())
.birthday(friend.getBirthday())
.active(friend.isActive())
.build();
}
}
isPresent() : Optional 객체가 값을 갖고 있는지 확인(boolean값 반환)get() : Optional 객체에 저장된 실제값 반환@Transactional : 클래스 내의 데이터베이스 작업이 2개 이상일 때 하나의 트랜잭션으로 처리하도록 함@Service
@Slf4j
@RequiredArgsConstructor
public class FriendService {
final FriendRepository repository;
/**
* DB에 데이터를 저장하기 위한 메소드
* @param friend
*/
public void insert(Friend friend) {
log.info("{}", friend.toString());
FriendEntity friendEntity = FriendEntity.toEntity(friend);
repository.save(friendEntity); // persist()
}
/**
* DB에서 Friend 목록을 조회
* @return
*/
public List<Friend> list() {
List<Friend> list = new ArrayList<>();
List<FriendEntity> entityList = repository.findAll();
// entity들을 dto로 변환한다! (데이터가 하나도 없을 경우에는 처리를 하면 안됨)
if(!entityList.isEmpty()) {
entityList.forEach((entity) -> list.add(Friend.toDTO(entity) ));
log.info("{}", list.get(0).toString());
}
return list;
}
/**
* DB에 fseq번호에 해당하는 데이터를 삭제함.
* @param fseq
*/
public void deleteOne(Integer fseq) {
log.info("삭제할 번호 ==> {}", fseq);
repository.deleteById(fseq);
}
/**
* DB에서 fseq 번호에 해당하는 데이터를 조회
* @param fseq
* @return
*/
public Friend selectOne(Integer fseq) {
// Null로 인한 오류로 인해 FriendEntity를 반환하지 않고
// Optional이라는 Wrapper 클래스로 감싸서 반환한다.
Optional<FriendEntity> entity = repository.findById(fseq);
if(entity.isPresent()) {
FriendEntity friendEntity = entity.get();
return Friend.toDTO(friendEntity);
}
return null;
}
@Transactional
public void updateOne(Friend friend) {
// dto를 entity로 수정하는 작업함.
// set을 하면 바뀜
log.info("updateOne ========> {} ", friend.toString());
// 1) PK를 이용해서 그 데이터가 있는지 조회한다. --> findById()
Optional<FriendEntity> entity = repository.findById(friend.getFseq());
// 2) 조회된 데이터에 수정할 정보를 setting한다.
if(entity.isPresent()) {
FriendEntity friendEntity = entity.get(); // 바뀌기 전 데이터
// 나이, 전화번호, 성향, 생년월일을 setting하면 DB값이 바뀐다
friendEntity.setAge(friend.getAge());
friendEntity.setPhone(friend.getPhone());
friendEntity.setActive(friend.isActive());
friendEntity.setBirthday(friend.getBirthday());
}
}
}
package com.kdigital.spring6.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.kdigital.spring6.entity.FriendEntity;
public interface FriendRepository extends JpaRepository<FriendEntity, Integer> {
}