[Spring] 친구 정보 관리 CRUD 작업

호빵·2024년 9월 4일

Spring_이론

목록 보기
5/7

📅 공부 기간 : 08. 23(금)

🔗 깃허브 링크

HTML

index.html(메인 화면)

<!-- 생략 -->
<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>

insertFriend.html(친구 정보 등록 화면)

<!-- 생략 -->
<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>외향적 &nbsp;
      <input type="radio" name="active" value="0">내향적
    </label>
    <br><br>

    <div>
        <input type="submit" value="저장">
        <input type="reset" value="초기화">
    </div>
</form>

listFriend.html(친구 목록 출력)

  • 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>

updateFriend.html(친구 정보 조회 화면)

<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}">외향적 &nbsp;
        <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>

Controller

  • @Slf4j : 로그(log) 객체 생성
    • ex. 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"; // 브라우저에게 목록을 다시 요청하도록 지시
	}
	
	
}

DTO

  • @Builder : 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();
	}
}

Entity

  • @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();
	}
}

Service

  • Optional : wrapper 클래스
    • 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());
		}
	}
}

Repository

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> {

}
profile
인류의 위대한 대화에 참여하기 위해 다양한 언어를 탐구합니다.

0개의 댓글