자바 어노테이션
어노테이션은 자바 코드에 추가되는 특별한 형태의 메타데이터로, 클래스, 메서드, 변수 등에 대한 추가적인 정보를 제공
어노테이션이 가능한 기술적 근거는 java의 리플렉션, 프록시 등의 기술
- 리플렉션은 런타임에 클래스의 메타데이터를 조회하거나 "수정"할 수 있게 해주는 Java API ⭐ ⭐
- ex) DB 조회기술 - 사용자가 어떤 엔티티를 조회할지 모르기 때문에 이는 런타임에서 결정된다. 이를 객체에 반영해줘야 화면에 객체의 데이터를 띄워줄 수 있다.
- 스프링의 많은 기능이 런타임에서 동작하는 이유는 소스코드의 유연성과 빌드의 효율성을 위해 사용
통신 기능 사전 구현 + 내 소스 코드 => 새 소스 코드 => 컴파일해야지 실행
통신 기능 사전 구현, 내 소스 코드 => 새 소스 코드 X
리플렉션 : 미리 구현되어 있는 어노테이션 코드들을 런타임 상황에서 라이브하게 서로 호환이 되도록 만들어 구동한다.
소스 코드가 가벼워진다는 장점이 있다.
⭐ ⭐ DTO를 사용해보자 ⭐ ⭐
Member는 Getter만 남기고 사용자에게 값을 입력받을 임시 객체 MemberRequestDto를 만들어준다.
Member : DB에 저장될 실제 데이터(엔티티)
MemberRequestDto : 사용자에게 필요한 값만 주거나, 받는 객체
(1) Member는 DB 엔티티에 저장되어야 할 진짜 데이터이기 때문에 Setter를 제거해 변경 가능성 없애준다
(2) 사용자에게 받는 정보와 실제 저장되어야 할 정보가 다르기 때문에 DTO가 필요하다.
--> 사용자는 id 값과, create_time을 모른다. id와 create_time은 개발자 및 시스템 상에서 필요한 속성이다.
@Getter
public class Member {
private int id;
private String name;
private String email;
private String password;
private LocalDateTime create_time;
}
@Data
public class MemberRequestDto {
private String name;
private String email;
private String password;
}
사용자에게 객체를 보여줄 때는 실제 저장된 정보( id, create_time )는 제외하고 보여주어야 한다.
하지만, id 값으로 마지막 실습 예제에서 사용자에게 create_time을 보여주기 때문에 두 속성을 미리 추가해줬다.
사용자에게 반환할 임시 객체 MemberResponseDto를 만들어 생략된 정보로 반환해준다.
@Data
public class MemberResponseDto {
private int id;
private String name;
private String email;
private String password;
private LocalDateTime create_time;
}
Controller는 DTO만 사용하고,
Repositoty에서는 Member 객체만 사용한다.
Service에서는 DTO와 Member를 서로 변환해주는 기능을 수행한다.
MemberRequestDto와 MemberResponseDto를 사용한 Controller, Service, Repository 코드를 살펴보자.
[클래스 다이어그램 추가]
컨트롤러(service 객체 생성) -> 서비스(repository 객체 생성) -> 리포지토리(db 만들어 저장)
MemberRequestDto <--> Member
Member <--> MemberResponseDto 변환 과정은 Service가 처리하고, Controller는 Dto객체만 사용함에 유의하자.
[ MemberController.java ]
✔️ 컨트롤러
사용자 요청 처리, 응답 -> DTO를 Member(DB에 들어갈 진짜 데이터)로 가공하는 과정은 Service에게 넘긴다.
@Controller
@RequestMapping("members")
public class MemberController {
private final MemberService memberService;
MemberController(){
memberService = new MemberService();
}
//회원 목록 조회
@GetMapping()
public String members(Model model){
// memberResponseDtos 가져옴
// 사용자는 Member 객체가 아닌 DTO 객체에 있는 정보만 필요함
model.addAttribute("members", memberService.members());
return "member/memberList";
}
//회원가입
@GetMapping("create")
public String createMember(){
return "member/member-create-screen";
}
@PostMapping("create")
@ResponseBody
public String create(MemberRequestDto memberRequestDto){
System.out.println(memberRequestDto);
memberService.memberCreate(memberRequestDto);
return "ok";
}
}
[ MemoryMemberRepository.java ]
✔️ 리포지토리
데이터가 담긴 공간.
DB 연결하게 되면 DB에서 조회해서 값을 가져와 담아놓는다.
public class MemoryMemberRepository {
private final List<Member> memberDB;
public MemoryMemberRepository(){
memberDB = new ArrayList<>();
}
public List<Member> members(){
return memberDB;
}
public void memberCreate(Member member){
memberDB.add(member);
}
}
[ MemberService.java ]
✔️ 서비스
핵심 로직들을 처리한다.
Controllter와 Repository 사이에서 DTO <--> Member 가공해서 전달한다.
예외 처리한다.
public class MemberService {
private final MemoryMemberRepository memoryMemberRepository;
static int total_id;
public MemberService(){
memoryMemberRepository = new MemoryMemberRepository();
}
public List<MemberResponseDto> members(){
List<Member> members = memoryMemberRepository.members();
List<MemberResponseDto> memberResponseDtos = new ArrayList<>();
for(Member member : members){
MemberResponseDto memberResponseDto = new MemberResponseDto(member.getId(), member.getName(), member.getEmail(), member.getPassword());
memberResponseDtos.add(memberResponseDto);
}
return memberResponseDtos;
}
// 사용자의 입력 값이 담긴 DTO를 통해, 실제 시스템에서 사용되는 정보를 조합해 Member 객체로 변환 후 저장
public void memberCreate(MemberRequestDto memberRequestDto){
LocalDateTime now = LocalDateTime.now();
++total_id;
Member member = new Member(total_id, memberRequestDto.getName(), memberRequestDto.getEmail(), memberRequestDto.getPassword(), now);
memoryMemberRepository.memberCreate(member);
}
}
회원등록 혹은 회원목록조회를 누르면 새 창으로 열렸는데,
두 가지 동작 버튼은 상단에 고정되고, 눌렀을 때 아래 내용만 바뀌도록 수정해보자
기존 Home.html 코드를 header로 이름을 바꾸고, 이 header 파일을 다른 html에서 헤더로 갖게 수정하면 된다.
<div th:replace="member/header :: headerFragment"></div>
[ header.html ]
<!DOCTYPE html>
<!-- 템플릿 엔진(JSP, Thymlear)을 사용-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<div th:fragment="headerFragment">
<a href="/members/create">회원등록</a>
<a href="/members">회원목록조회</a>
</div>
</body>
</html>
[ member-create-screen.html ]
<!DOCTYPE html>
<!-- 템플릿 엔진(JSP, Thymlear)을 사용-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
<meta charset="UTF-8">
<div th:replace="member/header :: headerFragment"></div>
<title>회원가입</title>
</head>
<body>
<form action="http://localhost:8080/members/create" method="POST">
<p>
<label>이름</label>
<input type="text" name="name" maxlength="10" size="15">
</p>
<p>
<label>email</label>
<input type="text" name="email" maxlength="15" size="20">
</p>
<p>
<label>password</label>
<input type="password" name="password" maxlength="15" size="20">
</p>
<input type="submit" value="제출">
</form>
</body>
</html>
[ membeList.html ]
<!DOCTYPE html>
<!-- 템플릿 엔진(JSP, Thymlear)을 사용-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>회원 목록 조회</title>
</head>
<body>
<div th:replace="member/header :: headerFragment"></div>
<table>
<tr>
<th>이름</th>
<th>이메일</th>
<th>비밀번호</th>
</tr>
<!--반복문으로 객체 개수만큼 row 생성-->
<tr th:each = "member : ${members}">
<td th:text="${member.name}"></td>
<td th:text="${member.email}"></td>
<td th:text="${member.password}"></td>
</tr>
</table>
</body>
</html>
header 부분은 통일성 있게 유지되고,
회원등록/회원목록조회를 누름에 따라 아래 화면만 블록 끼우듯이 바뀌는 것을 확인할 수 있다.
추가로 회원 가입하면, 회원 목록으로 리다이렉트 하도록 코드를 수정했다.
@PostMapping("members/create")
public String create(MemberRequestDto memberRequestDto){
System.out.println(memberRequestDto);
memberService.memberCreate(memberRequestDto);
return "redirect:/members"; //url 리다이렉트
}
DI, 의존성 주입 방법이란?
https://rookie-programmer.tistory.com/manage/posts/
실습
1. responseDto에 id 값 추가 (위에서 추가)
2. Controller에 /member/find?id=xx
3. Service 비즈니스 로직 : Repository 호출
4. Repository에서 실질적으로 data Search -> interface도 수정
5. 상세보기화면 : name, email, password, 가입 시간 출력
[ MemberRepository.java ]
public Member findById(int id);
[ MemoryMemberRepository.java ]
@Override
public Member findById(int id) {
for(Member member : memberDB){
if(member.getId() == id){
return member;
}
}
return null;
}
[ MemberService.java ]
@Service // 싱글톤 컴포넌트로 생성 -> 내부 @Component를 통해 "스프링 빈"으로 등록
// 스프링 빈이랑, 스프링이 생성하고 관리하는 객체를 의미
// 제어의 역전(Inversion of Control)
// IoC 컨테이너가 스프링 빈을 관리한다.(빈 생성, 의존성 주입)
public class MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberService(MemoryMemberRepository memoryMemberRepository) {
this.memberRepository = memoryMemberRepository;
}
static int total_id;
public List<MemberResponseDto> members(){
List<Member> members = memberRepository.members();
List<MemberResponseDto> memberResponseDtos = new ArrayList<>();
for(Member member : members){
MemberResponseDto memberResponseDto = new MemberResponseDto();
memberResponseDto.setId(member.getId());
memberResponseDto.setName(member.getName());
memberResponseDto.setEmail(member.getEmail());
memberResponseDto.setPassword(member.getPassword());
memberResponseDto.setCreate_time(member.getCreate_time());
memberResponseDtos.add(memberResponseDto);
}
return memberResponseDtos;
}
// 사용자의 입력 값이 담긴 DTO를 통해, 실제 시스템에서 사용되는 정보를 조합해 Member 객체로 변환 후 저장
public void memberCreate(MemberRequestDto memberRequestDto){
LocalDateTime now = LocalDateTime.now();
++total_id;
Member member = new Member(total_id, memberRequestDto.getName(), memberRequestDto.getEmail(), memberRequestDto.getPassword(), now);
memberRepository.memberCreate(member);
}
public MemberResponseDto findById(int id){
//Member 객체를 MemberRequestDto로 변환
//생성자 초기화보다는 유연성이 좋다.
Member member = memberRepository.findById(id);
MemberResponseDto memberResponseDto = new MemberResponseDto();
memberResponseDto.setId(member.getId());
memberResponseDto.setName(member.getName());
memberResponseDto.setEmail(member.getEmail());
memberResponseDto.setPassword(member.getPassword());
memberResponseDto.setCreate_time(member.getCreate_time());
return memberResponseDto;
}
}
[ MemberController.java ]
@GetMapping("member/find")
public String findMember(@RequestParam("id") int id, Model model){
MemberResponseDto memberResponseDto = memberService.findById(id);
model.addAttribute("member", memberResponseDto);
return "member/member-find-screen";
}
[ member-find-screen.html ]
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
<meta charset="UTF-8">
<title>회원 상세조회</title>
</head>
<body>
<div th:replace="member/header :: headerFragment"></div>
<table>
<tr>
<th>이름</th>
<th>이메일</th>
<th>비밀번호</th>
<th>가입시간</th>
</tr>
<tr>
<td th:text="${member.name}"></td>
<td th:text="${member.email}"></td>
<td th:text="${member.password}"></td>
<td th:text="${member.create_time}"></td>
</tr>
</table>
</body>
</html>
'Back-End 공부 > Spring' 카테고리의 다른 글
스프링 DB 연결 및 Transactional 처리 (0) | 2024.01.16 |
---|---|
스프링 용어 쉽게 정리하기 (빈, 싱글톤, DI, 제어의 역전, IoC 컨테이너) (0) | 2024.01.16 |
application.properties와 application.yml 환경 설정 변경 (1) | 2024.01.15 |
실습으로 CSR, SSR 작동원리 이해하기 CORS 에러 해결 (1) | 2024.01.14 |
렌더링이란? CSR, SSR 그게 도대체 뭔데? (0) | 2024.01.14 |