부스트코스 링크
명함관리 CLI기획서를 보고 Spring Web으로 만들어보자!
Spring 의존성이다.
Spring Data JPA, Web ,타임리프, 롬복을 추가해준다.
spring.application.name=businesscard
# H2 데이터베이스 연결 설정
spring.datasource.url=jdbc:h2:tcp://localhost/~/businesscard
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
Database로는 H2 DB를 선택했다. 고른 이유는 특별하지 않고 그냥 당장 쓸 수 있어서 선택함
h2 콘솔에 URL로 DB를 만들어준다. 참고로 안되면
User폴더에서 mv.db파일 만들어주면 된다. 나는 안돼서 test.mv.db복붙하고 이름 바꿔서 수행함
다급함이 보이는 글씨
2시간 타임어택 시간에 내가 작성한 패키지 + 파일 목록인데 이 글을 쓰면서 나중에 구조를 조금 바꿧다
내가 작성한 순서대로 설명하겠다
public interface BusinessCardService {
public void saveCard(BusinessCard businessCard);
public List<BusinessCard> findBusinessCardByName(String name);
}
BusinesesCardService interface이다. 사실 구현체가 하나이기때문에 굳히 역할은 없어도 되지만 역할과 구현을 쌍으로 생각하라고 했기 때문에 작성했다.
@Repository
public interface BusinessCardRepository extends JpaRepository<BusinessCard, Long> {
List<BusinessCard> findByName(String name);
public List<BusinessCard> findByNameContaining(String name);
}
DB와 연결 역할을 하는 Repository
여기서 findByNameContaining SQL의 %Like%과 같은 기능을 한다. 즉 포함하는 것을 return해준다.
리턴하는 값을 List에 넣어 반환해준다.
참고로 findByName은 Name과 정확히 일치하는 항목을 찾는 것이다.
@Service
@RequiredArgsConstructor
@Transactional
public class BusinessCardServiceImpl implements BusinessCardService{
private final BusinessCardRepository businessCardRepository;
@Override
public void saveCard(BusinessCard businessCard) {
businessCardRepository.save(businessCard);
}
@Override
public List<BusinessCard> findBusinessCardByName(String name) {
List<BusinessCard> cardList = businessCardRepository.findByName(name);
return cardList;
}
}
서비스 구현체
BusinessCardRepository에 의존한다. 하는 역할은 간단하다.
명함 등록과 검색기능을 제공한다.
Controller를 만들기 전 html파일을 먼저 만들었다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>hello</title>
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="content">
<div class ="contentsBox">
<form action="/register" method="post">
<div class="form-group">
<label for="name">이름</label>
<input type="text" class="form-control" name = "name" id="name" aria-describedby="name" placeholder="이름을 입력하세요" required>
</div>
<div class="form-group">
<label for="phone">전화번호</label>
<input type="tel" pattern="\d{11}" maxlength="11" title="전화번호는 숫자 11자리를 입력해야 합니다." class="form-control" name = "phone" id="phone" placeholder="전화번호" required>
</div>
<div class="form-group">
<label for="companyName">회사이름</label>
<input type="text" class="form-control" name = "companyName" id="companyName" placeholder="회사이름" required>
</div>
<div> <button type="submit" class="btn btn-primary">명함등록</button>
</div>
</form>
</div>
<div class="contentsBox">
<form action="/search" method="post">
<div class="form-row align-items-center">
<div class="col-auto">
<label class="sr-only" for="searchName">이름 검색</label>
<input type="text" class="form-control mb-2" name = "searchName" id="searchName" placeholder="김동동씨" required>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-2">검색하기</button>
</div>
</div>
</form>
</div>
</div>
</body>
</html>
정적 파일이 될 index.html파일 css는 부트스트랩이랑 가운데 정렬을 위해서 내가 만든거 두 개뿐이다.
여기서 집중해야할 부분은
<div class="form-group">
<label for="name">이름</label>
<input type="text" class="form-control" name = "name" id="name" aria-describedby="name" placeholder="이름을 입력하세요" required>
</div>
form태그 안에서 input태그 안이다. 여기서 name이라는 속성을 사용하는데 이는 Spring에서 데이터를 가져올 때 필요하다. 해당 부분은 스터디가 필요한 부분이라서 지금은 간략하게 표현하겠다.
그럼 해당 POST메소드에 맞는 Controller를 확인해보자
@Controller
@Slf4j
@RequiredArgsConstructor
public class RegisterController {
private final BusinessCardService businessCardService;
@PostMapping("/register")
public String registerBusinessCard(
@RequestParam("name") String name,
@RequestParam("phone") String phone,
@RequestParam("companyName") String companyName,
Model model
){
BusinessCard businessCard = new BusinessCard(name,phone,companyName, LocalDateTime.now());
businessCardService.saveCard(businessCard);
//model , 컨트롤러에서 ㅊ ㅓ리한 데이터(모델, 객체) 를 뷰 템플릿에 넘기기 위해 사용된다.
// 주로 타임리프, JSP같은 뷰 템플릿 엔진에서 데이터를 활용해 THML에 동적으로 표시한다.
//model객체는 키-값 쌍으로 데이터를 저장하고, 템플릿 엔진에서 해당 키를 통해 값을 참조
model.addAttribute("message", "명함이 성공적으로 등록되었습니다!");
return "index"; // 성공 메시지를 보여주면서 다시 메인 페이지로 이동
}
}
여기서 사용되는게 Model 객체인데 Spring MVC에서 사용한다. @RequestParam으로 name속성을 지정한 값들을 가져온다.
나머지는 그냥 저장해주는 기능..
@Slf4j
@RequiredArgsConstructor
public class SearchController {
private final BusinessCardRepository businessCardRepository;
@PostMapping("/search")
public String seacrhRestul(@RequestParam("searchName") String searchName, Model model){
// List<BusinessCard> results = businessCardRepository.findByName(searchName);
List<BusinessCard> results = businessCardRepository.findByNameContaining(searchName);
model.addAttribute("results", results);
model.addAttribute("searchName", searchName);
return "searchList";
}
검색도 마찬가지이다.
String으로 값을 리턴하게 되면 Spring이 view 템플릿을 templates폴더에서 찾는다!
model은 view와 스프링 데이터를 이어주는 역할을 한다.
근데 여기서 한 가지 문제점
나는 RegisterController, SearchController를 따로따로 구분했다. 하지만 두 가지 기능은 모두 명함관련 기능으므로 하나의 Controller에서 관리하는 것이 좋다고 생각했다.
그리고 심지어 SearchController는 Repository에 service가 아닌 Repository에 의존하고 있었다.
참고로 이 부분은 글 쓰다가 발견해서 얼른 리팩토링해줬다.
@Controller
@Slf4j
@RequiredArgsConstructor
public class BusinessCardController {
private final BusinessCardService businessCardService;
@PostMapping("/register")
public String registerBusinessCard(
@RequestParam("name") String name,
@RequestParam("phone") String phone,
@RequestParam("companyName") String companyName,
Model model
){
BusinessCard businessCard = new BusinessCard(name,phone,companyName, LocalDateTime.now());
businessCardService.saveCard(businessCard);
//model , 컨트롤러에서 ㅊ ㅓ리한 데이터(모델, 객체) 를 뷰 템플릿에 넘기기 위해 사용된다.
// 주로 타임리프, JSP같은 뷰 템플릿 엔진에서 데이터를 활용해 THML에 동적으로 표시한다.
//model객체는 키-값 쌍으로 데이터를 저장하고, 템플릿 엔진에서 해당 키를 통해 값을 참조
model.addAttribute("message", "명함이 성공적으로 등록되었습니다!");
return "index"; // 성공 메시지를 보여주면서 다시 메인 페이지로 이동
}
@PostMapping("/search")
public String seacrhRestul(@RequestParam("searchName") String searchName, Model model){
// List<BusinessCard> results = businessCardRepository.findByName(searchName);
List<BusinessCard> results = businessCardService.findBusinessCardByName(searchName);
model.addAttribute("results", results);
model.addAttribute("searchName", searchName);
return "searchList";
}
BusinessCardController하나에 명함관련 처리를 다 넣어줬다. 또한 Repository에 의존하고 있떤 searchResult를 Service에 의존하도록 변경해줬다.
내 머리 + 챗 지피티