클래스 차원이나 메서드차원에 트랜잭션을 적용할 수 있다. 예외 발생시 롤백처리가 자동화 되는 어노테이션이다.(단, uncheck예외에만 해당)
의존성 주입 방법 2. 생성자 주입 방식
(1)final 키워드를 통해 상수로 사용하므로 재할당을 막음으로써 안정성을 향상하는 장점
(2)다형성 구현 가능(인터페이스를 활용하여)
(3) 순환참조를 컴파일 타임에 체크할 수 있다.
의존성 주입 방법 3. RequiedArgsConstructor 어노테이션 사용
(1)RequiredArgsConstructor은 반드시 초기화 되어야하는 필드(static)를 대상으로 생성자를 자동으로 만들어주는 어노테이션이다.
(2)먼저 상수로 가져올 계층의 필드값을 선언하고 클래스에 RequiredArgsConstructor어노테이션을 붙이면 자동으로 해당 필드값을 가지는 생성자가 만들어지는 것.
@Controller
@RequestMapping("/member")
@RequiredArgsConstructor
public class MeberController {
의존성주입(Dependency Injection)
방법1. Autowired 어노테이션 사용 : 필드 주입
@Autowired -->싱글톤 객체를 여기 컨트롤러로 가지고 온다.
private MemberService memberService;
**방법2. 생성자 주입 방식 : 객체가 생성될 때 필요한 의존성을 생성자에 전달하여 주입하는 것
-->1.먼저 상수로 가져올 계층을 필드값으로 선언**
private final static MemberService memberService;
**-->2.컨트롤러 생성자 선언을 하는데 매개변수로 가져올 계층을 받아 나의 필드값에 주입.**
@AutoWired **-->여기서 AutoWired는 매개변수에 해당하는 것으로 주입할 필드값이 하나일 경우 생략가능**
public MemberController(MemberService memberService){
this.memberService = memberService;
}
**방법3. RequiredArgsConstructor 어노테이션 사용**
private final static MemberService memberService;
// 홈화면
@GetMapping("")
public String memberHome(){
return "member/home";
}
// 회원 목록 조회
@GetMapping("/list")
public String memberList(Model model){
List<MemberListRes> memberListResList = memberService.findAll();
model.addAttribute("memberList",memberListResList);
return "member/member-list";
}
// 회원 상세 조회(name,email,password 조회되게)
@GetMapping("/detail/{id}")
public String memberDetail(@PathVariable Long id,Model model){
try{
MemberDetailDto dto = memberService.findById(Long id)
return "member/member-detail";
}catch(NoSuchElementException e){
model.addAttribute("errorMessage", e.getMessage());
catch (RuntimeException e) {
model.addAttribute("errorMessage", e.getMessage());
return "member/member-error";
}
}
**-->Service계층에서 id에 해당하는 값이 없으면 예외를 컨트롤러 쪽으로 던지게 구현했기 때문에
try-catch로 예외처리를 해야한다. 그리고 이 예외의 메시지를 모델에 담아 mvc패턴으로 화면에
에러메세지를 클라이언트에게 보낸다.
-->런타임익셉션은 왜 받아야하는 건지 이해 못함...**
// 회원가입(name,email,password)=>MemberCreateDto
@GetMapping("/create")
public String memberCreate(){
return "member/member-create";
}
// 새로운 화면 리턴이 아닌 url 재호출을 통해 redirect
@PostMapping("/create")
public String memberCreatePost(@ModelAttribute MemberCreateDto mcd, Model m1){
try{
memberService.save(mcd);
return "redirect:/member/list"; //redirect는 html화면을 직접 리턴하는 것이 아니라 url링크가 Getmapping된 메서드를 리턴
} catch(illgalArgumentException e){
m1.addAttribute("ErrorMessage",e.getMessage());
return "member/member-error";
}
**-->Service계층의 save메서드에서 중복된 이메일 혹은 8자리 미만의 비밀번호에 대해서는
에러를 던지게 설계해 놓았으므로 여기서 에러를 처리해야한다.**
}
}
public class MemberService {
@Autowired
private MemberMemoryRepository memberMemoryRepository;
case1.
public List<MemberListRes> findAll(){
List<Member> members= memberMemoryRepository.findAll();
List<MemberListRes> memberListRes= new ArrayList<>();
for(Member m : members){
MemberListRes m1 = new MemberListRes(m.getId(),m.getName(),m.getEmail());
memberListRes.add(m1)
}
return memberListRes ;
}
**-->실개발에서 Member객체를 DTO객체로 변환하는 과정이 여러메서드에 중복될 수 있다.
그래서 그 과정을 따로 메서드화 하여 간편화 할 수 있는데, DTO객체를 만드는 과정에서
본연의 엔티티 객체로 부터 속성값을 뽑아내어 조립하니 Member클래스에 메서드를 정의하는 것이 편하다.**
case2.
public List<MemberListRes> findAll(){
return
memberMemoryRepository.findAll().stream().map(m->m.listFromEntity()).Collect(Collector.toList());)
}
**-->findAll까지 는 레포지토리로부터 멤버객체가 담긴 리스트가 리턴되고 스트림api를 활용.
map은 스트림의 각 요소를 특정함수에 적용하여 새로운 값으로 변환하는 중간연산이므로.
스트림에 있는 각 Member요소를 조립메서드를 사용하여 MemberListRes객체로 변환.
그것들을 다시 리스트로만드는 Collect(Collector.toString())사용하면 최종적으로
List<MemberListRes>가 만들어지는 것이다.**
-------------------------------Member클래스------------------------------------
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
public class Member {
private Long id;
private String name;
private String email;
private String password;
public MemberListRes listFromEntity(){
return new MemberListRes(this.id, this.name, this.email);
}
--------------------------------------------------------------------------------------------
case1.
public void save(MemberCreateDto memberCreateDto){
if(MemberMemoryRepository.findByEmail(memberCreateDto.getEmail()).isPresent()){
throw new illgalArgumentException("중복된 이메일 입니다");
}
**-->레포지토리로부터 해당 이메일을 가지는 옵셔널 객체를 전달받는데 그게 널값이 아니라면
중복되는 이메일이 db에 있다는 거니까 에러를 프론트에 넘기는 것**
else if(MemberCreateDto.getPassword().length<8){
throw new illgalArgumentException("비밀번호가 8자리 미만입니다")
}
**-->이런식으로 프론트에서 전달받은 회원 가입 정보를 컨트롤러에서 판단하는 게 아니라
서비스계층에서 로직을 구현한다**
else{Member member = new Member(MemberMemoryRepository.id,memberCreateDto.getName(),
memberCreateDto.getEmail(), memberCreateDto.getPassword());
memberMemoryRepository.save(member);}
**-->문제가 없다면 레포지토리의 메서드를 호출하여 값을 넘긴다.**
**-->그런데, 실개발에서 DTO객체를 Member로 보강하는 과정(엔티티로 만드는 과정)은 여러메서드에 중복될 수 있다.
따라서 이 과정을 따로 메서드화 하여 간편화 할 수 있다.
이때 어찌됐든 DTO객체의 속성값을 활용하여 Member객체로 만드니 이 메서드는 DTO클래스에 정의
하는 것이 편하다.**
}
case2.->**save메서드를 엔티티로 조립하는 메서드를 활용하여 간편화 한 것**
public void save(MemberCreateDto memberCreateDto){
if(MemberMemoryRepository.findByEmail(memberCreateDto.getEmail()).isPresent()){
throw new illgalArgumentException("중복된 이메일 입니다");
}
else if(MemberCreateDto.getPassword().length<8){
throw new illgalArgumentException("비밀번호가 8자리 미만입니다")
}
else{memberMemoryRepository.save(memberCreateDto.toEntity());}
}
------------------------------------------MemberCreateDto 클래스-------------------------
@NoArgsConstructor
@AllArgsConstructor
@Data
public class MemberCreateDto {
private String name;
private String email;
private String password;
public Member toEntity(){
return new Member(MemberMemoryRepository.id,this.name, this.email, this.password);
}
-----------------------------------------------------------------------------------------
case1.
public MemberDetailDto findById(Long id){
Optional<Member> opmember = memberMemoryRepository.findById(id);
Member member =opmember.orElsethrow(()->throw new NoSuchElementException("없는 아이디입니디"))
MemberDetailDto dto = new MemberDetailDto(member.getName(),member.getEmail(),member.getPassword());)
return dto;
}
**-->레포지토리 계층으로부터 옵셔널 객체를 전달받고 orElsethrow()를 통해 예외를 메서드를 호출하는
컨트롤러 에게 넘긴다.
-->그리고 orElsethrow()해서 null이 아니라면 member객체가 반환되니까 그 객체로 필요한 속성값만
뽑아서 DTO객체를 만들어 컨트롤러 계층에 전달하면 된다.
-->그런데! 실제개발에서 Member객체(본연의 객체)를 DTO객체로 만드는 것이 여러 메서드에 중복될 수 있다.
이를 간편화 하기 위해 이 과정을 따로 하나의 메서드 화를 할 수 있음.
예를 들어, 여기서는 Member객체가 DTO객체로 조립하는 과정을 메서드화한다면 어찌됐든 Member
의 속성값을 활용해야하므로 Member클래스에 조립메서드를 생성하는 것이 편하다.**
}
case2.->findById메서드를 조립메서드를 활용하여 간편화한 것
public MemberDetailDto findById(Long id){
return memberMemoryRepository.findById(id).orElseThrow
(()->new NoSuchElementException("없는 아이디").detailFromEntity;
}
**-->(id)까지하면 Optional<Member>가 나오니 orElseThrow가 나오고. null값이 아니면
Member객체가 반환되니. Member 클래스에 구현한 detailFromEntity메서드를 사용하면
DTO객체가 반환되니 그 값을 바로 리턴 할 수 있는 것!**
---------------------Member클래스------------------------------------------
@NoArgsConstructor
@AllArgsConstructor
@Getter
@ToString
public class Member {
private Long id;
private String name;
private String email;
private String password;
public MemberDetailDto detailFromEntity(){
return new MemberDetailDto(this.name, this.email, this.password);
}
**-->자신의 속성값으로 바로 DTO객체를 조립하여 리턴.**
}
@Repository //레포지토리도 안에 컴포넌트 어노테이션이 내장->싱글톤 객체
public class MemberMemoryRepository {
private List<Member> memberList = new ArrayList<>();
public static Long id=1L;
public List<Member> findAll(){
return this.memberList;
}
public void save(Member member){
this.memberList.add(member);
id++;
}
public Optional<Member> findById(Long id){
Member member = null;
for(Member m : memberList){
if(m.getId().equals(id)){
member =m;}
}
return Optional.ofNullable(member);
}
-->**프론트에서 PathVariable방식으로 id를 조회하면 Controller계층에서 Service계층으로 부터
조회용 객체 MemberDetailDto를 넘겨받는다. Service계층에서는 Repository계층에서 Member객체를
전달받아 MemberDetailDto를 조립하여 컨트롤러에 넘겨주는 건데,
프론트에서 전달한 id값이 db에 없다면 예외처리를 해주어야한다. 그래서 레포지토리 계층에서
매개변수로 전달받은 id값을 가진 Member가 없을 수도 있으므로 Optional로 감싸서 서비스 계층에 전달한다**.
public Optional<Member> findByEmail(String inputEmail){
Member member = null;
for(Member m = memberList){
if(m.getEmail().equals(inputEmail)){
member = m;
}
return Optional.ofNullable(member);
}
**-->프론트에서 회원가입할 때 데이터를 보내면 컨트롤러에서 그 데이터를 서비스 계층에 넘기고
서비스 계층에서 중복되는 이메일인지 체크하기 위해 구현된 메서드이다.
서비스 계층에 옵셔널 객체를 전달하여 만약 null값이 아니라면 중복되는 이메일이라는 걸 파악할
수 있으니 서비스계층에서 그때 컨트롤러에 에러를 넘기면 된다.**
}