반응형

먼저 전원 관리 옵션에서 고급 전원 관리 옵션 설정 변경을 누르고

디스플레이 밝기 관련 설정이 보인다면

배터리 사용시 - 최대 성능이나 최대 밝기 와같이 선택을 해준다. 

 

그래도 안되면

AMD - Radeon Softwre

(Intel는 인텔 그래픽 제어판)

에 들어가서 화면 밝기 관련 설정을 찾아보자!

 

나는 AMD 그래픽을 사용해서 이걸 기준으로 보여주겠다.

 

디스플레이에 들어가서 Vari-Bright로 들어가서 최대 밝기로 바꾸어 주면 끝!!

 

참고로 전원 관리 옵션에서 

 

이처럼 각 상황에 맞는 전원 옵션을 만들어주면 배터리만 쓰는 경우, 전원이 꼽혀있는 경우, 무조건 고성능이 필요한 경우 이런식으로 맞게 고르면 편하다.

728x90
반응형
블로그 이미지

아상관없어

,
반응형

스프링 입문 복습


(자바 문법을 모를땐, https://www.youtube.com/playlist?list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp 여기서 해당 부분을 참고하자!)

스프링 프로젝트 생성!

"Https//start.spring.io" => 스프링 관련 프로젝트를 생성해주는 site이다.

Maven/Gradle : 라이브러리를 가져오고 빌드하는 Life Cycle까지 관리해주는 툴이며 요즘은 Gradle을 주로 사용한다.

 

build.gradle

plugins {
	id 'org.springframework.boot' version '2.3.1.RELEASE'
	id 'io.spring.dependency-management' version '1.0.9.RELEASE'
	id 'java'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
    
repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'//thyleaf템플릿 엔진 사용 html틀 만들어줌
	implementation 'org.springframework.boot:spring-boot-starter-web'//해당 라이브러리 안에 톰캣 서버가 있음
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
	exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }//Test를 위한 라이브러리들
}
	test {
	useJUnitPlatform()
}

 

View 환경설정

웰컴 페이지 생성 후 접속

 

localhost:8080/hello를 입력하면 내장톰캣서버가 받고 스프링컨테이너는 hello를 getmapping으로 입력받아

""@GetMapping("hello")" hello를 getmapping으로 받는 컨트롤러 hellocontroller를 실행하고

hellocontroller는 template/hello로 리턴하여 hello.html이 열리게 된다.

그리고 thymeleaf 엔진이 hello.html을 처리한다.

 

<resources/static/index.html>=> 이 경로가 기본 웰컴페이지이다. (다른 파일이 없는 경우 얘가 뜸)

<!DOCTYPE HTML>
<html>
<head>
    <title>Hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
Hello
<a href="/hello">hello</a>
</body>
</html>

 

<controller/HelloController>

package com.example.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

//웹 앱에서 첫번째 진입점이다.
@Controller
public class HelloController {

    //get메소드로 hello가 들어오면
    @GetMapping("hello")
    //spring이 모델을 만들어서 넣어준다.
    //Model은 HashMap 형태를 갖고 있으며, key, value값을 가지고 있습니다. 또한 addAttribute()와 같은 기능을 통해 모델에 원하는 속성과 그것에 대한 값을 주어 전달할 뷰에 데이터를 전달할 수 있습니다.
    public String hello(Model model){
        model.addAttribute("data", "hello!!");//속성, 값
        return "hello"; //template의 hello로
        //기본적으로 resources:template/+{viewname}+.html로 매핑된다.
        //따라서 resources/tempalate/hello.html로 이동한다
    }

}

 

 

<resources/templates/hello.html>

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <!-- th는 thymeleaf 엔진을 뜻함-->
<p th:text="'안녕하세요. ' + ${data}" >안녕하세요. 손님</p>
</body>
</html>

data에 key값으로 hello!!를 넣어줬으므로 hello!!가 뜬다.

 

 

 

정적 컨텐츠

=> 파일을 그대로 뿌려준다. (파일 그대로 전달)

/static 폴더안의 파일을 찾는다.

 

"localhost:8080/hello-static.html"가 넘어오면 먼저 hello-static 관련 컨트롤러가 있는지 확인한다.

왜냐하면 컨트롤러가 우선순위를 가지기 때문이다.

스프링 컨테이너가 hello-static관련 컨트롤러가 없음을 확인하면 /static 폴더안을 살펴보고 있으면 그 파일을 반환한다.

 

<resources/static/hello-static.html>

<!DOCTYPE HTML>
<html>
<head>
    <title>static content</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
정적 컨텐츠 입니다.
</body>
</html>

 

 

MVC와 템플릿 엔진

Model, View, Controller로 분리하여 처리한다.

(예전에는 view에서 모든 로직을 다 처리하여 복잡했다.)

"localhost:8080/hello-mvc?name=spring"이 요청되면 @Getmapping("hello-mvc")가 받아서 뒤의 파라미터 name을 넘겨준다.

그리고 hello-template로 반환한다.

 

=> controller

package com.example.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

//웹 앱에서 첫번째 진입점이다ㅣ.
@Controller
public class HelloController {

    @GetMapping("hello-mvc")
    //@RequestParam(value= "name", required = True ) ctrl+p 할 경우 필요한 파라미터 정보 볼수 있음
    //required가 기본이 트루이기 때문에 name값을 넘겨야됨
    //따라서 localhost:8080/hello-mvc?name=spring
    public String helloMvc(@RequestParam("name") String name, Model model){
        model.addAttribute("name", name);
        //모델이 name값을 넘겨줌
        return "hello-template";
    }


}

 

hello-template.html은 넘겨받은 name값을 표시한다.

<resources/templates/hello-template.html> => View

<html xmlns:th="http://www.thymeleaf.org">
<body>
<p th:text="'hello ' + ${name}">hello! empty</p>
    <!-- 템플릿 엔진이 model의 키 값에서 name인 것을 꺼낸다. 뒤의 hello! empty는 name값이 안넘어오면 표시된다. -->
</body>
</html>

 

 

API

주로 json포맷으로 data가 전달된다.

즉, data만 전달하는 경우이다.

"localhost:8080/hello-api?name=spring"

@ResponseBody를 사용하고 객체를 반환하면 객체가 json으로 변환된다.

(json은 key, value로 이루어진 데이터)

 

"localhost:8080/hello-api?name=spring"

요청시 @Controller가 있으므로 getmapping hello-api를 찾아서 실행한다.

@ResponseBody어노테이션이 있으므로, http의 body부에 그대로 값을 넘긴다.

(즉, viewResolver대신에 HttpMessageConverter가 동작한다.)

반환값이 단순 문자이므로 StringHttpMessageConverter가 기본문자를 처리한다.

원래는 @ResponseBody가 있으면 객체를 반환할 경우 MappingJackson2HttpMessageConverter에 의해서 객체를 자동으로 json타입으로 바꾸어서 반환한다. (key:value )와 같은 형태

-> 이것은 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입정보 두가지를 조합해서 선택한다.

(대부분은 json을 선택하여 ,json으로 반환한다.)

 

package com.example.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

//웹 앱에서 첫번째 진입점이다ㅣ.
@Controller
public class HelloController {

    //문자를 넘기는 경우
     //get방식인데 body에 직접 전달함.
    //requestparam은 name으로 전달달
    @GetMapping("hello-string")
    @ResponseBody //http의 body부에 전달
    public String helloString(@RequestParam("name") String name) {
        return "hello " + name;//"hello spring" (StringConverter)
    }
    
    
    //객체를 넘기는 경우
    @GetMapping("hello-api")
    @ResponseBody//http의 body부에 전달 (viewResolver를 사용하지 않는다.), 객체반환시 json으로 반환된다.
    public Hello helloApi(@RequestParam("name") String name){
        Hello hello = new Hello();
        hello.setName(name);
        return hello;//Hello 객체를 넘기므로 json으로 바꿔짐 (json 컨버터가 바꿔줌(속성 : 값)으로)
        //jsonConverter
    }

    
   static class Hello{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }


}

 

 

 

 

회원 관리 예제


 

비지니스 요구 사항

  1. 데이터 = 회원id, 이름
  2. 기능 = 회원등록, 조회
  3. 데이터 저장소 = 아직 정해지지 않음

 

일반적인 웹 애플리케이션 계층 구조

컨트롤러 : 웹 MVC의 컨트롤러 역할

서비스 : 핵심 비지니스 로직

리포지토리 : DB에 접근, 도메인 객체를 DB에 저장하고 관리

도메인 : 비지니스 도메인 객체 ex) 회원, 주문, 쿠폰 등등 주로 DB에 저장하고 관리됨

 

컨트롤러 -> 서비스 -> 리포지토리 -> DB

컨트롤러, 서비스, 리포지토리 -> 도메인

 

클래스 의존관계

멤버서비스 -> 멤버리포지토리(인터페이스) <- 메모리멤버리포지토리(구현체)

(저장소가 정해지지 않았으니까 일단 멤버리포지토리를 인터페이스로 구현해둠)

 

회원 도메인과 리포지토리 생성

<회원 객체> = 회원 도메인

id와 name을 저장해야하므로 id와 name이 필요하다.

 

<멤버 도메인>

package com.example.hellospring.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

//1. 회원 id가 필요
//2. 회원 name이 필요
public class Member {

    //id, name 생성
    private Long id;
    private String name;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

 

 

<멤버리포지토리> -인터페이스 (구현은 데이터 저장소가 정해지면 구현)

package com.example.hellospring.repository;

import com.example.hellospring.domain.Member;

import javax.swing.text.html.Option;
import java.util.List;
import java.util.Optional;

/*
1. 저장
2. 찾기
2.1. id로 찾기
2.2. name으로 찾기
2.3. 전부 다 찾기
 */
public interface MemberRepository {
    //1. 저장(도메인은 member 클래스)
    Member save(Member member);

    //2.1 id로 찾기
    //optional은 null일경우 값이 없다고 알려준다.
    Optional<Member> findbyId(Long id);
    //2.2 name으로 찾기
    Optional<Member> findbyName(String name);
    //2.3 모두다 찾기
    //Member형의 List
    List<Member> findAll();

}

 

<메모리멤버리포지토리> - 멤버리포지토리를 일단 메모리에 저장, 따라서 멤버리포지토리 인터페이스 구현

package com.example.hellospring.repository;

import com.example.hellospring.domain.Member;
import org.springframework.stereotype.Repository;

import java.util.*;

//MemberRepository 구현
//리포지토리는 저장
//@Repository //리포지토리라고 알려줌 스프링에, 따라서 스프링이 가져옴 즉, 스프링 빈 등록
public class MemoryMemberRepository implements MemberRepository{

    //map을 이용하여 저장(키, 값)
    private static Map<Long, Member> store = new HashMap<>();
    // hashmap은 키와 값을 가지는 자료구조이다.
    //hasing을 사용하기 때문에 검색이 빠르다. 
    //A -> Hash(A) -> 값 :: A로 값이 매칭된다.
    private static long sequence = 0L;//시퀀스

    @Override
    public Member save(Member member) {
        //id값 설정
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional<Member> findbyId(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public Optional<Member> findbyName(String name) {
        return store.values().stream()
                .filter(member -> member.getName().equals(name))
                .findAny();//하나라도 찾으면
    }

    @Override
    public List<Member> findAll() {
        return new ArrayList<>(store.values());
    }

    public void clearStore(){
        store.clear();
    }
}

 

 

회원 리포지토리 테스트 케이스 작성

자바는 JUnit이라는 프레임워크로 테스트를 실행할 수 있다.

따라서 반복실행, 여러테스트를 한꺼번에 할 수 있다.

"src/test/java" 하위 폴더에 생성한다.

 

package com.example.hellospring.repository;

import com.example.hellospring.domain.Member;
//import org.junit.jupiter.api.Assertions;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.*;

//테스트만을 위해쓰니까 굳이 public으로 선언하지 않아도 됌
class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();


    //테스트가 끝날때 마다 동작
    //어떤 메소드가 동작이 끝나면 실행됨
    //한번에 여러테스트 진행시 db에 이전 테스트 데이터가 남으므로 메소드가 끝나면 데이터를 지워준다,.
    //테스트는 순서가 보장되지 않으므로 서로 독립적이게 설정해야함
    @AfterEach
    public void afterEach(){
        repository.clearStore();
    }


    @Test
    public void save(){
        Member member = new Member();
        member.setName("Spring");

        repository.save(member);
        Member result = repository.findbyId(member.getId()).get();
        //단순히 출력해보아도됨 하지만 글자로 볼순 없음
        //Assertions.assertEquals(member, result);//기대, 비교값
        //member객체와 result 객체를 비교한다.
        //사용법만 익히자
        assertThat(member).isEqualTo(result); //멤버가 result와 같으냐
    }


    @Test
    public void findbyName(){
        //멤버1 생성후 리포지토리에 저장
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        //멤버2 생성후 리포지토리에 저장
        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findbyName("spring1").get();

        assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll(){
        //멤버1 생성후 리포지토리에 저장
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        //멤버2 생성후 리포지토리에 저장
        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();
        assertThat(result.size()).isEqualTo(2);
    }
}

 

회원 서비스 개발

 

package com.example.hellospring.service;

import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;


public class MemberService {
    
    private final MemberRepository memberRepository = new MemberRepository();


    /**
     * 회원가입
     * 가입시 id값 반환
     * 먼저 중복 이름을 가지는 회원이 있는지 확인
     */
    public Long join(Member member){
        
        //만약 존재한다면 에러 던짐
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();

    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findbyName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });//ctrl + T로 람다식 변환
    }

    /**
     * 전체회원 조회
     */
    public List<Member> findMember(){
        return memberRepository.findAll();
    }

    /**
     * 한 회원만 조회
     */
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findbyId(memberId);
    }
}

 

 

회원 서비스 테스트

package com.example.hellospring.service;

import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;


public class MemberService {
    
    //기존에는 회원 서비스가 메모리 회원 리포지토리를 직접 생성하게 했음
    //private final MemberRepository memberRepository = new MemberRepository();
    
    /*
    멤버 서비스에서 사용하는 멤버리포지토리랑
    멤버서비스테스트에서 사용하던 멤버리포지토리랑 다르다.
    멤버리포지토리의 store가 static이므로 같은 멤버리포지토리를 가르키지만 static이 아닌 경우에는 다르게된다.
    */
    private final MemberRepository memberRepository;
    
    //멤버리포지토리를 받아서 멤버 서비스에 넘겨준다.
	public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }


    /**
     * 회원가입
     * 가입시 id값 반환
     * 먼저 중복 이름을 가지는 회원이 있는지 확인
     */
    public Long join(Member member){
        
        //만약 존재한다면 에러 던짐
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();

    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findbyName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });//ctrl + T로 람다식 변환
    }

    /**
     * 전체회원 조회
     */
    public List<Member> findMember(){
        return memberRepository.findAll();
    }

    /**
     * 한 회원만 조회
     */
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findbyId(memberId);
    }
}

 

멤버리포지토리를 받아서 멤버 서비스에 넘겨줫으므로 멤버서비스 테스트를 보면

package com.example.hellospring.service;

import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemberRepository;
import com.example.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

class MemberServiceTest {

    //멤버 서비스, 멤버 리포지토리 객체 생성
    MemberService memberService;
    MemoryMemberRepository memberRepository;

    //같은 리포지토리를 사용하기 위해서 "멤버 서비스에 넘겨줌" => 위에서 바꿨음
    //해당 테스트 클래스를 초기화할 때 딱 한번 수행되는 메서드
    //각 테스트 실행 전에 호출된다. 
    //테스트가 서로 영향이 없도록 항상 새로운 객체를 생성하고 의존관계도 새로 맺어줌
    @BeforeEach
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        //같은 멤버 리포지토리가 사용됨
        memberService = new MemberService(memberRepository);
    }

    @AfterEach
    public void afterEach(){
        memberRepository.clearStore();
    }
    @Test
    void join() {

        //저장한게 리포지토리에 있는게 맞아?? => 멤버리포지토리 필요
        //given
        Member member = new Member();
        member.setName("hello");
        //만약 hello가 아니라 spring인 경우 밑 중복회원예외 메소드랑 값(db에 겹침)이 겹침

        //when
        //회원추가 할때
        Long saveId = memberService.join(member);


        //then
        //중복되는 경우 잘 걸러냄?
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외(){
        //given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        //when
        memberService.join(member1);

        //2. assertThrows를 이용하는 방법
        //뒤 로직을 동작할때 앞의 예외가 터져야댐
        //assertThrows(IllegalStateException.class, () -> memberService.join(member2));

        //3. IllegalStateException 객체를 받아서 사용하는 방법법
        IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

        //1. try-catch를 사용하는 방법 ->복잡
        /*
        try{
            memberService.join(member2);
            fail();//혹시 catch로 안가면
        } catch (IllegalStateException e){
            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다."))
        }*/


    }
    @Test
    void findMember() {
    }

    @Test
    void findOne() {
    }
}

 

 

 

스프링 빈과 의존관계


컴포넌트 스캔과 자동 의존관계 설정

회원 컨트롤러가 회원서비스와 회원 리포지토리를 사용할 수 있게 의존관계를 준비!

(회원 컨트롤러가 회원서비스와 회원리포지토르 두개를 통해서 데이터 조회를 할 수 있음.)

 

 

멤버컨트롤러에서 멤버서비스를 하나만 생성해두고 공용으로 사용한다.

이때 공용으로 사용하기 위해서 Autowired를 사용하여 컨트롤러 등록시 컨테이너가 객체를 생성해서 가지고 있게 한다.

package com.example.hellospring.controller;

import com.example.hellospring.domain.Member;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

//스프링이 처음 실행될때 스프링 컨테이너에 컨트롤러 어노테이션이 있으면 스프링이 객체를 생성해서 들고 있음
// => 스프링 컨트롤러에서 스프링 빈이 관리된다고 함
@Controller
public class MemberController {
    //하나만 생성해두고 공용으로 사용하면됨 굳이 new해서 새로 생성할 필요가 없다
    //private final MemberService memberService = new MemberService();
    //new해서 새로 생성해서 사용하면 멤버 컨트롤러 외의 다른 컨트롤러들(예를들어 주문)이 가져가서 쓸 수 있음.
    //하지만 멤버서비스를 가면 별기능이 없음. 따라서 하나만 생성해서 공용으로 사용하는 것이 나음
    //따라서 스프링 컨테이너에 등록을 하고 사용한다.   

    private final MemberService memberService;

    //컨트롤러 등록시 컨테이너가 객체를 생성(이때 생성자를 호출한다.)해서 들고 있게된다.
    //즉, 생성자에 Autowired가 선언되어 있으면, 멤버서비스를 스프링이 컨테이너에 있는 멤버서비스에 연결시켜준다.
    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    //회원등록 폼 이동
    @GetMapping("/members/new")
    public String createForm(){
        return "members/createMemberForm";
    }

    //회원 등록 값 입력후 회원 생성
    @PostMapping("/members/new")
    public String create(MemberForm form){
        Member member = new Member();
        member.setName(form.getName());

        memberService.join(member);

        return "redirect:/";
    }

    //회원 조회
    @GetMapping("/members")//members로 이동(home.html에서 회원목록 누르면 /members로 이동하게 되어 있음)
    public String list(Model model){
        List<Member> members = memberService.findMember();
        model.addAttribute("members", members);
        return "members/memberList";
    }
}

즉, 스프링컨테이너가 뜰때 MemnberController가 등록(스프링빈으로 등록)되는데 Autowired로 MemberSerivce를 연결했다.

하지만 MemberSerivce(@Controller 어노테이션이 없다!)는 순수 자바 파일이므로 스프링빈으로 등록되지 않았으므로 스프링 컨테이너 실행시 등록되어있지 않는다.

따라서 MemberSerivce에 @Service 어노테이션을 붙여주면 스프링이 올라올때 스프링 컨테이너에 멤버 서비스를 등록시켜준다.

 

package com.example.hellospring.service;

import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;


//Service어노테이션은 MemberService를 스프링 컨테이너에 등록시켜준다
//원래 멤버 서비스 클래스는 어떠한 어노테이션도 없었다.
//보통은 컨트롤러 어노테이션을 붙이면 스프링 컨테이너가 알아서 객체를 생성해서 갖고 있지만
//멤버 서비스는 그렇지 않았다.
@Service
public class MemberService {
    
    //기존에는 회원 서비스가 메모리 회원 리포지토리를 직접 생성하게 했음
    //private final MemberRepository memberRepository = new MemberRepository();
    
    /*
    멤버 서비스에서 사용하는 멤버리포지토리랑
    멤버서비스테스트에서 사용하던 멤버리포지토리랑 다르다.
    멤버리포지토리의 store가 static이므로 같은 멤버리포지토리를 가르키지만 static이 아닌 경우에는 다르게된다.
    */
    private final MemberRepository memberRepository;
    
    //멤버리포지토리를 받아서 멤버 서비스에 넘겨준다.
    @Autowired 
    //=>memberRepository가 필요하니까 연결, memberRepository는 @Repository로 스프링 빈 등록되어있음
	public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }


    /**
     * 회원가입
     * 가입시 id값 반환
     * 먼저 중복 이름을 가지는 회원이 있는지 확인
     */
    public Long join(Member member){
        
        //만약 존재한다면 에러 던짐
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();

    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findbyName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });//ctrl + T로 람다식 변환
    }

    /**
     * 전체회원 조회
     */
    public List<Member> findMember(){
        return memberRepository.findAll();
    }

    /**
     * 한 회원만 조회
     */
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findbyId(memberId);
    }
}

 

 

스프링 빈을 등록하는 2가지 방법

  1. 컴포넌트 스캔과 자동 의존관계 설정

    => 어노테이션을 사용한다. EX) @Service 어노테이션 안에는 @Component 어노테이션이 들어있다.

    컴포넌트 스캔 원리

    @Component 어노테이션이 있으면 스프링 빈으로 자동등록된다.

    객체를 생성해서 스프링에 등록한다. 그 후 Autowired가 각각 연결시켜준다.(각 객체들을)

    @Controller 컨트롤러가 스프링빈으로 자동등록된 이유도 컴포넌트 스캔때문이다.

    @Controller, @Service, @Repository 전부다 @Component를 포함하고 있다.

     

    따라서 MemoryMemberRepository 또한 어노테이션을 붙여준다.

    package com.example.hellospring.repository;
    
    import com.example.hellospring.domain.Member;
    import org.springframework.stereotype.Repository;
    
    import java.util.*;
    
    //MemberRepository 구현
    //리포지토리는 저장
    @Repository //리포지토리라고 알려줌 스프링에, 따라서 스프링이 가져옴 즉, 스프링 빈 등록
    public class MemoryMemberRepository implements MemberRepository{
    
        //map을 이용하여 저장(키, 값)
        private static Map<Long, Member> store = new HashMap<>();
        private static long sequence = 0L;//시퀀스
    
        @Override
        public Member save(Member member) {
            //id값 설정
            member.setId(++sequence);
            store.put(member.getId(), member);
            return member;
        }
    
        @Override
        public Optional<Member> findbyId(Long id) {
            return Optional.ofNullable(store.get(id));
        }
    
        @Override
        public Optional<Member> findbyName(String name) {
            return store.values().stream()
                    .filter(member -> member.getName().equals(name))
                    .findAny();//하나라도 찾으면
        }
    
        @Override
        public List<Member> findAll() {
            return new ArrayList<>(store.values());
        }
    
        public void clearStore(){
            store.clear();
        }
    }
    
    

     

    스프링컨테이너 안을 보면

    memberController --> memberService --> memberRepository 와 같이 연결되어있다.

    즉, memberController는 memberService, memberRepository가 필요하다.

    따라서 각각 @Controller, @Service, @Repository로 등록이 되고 사용시 @Autowired로 연결시켜준다.

     

    memberController --> memberService --> memberRepository

    이것들은 각각 스프링 빈인데, 스프링은 스프링 컨테이너에 스프링 빈을 등록할때 기본으로 싱글톤으로 등록한다.

    즉, 각 객체들을 하나만 등록해서 공유한다. => 같은 스프링빈이면 모두 같은 인스턴스이다.

    (memberController는 memberController 하나만, memberService는 memberService 하나만 등록과 같이)

    예를 들어 OrderService가 있어서 memberRepository에 접근시, 멤버 서비스나 오더 서비스는 동일한 멤버 리포지토리에 접근한다.

    따라서 메모리가 절약된다.

     

     

    만약 아무곳에나 @Component가 있어도 될까?(ex 다른곳에 아무 패키지 만들어서 demo라는 파일을 만들경우라던가)

    =>안된다.

    HelloSpring.java(main있음)를 실행시키면, Package hello.hellospring와 동일하거나 하위에 있는 것들은 스프링이 다 찾는다.

    즉, 다른 패키지에 있으면 스프링이 컴포넌트 스캔을 안하므로 등록이 안된다.

 

 

  1. 자바 코드로 직접 스프링 빈 등록하기

회원 서비스와 회원 리포지토리의 @Service, @Repository, @Autowired 어노테이션을 제거하고 진행한다.

 

hellospring/SpringConfig 생성

package com.example.hellospring;


import com.example.hellospring.repository.JdbcTemplateMemberRepository;
import com.example.hellospring.repository.JpaMemberRepository;
import com.example.hellospring.repository.MemberRepository;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

//자바 코드로 직접 스프링 빈 등록
@Configuration
public class SpringConfig {


    //직접 스프링 빈으로 등록한다고 알려줌으로써 DI(의존성 주입)를 해줌, 즉 연결시킴
    //스프링 빈을 등록할거임!! 알려줌줌 => 등록하렴!
    //자바 코드로 직접 멤버 서비스를 등록함/ @Service 어노테이션으로 등록하는게 아니라
    //직접 memberService 객체를 생성시켜서 등록함!
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository()); //생성자에서 memberRepository를 넣어줘야함
        //밑의 memberRepository를 엮어야댐
    }

    //생성자에서 memberRepository를 넣어줘야하므로 등록시켜줌 리포지토리도
    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository(); 


    /**
     * 실행시 멤버 서비스와 멤버리포지토리를 스프링 빈에 등록하고
     * 스프링 빈에 등록된 멤버리포지토리를 멤버서비스에 넣어줌
     * 따라서 memberService -> memberRepository 연결됨
     * @Controller는 스프링이 관리하는거이기때문에 어쩔 수 없다.
     * 컴포넌트 스캔으로 올라가고 컴포넌트 스캔이므로 Autowired로 연결되어야한다.
     * 장단점이 있다.
     *
     */


}

 

DI에는

  • 필드주입 = @Autowired

    @Autowired를 통한 DI는 helloController, memberService 등과 같이 스프링이 관리하는 객체에서만 동작을한다!

    스프링빈으로 등록하지 않고 직접 생성한 객체에서는 동작하지 않는다.

    왜냐하면 스프링 컨테이너가 들고 있지 않으니까!

 

  • 생성자 주입 = 생성자를 통해서 주입

  • setter 주입 = setter 설정으로 생성하고 @Autowired (말 그대로 setter로 주입됨.)

    (누군가 멤버 컨트롤러를 호출 했을때 public으로 열려 있어야함.

    => public하게 노출되어버린다.(아무나 변경가능해진다.) 중간에 변경되면 문제가 생길 수 도 있다.)

 

** 실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔방식을 사용한다. 정형화 되지 않거나 상황에 따라 구현 클래스를 변경해야하마녀 설정을 통해 스프링 빈으로 등록한다.

{정형화(보통 일반적으로 자주 쓰는 틀로 만든(저장, 조회 등 일반적으로 자주 쓰는))}

 

==> 향후 메모리 리포지토리에서 다른 리포지토리로 변경할 예정이므로 컴포넌트 스캔방식이 아닌 자바코드로 스프링 빈을 설정했다.

 

 

 

회원 관리 예제 - 웹 MVC 개발


회원 웹 기능 - 홈 화면 추가

 

HomeController 추가

package com.example.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

    //컨트롤러가 정적파일보다 우선순위가 높음
    //도메인 "/"
    @GetMapping("/")
    public String home(){
        return "home"; //home.html 불러옴
    }
}

 

회원 관리용 홈

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
    <div>
        <h1>Hello Spring</h1>
        <p>회원 기능</p>
        <p>
            <a href="/members/new">회원 가입</a> <!-- 링크 -->
            <a href="/members">회원 목록</a>
        </p>
    </div>
</div> <!-- /container -->
</body>
</html>

 

 

회원 웹기능 - 등록

회원 등록 폼 컨트롤러

package com.example.hellospring.controller;

import com.example.hellospring.domain.Member;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

//스프링이 처음 실행될때 스프링 컨테이너에 컨트롤러 어노테이션이 있으면 스프링이 객체를 생성해서 들고 있음
// =>
@Controller
public class MemberController {
    //하나만 생성해두고 공용으로 사용하면됨 굳이 new해서 새로 생성할 필요가 없다
    //private final MemberService memberService = new MemberService();
    //따라서 스프링 컨테이너에 등록을 하고 사용한다.

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    //member/new 요청시 member/createMemberForm 반환
    //회원등록 폼 이동
    @GetMapping("/members/new")
    public String createForm(){
        return "members/createMemberForm";
    }



}

 

회원 등록 폼 HTML

(resources/templates/members/createMemberForm)

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
    <!-- 등록을 누르면 Post방식으로 넘어온다. (데이터 전달) "/members/new"에 전달해줌-->
    <form action="/members/new" method="post">
        <div class="form-group">
            <label for="name">이름</label>
            <input type="text" id="name" name="name" placeholder="이름을
입력하세요">
        </div>
        <button type="submit">등록</button>
    </form>
</div> <!-- /container -->
</body>
</html>

 

회원 등록 컨트롤러

웹 등록 화면에서 데이터를 전달 받을 폼 객체

package com.example.hellospring.controller;

public class MemberForm {
    private String name;//name을 입력받음

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

그러면 이제 실제 회원을 등록하는 기능을 MemberController에 추가해준다.

package com.example.hellospring.controller;

import com.example.hellospring.domain.Member;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

//스프링이 처음 실행될때 스프링 컨테이너에 컨트롤러 어노테이션이 있으면 스프링이 객체를 생성해서 들고 있음
// =>
@Controller
public class MemberController {
    //하나만 생성해두고 공용으로 사용하면됨 굳이 new해서 새로 생성할 필요가 없다
    //private final MemberService memberService = new MemberService();
    //따라서 스프링 컨테이너에 등록을 하고 사용한다.

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    //회원등록 폼 이동
    @GetMapping("/members/new")
    public String createForm(){
        return "members/createMemberForm";
    }

    /*==========================================================*/
    //회원 등록 값 입력후 회원 생성
    //post로 /members/new를 가져옴
    @PostMapping("/members/new")
    public String create(MemberForm form){ //위에서 만든 MemberForm으로 데이터 받음
        Member member = new Member();
        member.setName(form.getName());

        memberService.join(member);

        return "redirect:/";//홈화면으로 보냄
        
    }
    /*==========================================================*/

}

 

 

회원 웹 기능 - 조회

회원 컨트롤러에 조회기능 생성

package com.example.hellospring.controller;

import com.example.hellospring.domain.Member;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

//스프링이 처음 실행될때 스프링 컨테이너에 컨트롤러 어노테이션이 있으면 스프링이 객체를 생성해서 들고 있음
// =>
@Controller
public class MemberController {
    //하나만 생성해두고 공용으로 사용하면됨 굳이 new해서 새로 생성할 필요가 없다
    //private final MemberService memberService = new MemberService();
    //따라서 스프링 컨테이너에 등록을 하고 사용한다.

    private final MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    //회원등록 폼 이동
    @GetMapping("/members/new")
    public String createForm(){
        return "members/createMemberForm";
    }

    //회원 등록 값 입력후 회원 생성
    @PostMapping("/members/new")
    public String create(MemberForm form){
        Member member = new Member();
        member.setName(form.getName());

        memberService.join(member);

        return "redirect:/";
    }

    /*===============================================*/
    //회원 조회
    @GetMapping("/members")//members로 이동(home.html에서 회원목록 누르면 /members로 이동하게 되어 있음)
    public String list(Model model){
        List<Member> members = memberService.findMember();
        model.addAttribute("members", members);
        return "members/memberList";//여기로 이동
    }
    /*===============================================*/
}

 

 

회원 리스트 html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div class="container">
    <div>
        <table>
            <thead>
            <tr>
                <th>#</th>
                <th>이름</th>
            </tr>
            </thead>
            <tbody>
                <!-- members를 읽어들이고 id와 name가져옴(thymeleaf버전의 for each같은 문법임)
				즉 회원들을 조회를 하는 것임-->
            <tr th:each="member : ${members}">
                <td th:text="${member.id}"></td>
                <td th:text="${member.name}"></td>
            </tr>
            </tbody>
        </table>
    </div>
</div> <!-- /container -->
</body>
</html>

 

 

 

 

 

 

 

스프링 DB 접근 기술


H2 데이터베이스를 사용하여 스프링에서 접근해본다.

h2데이터베이스는 개발이나 테스트 용도로 가볍고 편리한 DB, 웹화면을 제공해준다.

 

drop table if exists member CASACDE;
create table member{
	id bigint getnerated by default as identity, 
--값을 입력하지 않으면 DB가 알아서 채운다는 뜻이다.(identity 방식)
	name varchar(255),
	primary key(id)
}

 

순수 Jdbc

고대의 기술이라 불린다...

대충 이렇구나만 하고 넘어가자..

 

gradle.build에 관련 라이브러리 추가

implementation 'org.springframework.boot:spring-boot-starter-jdbc'//자바-db 연동시 필요
runtimeOnly 'com.h2database:h2'//db가 제공하는 클라이언트

 

스프링 부트 데이터베이스 연결 설정 추가

resources/application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa

이렇게 세팅하면 스프링 부트가 DataSource를 만들어 놓는다.

즉, 데이터베이스 접속 정보를 만들어 놓는다.

 

Jdbc 회원 리포지토리

package com.example.hellospring.repository;


import com.example.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;//스프링에게서 주입받아야한다.
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource;//스프링에게서 주입받아야한다.
    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    //저장을 위해서 insert 쿼리가 필요하다 따라서 직접 작성
    @Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection(); //DB 연결 만듬
            pstmt = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS);//sql 입력
            pstmt.setString(1, member.getName()); // sql의 ?에 매칭됨
            pstmt.executeUpdate(); //DB에 쿼리 날림
            rs = pstmt.getGeneratedKeys(); 
            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException("id 조회 실패");
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs); //외부에 연결을 했으므로 다 끊어줘야한다. 아니면 위험하다.
        }
        //예외를 많이 던져서 복잡하다..
    }
    @Override
    public Optional<Member> findbyId(Long id) {
        String sql = "select * from member where id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public List<Member> findAll() {
        String sql = "select * from member";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            List<Member> members = new ArrayList<>();
            while(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                members.add(member);
            }
            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public Optional<Member> findbyName(String name) {
        String sql = "select * from member where name = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }

    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs)
    {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}

 

 

스프링 설정 변경

package com.example.hellospring;


import com.example.hellospring.repository.JdbcTemplateMemberRepository;
import com.example.hellospring.repository.JpaMemberRepository;
import com.example.hellospring.repository.MemberRepository;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

//자바 코드로 직접 스프링 빈 등록
@Configuration
public class SpringConfig {

    private final DataSource dataSource;
    private final EntityManager em;


    @Autowired //생성자가 하나라면 생략가능
    public SpringConfig(DataSource dataSource, EntityManager em) {
        this.dataSource = dataSource;
        this.em = em;
    }

    //스프링 빈을 등록할거임!! 알려줌줌 => 등록하렴!
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository()); //생성자에서 memberRepository를 넣어줘야함
        //밑의 memberRepository를 엮어야댐
    }

    //생성자에서 memberRepository를 넣어줘야하므로 등록시켜줌 리포지토리도
    @Bean
    public MemberRepository memberRepository(){
        //return new MemoryMemberRepository(); //MemberRepository는 인터페이스 이니까
        return new JdbcMemberRepository(dataSource); //이 코드추가로 메모리멤버리포지토리에서 jdbc리포지토리로 바뀌엇다.
        //다른 코드를 변경하지 않고 저장소를 단 한줄로 바꾸었따.!!
 

    /**
     * 실행시 멤버 서비스와 멤버리포지토리를 스프링 빈에 등록하고
     * 스프링 빈에 등록된 멤버리포지토리를 멤버서비스에 넣어줌
     * 따라서 memberService -> memberRepository 연결됨
     * @Controller는 스프링이 관리하는거이기때문에 어쩔 수 없다.
     * 컴포넌트 스캔으로 올라가고 컴포넌트 스캔이므로 Autowired로 연결되어야한다.
     * 장단점이 있다.
     *
     */


}

DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체다. 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둔다. 그래서 DI를 받을 수 있다.

스프링 부트가 DataSource를 생성하고 스프링 빈 등록하니까 주입받아서 쓸수 있음

 

OCP (Open-Closed 원칙)

개방-폐쇄 원칙.

확장에는 열려있고 수정 변경에는 닫혀있다.

스프링의 DI를 사용하면 기존코드를 손대지않고 설정만으로 구현클래스를 변경할 수 있다.

위의 예시서첨 Memory멤버리포지토리에서 코드만 변경하여 jdbc멤버 리포지토리로 변경한것처럼.

 

스프링 통합 테스트

스프링 컨테이너와 DB까지 연결한 통합 테스트!

(이전 테스트코드들은 그냥 자바파일들이엿지만, 지금은 데이터가 DB에 저장되고 DB연결 정보도 스프링이 가지고 있따.)

package com.example.hellospring.service;

import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest//테스트시 스프링부트 가져옴 (스프링 컨테이너와 테스트를 함께 실행한다.)
@Transactional//데스트시 DB 트랜잭션 롤백시킴(테스트가 끝나면 커밋 안함), 따라ㅓㅅ 다음 테스트에 영향안줌
public class MemberServiceIntegrationTest {
    
    //Test 케이스 이므로 그냥 필드 주입시킴
    //new 로 생성하지 않고 스프링에게 달라고 요청함
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    public void 회원가입() throws Exception {
        //Given
        Member member = new Member();
        member.setName("hello");
        //When
        Long saveId = memberService.join(member);
        //Then
        Member findMember = memberRepository.findbyId(saveId).get();
        assertEquals(member.getName(), findMember.getName());
    }
    @Test
    public void 중복_회원_예외() throws Exception {//thorws Exception => 이 메소드가 예외를 던질 수 도 있음
        //Given
        Member member1 = new Member();
        member1.setName("spring");
        Member member2 = new Member();
        member2.setName("spring");
        //When
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> memberService.join(member2));//예외가 발생해야 한다.
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }
}


 

 

스프링 JdbcTemplate

순수 jdbc에서 봣던 반복 코드들을 대부분 제거해준다.

하지만 SQL은 직접 작성해야한다.

(Jdbc => DB와 스프링 중간에서 번역해서 아무 DBMS에 다 맞게 번역해줌)

package com.example.hellospring.repository;

import com.example.hellospring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;

import javax.print.attribute.HashPrintJobAttributeSet;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class JdbcTemplateMemberRepository implements MemberRepository {

    //JdbcTemplate 사용!!
    private final JdbcTemplate jdbcTemplate;//인젝션 받을 수 있는건 아님

    //JdbcTemplate 사용시 DataSource가 필요하다.
    @Autowired //생성자가 하나인 경우에 생략가능하다.
    public JdbcTemplateMemberRepository(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);//datasource = db접근 정보 넘겨줌
    }

    //save하는 경우는 DB에 Insert하는 경우이다. 따라서 명시적으로 insert하기 위해 SimpleInsert사용
    @Override
    public Member save(Member member) {
        /*
        SimpleJdbcInsert은 알아서 insert문을 생성해준다.
        테이블명과 키 컬럼을 명시해주면 알아서 insert해준다.
        또한 이전 jdbc와 다르게 ?에 값을 매칭시키는게 아니라 명시적으로 이름을 지정하여 의존성을 높여준다.
        만약 ?, ?로 두개의 값을 매칭할때 두 값이 순서가 바뀌면 문제가 되버리니 명시적으로 지정한다.
        */
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
        //테이블명은 member이고 키 컬럼은 id이다.

       
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", member.getName()); //parameters는 Map타입이므로 각 값들 put해줌

        //key를 받고 넘겨줌, key는 sql 결과
        //이구문은 다시한번 봐야할듯!!!!
        //파라미터를 넘겨서 실행하고 생성된 키 값을 받아옴
        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        member.setId(key.longValue());//member에 setID해서 넣어줌 id값
        return member;
    }

    @Override
    public Optional<Member> findbyId(Long id) {
        List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
        return result.stream().findAny();//stream으로 변환해서 하나라도 있으면 반환
    }

    @Override
    public Optional<Member> findbyName(String name) {
        //Member객체들의 List이므로 List<Member>라고 사용
        List<Member> result = jdbcTemplate.query("select * from member where name=?", memberRowMapper(), name);
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        //memberRowMapper는 member객체를 반환하므로 그대로 씀
        //해당 쿼리 결과(리스트로 반환됨)을 memberRowMapper()가 매핑해준다. 그리고 그걸 반환함
        // sql, 반환타입, 인자 (현재 sql문에는 인자가 필요없으므로 없다.)
        return jdbcTemplate.query(("select * from member"), memberRowMapper());
    }

    //ResultSet rs를 받아서 rowNum만큼 반복해서 member객체에 id와 name을 설정해준다.
    private RowMapper<Member> memberRowMapper(){
            return (rs, rowNum) -> {
                Member member = new Member();
                //rs로 결과를 받아서 member에 매핑해서 member를 반환한다.
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return member;

            };
        
     /*
     템플릿으로부터 ResultSet을 받고 필요한 정보를 추출해서 리턴한다.
     ResultSet의 row하나만 매핑하기 위해 사용한다.
     */
            /*
            Alt+Enter로 람다식으로 바꿀 수 있음
            return new RowMapper<Member>() {
                @Override
                public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Member member = new Member();
                    member.setId(rs.getLong("id"));
                    member.setName(rs.getString("name"));
                    return member;

                }
            };
             */
    }
}

 

RowMapper

기존에는

ResultSet rs = stat.executeQuery("select ~~~ ")
while(rs.next()){//다음 row로
    user = new User();
    ~~setId
    ~~setName
        ...
}

위와 같은 방식이였다.

즉, ResultSet으로 값을 받고 그 다음 User 객체에 담아서 반환하는 방식이였다.

(

ResultSet(java.sql.ResultSet)은 즉 결과임 쿼리문에 대한 결과임.

Select문 결과 조회할 수 있는 방법을 정의한 인터페이스이다.

즉, 결과 집합이다.

next(), getString(), getInt() ...등을 사용해서 값을 가져올수 잇다.

)

JdbcTemplate는 이것을 줄여서 쓴다.

//ResultSet rs를 받아서 rowNum만큼 반복해서 member객체에 id와 name을 설정해준다.
    private RowMapper<Member> memberRowMapper(){
            return (rs, rowNum) -> {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return member;

            };

rs( = ResultSet)이 값을 가져오고

rowNum만큼 반복해서

객체에 저장해서 반환한다.

 

스프링 설정에서 기존 jdbc에서 jdbcTemplate으로 리포지토리를 바꾼다.

package com.example.hellospring;


import com.example.hellospring.repository.JdbcTemplateMemberRepository;
import com.example.hellospring.repository.JpaMemberRepository;
import com.example.hellospring.repository.MemberRepository;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

//자바 코드로 직접 스프링 빈 등록
@Configuration
public class SpringConfig {

    private final DataSource dataSource;
    private final EntityManager em;


    @Autowired
    public SpringConfig(DataSource dataSource, EntityManager em) {
        this.dataSource = dataSource;
        this.em = em;
    }

    //스프링 빈을 등록할거임!! 알려줌줌 => 등록하렴!
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository()); //생성자에서 memberRepository를 넣어줘야함
        //밑의 memberRepository를 엮어야댐
    }

    //생성자에서 memberRepository를 넣어줘야하므로 등록시켜줌 리포지토리도
    @Bean
    public MemberRepository memberRepository(){
        //return new MemoryMemberRepository(); //MemberRepository는 인터페이스 이니까
        //return new JdbcMemberRepository(dataSource); //이 코드추가로 메모리멤버리포지토리에서 jdbc리포지토리로 바뀌엇다.
        return new JdbcTemplateMemberRepository(dataSource);//jdbc 템플릿으로 바꿈 레포지토리를

    }

    /**
     * 실행시 멤버 서비스와 멤버리포지토리를 스프링 빈에 등록하고
     * 스프링 빈에 등록된 멤버리포지토리를 멤버서비스에 넣어줌
     * 따라서 memberService -> memberRepository 연결됨
     * @Controller는 스프링이 관리하는거이기때문에 어쩔 수 없다.
     * 컴포넌트 스캔으로 올라가고 컴포넌트 스캔이므로 Autowired로 연결되어야한다.
     * 장단점이 있다.
     *
     */


}

 

 

JPA


jpa는 개 편한방식이다...

기존 반복코드도 줄여주고, 기본적인 sql문도 자기가 직접 다 만들어준다.

따라서 개발 생산성이 크게 높아진다.

 

gradle에 관련 라이브러리 추가

plugins {
	id 'org.springframework.boot' version '2.5.10'
	id 'io.spring.dependency-management' version '1.0.11.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	//implementation 'org.springframework.boot:spring-boot-starter-jdbc'//자바-db 연동시 필요
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'//jpa, jdbc다 포함함
	runtimeOnly 'com.h2database:h2'//db가 제공하는 클라이언트
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

스프링 부트에 JPA 설정 추가

resources/application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa

spring.jpa.show-sql=ture//jpa가 날리는 쿼리를 볼수잇게 해줌
spring.jpa.hibernate.ddl-auto=none
    //JPA를 사용하면 member객체를 보고 자기가 table을 자동으로 만든다.
    //하지만 위에서 이미 테이블을 다 만들었으므로 이 옵션은 끔
    //자동 생성을 원하면 create로 설정해두면됨

 

JPA는 인터페이스(자바 표준 인터페이스, 구현은 여러업체에서한다.)이다. 인터페이스만 제공해준다.

구현체로 hibernate ...등이 있다. (대부분 hibernate만 사용한다.)

JPA는 객체와 ORM(object, Relational(관계형DB), Mapping(어노테이션으로))

 

JPA 엔티티 매핑

package com.example.hellospring.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

//1. 회원 id가 필요
//2. 회원 name이 필요
@Entity//jpa가 관리하는 엔티티라고 알림
public class Member {

    //id -> p.k라고 매핑시킴, 기본키 지정시 설정해야됨 db에서 자동으로 id값을 생성해주므로 identiy라고 적음
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    //id, name정보로 JPA는 sql문을 자동으로 작성해준다.
    //ex) @Column (name = "username") -> 만약 DB컬럼명이 username인경우, 매핑시킴 (DB에 있는 컬럼명은 username이라고 매핑됨))

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

 

JPA 회원 리포지토리

package com.example.hellospring.repository;

import com.example.hellospring.domain.Member;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.swing.text.html.parser.Entity;
import java.util.List;
import java.util.Optional;


public class JpaMemberRepository implements MemberRepository {

    /*
    gradle에서 "~~-data-jpa"라이브러리를 받으면
    스프링부트가 자동으로 EntityManager(DB랑 연결)를 생성해준다.
     */
    private final EntityManager em;

    //injection 받음
    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        //persist => 영구 저장하다.
        em.persist(member);//쿼리는 알아서 다 작성해줌
        return member;
    }

    @Override
    public Optional<Member> findbyId(Long id) {
        //find(타입, 식별값=p.k)
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findbyName(String name) {
        //JPQL이다. 객체대상으로 쿼리를 날리면 sql로 번역이된다. P.K가 아닌 경우에 사용된다.
        //JPQL, 반환Type
        List<Member> result = em.createQuery("select m from Member m where m.name= :name", Member.class)
                .setParameter("name", name) //name컬럼에서 name값 설정
                .getResultList();
        return result.stream().findAny();//해당 name을 찾으면 반환함
    }

    @Override
    public List<Member> findAll() {
        //기본키가 아니므로 JPQL문을 사용하여 가져옴
        //m은 member Entity 자체를 select(이미 다 매핑되어있음)
        //결과를 list로 가져옴
        return em.createQuery("select m from Member m", Member.class).getResultList();
    }
}

 

그리고 서비스 계층에 트랜젝션 어노테이션을 추가해야된다.

왜냐면 DB에 접근하기때문에.

스프링은 해당 클래스의 메서드를 실행할때 트랜잭션을 시작하고 메서드가 종료되면 트랜잭션을 커밋함.

만약 런타임 예외가 발생하면 롤백한다.

JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.

package com.example.hellospring.service;

import com.example.hellospring.domain.Member;
import com.example.hellospring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

//Service어노테이션은 MemberService를 스프링 컨테이너에 등록시켜준다
//원래 멤버 서비스 클래스는 어떠한 어노테이션도 없었다.
//보통은 컨트롤러 어노테이션을 붙이면 스프링 컨테이너가 알아서 객체를 생성해서 갖고 있지만
//멤버 서비스는 그렇지 않았다.
//@Service
@Transactional //data를 저장하거나 변경하므로 필요하다.
//해당 클래스의 메서드를 실행할때 트랜잭션을 시작하고 종료되면 트랜잭션을 커밋함, 런타임 예외시는 롤백
public class MemberService {
    
    //private final MemberRepository memberRepository = new MemberRepository();
    private final MemberRepository memberRepository;

    //alt+ insert => constructor 생성
    //외부에서 멤버리포지토리를 넣어주게 만듬 //테스트환경에서 필요해짐
    //생성자에서 멤버리포지토리가 필요하므로 스프링이 가져와서 넣어줌(MemberRepository는 MemoryMemberRepository가 구현함)
    //따라서 MemoryMemberRepository를 연결시켜준다.(서비스에 주입시켜줌)
    //@Autowired //스프링 빈에 등록
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /*
    setter 주입법
    private memberRepository memberRepository; //주의! -> final 선언이 아님

    @Autowired
    public setMemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    이러면 다른곳에서 memberService.setMemberService()로 변경가능해진다.
    따라서 별로 잘 안씀

    의존관계가 실행중에 동적으로 변하는 경우는 잘 없다.!
     */


    /**
     * 회원가입
     * 가입시 id값 반환
     * 먼저 중복 이름을 가지는 회원이 있는지 확인
     */
    public Long join(Member member){
        
        //만약 존재한다면 에러 던짐
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();

    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findbyName(member.getName())
                .ifPresent(m -> {
                    throw new IllegalStateException("이미 존재하는 회원입니다.");
                });
    }

    /**
     * 전체회원 조회
     */
    public List<Member> findMember(){
        return memberRepository.findAll();
    }

    /**
     * 한 회원만 조회
     */
    public Optional<Member> findOne(Long memberId){
        return memberRepository.findbyId(memberId);
    }
}

 

JPA를 사용하도록 스프링 설정을 변경해준다.

package com.example.hellospring;


import com.example.hellospring.repository.JdbcTemplateMemberRepository;
import com.example.hellospring.repository.JpaMemberRepository;
import com.example.hellospring.repository.MemberRepository;
import com.example.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

//자바 코드로 직접 스프링 빈 등록
@Configuration
public class SpringConfig {

    private final DataSource dataSource;
    private final EntityManager em;


    @Autowired
    public SpringConfig(DataSource dataSource, EntityManager em) {
        this.dataSource = dataSource;
        this.em = em;
    }

    //스프링 빈을 등록할거임!! 알려줌줌 => 등록하렴!
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository()); //생성자에서 memberRepository를 넣어줘야함
        //밑의 memberRepository를 엮어야댐
    }

    //생성자에서 memberRepository를 넣어줘야하므로 등록시켜줌 리포지토리도
    @Bean
    public MemberRepository memberRepository(){
        //return new MemoryMemberRepository(); //MemberRepository는 인터페이스 이니까
        //return new JdbcMemberRepository(dataSource); //이 코드추가로 메모리멤버리포지토리에서 jdbc리포지토리로 바뀌엇다.
        //return new JdbcTemplateMemberRepository(dataSource);//jdbc 템플릿으로 바꿈 레포지토리를
        return new JpaMemberRepository(em);//jpa는 EntityManager가 필요하다.
    }

    /**
     * 실행시 멤버 서비스와 멤버리포지토리를 스프링 빈에 등록하고
     * 스프링 빈에 등록된 멤버리포지토리를 멤버서비스에 넣어줌
     * 따라서 memberService -> memberRepository 연결됨
     * @Controller는 스프링이 관리하는거이기때문에 어쩔 수 없다.
     * 컴포넌트 스캔으로 올라가고 컴포넌트 스캔이므로 Autowired로 연결되어야한다.
     * 장단점이 있다.
     *
     */


}

 

 

 

스프링 데이터 JPA


스프링 부트와 jpa를 사용하면 편하지만, 여기에 스츠링 데이터 jpa를 사용하면 더욱 편해진다.

예를 들어 리포지토리에 구현 클래스 없이 인터페이스만으로도 개발을 완료 할 수 있다.

즉, 단순 반복도 줄이고 기본 CRUD기능도 스프링 데이터 jpa가 모두 제공한다.

 

crud => 데이터 생성(Create), 검색(Read), 갱신(Update), 삭제(Delete)

 

스프링 데이터 JPA 회원 리포지토리

package com.example.hellospring.repository;

import com.example.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;//스프링 데이터 jpa가 제공함(인터페이스에 대한 구현체를 자동으로 만든다.)

import java.util.Optional;

//인터페이스 다중상속
//JpaRepository 와 MemberRepository 인터페이스를 가져옴
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
    Optional<Member> findByName(String name);// 구현 완료!!! 나머진 데이터 JPA CRUD 다 만들어준다.

}

 

스프링 데이터 jpa 회원 리포지토리를 사용하도록 스프링 설정 변경

package com.example.hellospring;


import com.example.hellospring.repository.MemberRepository;
import com.example.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.swing.*;

//자바 코드로 직접 스프링 빈 등록
@Configuration
public class SpringConfig {
    
    private final MemberRepository memberRepository;

    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    //스프링 컨테이너가 멤버리포지토리를 찾는다.
    //하지만 등록한게 없다. 킹치만.. => SpringDataJpaMemberRepository 가 있음
    //스프링 데이터 JPA가 자동으로 SpringDataJpaMemberRepository를 스프링 빈으로 자동등록해줌
    
    //memberRepository를 인젝션 받아서 등록해준다.
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository);
    }
    //스프링빈 등록해줌
    
    
/*
    private final DataSource dataSource;
    private final EntityManager em;

    @Autowired
    public SpringConfig(DataSource dataSource, EntityManager em) {
        this.dataSource = dataSource;
        this.em = em;
    }

    //스프링 빈을 등록할거임!! 알려줌줌 => 등록하렴!
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository()); //생성자에서 memberRepository를 넣어줘야함
        //밑의 memberRepository를 엮어야댐
    }

    //생성자에서 memberRepository를 넣어줘야하므로 등록시켜줌 리포지토리도
    @Bean
    public MemberRepository memberRepository(){
        //return new MemoryMemberRepository(); //MemberRepository는 인터페이스 이니까
        //return new JdbcMemberRepository(dataSource); //이 코드추가로 메모리멤버리포지토리에서 jdbc리포지토리로 바뀌엇다.
        //return new JdbcTemplateMemberRepository(dataSource);//jdbc 템플릿으로 바꿈 레포지토리를
        return new JpaMemberRepository(em);//jpa는 EntityManager가 필요하다.
    }

    /**
     * 실행시 멤버 서비스와 멤버리포지토리를 스프링 빈에 등록하고
     * 스프링 빈에 등록된 멤버리포지토리를 멤버서비스에 넣어줌
     * 따라서 memberService -> memberRepository 연결됨
     * @Controller는 스프링이 관리하는거이기때문에 어쩔 수 없다.
     * 컴포넌트 스캔으로 올라가고 컴포넌트 스캔이므로 Autowired로 연결되어야한다.
     * 장단점이 있다.
     *
     */

*/
}

 

 

스프링 데이터( Repository(interface) <- CrudRepository(interface) <- PagingAndSortingRepository(interface )) <- 스프링 데이터 JPA(JpaRepository(interface)) 와 같은 관계를 가진다.

각 리포지토리들에 기본적인 CRUD 메소드들과 자주 사용하는 기본 메소드들이 있다.

따라서 자동으로 다 생성해준다.

그러므로 가져다쓰기만 하면됌!

findByName()이나 findByEmail()처럼 메서드 이름만으로 조회 기능을 제공해준다.

 

package com.example.hellospring.repository;

import com.example.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;//스프링 데이터 jpa가 제공함(인터페이스에 대한 구현체를 자동으로 만든다.)

import java.util.Optional;

//인터페이스 다중상속
//JpaRepository 와 MemberRepository 인터페이스를 가져옴
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
    //JPA는 select m from Member m where m.name=?로 JPQL을 짠다.
    //메소드 이름이 findBy"Name"이니까!!
    //따라서 name이 아니라 username이라던가 nameAndid라고 되어잇는경우는
    //By뒤에만 바꾸어 주면 나머지는 스프링 데이터 JPA가 알아서 다 짜준다.
    Optional<Member> findByName(String name);

}

 

복잡한 동적쿼리는 Querydsl이라는 라이브러리를 사용하면 된다.

이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나 스프링 JdbcTemplate를 사용하면된다.

 

728x90
반응형
블로그 이미지

아상관없어

,
반응형

회원관리 예제


  • 회원 객체
package hello.hellospring.domain;

public class Member {
    //id식별자와 이름이 요구됨
    private Long id;
    private String name;

    //getter, setter생성
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 

  • 회원 리포지토리 인터페이스
package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.List;
import java.util.Optional;

//회원 객체 저장소
public interface MemberRepository {
    Member save(Member member);//저장소에 회원 저장
    Optional<Member> findById(Long id);
    Optional<Member> findByName(String name);
    //optional 가져오는 값이 없으면 null일 수 잇음(NPE 방지)
    List<Member> findAll();//저장된 모든 회원 리스트 반환
}

 

  • 회원 리포지토리 메모리 구현체
package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.*;

public class MemoryMemberRepository implements MemberRepository{

    //key는 long 값은 Memeber로
    private  static Map<Long, Member> store = new HashMap<>();//hashmap객체 map 생성(key는 long, value는 member
    private static long sequence = 0L;//0부터 키값 생성해줌

    @Override
    public Member save(Member member) {
        //id값 세팅후 store에 저장
        member.setId(++sequence);//save시 시퀀스값 올려줌
        store.put(member.getId(),member);

        return member;
    }

    @Override
    //store에서 꺼내서 찾으면됨
    public Optional<Member> findById(Long id) {
        //return store.get(id); => null이 반환될수도 있음
        return Optional.ofNullable(store.get(id));//NPE가 발생되지 않게 Optional로 감싸줌
    }

    @Override
    //store에서
    public Optional<Member> findByName(String name) {
        return store.values().stream()
                //stream은 iterator와 유사 => 요소를 순차적 처리, stream()은 stream객체로 반환
                .filter(memeber -> memeber.getName().equals(name))//member 객체를 받아서 equal하면 리턴
                .findAny();//filter로 찾은 값 중 아무값이나 하나 선택
        //루프를 돌면서 찾아지면 반환함, 끝까지 돌려서 없으면 Optional에 null이 포함되어 반환
    }

    @Override
    public List<Member> findAll() {
        //리스트로 반환
        //매개변수로 넘어온 컬렉션 객체가 저장되어 있는 ArrayList를 만듦
        //values는 값의 목록을 Collection 타입으로 리턴함
        return new ArrayList<>(store.values());
    }

    public void clearStore(){
        store.clear();
    }
}

 

 

  • 회원 리포지토리 메모리 구현체 테스트
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.junit.jupiter.api.Test;

//굳이 public하지 않아도됨 어디서 가져다 쓰지 않기 때문에
class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository= new MemoryMemberRepository();

    @Test// 테스트를 수행하는 메소드가 된다.
    public void save(){
        Member member = new Member();
        member.setName("spring");

        repository.save(member);

        //반환타입이 Optional이므로 값을 꺼낼때 get으로 꺼냄냄
        //get() 메소드를 사용하면 Optional 객체에 저장된 값에 접근할 수 있습니다.
        //Optional<Member>이므로
        Member result = repository.findById(member.getId()).get();
        System.out.println("result = "+(result == member));

    }
}

 

 

출력하여서 확인하여도 된다.

 

출력하지 않고서 확인하는 법은

Assertion을 사용하는 것이다.

 

Assertions.assertThat(member).isEqualTo(result);

를 사용하여도 된다.(org.assertj.core.api)

 

 

    @Test
    public void findByName(){
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByName("spring1").get();

        Assertions.assertThat(result).isEqualTo(member1);

 

정리하면

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

//굳이 public하지 않아도됨 어디서 가져다 쓰지 않기 때문에
class MemoryMemberRepositoryTest {
    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach//메소드가 끝날때 마다 동작
    //test시 member객체를 계속 만들어서 중복이 되버리므로
    public void afterEach(){
        repository.clearStore();
    }

    @Test// 테스트를 수행하는 메소드가 된다.
    public void save() {
        Member member = new Member();
        member.setName("spring");

        repository.save(member);

        //반환타입이 Optional이므로 값을 꺼낼때 get으로 꺼냄냄
        //get() 메소드를 사용하면 Optional 객체에 저장된 값에 접근할 수 있습니다.
        //Optional<Member>이므로
        Member result = repository.findById(member.getId()).get();
        //System.out.println("result = "+(result == member));
        //Assertions.assertEquals(member, null);
        Assertions.assertThat(member).isEqualTo(result);

    }

    @Test
    public void findByName() {
        Member member1 = new Member();
        member1.setName("spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        Member result = repository.findByName("spring1").get();

        Assertions.assertThat(result).isEqualTo(member1);
    }

    @Test
    public void findAll() {
        Member member1= new Member();
        member1.setName("Spring1");
        repository.save(member1);

        Member member2 = new Member();
        member2.setName("spring2");
        repository.save(member2);

        List<Member> result = repository.findAll();
		//크기가 2인지 확인한다.
        Assertions.assertThat(result.size()).isEqualTo(2);
    }

}

=> 자바는 Junit이라는 프레임워크로 테스트를 실행한다.

(main메소드나 웹 애플리케이션의 컨프롤러를 통하여 테스트할 경우 오래걸리고 반복실행, 여러테스트를 한번에 실행하기 어렵다.)

  • JUnit

    자바에서 독립단위 테스트(Unit Test)를 지원해주는 프레임워크

    assert메소드로 테스트케이스의 수행결과를 판별한다.

    @Test : 테스트를 수생하는 메소드 (각각 테스트가 서로 영향을 주지 않고 독립적으로 실행, @Test마다 객체를 생성)

    @AfterEach, BeforeEach : Test메소드가 실행될때 호출됨(각각 메소드 실행 후, 실행전 호출)

    @AfterAll, BeforeAll : 클래스에 존재하는 모든 메소드를 실행한다고 할때, 메소드 시작전, 끝난후에 실행됨

    메소드설명
    assertEquals(x, y)· 객체 x와 y가 일치함을 확인합니다.· x(예상 값)와 y(실제 값)가 같으면 테스트 통과
    assertArrayEquals(a, b);· 배열 A와 B가 일치함을 확인합니다.
    assertFalse(x)· x가 false 인지 확인합니다.
    assertTrue(x)· x가 true 인지 확인합니다.
    assertTrue(message, condition)· condition이 true이면 message표시
    assertNull(o)· 객체o가 null인지 확인합니다.
    assertNotNull(o)· 객체o가 null이 아닌지 확인합니다.
    assertSame(ox, oy)· 객체 ox와 oy가 같은 객체임을 확인합니다.· ox와 oy가 같은 객체를 참조하고 있으면 테스트 통과· assertEquals()메서드는 두 객체의 이 같은지 확인하고, assertSame()메서드는 두 객체의 레퍼런스가 동일한가를 확인합니다. (== 연산자)
    assertNotSame(ox, oy)· ox와 oy가 같은 객체를 참조하고 있지 않으면 통과
    assertfail()· 테스트를 바로 실패처리

 

728x90
반응형

'공부 > Spring' 카테고리의 다른 글

스프링 웹개발 기초  (0) 2021.10.20
자바 - Map  (0) 2021.10.19
자바 - Set  (0) 2021.10.18
자바 - 컬렉션(List)  (0) 2021.10.15
자바 - 제네릭  (0) 2021.10.14
블로그 이미지

아상관없어

,
반응형

스프링


스프링은 자바의 웹 프레임워크이다.

자바로 다양한 앱을 만들기 위한 프로그래밍 툴

 

thymeleaf : html 템플릿 엔진

 

.idea : intellij가 사용하는 설정파일

 

gradle : gradle관련 폴더

 

src : main, test가 있음 main이랑 test가 나뉘어져있음

main밑에 가면 자바랑 리소스(실제 자바코드파일을 제외한 어떤 xml, properties, 설정파일, html등)가 있음, 자바 밑에 실제 소스파일

test는 테스트코드들과 관련된 소스들이 있음

테스트코드가 중요하단 소리 요즘 개발 트렌드

 

build.gradle 이 중요

버전설정, 라이브러리 땡겨옴

plugins {
   id 'org.springframework.boot' version '2.5.4'
   id 'io.spring.dependency-management' version '1.0.11.RELEASE'
   id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11' //자바 11버전

repositories {
   mavenCentral() //mavencentral싸이트에서 라이브러리를 다운받음
}

//라이브러리들
dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'//thymeleaf
   implementation 'org.springframework.boot:spring-boot-starter-web'//web
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
   //테스트라이브러리가 자동으로 들어감 
}

test {
   useJUnitPlatform()
}

 

.gitignore

깃에는 필요한 소스코드만 올라감, start.spring.io에서 관리해줌

 

gradlew

gradlew.bat

 

settings.gradle

 

 

 

콘솔로 빌드 하기

해당 디렉토리로 이동후 "gradlew.bat build" 입력

image-20210928173216290

cd build

cd libs

java -jar hello-spring-0.0.1-SNAPSHOT.jar

image-20210928173505805image-20210928173523377

 

서버배포시 "hello-spring-0.0.1-SNAPSHOT.jar" 파일만 복사해서 옮기고 실행하면 된다.

그러면 서버에서 스프링이 동작하게 된다.

 

"gradle clean" or "gradle clean build" 명령어 입력시 build폴더가 없어진다.

 

 

스프링 웹 개발 기초


정적 컨텐츠 : 파일을 그대로 웹브라우저에 내려줌. 그냥 파일을 그대로 전달해줌

MVC와 템플릿 엔진 : 가장많이 하는 방식, (jsp, php 등 템플릿 엔진), 서버에서 프로그래밍해서 동적으로 웹브라우저에 내림, 서버에서 변형을 해서 전달해줌

API : json데이터 포맷으로 클라이언트에게 데이터를 전달해줌, 뷰나 리액트에서 사용(데이터만 주면 화면은 클라이언트가 알아서 그림), 서버끼리 통신할때(데이터만 왓다갓다)

 

 

- 정적컨텐츠

image-20210928174256185

resources/static 폴더 안 아무 html파일 하나 생성후 작성

image-20210928174427627

정적파일이 그대로 반환이 됨.

따로 프로그래밍을 할 수는 없다.

 

thymeleaf엔진은 서버없이 html파일을 열어볼 수 있음

image-20210930220333376image-20210930220344251

 

 

image-20210930220846830

동작하지 않음

 

2021-09-30 22:08:36.038 WARN 3676 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'name' for method parameter type String is not present]

"Required request parameter 'name' for method parameter type String is not present" 임

 

image-20210930221110958image-20210930221354214

변환된 html이 넘어간다.

템플릿 방식은 뷰가 있고 거기서 화면을 조작함

 

 

api사용법

@GetMapping("hello-string")
    @ResponseBody //http에서 body부분의 데이터를 직접 넣어주겟다 라는 뜻
    public String helloString(@RequestParam("name") String name){
        return "hello" + name;//문자가 그대로 내려감,
        // 템플릿 엔진과의 차이점은 뷰 엔진이 없고 문자가 그대로 내려감

    }

 

http://localhost:8080/hello-string?name=string!!!

image-20210930221801049image-20210930221809163

문자만 그대로 내려감

728x90
반응형

'공부 > Spring' 카테고리의 다른 글

회원관리 예제 - 회원도메인, 리포지토리, 테스트케이스  (0) 2021.10.20
자바 - Map  (0) 2021.10.19
자바 - Set  (0) 2021.10.18
자바 - 컬렉션(List)  (0) 2021.10.15
자바 - 제네릭  (0) 2021.10.14
블로그 이미지

아상관없어

,

자바 - Map

공부/Spring 2021. 10. 19. 15:49
반응형

Map

key와 value로 이루어져있다.

키는 해당 map에서 중복되지않고 키와 값이 1:1로 저장된다.

(키가 다르고 값이 같은 경우 서로 다른 것으로 간주한다.)

 

모든 데이터는 키와 값이 존재

키없이 값만 저장 불가

값 없이 키만 저장 불가

키는 해당 맵에서만 고유함

값은 중복되어도 상관없음

 

java.util.Map 인터페이스로 선언되어 있다.

 

주요 메소드는

V put(K key, V value) : 키와 값을 데이터에 저장한다.

void putAll(Map<? extends K, ? extends V> m) : 매개변수로 넘어온 Map의 모든 데이터를 저장

V get(Object key) : key에 해당하는 값을 넘겨줌

V remove(Object key) : key에 해당되는 값을 넘기고, 해당 키와 값을 삭제한다.

Set keySet() : 키의 목록을 Set타입으로 리턴함

Collection values() : 값의 목록을 Collection 타입으로 리턴함

Set<Map.Entry<K,V>> entrySet() : Map안에 Entry라는 타입의 Set을 리턴함

int size() : Map 크기 리턴

void clear() : Map내용 지움

 

Map을 구현한 주요 클래스들은 HashpMap, TreeMap, LinkedHashMap, Hashtable 등이 있다.

 

Hashtable 클래스는 Map 인터페이스를 구현하였지만, 다른 Map을 구현한 클래스와는 조금 다르다.

Map은 키,값, 키-값 쌍으로 데이터를 순환하여 처리 가능 // Hashtable은 키-값 쌍으로 데이터를 순환하여 처리 불가

Map은 iteratrion을 처리하는 도중에 데이터를 삭제하는 안전한 방법 제공 // Hashtable은 제공 X

Map은 Collection view 사용 // Hashtable은 Enumeration 객체를 통하여 데이터 처리

 

HashMap과의 차이는

Hashtable은 키나 값에 null을 저장할 수 없고

HashMap과 다르게 여러 쓰레드에서 동시 접근이 가능하다.

 

=> 중요한 것은 어떤 작업에 어떤 클래스가 어울리는지 알고 사용하는 것이다.

 

HashMap

Object - AbstractMap - HashMap

 

생성자

HashMap() : 16개의 저장곤간을 갖는 HashMap 객체 생성

HashMap(int initialCapacity) : 매개변수만큼의 저장공간을 갖는 HashMap객체 생성

HashMap(int initialCapactiy, float loadFactor) : 첫 매개변수의 저장공간을 갖고, 두번째 매개 변수의 로드 팩터를 가지는 객체 생성

HashMap(Map<? extends K, ? extends V> m) : 매개 변수로 넘어온 Map을 구현한 객체에 있는 데이터를 갖는 HashMap객체 생성

 

대부분 HashMap 객체 생성시에는 매개변수가 없는 생성자를 사용한다.

 

import java.util.HashMap;
import java.util.Set;
import java.util.Map.Entry;

public class MapSample {
	public static void main(String[] args){
		MapSample sample=new MapSample();
		sample.checkHashMap();
	}
	//HashMaP객체 생성 후 값 넣기
	public void checkHashMap(){
		HashMap<Strig, String> map = new HashMap<String, String>();
		map.put("A", "a"); //키-값 순서이다.
		
		System.out.println(map.get("A"));
		System.out.println(map.get("B")); // ->null을 리턴 없는값이므로\
		
		map.put("A", "1"); // A의 값이 1로 변경된다.(값 입력, 수정 모두 put을 이용)
		
		//키와 값 모두 확인하려면??
		map.put("C", "c");
		map.put("D", "d");
		Set<String> keySet=map.keySet();//keySet()메소드 사용
		//출력의 순서는 뒤죽박죽이다. List와 Queue외에는 저장순서가 중요하지 않다
		for(String tempKey:keySet){
			System.out.println(tempkey+"="+map.get(tempKey));//값은 get으로 가져옴
		}
		
		//객체에 담겨있는 값만 확인하려면?? => value()
		Collection<String> values=map.values();//HashMap 객체에 담긴 값을 Collection 타입의 목록으로 반환
		for(String tempValue:values){
			System.out.println(tempValue);
		}
		
		
		//Entry 객체를 사용하여 키와 값을 얻기 => getKey(), getValue()사용
		Set<Entry<String, String>> entries= map.entrySet();
		for(Entry<String, String> tempEntry:entries){
			System.out.println(tempEntry.getKey()+"="+tempEntry.getValue())
		}
		
		//키나 값이 존재하는지 확인 => containsKey(), containsValue() 리턴값은 참 거짓
		map.containsKey("A");
		map.containsValue("a");
		
		//데이터 삭제 => remove()
		map.remove("A"); //A를 키로 갖는 데이터가 삭제됨

 

728x90
반응형

'공부 > Spring' 카테고리의 다른 글

회원관리 예제 - 회원도메인, 리포지토리, 테스트케이스  (0) 2021.10.20
스프링 웹개발 기초  (0) 2021.10.20
자바 - Set  (0) 2021.10.18
자바 - 컬렉션(List)  (0) 2021.10.15
자바 - 제네릭  (0) 2021.10.14
블로그 이미지

아상관없어

,

자바 - Set

공부/Spring 2021. 10. 18. 18:15
반응형

Set

순서 상관없이 어떤 데이터가 조재하는지 확인하기 위한 용도로 사용

중복되는 것을 방지하고 원하는 값이 포함되어 있는지 확인하는 용도

 

Set 인터페이스를 구현한 주요 클래스는 HashSet, TreeSet, LinkedHashSet가 있다

 

  • HashSet

순서가 필요없는 데이터를 해시테이블에 저장함. 성능이 가장 좋음

Object - AbstractCollection - AbstarctSet - HashSet 구조로 상속받는다.

 

HashSet의 생성자에는

HashSet() : 데이터를 저장할 수 있는 16개의 공간과 0.75의 load factor를 갖는 객체를 생성한다.

HashSet(Collection<? extends E> c) : 매개 변수로 받은 컬랙션 객체의 데이터를 HashSet에 담는다.

HashSet(int initialCapacity) :매개 변수로 받은 개수만큼의 데이터 저장 공간과 0.75의 load factor를 갖는 객체를 생성

HashSet(int initialCapacity, float loadFactor) : 첫번째 매개변수로 받은 개수만큼의 데이터 저장공간과, 두번째 매개변수로 받은 만큼의 load factor를 갖는 객체를 생성한다.

 

load factor : 데이터 개수/ 저장공간

데이터 개수가 증가하여 load factor 보다 커지면 저장공간의 크기는 증가되고 해시 재정리 작업을 해야한다.

=> 자료구조를 다시 생성하므로 "성능에 영향"

load factor가 클수록 공간은 넉넉해지지만, 데이터를 찾는 "시간은 증가"하게 된다.

=> 초기 공간 개수와 load factor는 데이터의 크기를 고려하여 산정하는 것이 좋음

 

HashSet의 주요 메소드들

boolean add(E e) : 데이터 추가

void clear() : 모든 데이터 삭제

Object clone() : HashSet 객체를 복제, 담겨잇는 데이터는 복제X

boolean isEmpty() : 데이터가 있는지 확인함

boolean contains(Object o) : 해당 객체가 있는지 확인

Iterator iterator() : 데이터를 꺼내기 위한 Iterator 객체를 리턴함

boolean remove(Object o) : 매개 변수로 넘어온 객체를 삭제함

int size() : 데이터의 개수

 

import java.util.HashSet;
import java.util.Iterator;

...
... main ...
String[] cars = new String[]{"Hyundai", "Kia", "Samsung", "BMW", "Audi", "Kia", "Samsung"};

...

//차종의 개수를 알아내려는 메소드
public int getCarKinds(String[] cars){
	if(cars == null) return 0;
	if(cars.length==1) return 1;
	
	//HashSet을 생성하여 add해줌 => 중복된 값이 없어짐
	HashSet<String> carSet=new HashSet<String>();
	for(String car : cars){
		carSet.add(car);
	}
	//size를 반환하면 차종의 수를 알 수 있음
	return carSet.size();
}

//값을 꺼낼경우 1=> 데이터의 저장순서는 뒤죽박죽이다. Set은 데이터의 저장순서가 중요하지않은 경우 사용한다.
public void printCarSet(HashSet<String> carSet){
	for(String temp : carSet){
		System.out.print(temp + " ");
	}
	System.out.println();
}

//값을 꺼낼 경우 2 => Iterator의 hasNext, next이용
public void printCarSet2(HashSet<String> carSet){
	Iterator<String> iter=carSet.iterator();
	while(iter.hasNext()){
		System.out.print(iter.next()+" ");
	}
}

 

 

TreeSet

저장된 데이터의 값에 따라서 정렬됨

red-black(이진트리)이라는 트리 타입으로 저장되며 HashSet보다 약간 느리다.

 

LinkedHashSet

연결된 목록타입으로 구현된 해시 테이블에 데이터 저장

저장된 순서에 따라 값이 정렬됨. 성능이 가장 나쁘다.

728x90
반응형

'공부 > Spring' 카테고리의 다른 글

스프링 웹개발 기초  (0) 2021.10.20
자바 - Map  (0) 2021.10.19
자바 - 컬렉션(List)  (0) 2021.10.15
자바 - 제네릭  (0) 2021.10.14
자바 문법  (0) 2021.10.13
블로그 이미지

아상관없어

,
반응형

컬렉션

자바 컬렉션은 목록성 데이터를 처리하는 자료구조를 통칭함

 

만약 여러개의 String 객체를 하나의 객체에 담으려면? => 배열을 이용하면된다.

하지만 담으려는 데이터의 크기를 모르면?

 

자바에선 이러한 문제를 해결하는 클래스가 미리 만들어져있다.

 

순서가 있는 목록인 List형

순서가 중요하지 않은 목록인 Set형

먼저 들어온 것이 먼저 나가는 Queue형

키-값(key-value)로 저장되는 Map형

 

List, Set, Queue는 Collection이라는 인터페이스를 구현한다.

Collection 인터페이슨느 java.util 패키지에 선언되어있고,

여러 개의 객체를 하나의 객체에 담아 처리할때 공통적으로 사용되는 여러 메소드를 선언해두었다.

 

Map은 Collection과 관련없는 별도의 인터페이스로 선언되어있다.

 

public interface Collection<E> extends Iterable<E>

Collection인터페이스는 Iterable라는 인터페이스를 확장했다.

해당 인터페이스는 iterator()메소드만 선언되어있고 Interator라는 인터페이스를 리턴한다.

 

Iterator라는 인터페이스에는

hasNext() : 추가데이터가 잇는지 확인

next() : 현재 위치를 다음 요소로 넘기고 그 값을 리턴해줌

remove() : 데이터를 삭제

라는 메소드가 있다.

 

Collection인터페이스가 Iterable 인터페이스를 확장했으므로 Iterator 인터페이스로 데이터를 순차적으로 가져올 수 있다.


 

 

List

List 인터페이스는 Collection인터페이스를 확장한다.

Collection 인터페이스를 확장하는 다른 인터페이스와 다른점은 배열처럼 순서가 있다는 점이다.

 

List 인터페이스를 구현한 클래스들 중 많이 사용하는 것은

  • ArrayList
  • Vector
  • Stack
  • LinkedList

이다.


 

ArrayList

크기 확장이 가능한 배열이다.

 

상속관계를 보면

java.lang.Object

jaba.util.AbstractCollection

java.util.AbstractList

java.util.ArrayList

이다.

=> AbstractCollection,AbstractList는 각각 Collection, List 인터페이스 중 일부만 구현했다.

 

ArrayList가 구현한 inerface들은

Serializable, Cloneable, Iterable, Collection, List, RandomAccess

이다.

 

public class ListSample{
	public static void main(String[] args){
		ListSample sample = new ListSample();
		sample.checkArrayList1();
	}
	
	public void checkArrayList1(){
		//list1 객체 생성 => ArrayList객체인 list1에 객체들을 넣을 수 있음
		ArrayList list1=new ArrayList();
		
		//list1에 객체들을 넣음
		list1.add(new Object());
		list1.add("ArrayListSample");
		list1.add(new Double(1));
	}
}

컴파일 되지 않는다. => ArrayList는 java.lang에 속한 클래스가 아니기 때문이다.

따라서

import java.util.ArrayList //를 해주어야한다.

 

ArrayList 객체에 여러 객체들을 넣을 수 있지만, 한가지 종류의 객체만을 주로 저장한다.

여러 종류를 하나의 객체에 담을 때는 DTO라는 객체를 만들어서 담는것이 좋다.

따라서 컬렉션 관련 클래스의 객체를 선언할때는 제네릭을 사용하여 선언하는것이 좋다.

=> 한가지 종류의 객체만을 저장

ArrayList<String> list1=new ArrayList<String>();

위와 같이 선언하면 list1은 String 타입의 객체만 넣을 수 있다.

(제네릭을 사용하여, 컴파일 시점에 다른 타입이 잘못 지정되는 것을 막을 수 있다.)

 

ArrayList 객체를 선언할 때 매개변수를 넣지 않으면 기본 초기값은 10이다.

(10개 이상의 데이터가 들어가면 크기를 늘리는 작업은 ArrayList내부에서 자동으로 수행된다.)

ArrayList<String> list1 = new ArrayList<String>(100);

 

 

ArrayList에 데이터를 담기위해선 add와 addAll메소드를 사용하면된다.

 

boolean add(E e) : 매개 변수로 넘어온 데이터를 가장 끝에 담음(리턴값은 제대로 추가되었는지 확인)

void add(int index, E e) : 매개 변수로 넘어온 데이터를 지정된 index위치에 담는다.

boolean addAll(Collection<? extends E> c) : 매개 변수로 넘어온 컬렌션 데이터를 가장 끝에 담는다.

boolean addAll(int index, Collection<? extends E> c) : 매개 변수로 넘어온 컬렉션 데이터를 index에 지정된 위치부터 담는다.

 

만약 list1의 내용을 list2에 복사하고 싶으면

ArrayLsit<String> list2 = new ArrayList<String>(list);

와 같이 사용하면 된다.

ArrayList에는 Collecion 인터페이스를 구현한 어떠한 클래스도 포함시킬 수 있는 생성자가 있기 때문

 

list2=list1

을 해버리면 주소가 같으므로 위와같이 생성자를 이용하여 복사하거나

addAll()을 이용하여 복사하여야 한다.

 

데이터를 꺼내는 법

size() : ArrayList객체에 들어가 있는 데이터의 개수를 가져오는 메소드

=> ArrayList의 크기를 말하는 것이 아님

get(int index)메소드를 사용하여 데이터를 가져올 수 있다.

 

중복된 값이 잇을경우에는

int indexOf(Object o) : 매개 변수로 넘어온 객체와 동일한 데이터의 위치를 리턴한다.

=>앞에서부터 찾을 경우

int lastIndexOf(Object o) : 매개 변수로 넘어온 객체와 동일한 마지막 데이터의 위치를 리턴한다.

=> 뒤에서부터 찾을 경우

 

ArrayList 객체에 있는 데이터들을 배열로 뽑아낼 경우엔 toArray()메소드를 사용한다.

Object[] toArray() : 객체에 있는 값들을 Object[] 타입의 배열로 만듬

T[] toArray(T[] a) : 객체에 있는 값들을 매개변수로 넘어온 T타입의 배열로 만든다.

 

매개변수가 없는 toArray는 Object타입의 배열로만 리턴하므로, 제네릭을 사용하여 선언한 ArrayList 객체를 배열로 생성할 때는 매개변수가 있는 toArray를 사용하는것이 좋다.

String[] temp = new String[5];
String[] strList=list.toArray(temp);

만약 list의 size가 3이라면 (데이터는 1, 2, 3이라 가정)

strList를 출력하면 1, 2, 3, null, null로 출력될것이다.

 

ArrayList에 저장된 데이터 size > 매개변수로 넘어온 배열의 크기

즉, size(list)=3 > temp=0 크기이면

새로운 배열을 생성하여 넘겨주므로

String[] strList=list.toArray(new String[0]);

와 같이 크기가 0인 배열을 넘겨주는 것이 좋다.

 

데이터 삭제

void clear() : 모든 데이터 삭제

E remove(int index) : 매개 변수에서 지정한 위치에 있는 데이터를 삭제하고 리턴

boolean remove(Object o) : 매개변수에 넘어온 객체와 동일한 "첫번째" 데이터를 삭제한다.

boolean removeAll(Collection<?> c) : 매개 변수로 넘어온 "컬렉션 객체에 있는 데이터"와 "동일한 모든 데이터"를 삭제한다.

 

데이터 변경

E set(int index, E element) : 지정한 위치에 있는 데이터를 두번째 매개변수로 넘긴 값으로 변경, 해당위치에 있던 데이터를 리턴

=> 원래 값 반환하고 설정값으로 값 바꿈

=> remove, add 두 과정을 거칠필요 없음

 

trimToSize() : 객체 공간의 크기를 데이터의 개수만큼으로 변경함, 즉 앞뒤 공백을 없앰

 

728x90
반응형

'공부 > Spring' 카테고리의 다른 글

스프링 웹개발 기초  (0) 2021.10.20
자바 - Map  (0) 2021.10.19
자바 - Set  (0) 2021.10.18
자바 - 제네릭  (0) 2021.10.14
자바 문법  (0) 2021.10.13
블로그 이미지

아상관없어

,
반응형

스택 : 후입선출

큐 : 선입선출

 

리스트는 동적할당으로 구현되어있어서 큐의 연산을 수행하기에는 효율적이지 않다.

데크를 사용하면 좋은 성능을 낼 수 있다.

 

스택 구현

class Node:
	def __init__(self, item, next):
		self.item = item
		self.next = next
class Stack:
	def __init__(self):
		self.last = None
	
    #맨 위에 집어넣음
	def push(self,item):
		self.last = Node(item, self.last)
	
	#맨 위값 꺼냄
	def pop(self):
		item = self.last.item
		self.last = self.last.next
		return item

 

  1. 유효한 괄호
'''
https://leetcode.com/problems/valid-parentheses
괄호로 입력된 값이 올바른지 판별하라.

ex)
Input: s = "()[]{}"
Output: true

Input: s = "([)]"
Output: false

Input: s = "{[]}"
Output: true

방법1)
last in fisrt out. 
열린 괄호를 만날때 스택에 push하고
닫힌 괄호를 만날때 pop
pop했을때 닫힌 괄호와 일치하는지 확인

'''



class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        table = { ')':'(', 
                 '}' : '{',
                ']' : '['
                }
        for char in s:
            #괄호에 해당되지 않을 경우 stack에 추가 X
            if char not in table:
                stack.append(char)
            #stack이 비어있지 않고, pop한 결과와 다르다면
            elif not stack or table[char] != stack.pop():
                return False
            
        #스택이 비어있지 않다면 쌓이기만 했으므로 false
        return len(stack) == 0

 

728x90
반응형

'공부 > 파이썬 알고리즘' 카테고리의 다른 글

문자열  (0) 2021.09.16
연결리스트  (0) 2021.09.15
배열  (0) 2021.09.15
etc  (0) 2021.07.01
파이썬 문법  (0) 2021.06.28
블로그 이미지

아상관없어

,

자바 - 제네릭

공부/Spring 2021. 10. 14. 18:33
반응형

제네릭

import java.io.Serializable;

public class CastingDTO implements Serializable{
	//타입이 Object이므로 어떤 타입이든 사용할 수 있다.
	priavet Object object;
	public void setObject(Object object){
		this.object=object;
	}
	
	public Object getObject(){
		return object;
	}
}

위 경우 Object타입을 인자로 받으므로 생성시 String이나 StringBuffer등 여러타입으로 생성할 경우 각각 타입으로 형 변환을 해야한다. 또한 여러 타입으로 생성시 각 객체가 어떤 타입인지 혼동 될 수 있다.

 

따라서 

import java.io.Serializable;

public class CastingGenericDTO<T> implements Serializable{
	priavet T object;
	public void setObject(T object){
		this.object=object;
	}
	
	public T getObject(){
		return object;
	}
}

T는 아무런 이름을 지정하여도된다.

<>안에는 현재 존재하는 클래스를 사용해되 되고, 존재하지 않는 것을 사용해도 된다.

되도록이면 클래스 이름의 명명 규칙과 동일하게 지정하는 것이 좋다.

선언 후 클래스 안에서 하나의 타입 이름처럼 사용하면된다.

만약 <>안의 이름이 현재 존재하는 클래스여야한다면, 컴파일시 에러가 발생할 것이다.

따라서, 가상 타입의 이름이라고 생각하면 된다.

 

 

제네릭을 사용하지 않을 경우 예시를 보면,

public class GenericSample{
	public static void main(String[] args){
		GenericSample sample = enw GenericSample();
		sample.checkCastingDTO();
	}
	
	public void checkCastingDTO(){
		CastingDTO dto1=new CastingDTO();
		dto1.setObject(new String());
		
		CastingDTO dto2=new CastingDTO();
		dto2.setObejct(new StringBuffer());
		
		CastingDTO dto3=new CAstingDTO();
		dto3.setObject(new StringBuilder());
	}
}

 

String temp1 = (String)dto1.getObject();
StringBuffer temp2 = (StringBuffer)dto2.getObject();
StringBuilder temp3 = (StringBuilder)dto3.getObject();

리턴 값으로 넘어는 타입은 Object이므로 형변환을 해주어야한다.

dto2의 인스턴스 변수 타입이 StringBuilder인지 StringBuffer인지 혼동된다면??

instanceof를 사용하여서 점검해도 되지만,

 

CastingGenericDTO클래스를 이용하여

public void checkGenericDTO(){
	CastingGenericDTO<String> dto1 = new CastingGenericDTO<String>();
	dto1.setObject(new String());
	
	CastingGenericDTO<StringBuffer> dto2 = new CasintgGenericDTO<StringBuffer>();
	dto2.setObject(new StringBuffer());
	
	CastingGenericDTO<StringBuilder> dto3 = new CasintgGenericDTO<StringBuilder>();
	dto3.setObject(new StringBuilder());
 }
String temp1 = dto1.getObject();
StringBuffer temp2 = dto2.getObject();
StringBuilder temp3 = dto3.getObject();

로 사용할 수 있다.

<>안에 타입을 명시적으로 적어주게되어 헷갈리지 않는다.

실행시에 타른 타입으로 잘못 형변환되어 예외가 발생하는 일이 없어진다

또한 형변환을 해주지 않아도 된다.

결과적으로 실행시 다른 타입으로 잘 못 형변환한하여 예외가 발생하는 일이 없어진다.

 

제네릭한 클래스의 타입만 바꾼다고 Overriding이 불가능하다.

public class WildcardGeneric<W>{
	W wildcard;
	public void setWildcard(W wildcard){
		this.wildcard = wildcard;
	}
	pubilc W getWildcard(){
		return wildcard;
	}
}

set, get하는 간단한 class이다

 

public class WildcardSample{
	public static void main(String[] args){
		WhildcardSample sample = new WildcardSample();
		sample.callWildcardMethod();
	}
	
	public void callWildMethod(){
		//String을 사용하는 제네릭한 객체를 생성함
		WildcardGeneric<String> wildcard = new WildcardGeneric<String>();
		wildcard.setWildcard("A");
		wildcardMethod(wildcard);
	}
	
	public void wildcardMethod(wildcardGeneric<String> c){
		String value = c.getwildcard();
		System.out.println(value);
	}
}

 

 

만약 <String>이 아니라 WildcardGeneric<Integer>과 같이 선언된 객체를 받으려면??

불가능하다.

제네릭한 클래스의 타입만 바꾼다고 오버라이딩이 가능하진 않다.

public void wildcardMethod(WhildcardGeneric<?> c){ 
//<- ?로 적어주면 어떤 제너릭 타입이 되더라도 상관없다.
	Object value=c.getWildcard(); //정확한 타입을 모르므로, Object로 값을 받아야한다.
	System.out.println(value);
}

 

이렇게는 사용 불가하다.

	public void callWildMethod(){
		//알수 없는 타입에 String을 지정할 수 없다고 에러가 난다.
		WildcardGeneric<?> wildcard = new WildcardGeneric<String>();
		wildcard.setWildcard("A");
		wildcardMethod(wildcard);
	}

객체에 제너릭 타입으로 값을 지정하는 것은 불가능하다.

 

제네릭 선언에 사용하는 타입의 범위도 지정할 수 있다.

<? extends 타입>으로ㅓ 선택한다.

 

//Car

public class Car{
	protected String name;
	public Car(String name){
		this.name=name;
	}
	
	public String toString(){
		return "Car name="+name;
	}
}
//Bus

public class Bus extends Car{
	public Bus(String name){
		super(name);
	}
	public String toString(){
		return "Bus name="+name;
	}
}
//WildcardSample

public class WildcardSample{
	public static void main(String[] args){
		WhildcardSample sample = new WildcardSample();
		sample.callWildcardMethod();
	}
	
	public void callWildMethod(){
		//String을 사용하는 제네릭한 객체를 생성함
		WildcardGeneric<String> wildcard = new WildcardGeneric<String>();
		wildcard.setWildcard("A");
		wildcardMethod(wildcard);
	}
	
	public void wildcardMethod(wildcardGeneric<String> c){
		String value = c.getwildcard();
		System.out.println(value);
	}
	
	public void callBoundedWildcardMethod(){
		//Car을 사용하는 제네릭 객체 생성
		WildcardGeneric<Car> wildcard = new WildcardGeneric<Car>();
		wildcard.setWildcard(new Car("BMW"));
		wildcardMethod(wildcard);
	}
	
	//?는 어떠한 타입이 와도 상관이 없다. 
	//제네릭 타입으로 Car를 상속받은 모든 클래스를 사용할 수 있다는 의미가 된다.
	//즉 반드시 Car클래스를 확장한 클래스가 넘어와야한다.
	public void boundedWildcardMethod(WildcardGeneric <? extends Car> c){
		Car value=c.getWildcard();
		System.out.println(value);
	}
}
public void callBoundedWildcardMethod(){
		//Bus을 사용하는 제네릭 객체 생성
		WildcardGeneric<Bus> wildcard = new WildcardGeneric<Bus>();
		wildcard.setWildcard(new Bus("Bus"));
		wildcardMethod(wildcard);
	}

callBoundedWildcardMethod()를 호출하면, Bus를 사용하는 제네릭한 객체를 넘겨주어도 실행이 잘 된다.

Bus클래스는 Car클래스를 상속받기 때문이다.

 

public class WildcardGeneric<W>{
	W wildcard;
	public void setWildcard(W wildcard){
		this.wildcard = wildcard;
	}
	pubilc W getWildcard(){
		return wildcard;
	}
}
//wildcardSample
...
public <T> void genericMethod(WildcardGeneric<T> c, T addValue){
	c.setWildcard(addValue);//값을 할당함
	T value = c.getWildcard();
	System.out.println(value);
}

//genericMethod 사용
public void callGenericMethod(){
	WildcardGeneric<String> wildcard = new WildcardGeneric<String>();
	genericMethod(wildcard, "Data");//string을 사용하는 제네릭한 객체와 T타입의 변수
}

 

public <S, T extends Car > void genericMethod(WildcardGeneric<T> c, T addValue, S another){
	...
}
//도 가능하다.
//S와 T라는 제네릭 타입을 메소드에서 사용할 수 있다.

 

리턴타입 앞 로 제네릭 타입을 선언했다.

매개변수에는 제네릭타입이 포함된 객체를 받아서 처리했다.

메소드 선언시 리턴타입 앞에 제네릭한 타입을 선언해주고 그 타입을 매개 변수에서 사용하면 컴파일할 때 문제가 없다.

 

728x90
반응형

'공부 > Spring' 카테고리의 다른 글

스프링 웹개발 기초  (0) 2021.10.20
자바 - Map  (0) 2021.10.19
자바 - Set  (0) 2021.10.18
자바 - 컬렉션(List)  (0) 2021.10.15
자바 문법  (0) 2021.10.13
블로그 이미지

아상관없어

,

자바 문법

공부/Spring 2021. 10. 13. 21:10
반응형

배열

int [] nums;
int nums[];

int [] nums;
nums = new int[5];

int nums[] = {1, 2, 3}


 

생성자

생성자는 자바 클래스의 객체를 생성하기 위해 존재한다.

Test test = new Test();

생성자의 리턴타입은 클래스의 객체이다.

다른 생성자가 없을 경우 기본으로 컴파일할 때 만들어진다.

-> 다른 생성자가 있을 경우, 기본 생성자는 만들어지지 않음


 

메소드 overloading

이름은 같지만 매개변수가 다르다. 매개변수의 종류, 개수, 순서가 다르다.


 

static 메소드

객체를 생성하지 않아도 호출할 수 있다.

static => 클래스가 메모리에 올라갈때 할당된다.

 

자바에서는 하나의 클래스를 컴파일할 때, 관련된 클래스가 컴파일 되어있지않으면 알아서 컴파일해준다.


 

static 블록

static {
//딱 한번만 실행되는 코드
}

객체가 생성되기 전에 한번만 호출됨.

메소드 내에 선언 불가 => static 이므로

생성자가 호출되기 전에 static 블록들이 호출된다.

static한 변수, 메소드를 사용할 수 있다.


 

String

string은 참조형이지만

String b="b";//new를 쓰지않았지만 객체를 생성한것임
b="z";

String b=new String("b");
b=new String("z");

String b="b";
b=b+"z" //더하기 연산시 기존의 객체는 버리고 새로운 객체를 만든다.

 

매개변수 개수가 몇개일지, 호출될때마다 바뀔때 방법

public void calc(int... nums){
}

//호출시
calc(1, 2, 3, 4)
calc(1, 2)


OR

public void calc(String op, int... nums){
}

**여러 매개변수가 있을때, "타입... 변수명"은 항상 맨 뒤에 와야함.
**"타입... 변수명"다음엔 메소드의 선언을 닫는 소괄호가 와야한다.

 

패키지

클래스들을 구분짓는 폴더

어떤 클래스가 어떤 일을 하는지 혼동되는 것을 방지

  • 패키지 이름을 java로 시작하면 안된다.
  • 패키지 선언은 소스 하나에는 하나만 있어야한다.
  • 패키지 이름과 위치한 폴더이름이 같아야한다.
  • (자바 파일을 만든 폴더 이름과 선언된 패키지 이름이 다르면 파일을 찾지 못한다.)
  • 패키지 이름은 모두 소문자로 지정
  • 자바의 예약어를 사용하지 않는다.(com.int.util과 같이 int가 없어야함 )
package c.javapackage;//패키지 선언문

public class Package{
...
}

컴파일시 소스코드와 컴파일된 클래스가 같은 디렉토리에 존재하게 된다.

 

import를 이요하여 다른패키지에 접근할 수 있다.

import 패키지이름.클래스이름


 

import static

static한 변수와 메소드를 사용할때 용이

package c.sub;

public class Sub{
...

	public final static String CLASS_NAME="sub";
	public static void method(){
		System.out.println('sub method');
	}
}
//1. 그냥 import하여 사용
package c;

immport c.sub;

...
public static void main(Strings[] args){
	Sub.method();
	System.out.println(Sub.CLASS_NAME);
}
//2. import static 사용

package c;

import static c.sub.Sub.CLASS_NAME;
import static c.sub.Sub.method;

...
~~ main ~~ {
	method();
	System.out.println(CLASS_NAME);
}
...

static 변수나 메소드가 중복될때는 자신의 클래스에 있는 static 변수나 메소드가 import static으로 가져온 것보다 우선이다.


 

접근제어자

  • public : 누구나 접근
  • protected : 같은 패키지내에있거나 상속받은 경우 접근
  • package-private : 같은 패키지내에 잇을때 (접근제어자 없을경우 기본)
  • private : 해당 클래스내에서만 접근 가능

=> 말그대로 접근을 제어하기 위해 사용, 주로 직접 접근하지 않고 메소드를 통하여 변경이나 조회를 할 수 있도록 할 때 사용

static => 클래스 변수/ 메소드 밖, 클래스 안

자바에서는 하나의 클래스를 컴파일할 때 관련된 클래스가 컴파일 되어 있지 않다면, 알아서 컴파일 해줌

한번만 호출되어야 하는 코드가 있따면 static 블록 사용

public으로 선언된 클래스가 소스내에 있으면, 그 소스 파일의 이름은 public 인 클래스 이름과 동일해야함

하나의 클래스 소스에서 여러 클래스가 선언가능하다 (단, 같은 패키지 내에 있는 클래스만 이 클래스의 객체를 생성하고 사용할 수 있다.)

 

public으로 선언된 클래스가 소스내에 있으면, 소스파일의 이름은 public인 클래스 이름과 동일해야한다.


 

상속

부모 클래스에 선언된 public, protected로 선언된 모든 변수와 메소드를 내가 갖고 잇는 것처럼 사용

자바에서 아무런 상속을 받지 않으면, java.lang.Object 클래스를 확장한다.

자바는 이중상속을 받을 수 없지만, 여러 단계로 상속을 받을 수 있다.

ex) Object -> Parent -> Child

Object 클래스에 있는 메소드를 통해서 클래스의 기본적인 행동을 정의할 수 있음.

(기본적으로 갖추어야될 메소드들 ex) equals, toString, getClass ... )

자식 클래스의 생성자가 호출되면 자동으로 부모클래스의 매개변수 없는 생성자가 실행된다.

=>하나를 만들고 파생되는 것을 조금씩 바꾸면 편리함

 

parent 클래스에 매개변수를 받는 생성자만 있을경우(기본생성자는 자동으로 생성되지 않음) => 에러

child 클래스의 모든 생성자가 실행될때 parent의 기본 생성자를 찾기 때문이다.

따라서 super(매개변수)와 같이 부모 클래스의 생성자를 호출 시켜주면된다.

super은 반드시 생성자의 첫줄에 있어야한다.


 

메소드 오버라이딩(덮어씀)

접근제어자, 리턴타입, 메소드 이름, 매개변수 타입 및 개수 모두 동일

접근제어자는 확장되는것은 괜찮지만 축소되는것은 문제가 된다.

 

참조 자료형의 형변환

Parent parent = new Parent();
Child child = new Child();

//부모 클래스에선 자식 클래스의 메소드와 변수를 사용할 수 없다.
Child obj1 = new Parent();//parent로 생성시 child의 기능을 사용할 수 없다.

//자식클래스에선 부모 클래스의 메소드와 변수 사용가능
Parent obj = new Child();//child로 생성시 parent의 기능을 사용할 수잇다.


Child child = new Child();
Parent parent = child; //child는 paret의 기능을 사용할 수 있다, parent는 실제로 child 객체이다.
Child child2 =(Child)parent;//따라서 형변환이 가능하다.

부모 타입의 객체를 자식 타입으로 형 변환을 할 때에는 명시적으로 타입을 지저해 주어야 한다. 부모타입의 실제 객체는 자식 타입이어야만 한다.


 

다형성

Parent parent1 = new Parent();
Parent parent2 = new Child();
Parent parent3 = new Child2();

//printName메소드는 각 클래스별로 있으며 해당 타입의 이름을 출력
parent1.printName();
parent2.printName();
parent3.printName();

//출력: 각각 타입
parent1 - parent
parent2 - child
parent3 - child2

모두 parent 타입으로 선언되었는데 각 객체의 실제 타입은 다르다.

그리고 형변환을 하더라도 실제 호출되는 것은 원래 객체에 있는 메소드가 호출된다.

=> 원래 객체의 printName 메소드를 호출하여 각각 다른 결과가 나온다.


 

java.lang.Object

아무런 상속을 받지 않으면 java.lang.Object 클래스를 확장한다.

java.lang.Object는 모든 자바 클래스의 부모이다. => Object에 있는 클래스의 메소드를 통해서 클래스의 기본 행동을 정의할 수 있기 때문(이정도 메소드는 잇어야한다~)

다중 상속은 되지 않으나, 여러단계로 상속을 받을 수 있다.


 

인터페이스

자바에선 클래스 파일 이외에 interface, abstract 클래스가 있다.

존재이유 => 프로그램 설계단계에서 인터페이스를 정해놓으면 메소드 이름, 매개변수 등등을 고민하지 않아도 된다. 인터페이스형식에 맞추어 메소드를 구현하면 된다.

구현시 implements로

정의된 메소드를 모두 구현하여야 컴파일이 된다.

인터페이스의 변수는 public static final로 자동 선언된다.

메소드의 경우도 public abstract로 자동 선언된다.

선언시 class 대신 interface를 사용

public interface test {
	methods....
}
MemberManagerInterface manager = new MemberManagerInterface();
//아무것도 구현해놓지 않은 인터페이스로 초기화 하려고하여 에러가남

MemberManagerInterface manager = new MemberManager();
//실제 구현은 MemberManager에 되어있으므로 실제 타입은 MemberManager에

 

abstract 클래스

마음대로 초기화하고 실행할 수 없다.

abstarct 클래스를 구현해 놓은 클래스로 초기화 및 실행이 가능하다.

선언시 class앞에 abstract를 사용

public abstarct class test{
	public abstract boolean addMemeber(MemberDTO member);
	public void test(){
		System.out.println("test");
	}
	...
}

abstract는 추상 메소드가 하나라도 있으면 사용한다.

인터페이스와 달리 구현되어 있는 메소드가 있어도 상관없다.

구현이 아니라 상속을 하여 사용하면된다.

extends하여 abstract한 메소드를 사용하면된다.

사용하는 이유는 어떤 메소드는 미리 만들어 놓아도 문제가 없는 경우가 발생한다.

하지만 해당 클래스를 만들기는 애매할 때, 공통적인 기능을 미리 구현하면 도움이 된다.

 

abstract의 경우 abstract 메소드(구현안된 메소드)가 하나라도 있을경우 class 예약어 앞에 붙인다.

상속과 동일하게 extends를 사용하면 된다.

어떤 메소드는 미리 만들어도되지만 클래스를 만들기는 애매할때 공통적인 기능을 미리 구현해놓으면 도움이 된다.


 

 

final

final의 경우 상속해줄수 없다.

확장해서는 안되는 클래스, 상속받아서 내용을 변경해서는 안되는 클래스 선언시 사용

기본 속성이 변경되면 안되는 클래스에 사용한다.

메소드 또한 final로 선언시 overriding할 수 없게 된다.

인스턴스 변수나 static으로 선언된 클래스 변수의 경우 생성과 동시에 초기화를 해주어야한다. 생성자나 메소드에서 초기화시, 중복되어 변수값이 선언될 수 있다.

하지만, 매개변수의 경우 이미 초기화가 되어 값이 넘어오고

지역변수의 경우 메소드를 선언하는 중괄호 내에서만 참조되므로 다른곳에서 변경할 일이 없으므로 컴파일시 문제발생X

 

객체를 final로 선언시 객체안의 변수들은 제약이 없다. final로 선언되어 있지 않으면

=> 해당 클래스가 final이라고 그 안의 인스턴수 변수나 클래스 변수가 무조건 final로 선언된건 아니다.


 

enum

상수의 집합이다.

클래스의 일종

public enum Time{
	THREE,
	FIVE,
	EIGHT;
}
...
	public int timecheck(Time value){
		switch(value){
			case THREE:
				System.out.println("3")
			...
		}
	}

 

main

~~ ~~~ main(~~){
	Time test = new Time();
	test.timecheck(Time.THREE);
}

클래스이름.상수이름 을 넘겨준다.

생성자를 만들 수 있지만, 생성자를 통하여 객체를 생성할 수는 없다.

 

enum클래스 선언시 각 상수의 값을 지정할 수 있다.

public enum Time{
	THREE(1000),
	FIVE(3000),
	private final int amount;
	
	Time(int amount){
		this.amount= amount;
	}
}

enum클래스의 생성자는 아무것도 명시하지 않는 pacakge-private와 private만 접근 제어자로 사용할 수 있다.

=> 각 상수를 enum 클래스내에서 선언할 때에만 이 생성자를 사용할 수 있다.

 

  • enum 클래스는 다른 클래스와 같이 컴파일 할때 생성자를 자동을 만들어준다.

 

 

Nested 클래스

클래스 안의 클래스

코드를 간단하게 표현하기 위해서

주로 UI처리시 사용자의 입력이나 외부의 이벤트에 대한 처리를 하는 곳에서 많이 사용됨

 

static으로 선언시 static nested 클래스 => 한 곳에서만 사용되는 클래스를 논리적으로 묶어서 처리할 필요가 있을때

아닌경우 내부클래스이다. => 캡슐화가 필요할 때(A클래스의 private 변수접근을 위한 B클래스를 선언하고, B클래스를 외부에 노출시키고 싶지 않을 경우), 다른클래스에서 전혀 필요가 없을 경우

내부클래스는 로컬내부클래스와 익명내부클래스로 구분된다.


 

 

static nested class

내부 클래스는 감싸고 있는 외부 클래스의 어떤 변수도 접근할 수 있다. 심지어 private 변수까지

하지만 "Static"하기 때문에 그렇게 사용하는 것은 불가능하다.

public class Test{
	staic class StaticTest{
		public void setVal(int val){
			...
		}
		...
	}
}

내부 static 클래스 객체 생성?

public class InterClassTest{

	public static void main(String[] args){
		InterClassTest sample = new InterClassTest();
		sample.StaticObject();
	}
	
	public void StaticObject(){
		Test.StaticTest staticNested = new Test.StaticTest();
		staticNested.setVal(1230);
	}
}

감싸는 클래스.내부클래스 로 생성한다.

 

사용하는 이유

예로 학교를 관리하는 school 클래스를 만들고 대학을 관리하느 univ클래스를 만들면,

student는 어디 학생인지 모름

따라서 school안에 static nested 클래스 student를 만들면 용도가 명확해짐

또한 해당 클래스는 univ에서 사용 불가함.


 

 

내부 클래스와 익명클래스

public class Test{
	 class Inner{
		public void setVal(int val){
			...
		}
		...
	}
}

 

생성시 감싸는 클래스의 객체를 만들어야한다.

public class InterClassTest{

...
	public void InnerObject(){
		Test outer = new Test();//감싸는 클래스 객체 생성
		Test.Inner inner = outer.new Inner();//객체를 통해서 생성
	}
...

}

=>다른 클래스에서는 그 클래스가 전혀 필요 없을때 이러한 내부클래스 사용

 

예를 들어 gui에서 버튼 클릭 이벤트 처리시 내부 클래스를 만드는 것이 편이함(따로 클래스를 만드는것 보다)

하지만 익명클래스를 사용하면 더 편리함

 

  1. 내부 클래스 사용방법
package a.inner;

public interface EventListener{
	public void onClick();
}

 

package c.inner;

public class MagicButton{
	public MagainButton(){
	
	}
	//EventListener는 인터페이스이다.
	private EventListener listener;
	public void setListener(EventListener listener){
		this.listener=listener;
	}
	public void onClickProcess(){
		if(listener!=null){
			listener.onClick();
		}
	}
}
package c.inner;

public class NestedSample{
	...
	public void setButtonListner (){
		MagicButton button = new MagicButton();
		MagicButtonListener listener = new MagicButtonListener();
		button.setListener(listener); //<- 별도 클래스를 넘기지 않고 밑의 내부 클래스를 넘김
		button.onClickProcess();
	}
}
package c.inner;

public class NestedSample{
	...
	//내부 클래스
	class MagicButtonListener implements EventListener {
		public void onClick(){
			System.out.println("clicked")
		}
	}
}

 

  1. 익명 클래스 사용방법
package c.inner;

public class NestedSample{
	...
	public void setButtonListner (){
		MagicButton button = new MagicButton();
		//MagicButtonListener listener = new MagicButtonListener();
		//button.setListener(listener); //<- 별도 클래스를 넘기지 않고 밑의 내부 클래스를 넘김
		
		//익명 클래스 생성로 인자 넘김
		button.setListener(new EventListener(){
			public void onClick(){
				System.out.println("button clicked");
			}
		});
		
		button.onClickProcess();
	}
}

new EventListener로 생성자 호출 후 바로 중괄호를 열고 메소드 구현

클래스 이름도 없고 객체 이름도 없어서 다른곳에서 참조할 수는 없다.

 

사용이유

클래스를 많이 만들면 메모리가 많이 필요해짐. 따라서 내부, 익명클래스로 간단하게 객체를 생성할 수 잇다.

클래스 개수를 줄일 수 있으면 좋으니까

또한 다른 클래스에서 재사용할 일이 없을때 유용하다.


 

 

예외

예외적인 일이 발생한 경우

잘못을 컴파일할 때 점검해주지 않으므로 예외처리 해야한다.

try{
...
}
catch (Exception e){
...
}

예외가 발생하는 부분만 묶어주면 된다.

try 블록 안에서 예외가 발생되면 그 이하의 문장은 실행되지 않고 바로 catch로 넘어간다.

그리고 try-catch 구문 밖에 잇는 문장이 실행된다.

 

try에서 선언한 변수를 catch에서 사용할 수 없다.

따라서 일반적으로 catch 문장에서 사용할 변수는 try 앞에 선언한다.

 

모든 예외 객체의 부모 클래스는 java.lang.Exception 클래스이다.

따라서 만약 catch가 여러개인 경우,

부모 예외 클래스가 잡고 자식 클래스가 잡도록 되어있으면 자식 클래스가 예외를 처리할 수 없다.

즉, 위에서 먼저 catch해버리므로 그 다음에서 catch할 수 없다.


 

Throw

모든 예외는 java.lang.Throwable 클래스를 상속받는다.

이러한 이유는 모두 동일한 이름의 메소드를 사용하여 처리할 수 있게 하기 위함이다.

 

예외를 발생시키기 위해서 Throw를 사용한다.

 

try{
	throw new Exception("wrong input");
	...
}

catch (Exception e){
	e.printStrackTrace();
}

try블록 내에서 throw라고 명시한 수 개발자가 예외 클래스의 객체를 생성하면 된다.

throw한 문장 이후에 있는 모든 try 블록 내의 문장들은 수행되지 않고 catch로 이동한다.

 

또한 예외가 발생된 메소드를 호출한 메소드로 던질수도 있다.

public class TestException {
	public void throwException(int num) throws Exception{

			if(num<0){
				throw new Exception("wrong");
			}

	}
}

try-catch로 묶지 않았지만,

throws가 선언되어 있기 때문에 throwException 메소드를 호출한 메소드에선 try-catch블록으로 감싸어서 예외를 받아서 처리해야한다.

 

try{
	test.throwException(-1);
}
catch(Exception e){
  ....
}

 

여러가지 예외를 던질수도 있다.

public void multiThrow() throws NullPointerException, ArrayIndexOutOfBoundsExceptioin{
	...
} 

 

예외를 만들수도 잇다.

public class MyException extends Exception{
	...
}

 

finally

어떠한 경우에도 반드시 실행

try{

}
catch (Exception e){

}
finally{

}

예외가 발생하면 catch가 실행되고 finally 블록이 실행되고 try-catch 이후 문장이 실행된다.

finally는 예외와 상관없이 실행된다.

코드의 중복을 피하기 위해서 반드시 필요하다.


 

어노테이션

클래스나 메소드 선언시 @를 사용함

메타데이터라고도 함

  • 컴파일러에게 정보를 알려줌
  • 컴파일할 때와 설치시의 작업을 지정
  • 실행시 별도의 처리가 필요할때

 

미리정해진 어노테이션은 3개

  • @Override즉, 메소드가 override되엇으니 잘못코딩되면 컴파일ㄹ러에게 알려달라고 한다.
  • 해당 메소드가 부모 클래스에 잇는 메소드를 override 했다고 명시적으로 선언
  • @Deprecated
  • 미리 만들어진 클래스나 메소드가 더이상 쓰이지 않을때 알려줌
  • @SuppressWarnings
  • 컴파일러에서 경고를 주는 경우, 일부러 이렇게 코딩햇으니 괜찮다고 알려줌

 

메타 어노테이션

어노테이션을 선언할때 사용

  • @Target
  • 어노테이션을 어떤 것에 적용할지 선언할때 사용
  • @Retention
  • 얼마나 오래 어노테이션 정보가 유지되는지 선언
  • @Documneted
  • 해당 어노테이션에 대한 정보가 javadocs 문서에 포함된다는 것을 선언
  • @Inherited
  • 모든 자식 클래스에서 부모 클래스의 어노테이션을 사용할 수 있다는 것을 선언
  • @interface
  • 어노테이션을 선언할때 사용

 

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
//이 어노테이션은 메소드에 사용할 수 있다고 지정

@Retention(RetentionPolicy.RUNTIME)
//실행시에 이 어노테이션을 참조하게 됨. 

//UserAnnotation이라는 어노테이션 선언
//@interface를 앞에 붙여서 사용시 @UserAnnotation으로 사용할 수 있다.
public @interface UserAnnotation {
	public int number();
	//메소드처럼 어노테이션 안에 선언해두면, 이 어노테이션을 사용할 때 해당항목에 대한 타입으로 값을 지정해야한다.
	public String text() default "This is first annotation";
	//default를 사용할 경우 뒤에 있는 값이 이 어노테이션을 사용할때 별도로 값을 지정해 주지 않을 때의 값이 된다.
}



 

사용시

만든 어노테이션의 target이 메소드였으므로 대상은 메소드 뿐이다.

public class Sample{
	@UserAnnotation(number=0)
	public static void main(String args[]){
		Sample sample = new Sample();
	}

	@UserAnnotation(number=3, text="Test")
	public void annotationexam(){
		
	}
}

 

 

 

 

728x90
반응형

'공부 > Spring' 카테고리의 다른 글

스프링 웹개발 기초  (0) 2021.10.20
자바 - Map  (0) 2021.10.19
자바 - Set  (0) 2021.10.18
자바 - 컬렉션(List)  (0) 2021.10.15
자바 - 제네릭  (0) 2021.10.14
블로그 이미지

아상관없어

,
반응형

https://leetcode.com/problems/reverse-string/

  1. Reverse String
class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        left, right = 0, len(s)-1
        while left < right:
            s[left], s[right] = s[right], s[left]
            left +=1
            right -=1

https://leetcode.com/problems/reorder-data-in-log-files/

  1. Reorder Data in Log Files
class Solution:
    def reorderLogFiles(self, logs: List[str]) -> List[str]:
        '''
        로그의 가장 앞 부분은 식별자
        문자로 구성된 로그가 숫자 로그보다 앞에 온다.
        식별자는 순서에 영향을 끼치지 않지만, 문자가 동일할 경우 식별자 순으로 한다.
        숫자로그는 입력 순서대로 한다.
        => 1. 숫자보다 문자 로그 먼저
        => 2. 문자 동일할 경우 식별자 순(식별자는 로그 가장 앞)
        '''

        letters, digits=[], []


        for log in logs:
            #로그가 숫자라면 (log.split()는 string을 반환)
            if log.split()[1].isdigit():
                digits.append(log)

            #로그가 문자라면
            else:
                letters.append(log)
        #문자일 경우 로그기준으로 정렬, 식별자를 뒤로, 문자 동일할 경우 식별자 순으로 정렬   
        letters.sort(key=lambda x: (x.split()[1:], x.split()[0]))

        #문자+숫자
        return letters+digits

https://leetcode.com/problems/most-common-word/

\19. Most Common Word

'''
금지된 단어를 제외한 가장 흔하게 등장하는 단어를 출력하라
대소문자를 구분하지 않으며, 구두점(마침표, 쉼표) 또한 무시한다.

Input: paragraph = "Bob hit a ball, the hit BALL flew far after it was hit.", banned = ["hit"]
Output: "ball"

1. 데이터 처리 => 대소문자 없애고, 구두점 없앰
2. 빈도수 체크
'''

class Solution:
    def mostCommonWord(self, paragraph: str, banned: List[str]) -> str:

        #cleaning
        #\w은 단어문자를 뜻하며 ^은 not을 의미한다.
        #문자열 앞에 r이 붙으면 해당 문자열이 구성된 그대로 문자열로 반환
        #즉 문자가 아닌 모든 문자를 공백으로 치환한다.
        #re.sub('패턴', '바꿀문자열', '문자열', 바꿀횟수)
        #words에는 cleaning된 word가 순차적으로 들어가게됨
        '''
        # 리스트 안에서 for 문 사용하기 1
        list = [ num * 3 for num in list ]
        '''

        #\w, 즉 단어 문자가 아니면 공백으로 치환하고 lower, split함
        words =[word for word in re.sub(r'[^\w]',' ', paragraph).lower().split()
            if word not in banned] #금지된 단어에 당되는 경우엔 포함X


        #개수 세기
        #딕셔너리 사용하여 횟수 셈
        #int형 딕셔너리 생성
        counts = collections.defaultdict(int)
        for word in words:
            counts[word] += 1 #해당되는 word 수 셈

        #딕셔너리 변수 counts에서 값이 가장 큰 키를 가져옴
        #max함수에 key를 지정하여 argmax를 간접적으로 추출
        '''
        >>> a = {'name':'pey', 'phone':'0119993323', 'birth': '1118'}
        >>> a.get('name')
        'pey'
        '''

        return max(counts, key=counts.get)

        #most_common(1)사용 가능 => 최빈값 1개 반환(리스트에 담긴 튜플 형태로
        '''
        counts=collections.Counter(words)
        return counts.most_common(1)[0][0] #=> [('ball', 2)]이므로
        '''

https://leetcode.com/problems/group-anagrams/submissions/

'''
애너그램 = 재배열하여 다른 뜻을 가진 단어로 바꾸는것

Input: strs = ["eat","tea","tan","ate","nat","bat"]
Output: [["bat"],["nat","tan"],["ate","eat","tea"]]

방법
1. strs의 단어들을 정렬하여 같은지 비교



'''


class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        #딕셔너리 생성
        anagrams = collections.defaultdict(list)

        for word in strs:
            #정렬하여 key, value딕셔너리에 삽입
            #list에서 문자열로 변환 -> 리스트(List)를 특정 구분자를 포함해 문자열(String)으로 변환
            anagrams[''.join(sorted(word))].append(word)

        #values() -> 모든 value들을 출력
        return list(anagrams.values())

https://leetcode.com/problems/longest-palindromic-substring/

'''
가장 긴 펠린드롬 부분 문자열을 출력

Input: s = "babad"
Output: "bab"

방법
1. 처음, 끝 비교 => 안맞으면 끝에서 2번째부터 계속 비교
(대칭되게 비교해야함 => aa, aaa, aaaa, aaaaa)
(1)b - d (x)
(2)b - a (x)
(3)b - b (O)
(4)길이가 3이므로 비교  X => 끝

(1)a - d
(2)a - a
.
.
.

2. 홀수칸, 짝수칸을 처음부터 이동 => 대칭되면 확장
더 효율적
'''



class Solution:
    def longestPalindrome(self, s: str) -> str:

        #s[left]==s[right]일 경우 범위 늘려가며 비교
        def expand(left:int, right:int)->str:
            while left >=0 and right < len(s) and s[left]==s[right]:
                left -= 1
                right += 1
            #left -= 1 했으므로 left+1, list범위는 right-1까지이므로 right
            return s[left+1:right]

        '''
        Input: s = "ac"
        Output: "a"
        '''
        #s가 짧거나 s자체가 펠린드롬일 경우 처리
        if len(s) < 2 or s==s[::-1]:
            return s


        #펠린드롬이 없는 경우
        result = ''

        for i in range(len(s) - 1):
            #max기준 len
            #홀수인 경우, 짝수인 경우
            result = max(result, expand(i, i+1), expand(i, i+2), key=len)

        return result
728x90
반응형

'공부 > 파이썬 알고리즘' 카테고리의 다른 글

스택과 큐  (0) 2021.10.14
연결리스트  (0) 2021.09.15
배열  (0) 2021.09.15
etc  (0) 2021.07.01
파이썬 문법  (0) 2021.06.28
블로그 이미지

아상관없어

,
반응형

* 펠린드롬 연결리스트

연결리스트가 펠린드롬 구조인지 판별하여라

https://leetcode.com/problems/palindrome-linked-list/


'''
펠린드롬 = 뒤집어도 같아야함

연결리스트가 펠린드롬 구조인지 판별하여라

1. 리스트를 이용하여 앞뒤 비교 = pop(0)?pop()
2. fast, slow runner활용
2.1 fast가 끝까지 가면 slow는 절반만큼 가게됨
2.2 slow는 이동하면서 역순으로 rev에 값을 저장
2.3 rev와 남은 slow가 가야하는 부분을 비교
2.4 홀수개일 경우 중앙값을 포함하면 안된다. 따라서 slow를 한칸 더 옮긴다.

'''
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        '''
        q = []
        q: Deque = collecitons.deque()

        if not head:
            return True

        node = head

        #리스트 변환
        while node is not None:
            q.append(node.val)
            node = node.next

        #펠린드롬 변환
        while len(q) > 1:
            if q.pop(0) != q.pop():
                return False
        return True
        '''

        if not head:
            return True

        fast = slow = head
        rev = None

        #절반만큼 이동/ 펠린드롬이 짝수 or 홀수 이므로 fast, fast.next값을 비교
        while fast and fast.next:
            #fast는 두칸씩 이동
            fast = fast.next.next
            #slow는 한칸씩 이동, rev에 역순으로 값 저장
            rev, slow, rev.next = slow, slow.next, rev

        #짝수일 경우 fast의 값은 None이 되고 홀수일 경우 값을 가지게 된다.
        if fast:
            slow = slow.next
        #rev와 남은 값 비교
        while rev and rev.val == slow.val:
            slow, rev = slow.next, rev.next

        #모든 값이 맞다면 rev는 none일 것이다.
        return not rev

 

 

 

 

* 정렬되어 있는 두 연결 리스트를 합쳐라

https://leetcode.com/problems/merge-two-sorted-lists/


'''
정렬되어 있는 두 연결 리스트를 합쳐라

Input: l1 = [1,2,4], l2 = [1,3,4]
Output: [1,1,2,3,4,4]

방법1)
1번째 연결리스트를 기준으로 두고 2번째 연결리스트의 값을 비교하여 삽입
1번째 연결리스트의 값과 같거나 크면 뒤로, 작으면 앞으로 => n^2만큼 비교해야..

방법2)
두 연결리스트를 합친뒤 정렬하기
정렬방법.. merge sort로 (nlogn)

'''

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def mergeTwoLists(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:

        #l1이 l2보다 크면 swap => 재귀로 반복
        if (not l1) or (l2 and l1.val > l2.val):
            l1, l2 = l2, l1

        #l1이 none 아니면, 재귀 호출
        if l1:
            l1.next = self.mergeTwoLists(l1.next, l2)

        return l1

 

 

 

 

* 연결리스트를 뒤집어라

https://leetcode.com/problems/reverse-linked-list/


'''
연결리스트를 뒤집어라

Input: head = [1,2,3,4,5]
Output: [5,4,3,2,1]

방법1)
러너문제 활용
rev, rev.next, slow = slow, rev, slow.next

방법2)
재귀활용
rev에 저장하게..

반복문을 사용하는것이 공간복잡도가 낮고 시간도 빠르다..

'''


# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:

        result = None
        temp = head

        while temp:
            temp, result, result.next = temp.next, temp, result

        return result


        '''
        def reverse(temp: ListNode, rev: ListNode = None):

            if not temp:
                return rev

            rev, temp, rev.next = temp, temp.next, rev

            return reverse(temp, rev)

        return reverse(head)
        '''

https://leetcode.com/problems/add-two-numbers/

다시....

 

 

 

 

* 홀짝 연결 리스트

연결리스트를 홀수 노드 다음에 짝수 노드가 오도록 재구성하라. 공간 복잡도 O(1), 시간 복잡도 O(n)에 풀이하라.

https://leetcode.com/problems/odd-even-linked-list/


nput: head = [1,2,3,4,5]
Output: [1,3,5,2,4]

Input: head = [2,1,3,5,6,4,7]
Output: [2,3,6,7,1,5,4]

홀수번째 노드가 짝수번째 노드 뒤에 와야한다.

방법1) 리스트로 바꾸고 슬라이싱으로 처리...는 파이썬 내장 함수로 간단...

방법2) 홀수, 짝수 노드 따로 구분하여 붙이기
1->2->3->4->5

  1. 1->3
  2. 2->4
  3. 3->5
  4. 5->2
    odd = 1->3->5
    even = 2->4
  5. 1->3->5->2->4

체크
=> a.next 가 null인가? 확인
=> 2->4노드에서 4->null이 되어야함

홀: odd.next = (even.next = odd.next.next)
짝: even.next = (odd.next = even.next.next)

시작
odd : head
even : head.next

마지막

  # Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def oddEvenList(self, head: Optional[ListNode]) -> Optional[ListNode]:

        if head is None:
            return head

        #odd, even으로 구분
        odd = odd_start = head
        even = even_start = head.next


        #while odd and odd.next:이 아니라 even을 비교하여야한다... odd일 경우 None.next에 값을 넣게 되버림(한번에 두개씩 바꾸므로)

        while even and even.next:
            odd.next, even.next = even.next, even.next.next
            #odd를 다음 odd로, even을 다음 even으로
            odd = odd.next
            even = even.next

        #odd->even 연결
        odd.next = even_start

        return odd_start   

728x90
반응형

'공부 > 파이썬 알고리즘' 카테고리의 다른 글

스택과 큐  (0) 2021.10.14
문자열  (0) 2021.09.16
배열  (0) 2021.09.15
etc  (0) 2021.07.01
파이썬 문법  (0) 2021.06.28
블로그 이미지

아상관없어

,
반응형

https://leetcode.com/problems/trapping-rain-water/

'''
    높이를 입력받아 비 온 후 얼마나 많은 물이 쌓일 수 있는지 계산
    Input: height = [0,1,0,2,1,0,1,3,2,1,2,1]
    Output: 6

    방법1)
        최대 높이 - 현재 높이 = 물이 고인 양

    방법2)
        현재 높이가 이전 높이 보다 높을 때 

'''
class Solution:
    def trap(self, height: List[int]) -> int:
        if not height:
            return 0

        #양쪽에서 시작하여 최대높이 까지
        volume = 0
        left, right = 0, len(height) - 1
        left_max, right_max = height[left], height[right]


        #좌우가 만날때 종료
        while left < right:
            #좌우 최대값 입력
            left_max, right_max = max(height[left], left_max), max(height[right], right_max)

            #좌우 높은 쪽으로 이동하므로,오른쪽이 크면 왼쪽이 이동, 왼쪽이 크면 오른쪽이 이동 
            #따라서 최대 지점에서 만나게됨
            if left_max <= right_max:
                volume += left_max - height[left]
                left += 1
            else:
                volume += right_max - height[right]
                right -= 1
        return volume

https://leetcode.com/problems/3sum

'''
배열을 입력받아 합으로 0을 만들 수 있는 3개의 요소를 출력하라
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]

방법1)
두요소의 합*(-1) 에 해당되는 값이 있는지 확인한다.
-1 => [0, 1, 2, -1, -4]
=> 모든 요소를 다 더해봐야함 = n^3

방법2)
정렬을 한 뒤 비교(투포인터 방식으로 비교) => nlog(n) + n^2
1. 정렬
2. 기준점을 둠, 나머진 투포인터 방식으로 비교(left, right지정)
3. 중복값이 있을경우 넘어감(계산낭비)

'''

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        result=[]
        #1.정렬
        nums.sort()

        #2.기준값 두고 비교(마지막 값 2개는 사용하지 않음)
        for i in range(len(nums)-2):
            #중복값일 경우 제외
            if i > 0 and nums[i] == nums[i-1]:
                continue    

            left, right = i + 1, len(nums)-1

            while left < right:
                sum = nums[i] + nums[left] + nums[right]
                if sum < 0:
                    left += 1
                elif sum > 0:
                    right -= 1
                else:
                    result.append([nums[i], nums[left], nums[right]])

                    #현재 값과 같은 값을 가질 경우 중복 제거
                    #왼쪽확인
                    while left < right and nums[left] == nums[left + 1]:
                        left += 1
                    #오른쪽 확인
                    while left < right and nums[right] == nums[right - 1]:
                        right -= 1
                    #확인이 끝난 후 각 값을 다음번째 값으로 옮김
                    left += 1
                    right -= 1


        return result

https://leetcode.com/problems/array-partition-i/

'''
n개의 페어를 이용한 min(a,b)의 합으로 만들 수 있는 가장 큰 수를 출력하라.
Input: nums = [1,4,3,2]
Output: 4

Explanation: All possible pairings (ignoring the ordering of elements) are:
1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
So the maximum possible sum is 4.

방법1) 
정렬한 뒤, 뒤에서부터 2개씩 묶어 계산 = nlogn

방법2)
짝수번째 값이 항상 min값이 됨

방법3)
슬라이싱 사용

'''
class Solution:
    def arrayPairSum(self, nums: List[int]) -> int:
        '''
        nums.sort()
        result = 0
        pair = []
        '''

        '''
        =====================================================
        방법 1)
        =====================================================
        '''
        '''
        387ms = 속도 더 나음
        for i in range(len(nums)-1, 0, -2):
            #배열의 개수가 홀수인 경우
            if i != 0:
                result += min(nums[i], nums[i-1])
        '''


        '''
        514ms = 가독성 좋음
        for n in nums:
            pair.append(n)
            if len(pair) == 2:
                result += min(pair)
                pair = []
        '''

        '''
        =====================================================
        방법2) 276ms
        =====================================================
        '''
        '''
        if len(nums)%2 == 0:
            for i, n in enumerate(nums):
                if i%2 == 0:
                    result += n
        else:
            for i, n in enumerate(nums):
                if i%2 != 0:
                    result += n

        return result
        '''

        '''
        =====================================================
        방법3) 314ms
        =====================================================
        return sum(sorted(nums)[::2])
        '''

https://leetcode.com/problems/product-of-array-except-self/

'''
배열을 입력받아 output[i]가 자신을 제외한 나머지 모든 요소의 곱셈 결과가 되도록 출력하라.
단 나눗셈을 하지 않고 O(n) 에 풀어라

Input: nums = [1,2,3,4]
Output: [24,12,8,6]

방법1)
자기자신 제외하고 나머지 값들 곱함
자기 자신 제외하고 왼쪽 오른쪽 구분하여 서로 곱함

1 2 3 4

1. 1*(2*3*4)
2. (1)*(3*4)
3. (1*2)*(4)
4. (1*2*3)*1

왼쪽 1, 1, 2, 6
오른쪽 24, 12, 4, 1


'''

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        result = []
        tmp = 1

        #왼쪽
        for i in range(len(nums)):
            result.append(tmp)
            tmp = tmp * nums[i]

        tmp = 1
        #오른쪽 => 반대로
        for i in range(len(nums)-1, -1, -1):
            result[i] = result[i] * tmp
            tmp = tmp * nums[i]

        return result

https://leetcode.com/problems/best-time-to-buy-and-sell-stock/

'''
주식을 사고팔기 가장 좋은 시점

한번의 거래로 낼 수 있는 최대 이익을 산출하라.
Input: prices = [7,1,5,3,6,4]
Output: 5
2일날 1에 사서 5일날 6에 팔면 최대의 이익이다.

방법1)
현재 값보다 다음값이 크면, 현재 이후의 값중 최대값을 구하여 이익을 계산 => 예시의 경우 
1->5, 3->6 두가지의 경우를 계산해야한다.

방법2) 방법1 복잡도 줄여야
최소값과 최대 이익값을 갱신하면서 마지막에 차이를 구함


'''


class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        '''
        방법1) 시간 초과남
        max_profit = -1
        profit = 0

        leng = len(prices)

        for i in range(leng):
            if i < leng-1 and prices[i] < prices[i+1]:
                profit = max(prices[i:]) - prices[i]

            if max_profit < profit:
                max_profit = profit
        '''

        profit = -sys.maxsize-1
        min_price =  sys.maxsize

        #갱신
        for i in range(len(prices)):
            min_price = min(min_price, prices[i])
            profit = max(profit, prices[i]-min_price)

        return profit
728x90
반응형

'공부 > 파이썬 알고리즘' 카테고리의 다른 글

문자열  (0) 2021.09.16
연결리스트  (0) 2021.09.15
etc  (0) 2021.07.01
파이썬 문법  (0) 2021.06.28
파이썬 기본 문법  (0) 2021.05.18
블로그 이미지

아상관없어

,
반응형

isdigit(), isnumeric() => 글자가 숫자모양이면 true (3²같은 제곱과 같은 형태 가능)

isdecimal() => 주어진 무나졍리 int형으로 변환 가능한지 (3²같은 제곱과 같은 형태 불가능)

 

 

for _ in range(10):

_뜻은 변수가 없다는 뜻

 

 

input().split()시

split의 기본값이 ' ' 공백이다.

 

list생성시

a = []

a = list(range(10)) 과같이 가능

 

 

SWAP 시 temp 불필요

a = 3

b = 'abc'

temp = a

a = b

b = temp

파이썬에서는 다음과 같이 한 줄로 두 값을 바꿔치기할 수 있다.

a = 3 b = 'abc'

a, b = b, a 

 

 

is => ID비교

== => 값 비교

 

728x90
반응형

'공부 > 파이썬 알고리즘' 카테고리의 다른 글

문자열  (0) 2021.09.16
연결리스트  (0) 2021.09.15
배열  (0) 2021.09.15
파이썬 문법  (0) 2021.06.28
파이썬 기본 문법  (0) 2021.05.18
블로그 이미지

아상관없어

,
반응형

def fn(a) :

a의 타입이 무엇인지 몰라 버그 유발 가능

def fn(a: int) -> bool:

a가 정수형임을 알리고 리턴값이 true or false임을 알림

리스트 컴프리헨션 : 기존 리스트를 기반으로 새로운 리스트를 만들어내는 구문

[n*2 for n in range(1, 10+1) if n&2==1]
# [2,6,10,14,18]

#리스트 컴프리헨션을 사용하지 않는 경우
a=[]
for n in range(1, 10+1):
    if n % 2 == 1:
        a.append(n*2)

print(a)
# [2,6,10,14,18]

리스트외에도 딕셔너리등이 가능함

a={}
for key, value in original.items():
    a[key]=value


 #한줄로 표현 가능
 a = {key: value for key, value in original.items()}


제너레이터 : 루프의 반복 동작을 제어할 수 있는 루틴


ex) 숫자 1억개를 만들어내 계산하는 프로그램

! 제너레이터가 없으면 메모리 어딘가에 1억개의 숫자를 보관해야함

제너레이터를 생성해두고 필요할때 언제든 숫자를 만들 수 있음

1억개 숫자중 100개만 사용하는 경우, 제너레이터를 사용하는 것이 나음

#함수의 리턴값은 제너레이터가 됨
def get_natural_number():
    n = 0
    #while true이므로 계속해서 값을 내보냄
    while True:
        n+=1
        yield n

yield구문을 사용하면 제너레이터를 리턴할 수 있다.

(기존함수는 return 구문을 만나면 값을 리턴하고 함수의 동작을 종료함)

yield는 제너레이터가 여기까지 실행중이던 값을 내보낸다는 뜻

중간값을 리턴한 다음 함수는 종료되지 않고 끝까지 실행됨

g = get_natural_number()
for _ in range(0. 100):
    print(next(g)) #next로 값을 추출 할 수 있다.

 #1
 #2
 #.
 #.
 #.
 #99
 #100

여러 타입의 값을 하나의 함수에서 생성하는 것도 가능

def generator():
    yield 1
    yield 'string'
    yield True


g = generator()

next(g) #1
next(g) #'string'
next(g) #True


range : 제너레이터의 방식을 활용하는 대표적인 함수


list(range(5))
#[0,1,2,3,4]

range(5)
#range(0, 5)


type(range(5))
#<class 'range'>

for i in range(5):
    print(i, end=" ")
 #0 1 2 3 4

range()는 range클래스를 리턴

for문에서는 내부적으로 제너레이터의 next를 호출하듯 숫자 생성

만약) 숫자가 100만개라면?

# 1번
a = [n for n in range(1000000)]

#2번
b = range(10000000)


len(a) #1000000
len(b) #1000000

len(a) == len(b) #True

a #range(0, 1000000) => 생성된 값이 담겨있음
b #<class 'range'> => 생성해야한다는 조건만 있음


#따라서 크기를 비교해보면
sys.getsizeof(a) #8697464
sys.getsizeof(b) #48 => 1억개라도 메모리 점유율은 동일함, 생성조건만 보관하고 있기 때문이다.
#인덱스로 접근 시에는 바로 생성하도록 구현되어 있음
b[999] #999


enumerate : "열거하다" 여러가지 자료형을 인덱스를 포함한 enumerate객체로 리턴함


a = [1,2,3,2,45,2,5]
a #[1,2,3,2,45,2,5]

enumerate(a)
#<enumerate object at ~~~>


#list로 결과를 추출할 경우, 인덱스를 자동으로 부여해줌
list(enumerate(a))
#[(0,1), (1,2), (2,3), (3,2), (4,45), (5,2), (6,5)]


#인덱스와 값을 함께 출력??
a = ['a1', 'b2', 'c3']

#1번 => range사용
for i in range(len(a)):
    print(i, a[i])


#2번 => 인덱스 위한 변수 따로 선언
i = 0
for v in a:
    print(i, v)
    i += 1

#3번 => 인덱스와 값 함께 처리
for i, v in enumerate(a):
    i += 1



나눗셈


// => 정수형으로 유지

5/3
#1.66666666666666667

5//3
#1

5//3 = int(5/3)

#몫과 나머지 한번에 구함
divmod(5, 3)
#(1, 2)

print


콤마로 구분

print('A1', 'B2')
#>>A1 B2

파라미터로 구분자 지정

print('A1', 'B2', sep=',')
#구분자를 ,로 지정
#>>A1, B2

print 함수는 항상 줄바꿈을 한다. 따라서 루프의 값을 반복적으로 출력하려면 end 파라미터를 공백처리한다.

print('aa', end =' ')
print('bb')
#>> aa bb

리스트 출력시 join으로 묶어서 처리

a = ['A', 'B']
print(' '.join(a))
#>>A B

문자열 함께 출력

idx = 1
fruit = "Apple"
print('{0}: {1}'.format(idx + 1, fruit))
#>>2: Apple

#인덱스 생략도 가능
idx = 1
fruit = "Apple"
print('{}: {}'.format(idx + 1, fruit))
#>>2: Apple

f-string(formated string literal)

print(f'{idx+1}: {fruit}')
#>>2: Apple

pass


class MyClass(object):
    def method_a(self):

    def method_b(self):
        print("Method B")

c = MyClass()

위 코드는 실행되지 않는다.

method_a()가 아무런 처리를 하지 않았기 때문에 method_b()에서 오류가 발생한다.

pass는 이런 오류를 막는다.

class MyClass(object):
    def method_a(self):
        #pass 삽입
        pass


    def method_b(self):
        print("Method B")

c = MyClass()

pass는 null연산으로 아무 작업도 하지 않는다. 따라서 인덴트 오류같은 불필요한 오류를 방지한다.

pass를 사용하여 mockup 인터페이스부터 구현한 다음 추후 구현을 할 수 있게 된다.


locals


locals()는 로컬 심볼 테이블 딕셔너리를 가져오는 메소드이다.

로컬에 선언된 모든 변수를 조회할 수 있다.

...
import pprint
pprint.pprint(locals())
...

pprint로 출력하게 되면 클래스 메소드 내부의 모든 로컬변수를 출력해준다.

{'num':[2,7,11,15].
'pprint': >module 'pprint' from '/user/lib/python3.8/pprint.py'>,
'self':<__main__.Solution object at 0x~~~~>,
'target; : 9'}

int


숫자 정수형 int만 제공

int가 충분하지 않으면 자동으로 long 타입으로 변환됨

bool은 논리 자료형이지만 내부적으로 int로 처리된다.

Ture == 1
>>True
False == 0
>>True

매핑


키와 자료형으로 구성된 복합 자료형

유일한 매핑 자료형은 딕셔너리이다.


집합


set은 중복된 값을 갖지 않는 자료형이다.

입력순서가 유지되지 않으며 중복된 값이 있을 경우 하나의 값만 유지한다.

a=set()
a
>>set()

type(a)
>><class 'set'>

{} 중괄호를 사용하여 선언함

a={'a','b','c'}

시퀀스


특정 대상의 순서있는 나열

str은 문자의 순서 있는 나열로 문자열을 이루는 자료형이고, list는 다양한 값들을 배열 형태의 순서있는 나열로 구성하는 자료형이다.

시퀀스는 불변(immutable)과 가변(mutable)로 구분한다.

불변 : 값을 변경할 수 없다. => str, tuple, bytes

a='abc' #abc할당
a='def' #def할당
type(a)
>><class 'str'>

a 변수에 처음 abc가 할당되고, 이후 다른 str타입인 def를 다시 참조했다.

a='abc'
id('abc')
>>4317530408
id(a)
>>4317530408

a='def'
id('def')
>>4318831648
id(a)
>>4318831648

각 메모리 주소를 보면 참조하는 주소가 바뀐 것을 알 수 있다.

변경되려면 다음과 같은 할당자가 처리되어야한다.

a[1]='d'
>> 오류 발생

하지만 오류가 발생한다.

가변 : 값을 변경할 수 있다. => list

list는 값을 추가/삭제할 수 있는 동적 배열이다.

728x90
반응형

'공부 > 파이썬 알고리즘' 카테고리의 다른 글

문자열  (0) 2021.09.16
연결리스트  (0) 2021.09.15
배열  (0) 2021.09.15
etc  (0) 2021.07.01
파이썬 기본 문법  (0) 2021.05.18
블로그 이미지

아상관없어

,