Spring Bean

스프링 컨테이너에 의해 관리되는 자바 객체를 의미합니다.

 

 

Spring Bean 을 왜 사용하는가?

개발자는 new 키워드, Interface 호출, 팩토리 호출 방식으로 객체를 생성하고 소멸합니다.

하지만 Spring Container를 사용하면 이러한 역할을 대신 해줍니다.

1) 제어 흐름을 외부에서 관리하게 됩니다.

2) 객체들 간의 의존 관계를 스프링 컨테이너가 런타임 과정에서 알아서 생성해줍니다.

 

 

Spring Bean 등록 방식

1. Component Scan

 

- @Component 를 클래스 위에 명시하면 스프링이 알아서 등록해줍니다.

- @Controller, @Service, @Repository, @Configuration 은 @Componenet를 상속 받고 있으므로

   모두 컴포넌트 스캔 대상입니다.

 

- Controller : Spring MVC Controller
- Repository : 스프링 데이터 접근 계층으로 해당 계층의 예외는 모두 DataAccessException 으로 변환
- Service : 핵심 비지니스로 인식 ( 가독성 역할 )
- Configuration : Spring 설정 정보로 인식하고, 스프링 빈이 싱글톤 유지를 하도록 처리해줌

 

  •   컴포넌트 스캔 방법1 ( @Component를 붙쳐주는 방법 )

1) 각 서비스/레포지토리 등에 어노테이션을 달아줌 ( @Repository, @Service, @Controller )

@Service
public class MemberServiceImp implements MemberServiceInterface{
    
    private MemberRepository memberRepository;

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

    // 회원가입
    @Override
    public Long join(Member member) {
        // 휴대폰 번호 중복 체크
        memberRepository.save(member);
        return member.getId();
    }
}
@Repository
public class JPQLMemberRepository implements MemberRepository{
    
    public Member save(Member member) {
        return member;
    }
}

 

2) 빈 등록이 됬으면 의존성 주입

 

 

  •   컴포넌트 스캔 방법2 ( @Bean를 붙쳐주는 방법 )

1) IoC컨테이너에 등록을 해줌 ( @Configuration )

@Configuration
public class SpringConfig {
	
    //등록
    @Bean
    public EntityManager getEm() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("5xik");
        return emf.createEntityManager();
    }
    
    @Bean
    public MemberServiceInterface memberService() {
        return new MemberServiceImp(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new JPQLMemberRepository(getEm());
    }
}

2) Repository / Serivce 에 필요한 의존성 주입

public class JPQLMemberRepository implements MemberRepository{

    private EntityManager em;

	@Autowired
    public JPQLMemberRepository(EntityManager em) {
        this.em = em;
    }
    
    public EntityTransaction getTx() {
        return em.getTransaction();
    }

    public Member save(Member member) {
        em.persist(member);
        return member;
    }
}

 

 

  •   컴포넌트 스캔 방법3 ( AutoConfig 설정 방식 )
@Configuration
@ComponentScan(
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
                Configuration.class))
public class AutoSpringConfig {

}

@ComponentScan 은 @SpringBootApplication 에 기본적으로 포함되어 있습니다.

그런데 만약 기존에 @Configuration 으로 컨테이너를 만들어 놨다면 두 설정파일을 읽어서 오류가 발생하겠죠??

 

이렇게 직접 컴포넌트 스캔을 설정해주면은 기존의 IoC컨테이너에 영향을 주지 않고

컴포넌트 스캔을 구현할 수 있습니다.

 

만약, 다른 IoC컨테이너가 없다면 따로 AutoConfig는 따로 설정할 필요 없습니다.

 

@SpringBootApplication 에 기본적으로 Component Scan 이 포함되어 있다는 의미는
IoC컨테이너가 없이도 @Component 관련 어노테이션을 다 읽는다는 의미이므로

@Repository 로 빈 등록하고,
@Configuration 내부에서 @Bean으로 또 등록하는 실수를 하게되면

Unique~~~ 와 관련 에러가 발생하니 유의하시길 바랍니다.

< 관련 오류 >
https://tjdwns4537.tistory.com/67?category=949574

 

 

[문제]

 

자신이 감옥에 간 사이 연인이었던 줄리아를 앤디에게 빼앗겨 화가 난 조지는 브레드, 맷과 함께 앤디 소유의 카지노 지하에 있는 금고를 털기로 합니다. 온갖 트랩을 뚫고 드디어 금고에 진입한 조지와 일행들. 조지는 이와중에 감옥에서 틈틈이 공부한 알고리즘을 이용해 target 금액을 훔칠 수 있는 방법의 경우의 수를 계산하기 시작합니다.

예를 들어 $50 을 훔칠 때 $10, $20, $50 이 있다면 다음과 같이 4 가지 방법으로 $50을 훔칠 수 있습니다.

  • $50 한 장을 훔친다
  • $20 두 장, $10 한 장을 훔친다
  • $20 한 장, $10 세 장을 훔친다
  • $10 다섯 장을 훔친다

 

 

냅색 알고리즘

유명한 다이나믹 프로그래밍 문제 중 하나입니다.

 

위의 문제를 풀기 전에 간단하게 1, 2, 5, 10 원 동전들이 있고, 이 동전들로 10원을 만드는 방법을 구해보겠습니다.

 

d [i][j] = i 가지 종류의 동전을 사용하여 j 금액을 만드는 경우의 수라고 가정을 해봅니다.
그러면 저희가 구할 수는
d [4][10] = 4가지 ( 1,2,5,10 ) 종류의 동전을 사용하여 10원을 만드는 경우의 수

 

  • 풀이
  1.  [ 코인을 저장할 coin 배열 ] [ 동전 개수를 저장할 dy배열 ] 을 생성
  2.  dy[j] = j원을 거슬러 주기 위해 사용된 동전의 최소 개수
  3. 코인별로 거슬러 줄 동전 개수를 계산하고 dy에 저장

 

 

아래의 표를 보고 이해해보겠습니다.

d [1][2] = 1원으로 2원을 만드는 방법 1가지

 

d [2][2] = 0원에 2원을 추가해 2원을 만드는 방법 1가지 ( d[2][0] ) + d [1][2]

d [2][3] = 1원에 2원을 추가해 3원을 만드는 방법 1가지 + 1원으로 3원 만드는 방법 1가지 ( d[2][1] + d[1][3] )

d [2][4] = 2원에 2원을 추가해 4원을 만드는 방법 + 1원으로 4원 만드는 방법

                 ( ( d[2][0] + d[2][2] )+ ( d[1][2] + d[2][2] ) + d[1][4] )

d [2][5] = 4원에 1원 추가하는 방법 ( 2 ) + 1원으로 5원

....

 

 

public class CoinChange {
    public static void main(String[] args) {
        int[] coin = {10,20,50};

        long result = napsac(50,coin);
        System.out.println(result);
    }

    public static long napsac(int target, int[] type){

        long[] dy = new long[target+1];
        // 인덱스 1부터 시작

        dy[0] = 1;
        // 최소의 개수인 1부터 시작

        for(int i=0; i<type.length; i++){

            for(int j=1; j<=target; j++){
                if(type[i] <= j){
                    dy[j] += dy[j-type[i]];
                    /*
                    ex.
                    type[i] = 10, j=11일때,
                    dy[11-10] = dy[1]
                    dy[11] += dy[1]
                    즉, dy[11] 자리에 dy[1] 의 값을 더해준다.
                    */
                }
            }
        }
        return dy[target];
    }
}

'알고리즘' 카테고리의 다른 글

인접 행렬 생성하기  (0) 2022.08.04
문자열에서 숫자 추출 알고리즘  (0) 2022.08.01
Binary Search Algorithm  (0) 2022.07.28
Brute Force Algorithm과 시뮬레이션  (0) 2022.07.28
Greedy Algorithm  (0) 2022.07.28
JPQL

엔티티 객체를 대상으로 쿼리를 작성하는 객체지향 쿼리언어

 

 

 

 

JPQL은 왜 등장했을까?

JPA를 사용하면 엔티티 객체를 중심으로 개발하게 됩니다.

검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색하게 되죠.

 

데이터가 수 만, 수십 만개가 있을 때 JPA만으로 모든 DB데이터를 객체로 변환해서 검색하는 것은 사실상 불가능합니다.

그래서 애플리케이션이 필요한 데이터만 DB에 불러오려면 결국 검색 조건이 포함된 SQL이 필요했습니다.

그래서 JPQL이 등장하게 되었습니다.

 

 

JPQL 특징
  1. JPA에서 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공합니다.
  2. SQL 문법과 유사합니다 ( SELECT ,FROM, WHERE, GROUP BY, HAVING, JOIN 지원 )
  3. JPQL은 엔티티 객체를 대상으로 쿼리합니다.
  4. 반면에 SQL은 데이터베이스 테이블을 대상으로 쿼리합니다.

 

JPQL 문법

기존 SQL 문법과 똑같습니다.

예제
List<Member> result =
 em.createQuery(
	SELECT m FROM Member m WHERE m.username like "%kim%",
	Member.class
 ).getResultList();

FROM Member 에서 Member은 테이블이 아닌 엔티티 객체를 의미합니다.

 

 

테스트

가장 먼저 main 함수로 테스트를 해보겠습니다.

데이터가 잘 들어옴을 알 수 있습니다.

 

하지만 인프런의 "김영한" 님이 말씀하시기를 테스트 코드를 필수로 작성할 줄 알아야 한다고 합니다.

그래서 테스트 코드를 통해 해당 쿼리가 정상작동하는지 보겠습니다.

 

 

 

테스트 코드

[ 구성도 ]

1. DIP를 지키기 위해 MemberRepository Interface 상속하여 JPQLMemberRepository 라는 클래스를 만들어서 테스트

2. persistence.xml 에 table = create로 테스트 진행 ( 테스트할 때만 create 해야됨 )

3. save(), findByName() 메소드 테스트

4. 생성자 주입을 이용

 

 

[ 의문점1]

- 기존에 배웠던 빈 등록과, 의존성 주입은 아래의 과정을 거칩니다.

  1) SpringConfig 라는 IoC컨테이너에 빈을 주입한다.

  2) 해당 빈을 사용하는 부분에서 @Autowired 를 통해 의존관계 주입을 한다.

 

 - 그런데 JPQLMemberRepository에서의 EntityManger(em)과 Test Code에서의 em은 같은 객체인가?

    에 대한 개념을 이해하기 위해 따로 정리하였습니다.

https://tjdwns4537.tistory.com/66

 

EntityManager

Entity Manger Entity를 관리하는 역할을 수행하는 클래스 Entity Jpa가 관리하는 객체 EntityMangaerFactory (emf) 고객의 요청이 올때마다 엔티티 매니저를 하나씩 만들어 줍니다. 이 엔티티 매니저는 내부적

tjdwns4537.tistory.com

 

[ 의문점1 해결완료 ]

즉, em은 쓰레드간에 공유를 하면 안되는다는 의미입니다.

그래서 각각의 em으로 영속해주는 방식을 사용하면 됩니다.

 

 

[ 의문점2 ]

EntitnyManger 은 의존관계를 어떻게 하는가?

 

1) JPQLMemberRepository 에서는 EntityManagerFactory를 직접 생성하지 않습니다.

     애플리케이션 로딩 시점에 생성되기 때문에 EntityManager를 생성자에서 피라미터로 받게 하였습니다.

     

2) IoC컨테이너에서 Repository에 대한 빈을 등록해야하는데, EntityManager를 어떻게 넣어줘야 하는가?

 

 

[ 의문점2 의 설명 ]

EntityManager 는 트랜잭션 단위로 생성하고, EntityManagerFactory는 DB당 하나를 만들어야 하는데,

클라이언트 코드에서 enf를 만들 것이니, IoC컨테이너에서는 enf를 만들 수 없다.

그렇다면 IoC컨테이너의 빈을 등록할 때, enf를 넣도록 해야하는가 ?

 

[ 의문점2의 해결시도1 ]

  • EntityManager는 @PersistenceContext로 주입을 해줄 수 있습니다. (최신버전은 Autowired로도 가능)

그래서 Autowired를 이용해 테스트 코드를 작성해봤습니다.

 

[ JPQLMemberRepository ]

@Transactional
@Repository
public class JPQLMemberRepository implements MemberRepository{

    private final EntityManager em;

    @Autowired // EntityManger 주입부분
    public JPQLMemberRepository(EntityManager em) {
        this.em = em;
    }

    public EntityTransaction getTx() {
        return em.getTransaction();
    }

    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    public void emClose() {
        em.close();
    }

    @Override
    public Optional<Member> findByid(Long id) {
        return Optional.empty();
    }

    @Override
    public Optional<Member> findByname(String name) {
        List<Member> result = em.createQuery(
                "SELECT m FROM Member m WHERE m.name = :name",
                Member.class
        ).setParameter("name", name).getResultList();

        System.out.println("--------------------------------");
        for (Member i : result) {
            System.out.println("member:" + i);
        }
        System.out.println("--------------------------------");

        return result.stream().findAny();
    }
    ... // 그외 인터페이스 메소드
}

 

[ SpringConfig ]

@Configuration
public class SpringConfig {

    @Bean //EntityManagerFactory Bean 등록
    public EntityManagerFactory getEnf() {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("5xik");
        return emf;
    }

    @Bean // EntityManager Bean 등록
    public EntityManager getEm() {
        return getEnf().createEntityManager();
    }

    @Bean
    public MemberServiceInterface memberService() {
        return new MemberServiceImp(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new JPQLMemberRepository(getEm());
    }
 }

 

[ TestCode ]

SpringBootTest
@Transactional
class JPQLMemberRepositoryTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);

    JPQLMemberRepository repository = ac.getBean("memberRepository",JPQLMemberRepository.class);

    EntityTransaction tx = repository.getTx();

    @Test
    @Commit
    void insertData() {
        tx.begin();
        try{
            Member member = new Member();
            member.setName("sungjun");
            member.setPhonenumber("010");

            repository.save(member);
            Optional<Member> result =  repository.findByname(member.getName());

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

        } catch (Exception e){
            tx.rollback();
        } finally {
            //repository.emClose();
        }
        //ac.close();
    }
}

 

 

그런데 아래와 같은 에러가 발생하였습니다.

caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'JPQLMemberRepository' defined in file [/Users/parksungjun/Desktop/창업동아리/ShoppingMall/out/production/classes/xik/ShoppingMall/Repository/JPQLMemberRepository.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 2: getEm,org.springframework.orm.jpa.SharedEntityManagerCreator#0

글이 길어졌으므로 에러 해결은 다음 블로깅으로 넘어가서 하도록 하겠습니다.

https://tjdwns4537.tistory.com/67

 

Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUnique

 

tjdwns4537.tistory.com

 

'JPA(MySQL,H2Database)' 카테고리의 다른 글

임베디드 값 타입  (0) 2022.08.08
SQL 내장함수  (0) 2022.08.05
EntityManager  (0) 2022.07.29
연관 관계 매핑  (0) 2022.07.15
exception just for purpose of providing stack trace  (0) 2022.07.13
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'JPQLMemberRepository' defined in file [/Repository/JPQLMemberRepository.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 2: getEm,org.springframework.orm.jpa.SharedEntityManagerCreator#0

해결 과정.

1) IoC컨테이너에서 EntityManagerFactory, EntityManager를 따로 빈 등록하는 것이 아닌 하나의 함수에 묶어서 빈 등록

2) 레포지토리에서는 엔티티 매니저만 주입을 받음

3) 테스트코드에서는 레포지토리만 주입 받음

 

 

 

위의 과정 중 발생한 오류

 

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'xik.ShoppingMall.Repository.JPQLMemberRepositoryTest': Unsatisfied dependency expressed through field 'repository'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'xik.ShoppingMall.Repository.MemberRepository' available: expected single matching bean but found 2: JPQLMemberRepository,memberRepository

 

 

 

오류 키워드

1) NoUniqueBeanDefinitionException

2) available: expected single matching bean but found 2: JPQLMemberRepository,memberRepository

 

해당 키워드를 생각해보면 MemberRepository 두 개가 빈 등록 되어있어서 발생하는 오류임을 알 수 있습니다.

 

 

해결방법

: 기존에는 IoC컨테이너에서도 레포지토리를 빈 등록해주고, 레포지토리 클래스에서 @Repository를 해서 두번 빈 등록 해주고 있었습니다.

  - JPQLMemberRepository 에서 @Repository 할 꺼면 컨테이너에 레포지토리에 대한 빈 등록은 필요가 없습니다.

  - IoC컨테이너를 쓰려면 @Repository는 필요가없습니다.

  - 만약 두 군대에 레포지토리 등록을 사용하고 싶다면 JPQLMemberRepository 에 @Primary 를 붙쳐주면 해결됩니다.

 

 

* 빈 등록에 대해 숙지가 제대로 안된 상태라서 발생한 문제였습니다.

  빈 등록에 대해서 아래 주소에 블로깅해놨습니다.

 

https://tjdwns4537.tistory.com/70

 

Bean 등록 방법

 

tjdwns4537.tistory.com

 

 

 

 

 

 

Entity Manger

Entity를 관리하는 역할을 수행하는 클래스

 

 

Entity

Jpa가 관리하는 객체

 

 

EntityMangaerFactory (emf)

고객의 요청이 올때마다 엔티티 매니저를 하나씩 만들어 줍니다.

이 엔티티 매니저는 내부적으로 DB Connection 을 통해 DB를 사용하게 됩니다.

 

그래서 JPA는 EntityManagerFactory가 필요하며,

애플리케이션 로딩시점에 DB당 딱 하나만 생성되야 합니다.

 

그리고 WAS가 종료되는 시점에 emf 를 close 해주면 됩니다.

 

 

EntityManager

실제 트랜잭션 단위를 수행할 때 마다 생성합니다.

즉, 고객의 요청이 올 때마다 사용했다가 닫습니다.

 

 * 트랜잭션 수행 후에는 반드시 엔티티 매니저를 닫아줘야합니다. 그래야 내부적으로 DB Connection을 반환합니다.

 

 

EntityTransaction

데이터를 변경하는 모든 작업은 반드시 트랜잭션 안에서 이뤄져야 합니다.

단순 조회의 경우는 상관 없습니다.

 

 

Persistenct Context (영속성 컨텍스트)

엔티티 매니저 내부에서 엔티티를 관리해주는 역할을 합니다.

이는 엔티티를 영구히 저장하는 환경을 가진다는 특징이 있습니다.

엔티티를 영속성 컨텍스트에 저장하는 과정에 대한 코드를 먼저 보겠습니다.

EntityManagerFactory emf = Persistenct.createEntityMangerFactory("persistenceUnit name")
EntityManager em = emf.createEntityManger();
EntityTransaction tx = em.getTransaction();

tx.begin();

Member member = new Member();
memer.setName("aaa");

em.persist(member);

tx.commit();

[ 각 소스 코드의 역할 ]

1) EntityTransaction : 커밋한 순간에만 Insert 쿼리를 날릴 수 있게 쓰기 지연 기능을 도와준다.

2) persist : 영속성 컨텍스트에 해당 엔티티를 저장한다. ( 영속화 )

3) commit : Insert 쿼리가 날라간다.

 

[ 영속 상태가 되는 경우 ]

1) persist

2) find

find 해서 DB에서 가져왔는데 그게 영속성 컨텍스트에 없는 경우 1차 캐시에 올리게 됩니다.

1차 캐시에 올린다는 의미는 영속 상태가 된다는 의미입니다.

 

 

영속성 컨텍스트의 장점 - 1차 캐시 ( 쓰기 지연 SQL 저장소 )

1차 캐시란,

[ 처음 들어온 데이터 ] : 영속성 컨텍스트에 데이터 없음 -> DB 접근해서 데이터 조회

[ 두번째 들어온 데이터 ] : 영속성 컨텍스트에 데이터가 있으므로 바로 불러온다 ( DB조회 안함 )

 

이렇게 빠르게 조회할 수 있다는 장점이 있습니다.

이 1차 캐시를 보통 [ 쓰기 지연 SQL 저장소 ] 라고 부릅니다.

그렇다면 쓰기 지연 SQL 저장소와 현재 로컬에서의 엔티티가 다르다면 어떻게 적용 할 수 있을까요?

 

 

영속성 컨텍스트의 장점 - 동일성 보장

동일한 DB를 조회한 객체의 경우 동일한 객체임을 보장합니다.

그래서 (==) 로 비교시 true 가 됨을 알 수 있습니다.

Member a = em.find(Member.class,"sun");
Member b = em.find(Member.class,"moon");

Assertions.assertThat(a).isEqualsTo(b);

 

 

영속성 컨텍스트 장점 - Dirty Checking

데이터 수정시 update() -> persist() 로 업데이트를 해야한다고 생각할 수 있지만

그럴 필요는 없습니다.

 

데이터가 변경되더라도 commit하면 알아서 DB에 반영됩니다.

 

 

플러시

영속성 컨텍스트의 변경사항과 DB의 내용을 맞추는 작업

플러시가 발생하면 아래의 동작과정이 발생합니다.

 

  1. 변경 감지
  2. 수정된 엔티티 쓰기 지연 SQL에 등록
  3. 쓰기 지연 SQL 저장소 쿼리를 DB에 전송 ( INSERT / SELECT / DELETE )

단, 플러시가 발생했다고 트랜잭션에 커밋이 되는건 아닙니다.

플러시 시점에는 해당 쿼리를 DB에 동기화 시키고, 커밋시에 DB에 전달하게 됩니다.

 

[ 플러시 방법 ] 

1) em.flush() : 직접 플러시

2) Transaction commit : 자동 플러시

3) JPQL Query execute : 자동 플러시

'JPA(MySQL,H2Database)' 카테고리의 다른 글

임베디드 값 타입  (0) 2022.08.08
SQL 내장함수  (0) 2022.08.05
JPQL 와 JPQL의 테스트 코드 작성  (0) 2022.07.29
연관 관계 매핑  (0) 2022.07.15
exception just for purpose of providing stack trace  (0) 2022.07.13
이진 탐색

데이터가 정렬된 상태에서 절반씩 반으로 나눠 분할 정복 기법으로 특정한 값을 찾아내는 알고리즘

 

 

동작 과정
  1. 정렬된 배열의 중간 인덱스 지정 ( 그 지정 값을 pivot 이라 부름 )
  2. 찾으려 하는 값과 동일하면 종료하고, 아니면 3단계로 이동
  3. 찾으려는 값이 중간 인덱스의 값보다 큰지, 작은지 확인
  4. pivot 보다 작은 경우 반으로 나눈 왼쪽 배열, pivot보다 큰 경우 반으로 나눈 오른쪽 배열을 선택
  5. 선택한 배열에서 다시 1단계부터 반복

 

이진 탐색을 사용 하는 경우
  1. 정렬된 배열에서 요솟값을 더 효율적으로 검색할 때 사용
  2. 데이터의 양이 많으면 많을수록 효율이 높아서 정렬된 데이터의 양이 많을 때 사용

 

* 단, 항상 효율이 좋은 것은 아닙니다.

-> 데이터의 양이 적고, 앞쪽에 위치한 데이터를 탐색할 때는 선형 탐색이 더 빠릅니다.

 

 

이진 탐색의 한계
  1. 정렬된 배열에서만 가능합니다.
  2. 규모가 작은 배열이라도 정렬되어 있지 않다면 이진 탐색을 사용해도 효율이 낮습니다.

 

이진 탐색 사용 사례
  1. 사전 검색
  2. 도서관 도서 검색
  3. 대규모 시스템에 대한 리소스 사항 파악
  4. 반도체 기업들에서 디지털/아날로그 레벨 측정하는 경우

 

BST 와 차이점

BST는 Tree는 하나의 노드가 두개의 하위 트리를 가지는 이진트리의 자료구조를 나타냅니ㅏㄷ

'알고리즘' 카테고리의 다른 글

문자열에서 숫자 추출 알고리즘  (0) 2022.08.01
동전 교환 알고리즘  (0) 2022.07.30
Brute Force Algorithm과 시뮬레이션  (0) 2022.07.28
Greedy Algorithm  (0) 2022.07.28
바빌로니아법으로 제곱근 값 구하기  (0) 2022.07.28
구현 능력을 보는 대표적인 사례

brute force ( 완전 탐색 )simulation ( 시뮬레이션 )

 

시뮬레이션

문제에서 요구하는 복잡한 구현 요구 사항을 하나도 빠뜨리지 않고 코드로 옮겨 마치 시뮬레이션 하는 것과 같은 모습을 그리는 것입니다.

 

- 예시
메신저로 대화할 때, 아래의 조건을 충족해야 한다.
1) 아이디는 닉네임, 소속이 담긴 배열이여야 한다.
2) 소속은 false, true, null 중 하나
3) 소속이 셋 중 하나가 아니라면 아이디, 닉네임, 소속, 대화 내용의 문자열을 모두 X로 표시
4) 아이디와 닉네임은 길이를 2진수로 바꾼 뒤, 바뀐 숫자를 더함
5) 닉네임과 대화 내용은 공백 : 공백 으로 구분 [ ex. 'blue', 'Green', 'null' : hello. ]

이러한 과정들을 소스코드로 옮겨 담는 것입니다.

 

 

 

Brute-Force Algorithm (BFA)

순수 컴퓨팅 성능에 의존하여 모든 가능성을 시도하여 문제를 해결하는 무차별 대입 방법입니다.

 

따라서 Brute Force를 사용한다는 것은 최적의 솔루션이 아니라는 것을 의미하기도 합니다.

 

공간복잡도, 시간복잡도의 요소를 고려하지 않고 최악의 시나리오를 취하더라도 솔루션을 찾으려고 하기 때문입니다.

 

그렇다면 브루트 포스 알고리즘을 왜 사용할까요?

 

 

BFA 사용하는 경우
  1. 프로세스를 높이는데 사용하는 다른 알고리즘이 없을 때
  2. 문제를 해결하는 여러 솔루션이 있고 각 솔루션을 확인해야 할 때
[ 예시 : 어떤 문서 중 'park' 란 문자열을 찾아야 한다고 가정 ]
문서는 사전처럼 정렬되어 있지 않기 때문에, 문서에 있는 모든 단어 들을 반복해서 비교해야합니다.
그래서 시간 복잡도는 O(n) 이 됩니다.

이처럼 브루트 포스 알고리즘은 문제에 더 적절한 해결 방법을 찾기 전에 시도하는 방법입니다.
그러나 데이터 범위가 점점 커질수록 상당히 비효율적입니다.
따라서 프로젝트 규모가 커진다면 더 효율적인 알고리즘을 사용해야 할 것입니다.

 

BFA는 어디서 사용하고 있을까?

반복문을 통해 범위를 줄이지 않고 하나하나 비교하는 것은 Brute Force 를 활용한 Algorithm입니다.

 

 1. 순차 검색 알고리즘

 

배열 안에 특정 값이 존재하는 0번 인덱스부터 차례로 검색

 

2. 문열 매칭 알고리즘

 

[ 길이가 n인 전체 문자열 ] 과 [ 길이가 m인 문자열 ] 을 비교해

길이가 m인 문자열 패턴을 포함하는지를 검색합니다.

 

3. 선택 정렬 알고리즘

 

 전체 배열을 검색해 현재 요소와 비교하고 컬렉션이 완전히 정렬될 때까지

현재 요소보다 더 작거나 큰 요소를 교환하는 정렬 알고리즘입니다.

 

4. 그 밖의 Brute Force 활용 알고리즘

 - 버블 정렬

 - BFS, DFS

 - DP

'알고리즘' 카테고리의 다른 글

문자열에서 숫자 추출 알고리즘  (0) 2022.08.01
동전 교환 알고리즘  (0) 2022.07.30
Binary Search Algorithm  (0) 2022.07.28
Greedy Algorithm  (0) 2022.07.28
바빌로니아법으로 제곱근 값 구하기  (0) 2022.07.28
Greedy Algorithm

매번 선택의 순간마다 최적의 상황을 쫓아 최종적인 해답에 도달하는 방법

 

탐욕 알고리즘의 해결 방법
  1. 선택 절차 : 현재 상태에서 최적의 해답 선택
  2. 적절성 검사 : 선택된 해가 문제의 조건에 만족하는지 검사
  3. 해답 검사 : 문제가 해결되었는지 확인 후 해결되지 않았다면 선택 절차로 돌아가 다시 반복
* 실생활 사례

손님으로 온 박씨가 "과자" , "음료" 를 선택해 물건 가격은 4040원이 나왔습니다.
손님은 계산을 위해 5000원을 내밀었고, "동전의 개수를 최소한"으로 거슬러 달라고 요청하였습니다.
4040원의 동전의 개수를 최소한으로 주는 방법이 탐욕 알고리즘의 대표적인 예제입니다.

1. 선택 절차
거스름돈 동전 개수를 줄이기 위해 현재 가장 가치있는 동전을을 우선 선택
 : 500원 -> 100원 -> 50원 -> 10원

2. 적절성 검사
1번 과정을 통해 동전들의 합이 거슬러 줄 금액을 초과하는지 검사합니다.
초과하면 가장 마지막에 선택한 동전 삭제 후, 1번으로 돌아가 한 단계 작은 동전을 선택

3. 해답 검사
선택된 동전들의 합이 거슬러 줄 금액과 일치하는지 검사합니다.
액수가 부족하면 1번부터 다시 반복합니다.

거스름돈 960원에 대한 위의 과정을 거치는 과정을 보겠습니다.

[1]
1. 500원 2개
2. 값이 초과됨으로 500원 1개, 100원 1개 선택

[2]
1. 500원 1개, 100원 5개
2. 값이 초과됨에 따라 500원 1개, 100원 4개, 50원 2개 선택 ....

이러한 과정을 거치게 됩니다.
그러면 총 500원 1개, 100원 4개, 50원 1개, 10원1개를 선택하게 됩니다.

 

탐욕 알고리즘의 반례 ( 마시멜로 실험 )

- 조건 : 지금 마시멜로를 받겠다고 하면 1개를 받고, 1분 기다렸다가 받으면 2개를 받을 수 있음

- 반례사항

: 탐욕 알고리즘에 따라 "현재" 최적의 선택을 하므로 1개의 마시멜로를 선택합니다.

 하지만 전체적으로 보면 1분뒤에 2개를 받는게 최적의 선택이 되므로 그리디 알고리즘은 최적의 해를 이끌어내지 못합니다.

 

탐욕 알고리즘이 최적의 해를 구하는 조건
  1. 탐욕적 선택 속성 : 앞의 선택이 이후의 선택에 영향을 주지 않는다.
  2. 최적 부분 구조 : 문제에 대한 최종 해결 방법은 부분 문제에 대해 최적 문제 해결 방법으로 구성됩니다.

 

결론

탐욕 알고리즘은 항상 최적의 결과를 도출하는 것은 아니지만,

어느정도 근사한 값을 빠르게 도출해준다는 장점이 있습니다.

문제 : Math.sqrt 없이 제곱근 구하기

루트4 = 2 처럼 한번에 구할 수 있는 수도 있지만

루트2 = 1.414213... 이 무리수인 것 처럼 근사값을 구하는 방식도 존재합니다.

그래서 이러한 경우를 위해 바빌로니아 법을 사용합니다.

 

우선 바빌로니아 법이 뭔지를 알아봅시다.

 

* 바빌로니아 법

임의의 수의 제곱근에 빠르게 수렴하는 수열을 만들어 근삿값을 구하는 방법입니다.
아래의 과정에 따라 양의 실수 a에 대하여 다음 과정을 따라 제곱근a의 근삿값을 구할 수 있습니다.



- 포인트는 다음과 같습니다.

 

1) x는 임의의 양의 실수

2) x는 항상 루트a보다 크므로 하한이 있는데 계속 감소하여 수렴

3) n이 높아질수록 정밀도가 올라감

 

위의 수식을 코드로 작성하기전에 적어보면 

X2 = Xn+1 , X1 = Xn 으로 가정하고,
X2 = 1/2 * (X1 + a/X1) = X1 * X1 + a / 2 * X1

그러면 위의 수식을 이용해 자바코드로 알고리즘을 풀어보도록 하겠습니다.

 

public class Babylonian {
    private double x,m;
    private double result;
    private int count;
    private double guess;

    // 근사값 구하기
    public double computeRoot(double a) {
        x = a;
        m = 0;
        while (m <= x) {
            // 제곱했을 때 딱 떨어진다면 바로 리턴
            // 제곱했을 때 수가 더 커진다면 제곱한 수의 -1을 리턴
            result = Math.floor((m+x) / 2);
            if (result * result > x) {
                x = result - 1;
            }
            else {
                m = x + 1;
            }
        }
        return x;
    }

    // 바빌로니아 공식
    public double BL(double num) {
        guess = computeRoot(num);
        if((num * num) == x) return num;
        while (guess * guess < num ) {
            guess += 0.01;
        }
        return guess;
    }
    
    public static void main(String[] args) {
        int num = 6;
        double b = BL(num);
        String res = String.format("%.2f",b);
        System.out.println(res);
    }
}

 

 

'알고리즘' 카테고리의 다른 글

문자열에서 숫자 추출 알고리즘  (0) 2022.08.01
동전 교환 알고리즘  (0) 2022.07.30
Binary Search Algorithm  (0) 2022.07.28
Brute Force Algorithm과 시뮬레이션  (0) 2022.07.28
Greedy Algorithm  (0) 2022.07.28

- 프로젝트의 전체적인 부분보다는 어려운 내용 중점적으로 블로깅할 계획입니다.

 

 

application.properties

내장 서버를 띄울 때 사용할 포트와 application 이름을 네이밍 할 수 있습니다.

 

* 포트는 따로 설정하지 않으면 8080이며, 80포트는 url 뒤에 포트 번호를 생략할 수 있습니다.

 

* applictaion.yml

application.properties 파일과 비교했을 때, 들여쓰기를 통해 설정 값들을 계층 구조로 관리할 수 있어서
가독성이 좋다는 장점이 있습니다.
하지만 문법이 엄격해진다는 단점이 있습니다. 예를 들어, 콜론 다음에 공백이 있는 경우에 동작하지 않습니다.
그래서 스터디에서는 원활한 진행을 위해 얌파일을 사용하지 않았습니다.

 

 

 

@RestController

Restful Web API를 좀 더 쉽게 만들기 위해 도입된 기능입니다.

그래서 아래에 Restful 웹서비스에 대한 설명을 적어 놓도록 하겠습니다.

 

@Controller , @ResponseBody 를 합쳐놓은 어노테이션입니다.

 - @Controller : 요청을 처리하는 컨트롤러
 - @ResponseBody : 자바 객체를 HTTP 응답 본문의 객체로 변환해 클라이언트에 전송합니다.
                       이를 통해 html파일을 만들지 않아도 웹 페이지에 문자열을 출력할 수 있습니다.

 

 

 

Restful 웹서비스

REST 방식의 웹 서비스를 쉽게 이해하기 위해 어떤 메시지를 주고 받게 되는지 동작 아키텍쳐를 살펴보겠습니다.

 

[ 일반적인 웹 서비스 ]

 

[ REST 방식의 웹 서비스 ]

 

REST 방식을 이용하면 복잡한 SOAP 메시지를 호출하지 않아도 URL을 이용해 데이터를 요청합니다.

위 그림을 보면 12133 이라는 URL 을 통해 데이터를 요청하고, 그 데이터는 XML 형식으로 반환됩니다.

즉, url 을 통해 데이터를 요청하면 아래의 소스코드가 나타납니다.

 

[ 반환 데이터 ]

 

그러면 여기서 REST의 특징을 알 수 있습니다.

  1. 레스트 방식의 웹서비스는 잘 정의된 url로 리소스를 표현
  2. 세션을 쓰지 않는다.
- 세션을 쓰지 않는다는 것은?
기존의 서블릿 개발에선 세션을 이용해 인증 정보를 가지고 다닙니다.
개발자에 따라 피라미터가 많이 들어가기도 하기때문에 요청 처리가 아주 무거워집니다.
또한 요청 전후 관계에 관련성이 생겨 요청을 하나의 서버가 처리해야합니다.
그래서 고가의 로드 밸런싱 서버가 필요해집니다.

REST는 이러한 세션을 사용하지 않기 때문에 요청을 완벽히 독립적으로 처리합니다.
따라서 각각의 요청과 이전 요청은 관련성이 없으므로 로드밸런싱이 간단해집니다.

 

  • REST 의 디자인 표준
  1. URL로 표현가능해야함
  2. 하나의 리소스들은 주변 리소스들과 연결되어 하나의 웹페이지처럼 표현되야함
  3. 현재 클라이언트의 상태를 서버에서 관리하지 않아야함
  4. 모든 요청은 일회성의 성격으로 이전 요청과 무관해야함
  5. url에 현재 상태를 표현할 수 있으면 좋다.
  6. HTTP에서 제공하는 기본적인 4가지 메서드, 추가적인 2가지 메서드를 이용해 리소스를 동작해야함

 

 

@GetMapping

클라이언트의 요청을 처리할 URL을 매핑합니다.

예를 들어, 

@GetMapping(value = "/")
public String HelloWorld() {
	return "Hello World";
}

를 하게되면 클라이언트가 url에 ip:port/ 를 입력하면 해당 요청에 대해 "Hello World" 를 출력해줍니다.

 

 

Lombok 라이브러리

롬복 라이브러리는 초기에 스프링 부트 프로젝트를 생성할 때 디펜던시에 추가를 하거나, 직접 추가하는 방법이 있습니다.

 

pom.xml 에 아래 Dependency를 추가해줍니다.

<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
</dependency>

 

롬복 라이브러리는 편리한 기능들을 제공해주는데 아래의 에노테이션을 살펴보겠습니다.

 

@Getter/@Setter 게터/세터 메소드
@ToString toString 메소드
@NonNull NPE 예외 발생 체크
@EqualsAndHashCode equals() , hashCode() 메소드 생성
@Builder 빌더 패턴 이용한 객체 생성
@NoArgsConstructor 피라미터가 없는 기본 생성자

 

 

 

'스터디' 카테고리의 다른 글

웹 애플리케이션 구현 방식  (0) 2022.08.03
네트워크3 - URL,DNS  (0) 2022.08.02
네트워크2 - IP  (0) 2022.08.02
네트워크1 - 애플리케이션과 TCP/IP  (0) 2022.08.02
스프링 부트를 왜 사용하는가?  (0) 2022.07.27

 

스프링 부트의 필요성

스프링 부트를 이해하려면 먼저 "왜" 쓰는지를 이해해야 합니다.

그래서 이번 블로깅을 통해서는 WHY에 대해 알아보도록 하겠습니다.

 

 

 

스프링 부트의 특징
  1. 내장 서버를 이용해 별도의 설정 없이 독립 실행이 가능한 스프링 애플리케이션
  2. 톰캣, 제티와 같은 웹 애플리케이션 서버 자체 내장
  3. 빌드 구성을 단순화 하기위한 Spring Boot Starter 의존성 제공
  4. XML 설정 없이 단순 자바 수준의 설정 방식 제공
  5. JAR 이용해 자바 옵션만으로 배포 가능
  6. 애플리케이션의 모니터링 관리를 위한 스프링 액추에이터 제공

위와 같은 여섯가지 장점이 있습니다.

이 장점들에 대해 알아보겠습니다.

 

 

 

WAS(Web Application Server ) 란?

웹 서버 + 웹 컨테이너입니다.

웹 컨테이너는 JSP와 Servlet 을 실행시킬 수 있는 환경으로 데이터를 동적으로 처리하는 부분입니다.

웹 서버는 클라이언트에게 데이터를 요청 받으면 컨테이너로 전송하여 데이터를 처리하는 정적인 데이터를 처리하는 부분입니다.

 

이러한 WAS 는 대표적으로 tomcat이 있습니다.

톰캣은 jsp, asp, php 등 개발 언어를 읽고 컨텐츠를 동적으로 처리하고, 서비스를 처리할 수 있게 해줍니다.

이러한 톰캣은 포트설정/ 서블릿 경로설정 / 라이브러리 환경 등 내부적으로 설정해야하는 부분이 많습니다.

 

그래서 스프링 부트가 WAS가 가진 내장 서버 때문에 별도의 내장 서버 설정을 안해도 된다는 것은 큰 장점입니다.

 

 

 

스프링 부트 스타터 의존성 제공이란?

스프링 부트에서 스타터란 설정을 자동화해주는 모듈입니다.

이는 프로젝트에서 설정해줘야하는 다양한 의존성을 사전에 미리 제공해준다는 것을 의미합니다.

 

이 기능의 큰 예시로 라이브러리를 보면 알 수 있습니다.

스프링 부트에선 Thymeleaf, lombok, Spring Web 등 많은 라이브러리를 코드 한 줄로 추가하여 사용할 수 있습니다.

실제 이러한 라이브러리를 사용하기 위해서는 이와 연관되어 있는 많은 라이브러리를 직접 추가해줘야합니다.

 

그리고 라이브러리의 버젼을 쉽게 변경하고 적용할 수 있습니다.

 

- 메이븐 환경 : pom.xml 수정

- 그레들 환경 : build.gradle 수정

 

 

 

XML 설정 없이 자바 수준 설정 방식이 왜 장점인가?

XML은 많은 태그를 가지고 있어 가독성이 떨어진다는 단점이 있습니다.

그래서 만약 문법을 잘못 작성하여 에러가 발생하면 그 원인을 찾기 힘듭니다.

 

web.xml의 servlet 설정을 예를 들어 보겠습니다.

 

servlet, servlet-mapping 등 각각의 순서를 맞춰야 하며 내부 코드에서 오타가 발생한다고 해도

에러가 친절히 나오는 것도 아닌 무슨 의미인지 모를 에러가 발생합니다.

이러한 설정들을 자바 코드로 클래스 단위로 설정하여 쉽게 관리할 수 있다는 장점이 있습니다.

 

 

 

스프링 액추에이터

서비스가 정상적으로 동작하고 있는지를 모니터링 해줍니다.

서비스는 개발 단계만큼 운영 단계가 중요합니다.

이러한 운영 단계에서 배포에 장애가 있고, 잘 동작하는지 확인해준다는 의미입니다.

 

 

 

스프링 프레임워크
  • 객체지향 설계원칙에 맞는 재사용과 확장이 가능한 애플리케이션 개발 스킬 향상
  • 보다 나은 성능과 서비스의 안전성이 필요한 복잡한 기업용 엔터프라이즈 시스템 구축 능력

스프렝 프레임워크를 학습한다면 위 두가지를 배울 수 있습니다.

 

스프링 프레임워크를 줄여서 보통 스프링이라고 부릅니다.

 

스프링을 배우는 이유를 과거의 JSP 를 사용하던 시절과 비교해 보겠습니다.

 

  1. 과거 JSP 를 사용하던 시기

JSP 개발 방식은 사용자에게 보여지는 View 페이지쪽 코드와 사용자의 요청을 처리하는 서버쪽 코드가 섞여 있는 형태의 개발 방식입니다.

쉽게 말해서 [ 웹 브라우저를 통해서 사용자에게 보여지는 HTML/JS 코드 ] 와 [ 사용자의 요청을 처리하는 서버의 Java 코드 ] 가 섞여 있습니다.

 예를 들어 보겠습니다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<!-- (1) 시작 -->
<%
    request.setCharacterEncoding("UTF-8");
    response.setContentType("text/html;charset=UTF-8");

    System.out.println("Hello Servlet doPost!");

    String todoName = request.getParameter("todoName");
    String todoDate = request.getParameter("todoDate");

    ToDo.todoList.add(new ToDo(todoName, todoDate));

    RequestDispatcher dispatcher = request.getRequestDispatcher("/todo_model1.jsp");
    request.setAttribute("todoList", ToDo.todoList);

    dispatcher.forward(request, response);
%>
<!-- (1) 끝 -->
<html>
<head>
    <meta http-equiv="Content-Language" content="ko"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>

    <title>TODO 등록</title>
    <style>
        #todoList {
            border: 1px solid #8F8F8F;
            width: 500px;
            border-collapse: collapse;
        }

        th, td {
            padding: 5px;
            border: 1px solid #8F8F8F;
        }
    </style>
    <script>
        function registerTodo(){
            var todoName = document.getElementById("todoName").value;
            var todoDate = document.getElementById("todoDate").value;

            if(!todoName){
                alert("할일을 입력해주세요..");
                return false;
            }
            if(!todoDate){
                alert("날짜를 입력해주세요.");
                return false;
            }

            var form = document.getElementById("todoForm");
            form.submit();

        }
    </script>
</head>
<body>
    <h3>TO DO 등록</h3>
    <div>
        <form id="todoForm" method="POST" action="/todo_model1.jsp">
            <input type="text" name="todoName" id="todoName" value=""/>
            <input type="date" name="todoDate" id="todoDate" value=""/>
            <input type="button" id="btnReg" value="등록" onclick="registerTodo()"/>
        </form>
    </div>
    <div>
        <h4>TO DO List</h4>
        <table id="todoList">
            <thead>
                <tr>
                    <td align="center">todo name</td><td align="center">todo date</td>
                </tr>
            </thead>
						<!-- (2) 시작 --->
            <tbody>
                <c:choose>
                    <c:when test="${fn:length(todoList) == 0}">
                        <tr>
                            <td align="center" colspan="2">할 일이 없습니다.</td>
                        </tr>
                    </c:when>
                    <c:otherwise>
                        <c:forEach items="${todoList}" var="todo">
                            <tr>
                                <td>${todo.todoName}</td><td align="center">${todo.todoDate}</td>
                            </tr>
                        </c:forEach>
                    </c:otherwise>
                </c:choose>
            </tbody>
						<!-- (2) 끝 -->
        </table>
    </div>
</body>
</html>

위의 코드는 사용자가 자신의 할일을 등록하는 화면과 등록한 할일을 목록으로 보여주는 간단한 로직입니다.

 

- (1) : 클라이언트 요청을 처리하는 서버쪽 코드

- (2) : 서버로부터 전달 받은 응답을 화면에 표시하기 위한 JSP 에서 지원하는 jstl 태그 영역

- 나머지 : (1),(2) 영역 이외에 나머지 구현 코드

 

이렇게 가독성이 아주 떨어집니다.

여기서 만약 Java 코드를 분리해 서블릿 클래스로 분리할 수도 있습니다.

참고로 Spring 을 사용한 웹 요청을 처리할 때에도 내부적으로 Servlet 을 사용합니다.

 

이렇게 아주 긴 코드를 SpringMVC 에서 구현한다면 다음과 같을 수 있습니다.

 

@Controller
public class ToDoController {
    @RequestMapping(value = "/todo", method = RequestMethod.POST)
    @ResponseBody
    public List<ToDo> todo(@RequestParam("todoName")String todoName,
                               @RequestParam("todoDate")String todoDate) {
        ToDo.todoList.add(new ToDo(todoName, todoDate));
        return ToDo.todoList;
    }

    @RequestMapping(value = "/todo", method = RequestMethod.GET)
    @ResponseBody
    public List<ToDo> todoList() {
        return ToDo.todoList;
    }
}

놀라올 정도로 간결해졌습니다.

 

그리고 이러한 SpringMVC 방식에서 데이터베이스 연결과 같은 복잡한 설정은

내부 속성 파일을 통해 아래의 코드와 같이 아주 간결하게 표현 가능합니다.

spring.h2.console.enabled=true
spring.h2.console.path=/console
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true

이렇게 복잡한 설정 작업들을 Spring 이 대신 처리해주기 때문에

개발자는 애플리케이션 핵슴 비지니스 로직에만 집중할 수 있습니다.

 

 

 

스프링 부트 프로젝트 생성하기

1. https://start.spring.io/ 접속
2. 각종 디펜던씨 추가
3. 프로젝트 설정
4. 경로를 정해 GENERATE

* 이번에 진행할 환경
빌드 툴 - maven
언어 - java11
패키징 - jar
의존성 추가 - Spring Web

 

'스터디' 카테고리의 다른 글

웹 애플리케이션 구현 방식  (0) 2022.08.03
네트워크3 - URL,DNS  (0) 2022.08.02
네트워크2 - IP  (0) 2022.08.02
네트워크1 - 애플리케이션과 TCP/IP  (0) 2022.08.02
쇼핑몰 초기 프로젝트 구성  (0) 2022.07.27
백트래킹

해를 찾는 도중 해가 아니어서 막히면, 돌아가서 다시 해를 찾아가는 기법입니다.

최적화 문제와 결정 문제를 풀때 주로 사용합니다.

 

DFS

가능한 모든 경로를 탑색합니다.

그래서 불필요할 것 같은 경로를 사전에 차단하거나 하는 행동이 없어서 경우의 수를 줄이기 힘듭니다.

그래서 경우의 수를 요구하는 문제는 DFS 로 처리하기 힘들 것입니다.

 

 

 

 

백트래킹

해를 찾아가는 도중, 지금 경로가 해가 될 것 같지 않으면 그 경로를 더 이상가지 않고 되돌아갑니다.

즉, 반복문의 횟수까지 줄일 수 있으므로 효율적입니다.

이를 가지치기라고 부릅니다.

 

일반적으로 불필요한 경로를 조기에 차단할 수 있게 되어 경우의 수가 줄어들어 효율성이 결정됩니다.

 

정리하자면, 백트래킹이란 모든 가능한 경우의 수 중에서 특정 조건을 만족하는 경우만 살펴보는 것입니다.

 

'JAVA' 카테고리의 다른 글

Stream  (0) 2022.08.10
HashMap  (0) 2022.08.10
람다식  (0) 2022.07.18
좋은 객체 지향 설계의 5가지 원칙  (0) 2022.07.08
객체 지향 프로그래밍  (0) 2022.07.08

저 같은 경우는 Entity 위치를 못 찾고 있어서 발생한 문제였습니다.

 

gradle 환경의 스프링에서 JPA를 사용하는데에에 있어서 persistence.xml로 설정을 하니 엔티티 위치를 못잡고 있었습니다.

 

persistence.xml에 아래 코드로 경로를 설정해주면 됩니다.

<class>xik.ShoppingMall.Domain.클래스명</class>
Lambda Expression

함수형 프로그래밍 기법을 지원하는 자바의 문법 요소

 

 

람다식이란?

메서드를 하나의 식으로 표현한 것으로, 코드를 간결하고 명확하게 표현할 수 있다는 장점이 있습니다.

JDK1.8 이후에 도입되었으며, 익명 객체이기 때문에 기존의 자바 문법 요소를 해치지 않는 프로그래밍 기법이

필요했습니다. 이에 따라 함수형 인터페이스가 만들어 졌습니다.

 

 

람다식의 기본 문법
//기존 메서드 표현 방식
void sayhello() {
	System.out.println("HELLO!")
}

//위의 코드를 람다식으로 표현한 식
() -> System.out.println("HELLO!")

기존의 메서드와 가장 두드러지게 나타나는 차이는

기본적으로 반환타입과 이름을 생략할 수 있다는 점입니다. 따라서 람다 함수를 익명 함수라고도 부릅니다.

 

 

람다식을 만드는 방법
// 기본 문법
int sum(int num1, int num2) {
	return num1 + num2;
}

// 람다식
(int num1, int num2) -> {
	num1+num2
}

1) 반환타입, 메서드명 제거 후 화살표 추가

2) return , 세미콜론 제거

 

// 기존 방식
void example1() {
	System.out.println(5);
}

// 람다식
() -> System.out.println(5);

: 실행문이 하나만 존재할 경우 중괄호 생략 가능

 

 

함수형 인터페이스

자바에서 함수는 반드시 클래스 안에서 정의되어야 하기 때문에 메서드가 독립적으로 있을 수 없습니다.

반드시 클래스 객체를 먼저 생성한 후 생성한 객체로 메서드를 호출해야합니다.

이러한 맥락에서 람다식 또한 사실은 객체입니다. 더 정확히는 이름이 없기 때문에 익명 클래스라 할 수 있습니다.

그래서 실제 우리가 사용하는 익명함수의 형태는 아래 코드와 같으며,

익명 클래스는 생성고 선언을 동시에 하는 단 한번만 사용된느 일회용 클래스입니다.

new Object() {
	int sum(int num1, int num2) {
		return num1 + num1;
	}
}

그런데 문제는 익명 객체를 Object 클래스에 담는다고 해도 sum 메서드를 사용할 방법이 없습니다.

이 같은 문제를 해결하기 위해 사용되는 것이 함수형 인터페이스입니다.

이 방법은 기존의 인터페이스 문법을 활용해 람다식을 다루는 것입니다.

아래의 예제 코드로 살펴볼 수 있습니다.

public class LamdaExample1 {
    public static void main(String[] args) {
		   /* Object obj = new Object() {
            int sum(int num1, int num2) {
                return num1 + num1;
            }
        };
			*/ 
		ExampleFunction exampleFunction = (num1, num2) -> num1 + num2
		System.out.println(exampleFunction.sum(10,15))
}

@FunctionalInterface // 컴파일러가 인터페이스가 바르게 정의되었는 지 확인할 수 있도록
interface ExampleFunction {
		public abstract int sum(int num1, int num2);
}

// 출력값
25

다시 순차적으로 설명해보겠습니다.

 

1. 함수형 인터페이스 작성

public interface MyFunctionalInterface {
    public void accept();
}

 

2. 인터페이스의 람다식 작성

MyFunctionalInterface example = () -> { ... };

람대식이 대입된 인터페이스의 참조 변수는 accept를 위와 같은 호출할 수 있습니다.

 

3. 람다식으로 호출

public class MyFunctionalInterfaceExample {
	public static void main(String[] args) throws Exception {
		MyFunctionalInterface example;
		example = () -> {
			String str = "첫 번째 메서드 호출!";
			System.out.println(str);
		};
		example.accept();

		example = () -> System.out.println("두 번째 메서드 호출!");
		//실행문이 하나라면 중괄호 { }는 생략 가능
		example.accept();
	}
}

// 출력값
첫 번째 메서드 호출!
두 번째 메서드 호출!

 

 

매개변수가 있는 람다식

1. 함수형 인터페이스에 매개변수가 존재

public interface MyFunctionalInterface {
    public void accept(int x);
}

 

2. 해당 인터페이스를 타겟 타입으로 갖는 람다식을 작성

public class MyFunctionalInterfaceExample {
    public static void main(String[] args) throws Exception {
        MyFunctionalInterface example;
        example = (x) -> {
            int result = x * 5;
            System.out.println(result);
        };
        example.accept(2);

        example = (x) -> System.out.println(x * 5);
        example.accept(2);
    }
}

// 출력값
10
10

 

 

 

return 문이 있는 람다식
public class MyFunctionalInterfaceExample {
    public static void main(String[] args) throws Exception {
        MyFunctionalInterface example;
        example = (x, y) -> {
            int result = x + y;
            return result;
        };
        int result1 = example.accept(2, 5);
        System.out.println(result1);
        

        example = (x, y) -> { return x + y; };
        int result2 = example.accept(2, 5);
        System.out.println(result2);
       

	      example = (x, y) ->  x + y;
				//return문 만 있을 경우, 중괄호 {}와 return문 생략가능
        int result3 = example.accept(2, 5);
        System.out.println(result3);
       

        example = (x, y) -> sum(x, y);
				//return문 만 있을 경우, 중괄호 {}와 return문 생략가능
        int result4 = example.accept(2, 5);
        System.out.println(result4);
 
    }

    public static int sum(int x, int y){
        return x + y;
    }
}

//출력값
7
7
7
7

여기서 주의 깊게 볼점은 return문만 있는 경우 중괄호를 생략한다는 점입니다.

 

 

메서드 레퍼런스

메서드 참조는 불필요한 매개변수를 제거할 때 주로 사용합니다.

람다식은 종종 기존 메서드를 단순히 호출만 하는 경우가 많습니다.

예를 들어, 아래 max 메서드를 호출하는 람다식에서 람다식의 역할은 단순히 매개 값 전달만 하여 불편해보입니다.

(left, right) -> Math.max(left, right);

그래서 이러한 경우 때문에 메서드 참조를 이용해 깔끔하게 처리할 수 있습니다.

Math :: max

 

 

메서드 레퍼런스의 정적 메서드와 인스턴스 메서드 참조
  1. 정적 메서드를 참조할 경우 클래스 이름 뒤에 :: 기호를 붙이고 정적 메서드 이름을 기술합니다.
  2. 인스턴스 메서드의 경우에는 먼저 객체를 생성한 다음 참조 변수 뒤에 ::기호를 붙이고 인스턴스 메서드 이름을 기술하면 됩니다.
public class Calculator {
  public static int staticMethod(int x, int y) {
                        return x + y;
  }

  public int instanceMethod(int x, int y) {
   return x * y;
  }
}

----------------------------------------------------------------

import java.util.function.IntBinaryOperator;

public class MethodReferences {
  public static void main(String[] args) throws Exception {
    IntBinaryOperator operator;

    /*정적 메서드
		클래스이름::메서드이름
		*/
    operator = Calculator::staticMethod;
    System.out.println("정적메서드 결과 : " + operator.applyAsInt(3, 5));

    /*인스턴스 메서드
		인스턴스명::메서드명
		*/
		
    Calculator calculator = new Calculator();
    operator = calculator::instanceMethod;
    System.out.println("인스턴스 메서드 결과 : "+ operator.applyAsInt(3, 5));
  }
}
/*
정적메서드 결과 : 8
인스턴스 메서드 결과 : 15
*/

 

메서드 레퍼런스의 생성자 참조

단순히 메서드 호출로 구성된 람다식은 메서드 참조로 대치할 수 있듯이,

단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치 가능합니다.

 

(a,b) -> {return new 클래스(a,b);};
// 이런 경우 아래로 바꾼다.
클래스 :: new

 

예제를 보겠습니다.

//Member.java
public class Member {
  private String name;
  private String id;

  public Member() {
    System.out.println("Member() 실행");
  }

  public Member(String id) {
    System.out.println("Member(String id) 실행");
    this.id = id;
  }

  public Member(String name, String id) {
    System.out.println("Member(String name, String id) 실행");
    this.id = id;
    this.name = name;
  }

  public String getName() {
    return name;
  }

public String getId() {
    return id;
  }
}
import java.util.function.BiFunction;
import java.util.function.Function;

public class ConstructorRef {
  public static void main(String[] args) throws Exception {
    Function<String, Member> function1 = Member::new;
    Member member1 = function1.apply("kimcoding");

    BiFunction<String, String, Member> function2 = Member::new;
    Member member2 = function2.apply("kimcoding", "김코딩");
  }
}

/*
Member(String id) 실행
Member(String name, String id) 실행
*/

 

스트림

배열, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자입니다.

 

  1. 스트림을 사용하면 List, Map, Set, 배열 등 다양한 데이터 소스로 부터 스트림을 만들 수 있고, 표준화된 방법으로 다룰 수 있습니다.
  2. 스트림은 데이터 소스를 다루는 풍부한 메서드를 제공합니다.
  3. 다량의 데이터에 복잡한 연산을 수행하면서도 가독성, 재사용성이 높은 코드를 작성할 수 있습니다.

 

선언형 프로그래밍

어떻게 수행하는지보다는 무엇을 수행하는지에 관심을 두는 프로그래밍 패러다임입니다.

명령형 방식은 하나하나 절차를 따라가야 코드를 이해할 수 있지만,

선언형 방식은 코드를 작성하면 내부 동작 원리를 모르더라도 코드가 무슨일을 하는지 이해할 수 있습니다.

즉, "어떻게" 영역은 추상화되있습니다.

 

// 기존의 리스트 조회 방식
public class ImperativeProgrammingExample {
    public static void main(String[] args){
        // List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
        int sum = 0;

        for(int number : numbers){
            if(number > 4 && (number % 2 == 0)){
                sum += number;
            }
        }

        System.out.println("# 명령형 프로그래밍 : " + sum);
    }
}


// 스트림 방식
public class DeclarativeProgramingExample {
    public static void main(String[] args){
        // List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
        List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);

        int sum =
                numbers.stream()
                        .filter(number -> number > 4 && (number % 2 == 0))
                        .mapToInt(number -> number)
                        .sum();

        System.out.println("# 선언형 프로그래밍: " + sum);
    }
}

 

 

람다식, 매서드 참조를 이용해 요소 처리
//Student.java
public class Student {
    private String name;
    private int score;

    public Student(String name, int score){
        this.name = name;
        this.score = score;
    }

    public String getName(){
        return name;
    }

    public int getScore(){
        return score;
    }
}

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamLambdaExample {
    public static void main(String[] args) throws Exception {
        List<Student> list = Arrays.asList(
            new Student("김코딩", 95),
            new Student("이자바", 92)
        );

        Stream<Student> stream = list.stream();
        stream.forEach( s -> {
            String name = s.getName();
            int score = s.getScore();
            System.out.println(name+ " - " +score);
        });
    }
}
/*
김코딩 - 95
이자바 - 92
*/

1) list를 stream형으로 바꿔서 stream 객체에 할당

2) stream 객체를 람다식으로 순환

 

 

내부 반복자를 사용하므로 병렬 처리가 쉽다는 장점이 있습니다.
  • 외부 반복자 : 개발자가 코드로 직접 컬렉션 요소를 반복해서 가져오는 것으로, [ for, Iterator, while ] 등이 해당됩니다.
  • 내부 반복자 : 컬렉션 내부에서 요소들을 반복시키고 개발자는 요소당 처리해야할 코드만 제공하는 코드 패턴입니다.

 - 내부 반복자의 장점

 

  1. 내부 반복자를 사용하게되면 컬렉션 내부에서 어떻게 반복시킬지는 컬렉션에 맡겨두고, 개발자는 요소 처리코드에만 집중할 수 있다는 점이 있습니다.
  2. 내부 반복자는 멀티 코어 CPU를 최대한 활용하기 때문에 병렬 작업을 통해 효율적으로 요소를 반복할 수 있습니다
  3. 중간 연산과 최종 연산을 할 수 있습니다.

* 병렬 처리

: 한 가지 작업을 서브 작업으로 나누고, 서브 작업들을 분리된 스레드에서 병렬처리하는 것을 의미합니다.

 병렬 스트림을 사용하기 위해선 스트림의 parallel() 메서드를 사용해야 합니다.

 

 * 중간연산과 최종연산 예시

: 학생 객체를 요소로 가지는 컬렉션에서 중간 연산에서는 학생의 점수를 뽑아내고, 최종 연산에는 점수의 평균값을 산출할 수 있습니다.

 

 

리덕션

데이터를 가공해서 축소하는 것을 의미합니다.

평균값, 카운팅, 최대/최소 값 등이 대표적인 예시입니다.

그러나 컬렉션의 요소를 바로 집계할 수 없을 때는 filter, mapping, sort 등 중간 연산이 필요합니다.

 

 

파이프라인

스트림은 필터링,매핑,그루핑,정렬 등의 중간 연산과 합계, 평균, 카운팅, 최대/최소 값 등의 최종 연산을

파이프라인으로 해결합니다.

 

파이프 라인은 여러 스트림이 연결되있는 구조입니다.

파이프 라인에선 최종 연산을 제외하곤 모두 중간 연산입니다.

 

중간 스트림이 생성될 때 중간 연산이 되는게 아니라 최종 연산이 시작되기 전까지 지연됩니다.

최종 연산이 시작되면 비로소 컬렉션 요소가 하나씩 중간 스트림에서 연산되고 최종 연산에 오게 됩니다.

 

예제로 살펴보겠습니다.

Stream<Member> maleFemaleStream = list.stream();
Stream<Member> maleStream = maleFemaleSTream.filter(m -> m.getGender() == Member.MALE);
IntStream ageStream = maleStream.mapToInt(Member::getAge);
OptionalDouble opd = ageStream.average();
double ageAve = opd.getAsDouble();

.filter(m-> m.getGender() == Member.MALE) 는 남자 Member 객체를 요소로 하는 새로운 스트림을 생성합니다.

.mapToInt(Member::getAge) 는 Member 객체를 age 값으로 매핑해서 age를 요소로 하는 새로운 스트림을 생성합니다.

average() 메소드는 age 요소의 평균을 OptionalDouble에 저장합니다. OptionalDouble에 저장된 평균 값을 읽으려면 getAsDouble() 메소드를 호출 하면 됩니다(Optional은 이후에 자세히 다룹니다).

 

위 코드에서 로컬 변수를 생략하고 연결하면 다음과 같은 형태의 파이프라인 코드만 남습니다.

 

스트림의 주의할 점
  1. 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않습니다.
  2. 스트림은 일회용입니다. 한번 사용하면 닫히므로 새로운 스트림을 열어야합니다.

* 단, 중간 스트림은 하나의 스트림에 여러번 사용할 수 있습니다.

 

 

스트림 생성
// List로부터 스트림을 생성
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> listStream = list.stream();
listStream.forEach(System.out::prinln); //스트림의 모든 요소를 출력.

 

 

중간 연산
  • distinct() : stream의 요소들에 중복된 데이터가 존재하는 경우, 중복을 제거하기 위해 사용합니다.
  • filter() : stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어냅니다.
  • map() : 기존의 stream 요소들을 대체하는 요소로 구성된 새로운 스트림을 형성하는 연산                                                    mapToInt(), mapToLong(), mapToDouble() 등이 있음
  • sorted() : Comparator 인자 없이 호출할 경우 오름차순으로 정렬되며, 내림차순 정렬은 reverseOrder 이용합니다.
  • peek() : 요소를 하나씩 돌면서 출력 

 

 

최종 연산

연산 결과가 스트림이 아니므로 한번만 연산이 가능합니다.

  • forEach() : 파이프라인 마지막에서 요소를 하나씩 연산
  • match() : 특정한 조건을 충족하는지 검사할 때 사용 [ allMatch, anyMatch, noneMatch ]
  • sum,count,average,max,min() : 기본 집계
  • reduce() : 하나의 응축으로 하는 방식입니다. count, sum 등 집계 메서드는 내부적으로 reduce가 있습니다.
  • collect() : List,Set,Map 등 다른 종류의 결과로 수집하고 싶은 경우 이용합니다.

 

'JAVA' 카테고리의 다른 글

HashMap  (0) 2022.08.10
백트래킹  (0) 2022.07.27
좋은 객체 지향 설계의 5가지 원칙  (0) 2022.07.08
객체 지향 프로그래밍  (0) 2022.07.08
String 중간 공백기준으로 배열 만들기  (0) 2022.07.06
연관 관계 매핑

객체의 참조와 테이블의 외래 키를 매핑하는 것

 

 

연관 관계 매핑이 왜 필요할까?

객체를 테이블에 맞춰 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.

아래 두가지의 객체와 테이블의 차이를 보면 알 수 있다.

 - 테이블은 외래 키로 조인을 사용해 연관된 테이블 찾음

 - 객체는 참조를 사용해 연관된 객체를 찾음

 

 

연관 관계 정의 규칙

1) 방향 : 단방향,양방향

2) 연관 관계의 부모 : 연관 관계에서 관리의 주체

3) 다중성 : N:1 , 1:N, 1:1, N:M

 

 

 

단방향, 양방향

여기서 방향이란 한 Entity가 다른 Entity를 가질 수 있느냐로 보면 됩니다.

* 데이터베이스의 Forein Key (외래키) : 데이터베이스의 테이블은 FK하나로 두 개의 테이블이 JOIN가능하다.

* 객체의 참조용 필드 : 객체의 경우 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능하다.

 

 1) 단방향 : 두 객체 사이에 하나의 객체만 참조용 필드를 갖고 참조

 

 2) 양방향 : 두 객체 모두가 각각 참조용 필드를 갖고 참조한다. 양방향 관계를 설정하면 많은 테이블과 연관 관계를 맺게 되고

                   클래스가 굉장히 복잡해진다. 그래서 기본적으로 단방향 매핑을 하고 나중에 역방향으로 탐색이 필요할 때 추가하는게 좋다.

 

 

 

모델링의 예시를 보자

회원과 주문의 관계를 보자

1명의 회원이 여러 상품을 주문을 할 수 있다. 즉, 1:n입니다.

회원은 주문할 때 주문 Id를 알 필요가 있고,

주문에서는 어떤 회원이 주문했는지 알아야하기 때문에 회원 Id를 알 필요가 있습니다.

그렇다면 양방향 관계 매핑이 필요합니다.

그러면 각각 @OneToMany, @ManyToOne 을 이용해야합니다.

 

 

[ MEMBER ]

@Entity
public class Member {

    @Getter
    @Setter
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="MEMBER_ID")
    private Long id; // 시스템에 저장하기 위해 시스템이 정하는 변수

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();

    @Getter
    @Setter
    @Column(name = "NAME")
    private String name; // 고객이 입력한 데이터

    public Member(){

    }
}

: MEMBER_ID 컬럼명의 id가 AUTO_INCREMET로 생성된다.

한 명의 회원이 여러 주문을 할 수 있으므로,

Member에서 Order은 @OneToMany를 합니다.

여기서 mappedBy는 양방향 관계 설정 시 관계의 주체가 되는 쪽에서 정의합니다.

그러니까 회원의 입장에서 여러 주문을 하는 것으로 즉, 주체가 회원이므로 mappedBy는 회원에 작성합니다.

 

 

 

[ ORDER ]

@Entity
@Table(name="ORDERS")
public class Order {
    @Getter
    @Setter
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ORDER_ID")
    private Long id;

    @Getter
    @Setter
    @ManyToOne
    @JoinColumn(name="MEMBER_ID")
    private Member member;
    
    public Order() {

    }
}

: Order에서 Member은 @ManyToOne을 합니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'JPA(MySQL,H2Database)' 카테고리의 다른 글

임베디드 값 타입  (0) 2022.08.08
SQL 내장함수  (0) 2022.08.05
JPQL 와 JPQL의 테스트 코드 작성  (0) 2022.07.29
EntityManager  (0) 2022.07.29
exception just for purpose of providing stack trace  (0) 2022.07.13

아래의 테스트 코드에서 롤백되는 에러가 발생하였습니다.

 

public class JpaTest {

    @Test
    void JpaTest() {
        // 엔티티 팩토리 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("5xik");
        // 엔티티 매니저 생성
        EntityManager em = emf.createEntityManager();
        // 트랜잭션 객체 생성
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            MemberTest member = new MemberTest();
            member.setId(1L);
            member.setName("parksung");
            member.setPhoneNumber("01074724537");

            em.persist(member);

            Member findMember = em.find(Member.class, 1L);

            System.out.println("findMember,Id = " + findMember.getId());
            System.out.println("findMember.Name = " + findMember.getName());
            tx.commit();
        } catch(Exception e){
            tx.rollback();
        } finally{
            em.close();
        }
        em.close();
    }

    @Entity
    class MemberTest{
        @Id
        @Setter
        @Getter
        private Long id;

        @Setter
        @Getter
        private String name;

        @Setter
        @Getter
        private String PhoneNumber;
    }
}

 

 

해결 시도 1.

 원래 이미 정의되어 있는 Member class 에서 불러오고 있었습니다.

 거기서 엔티티 Id에 대해서 아래와 같이 정의되어 있었습니다.

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)

 

 

Id 값을 자동으로 생성해주는 옵션인데,

setId (1L) 를 해주고 있으니 문제가 발생하나? 라는 생각을 했습니다.

그래서 멤버테스트 클래스를 만들어 해당 클래스에 대한 객체를 만들도록 하였습니다.

하지만 이렇게 해도 해결되지 않았습니다.

 

 

 

 

해결 시도 2.

em.persist(member);

영속성 컨텍스트에 저장할 때 문제가 발생한다고 합니다.

그러면 왜 이때 문제가 발생할까요 ??

에러 로그를 따라가보겠습니다. 

 

at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.markRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:324)

@Override
public TransactionStatus getStatus() {
	return rollbackOnly ? TransactionStatus.MARKED_ROLLBACK : jdbcResourceTransaction.getStatus();
}

@Override
public void markRollbackOnly() {
	if ( getStatus() != TransactionStatus.ROLLED_BACK ) {
		if ( log.isDebugEnabled() ) {
			log.debug(
					"JDBC transaction marked for rollback-only (exception provided for stack trace)",
			new Exception( "exception just for purpose of providing stack trace" )
			);
		}
		rollbackOnly = true;
	}
}

 

MARKED_ROLLBACK : Commit이 실패할 때 나는 오류입니다.

 

그러면 테스트 코드가 아닌 메인 메소드에서 실행해서 확인해보겠습니다.

java.lang.IllegalArgumentException: Unknown entity: xik.ShoppingMall.Domain.Member

Entity를 못찾고 있는것으로 보입니다.

 

 

 

[ Entity 위치를 못찾는 해결방법 ]

 

persistence.xml 파일에 엔티티가 위치하는 코드를 추가적으로 입력해줍니다.

이때, 테스트 코드에 임시로 적어놓은 클래스 파일을 지우고 원래 엔티티 클래스에 대한 엔티티의 경로를 설정하였습니다.

<class>xik.ShoppingMall.Domain.Member</class>

에러 없이 정상 출력됨을 확인할 수 있습니다.

 

 

 

'JPA(MySQL,H2Database)' 카테고리의 다른 글

임베디드 값 타입  (0) 2022.08.08
SQL 내장함수  (0) 2022.08.05
JPQL 와 JPQL의 테스트 코드 작성  (0) 2022.07.29
EntityManager  (0) 2022.07.29
연관 관계 매핑  (0) 2022.07.15
여러 클라이언트가 하나의 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태를 유지하게 설계하면 안된다.

 

이는 아주 중요한 개념입니다.

 

왜 중요할까요 ??

 

공유값이 바뀌는 에러가 발생

코드로 예를 들어보겠습니다.

[ SingleTon class ]

public class StateFulService {

    private int price; // 상태 유지 필드

    public void order(String name, int price) {
        System.out.println("name:" + name + " price:" + price);
        this.price = price;
    }

    public int getPrice() {
        return price;
    }
}

 

[ Test code ]

class StateFulServiceTest {
    @Test
    void FailSigleTon() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StateFulService stateFulService1 = ac.getBean("stateFulService", StateFulService.class);
        StateFulService stateFulService2 = ac.getBean("stateFulService", StateFulService.class);

        stateFulService1.order("parkA", 10000);
        stateFulService2.order("parkB", 20000);

        int price = stateFulService1.getPrice();

        System.out.println("price: " + price);

        Assertions.assertThat(stateFulService1.getPrice()).isEqualTo(20000);
    }

    static class TestConfig{

        @Bean
        public StateFulService stateFulService() {
            return new StateFulService();
        }
    }
}

위의 코드에서 보면 과정은 이렇습니다.

 

[ 테스트 코드에서 생성된 스프링 컨테이너는 기본적으로 싱글톤을 사용할 수 있게 설계되어 있습니다. ]

 

1)  parkA라는 주문을 후 getPrice함수로 바로 가격을 출력해줘야하는데

 

2) 그 중간에 parkB라는 주문이 들어옵니다.

 

3) 그러면 price 라는 객체를 공유하고 있기 때문에 10000 -> 20000으로 바꿔버리게 됩니다.

 

4) 그래서 stateFulService1 객체의 getPrice() 함수를 사용해도 20000이 출력되게 되는 오류입니다.

 

 

 

 

예시의 싱글톤이 이러한 에러가 발생하는 이유는 설계에 아주 큰 문제점이 있습니다.

 

*** " 테스트 코드 (클라이언트) 가 값을 변경한다 " 라는 것입니다. ***

 

그래서 스프링 빈은 항상 무상태로 설계해야 하는 것입니다.

 

그렇다면 위의 예시를 다시 무상태로 설계해보겠습니다.

 

 

[ SingleTon class ]

public class StateFulService {

    public int order(String name, int _price) {
        System.out.println("name:" + name + " price:" + _price);
        int price =  _price;
        return price;
    }
}

 

[ 테스트 코드 ]

class StateFulServiceTest {
    @Test
    void FailSigleTon() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
        StateFulService stateFulService1 = ac.getBean("stateFulService", StateFulService.class);
        StateFulService stateFulService2 = ac.getBean("stateFulService", StateFulService.class);

        int price1 = stateFulService1.order("parkA", 10000);
        int price2 = stateFulService2.order("parkB", 20000);

        Assertions.assertThat(price1).isEqualTo(10000);
    }

    static class TestConfig{

        @Bean
        public StateFulService stateFulService() {
            return new StateFulService();
        }
    }
}

 

이렇게 공유된 필드를 사용하는게 아니라 지역변수를 통해서 공유하지 못하게 해결할 수 있습니다.

 

 

이 외에 싱글톤의 문제점을 해결하는 방법으로 @Configuration 에노테이션을 사용하는 방법이 있습니다.
이 부분은 검색해보시면 많은 자료를 찾아볼 수 있습니다.

 

 

 

 

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springConfig': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'xik.ShoppingMall.Repository.MemberRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

위와 같은 에러가 발생하였습니다.
위의 에러를 해석해보면 빈을 등록할 때 에러가 발생한것으로 보입니다.

 

 

[ 기존 코드 ]

//    @Autowired
//    MemberServiceInterface memberService;
//
//    @Autowired
//    OrderService orderService;
//
//    @Autowired
//    DiscountPolicy discountPolicy;

 

 

[ 새로 작성한 코드 ]

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
MemberServiceInterface memberService = applicationContext.getBean("memberService", MemberServiceInterface.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);

 

 

클라이언트 코드에서 스프링 컨테이너를 사용하는 버젼으로 바꾸기 위해 사용하다가 에러가 발생했습니다.

 

뭐가 문제인지 에러 코드를 다시 읽어보겠습니다.

 

 

 

UnsatisfiedDependencyException : Error creating bean with name 'springConfig'

: springConfig 라는 이름을 찾고있네요 ??

근데 제 코드를 보면 applicationContext 객체를 불러올때도 SpringConfig를 사용해주고 있고, 

애초에 클래스명도 SpringConfig로 적어둔 상태입니다.

근데 확인을 해보니 애초에 맨 앞 철자가 소문자로 변경된 형태로 등록되기 때문에 상관없는 문제라고 하네요.

 

그러면 다음 에러코드를 읽어보겠습니다.

 

: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'xik.ShoppingMall.Repository.MemberRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

위의  코드를 읽어보니 Repository에서의 bean을 등록해줄 때 생기는 문제 같습니다.

코드의 구성도를 보면

 

[ SpringConfig ]

    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

 

[ SpringDataJpaRepository ]

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

    @Override
    Optional<Member> findByName(String name);

}

 

의심가는 부분이 있네요

applicationContext 로 getBean으로 빈들을 가져오는데,

제 코드 같은 경우 SpringConfig 생성자에서 JpaRepository로 구현된 레포지토리를 불러오는데

이 부분도  "@Autowired 로 주입을 해줄게 아니라 applicationContext로 주입을 해줘야하는게 아닌가? " 라는 생각입니다.

 

그런데 이 의문에도 확신이 없습니다.

의존성 주입과 빈 등록에 대해 확실히 알고 나서 진행해야할 것 같네요

https://tjdwns4537.tistory.com/52

 

의존성 주입과 빈 등록

IoC 컨테이너에 빈으로 등록이 되어야 의존성 주입을 할 수 있습니다. 먼저, 여러가지 의존성 주입 방법이 있지만 @Autowired를 통해서 주입하는 것을 알아보겠습니다. @Autowired 필요한 의존 객체의

tjdwns4537.tistory.com

 

정리를 해봤으니 에러에 대해 다시 분석해보겠습니다.

applicationContext는 스프링 컨테이너이고,

@Autowired는 의존관계 주입을 해주는 것이네요.

 

그러면 스프링 컨테이너에서 빈이 등록될 때 문제가 발생되는 것으로 보입니다.

 

[ IoC컨테이너 ]

[ 테스트 코드 ]

 

구성도를 보면

1) ApplicationContext 로 SpringConfig 컨테이너에 대한 빈 팩토리 생성

2) 팩토리의 getBean메서드를 통해 memberService,orderService라는 빈을 가져옴

* 그런데 memberRepository에 대한 빈이 없다는 에러내용

 => 결국, JpaRepository에서 자동으로 빈을 만들어주는 것으로 믿고 있었지만 실제로는 안만들어지고 있는것으로 보임

 => 순수 자바 코드인 MemoryMeberRepository로 @Bean등록해주고 테스트하니 정상 작동

 

 

 

* 결론

: @Autowired할 스프링 빈 대상이 없어 발생하는 문제입니다.

원인은 JpaRepository에서 빈을 등록해주는 과정에서 문제가 있는 것으로 보입니다.

그런데 JpaRepository를 상속받으면 자동으로 빈을 등록해주는 것으로 배웠는데 정확한 이유는 아직 파악하지 못했습니다.

이유를 알게되는대로 다시 블로그에 업데이트하도록 하겠습니다.

 

 

 

 

 

[ 2022.07.12 확인된 해결방법1 ]
Jpa이 버젼 문제였던 것으로 확인되었습니다.
spring-data-jpa 2버전은 spring-core 5부터 호환이 된다고 합니다.
버젼을 낮춰서 실행해보시면 해결됩니다.


[ 2022.07.12 확인된 해결방법2 ]
멤버 이름에 대소문자 구분 처리에 대한 에러가 있을 수 있다.

* 참고 : 아직 저는 해결되지 않았습니다.. ( 2022.07.19 해결됬습니다.. )

 

 

 

 

[ 해결완료 ]

해결방법 :

1) @Repository 어노테이션을 붙치면 레포지토리에 대한 에러가 @Component랑은 다르게 나온다는걸 알 게 되었습니다.

2) JpaRepository 상속받는 부분에 @Repository를 붙쳐주니 아래와 같은 에러로그를 볼 수 있습니다.

Repository인터페이스의 findall 메서드를 가리키고 있는거 같습니다. 그러면 서비스에서 findall을 호출해주는 부분을 찾아보겠습니다.

레포지토리의 findall을 실제 호출해주는 부분이 있습니다. 

 

그런데 기본적으로 jpaRepository를 상속받게 되면 기본적으로 제공되는 다양한 메서드들이 있습니다.

그 중에 findAll() 이라는 메서드가 있습니다.

그런데 저의 소스코드에서는 findAll과 같은 역할을 하는 findall 메서드가 존재합니다.

즉, 이유는 간단했습니다.

 

1) 컨트롤러에서 서비스의 findMeber() 메서드를 호출

2) findMember메서드에서는 findall() 메서드를 호출

3) 하지만 findall() 메서드는 jpaRepository 메서드와는 엄밀히 다른 메서드이므로 jpaRepository 상속 메서드 안에서 사용하고 싶다면

  따로 선언했어야함

   * JpaRepository 명명 규칙 : https://www.devkuma.com/docs/spring/jparepository%EC%9D%98-%EB%A9%94%EC%86%8C%EB%93%9C-%EB%AA%85%EB%AA%85-%EA%B7%9C%EC%B9%99/

 

4) MemberRepository에 선언되어 있는 findall() 메서드의 이름을 findAll() 로 바꿔준 후 서비스에서 사용

5) 테스트 결과 이상 없음

 

 

몇일 동안 이 문제로 삽질했는데 드디어 해결됬습니다 !!

IoC 컨테이너에 빈으로 등록이 되어야 의존성 주입을 할 수 있습니다.

먼저, 여러가지 의존성 주입 방법이 있지만 @Autowired를 통해서 주입하는 것을 알아보겠습니다.

 

@Autowired

필요한 의존 객체의 타입에 해당하는 빈을 찾아 주입합니다. 즉, IoC컨테이너 안에 등록되어 있는 빈에서 객체를 찾아 의존성 주입을 해줍니다.

 

 

Bean

스프링 IoC컨테이너가 관리하는 객체이다.

 

  •  장점

1) 의존성 관리가 용이

2) 빈으로 등록된 객체는 기본적으로 스코프가 "싱글톤"으로 정해짐

3) 라이프사이클 인터페이스를 지원해준다.

 

 

  •  BeanFactory

스프링 IoC컨테이너의 가장 최상위 인터페이스이다.

 

 

  • ApplicationContext

BeanFactory를 포함한 다른 컨테이너를 상속받는다.

 

 

  •   Bean 설정 방법 1

1) spring-boot-starter-web 의존성 설정

2) @Component 어노테이션을 클래스에 붙쳐준다. ( 자동으로 빈이 등록됨 )

3) @Autowired 어노테이션을 통해 의존성 주입을 해준다.

 

  • Bean 설정 방법 2 ( Java Config )

1) Config 파일에 @Configuration 어노테이션을 붙쳐준다.

2) @Bean을 통해 직접 빈을 등록해준다.

3) @Autowired를 통해 등록되어 있는 

 

 

 

 

정리

예제 코드를 보면

- @Bean은 Spring에 BookingService를 제공 ( 등록 )

- @Autowired는 이를 사용 ( 의존성 주입 )

 

@SpringBootApplication
public class Application {

  @Autowired
  BookingService bookingService;

  @Bean
  BookingService bookingService() {
    return new BookingService();
  }

  public static void main(String[] args) {
    bookingService.book("Alice", "Bob", "Carol");
  }
}
에러 코드 :
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [java.lang.Long arg0] in method [void xik.ShoppingMall.Service.OrderServiceImpTest.createOrder(java.lang.Long,java.lang.String,int)].

테스트 코드에서 의존성 주입을 하니 다음과 같은 에러가 발생하였습니다.

 

혹시나 싶어 순수 자바로 테스트를 해보려고 작성하다보니 이상한게 보이네요...

 

 

쓰지도 않는 인자들이 왜 들어있는지.. 지우니까 정상작동하네요..

 

허무한 해결완료입니다..

 

 

좋은 객체 지향 설계의 5가지 원칙 ( SOLID )

SRP, OCP, LSP, ISP, DIP

 

SRP ( Single repsonsibility principle ) 단일 책임 원칙
  1. 한 클래스는 하나의 책임만 가져야 한다.
  2. 하나의 책임이라는 것은 문맥과 상황에 따라 그 크기가 다를 수 있다.
  3. 중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙을 잘 따른 것이다.

        ex. 1) UI변경 : UI변경을 하는데 SQL 코드 등을 바꿔야한다면 그건 잘못 설계한 것이다.

              2) 객체의 생성과 사용을 분리

 

 

 

OCP ( Open/Closed Principle ) 개방-폐쇄 원칙 **
  1. 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
  2. 다형성을 활용해 인터페이스를 구현한 새로운 클래스를 하나 만들어 새로운 기능을 구현
  3. 역할과 분리를 이용한다.
  • OCP 문제점
  1. 예제에서 MemberService client에 Repository를 변경할때마다 구현 클래스를 변경하며 직접 선택
  2. 결국 구현 객체를 변경하려면 클라이언트 코드가 변경되야함
  3. 분명 다형성을 사용했지만 OCP원칙을 지킬 수 없음
  4. 해결하기 위해서는 객체를 생성하고, 연관관계를 맺어주는 별도의 조립/설정자가 필요함

        => 별도로 해주는게 스프링 컨테이너가 해주는 역할

            즉, OCP원칙을 지키기 위해 DI,Container가 필요한 것이다.

 

 

 

 

LSP 리스코프 치환 원칙
  1.  프로그램의 객체는 프로그램의 정확성을 깨드리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 함
  2.  다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야하며 인터페이스를 구현한 구현체는 믿고 사용하려면 이 원칙이 필요함
  3. 단순히 컴파일에 성공하는 것을 넘어서는 이야기
  • 예시

자동차 인터페이스의 엑셀은 앞으로 가라는 기능인데, 뒤로 가게 구현하면 LSP 위반임

느리더라도 무조건 앞으로 가야함

 

 

 

 

ISP 인터페이스 분리 원칙
  1. 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다
  2. 자동차 인터페이스 - 운전 인터페이스 / 정비 인터페이스로 분리
  3. 사용자 클라이언트 - 운전자 클라이언트 / 정비사 클라이언트로 분리
  4. 분리하면 정비 인터페이스 자체가 변해도 운전자 클라이언트에 영향을 주지 않음
  5. 인터페이스가 명확해지고 대체 가능성이 높아진다.

 

 

 

DIP 의존관계 역전 원칙 **
  1. 의존성 주입은 "추상화에 읜존해야지, 구체화에 의존하면 안된다" 라는 원칙을 따르는 방법이다.
  2. 즉, 구현클래스에 의존하지말고 인터페이스에 의존하라는 의미
  3. 역할에 의존하게 해야 한다는 것과 같다. 객체에서 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있음
  • 예시

MemberService client 코드를 바꿔야 했던 이유는 MemberService가 MemberRepository에도 의존을 하고, MemoryMeberMemberRepository도 의존을 하기때문에 

Repository를 변경하려고 할때마다 코드를 변경해야한다는 것입니다. 즉 인터페이스에 의존하지 못하는 상태로 DIP위반입니다.

 

 

 

* 정리

1) 객체 지향의 핵심은 다형성
2) 다형성 만으로는 쉽게 부품을 갈아 끼우듯이 개발할 수 없다.
3) 다형성 만으로는 구현 객체를 변경할 때 클라이언트 코드도 함께 변경된다.
4) 다형성 만으로는 OCP,DIP를 지킬 수 없다.
5)  스프링의 DI, Container 기능을 통해 가능하게 지원할 수있다.

 

 

 

 

 

 

 

 

 

 

 

 

'JAVA' 카테고리의 다른 글

백트래킹  (0) 2022.07.27
람다식  (0) 2022.07.18
객체 지향 프로그래밍  (0) 2022.07.08
String 중간 공백기준으로 배열 만들기  (0) 2022.07.06
Hamcrest, Matcher란?  (0) 2022.07.03
객체 지향 프로그래밍(OOP) 을 하는 이유는 뭘까?

프로그램을 유연하고 변경이 용이하게 만들기 때문에 대규모 소프트웨어 개발에 많이 사용됩니다.

 

 


객체 지향 프로그램을 설계하는 핵심적인 4가지 [ 다형성 ] [ 캡슐화 ] [ 상속 ] [ 추상화 ]

 

다형성

ㅇ 운전자와 자동차 예시

 

 운전자는 BMW를 타다가 아우디로 차를 바꾼다.

 차를 바꾼다고 해서 운전자가 운전을 못하게 되는 것은 아니다.

 여기서 요점은 자동차가 바뀌어도 운전자에게 영향을 안준다는 것이다. 

 이러한 것을 보고 "유연하고 변경이 용이하다" 라고 한다.

 

 이러한 다형성은 "클라이언트를 위해" 이뤄진다는 것이다.

 프로그램을 확장시키고, 새로운 기능을 추가를 해도 클라이언트는 무언가를 변경할 필요가 전혀 없다는 것입니다.

 이런게 가능 한 이유는 역할과 구현으로 세상을 구분했기 때문에 가능한 것입니다.

 여기서 중요한 요점은 새로운 기능을 추가한다는게 아니라 [ 새로운 기능이 추가되어도 클라이언트에게 전혀 영향을 미치지 않는다 ]

 라는 것입니다.

 

 

ㅇ 역할과 구현을 분리

  • 장점
  1. 클라이언트는 대상의 역할(인터페이스)만 알면 된다.
  2. 클라이언트는 내부 구조를 몰라도 된다.
  3. 클라이언트는 내부 구조가 변경되어도 전혀 영향을 받지 않는다.
  4. 클라이언트는 구현 대상 자체를 변경해도 영향을 받지 않는다. 
  • 자바언어 활용
  1. 자바 언어의 다형성을 활용 [ 역할=인터페이스 | 구현=인터페이스를 구현한 클래스, 구현 객체 ]
  2. 객체를 설계할 때 역할과 구현을 명확히 분리
  3. 객체 설계시 역할을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만들기
  • 자바 언어의 다형성
  1. 오버라이딩 된 메서드 실행
  2. 다형성으로 인터페이스를 구현한 객체를 실행 시점에 유연하게 변경할 수 있다.
  3. 클래스 상속 관계도 다형성이고, 오버라이딩 적용 가능하다.
  • 다형성의 본질
  1. 인터페이스를 구현한 객체 인스턴스 "실행시점"에 유연하게 변경할 수 있다.
  2. 협력이라는 객체사이의 관계에서 시작해야 한다.
  3. 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다
* 스프링과 객체 지향
스프링에서 제어의 역전(IoC), 의존 관계 주입(DI) 는 다형성을 활용해 역할과 구현을 편리하게 다룰 수 있도록 지원한다.

 

상속

기존의 클래스를 재사용하여 새로운 클래스를 작성하는 자바의 문법 요소

상위 클래스와 하위 클래소르 나누어 상위 클래스의 멤버를 하위 클래스에게 내려주는 것을 의미합니다.

 

ㅇ 클래스의 트리구조로 이해하기

위의 그림을 보게되면 세개의 클래스에 공통적인 속성과 기능이 정의되어 있다는 것을 알 수 있습니다.

즉, 사람이라면 누구나 먹고 배우고 걷는 특성이 있습니다. 

 

  • 왜 상속을 사용할까?

상속을 통해 앞선 예제처럼 코드를 재사용하여 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있습니다.

더 나아가 다형적 표현이 가능하다는 장점이 있습니다.

 

 * 다형적 표현이 가능하다 ?
프로그래머는 프로그래머다 = 참
프로그래머는 사람이다 = 참
이와 같이 하나의 객체가 여러 모양으로 표현될 수 있는것을 의미합니다.

 

  • 상속에서 주의할 점 !!

자바의 객체지향 프로그래밍에서는 다중 상속을 허용하지 않고, 단일 상속만을 허용한다는 것입니다. 

하지만 인터페이스를 통해 다중상속과 비슷한 효과를 낼 수 있습니다.

 

  • 상속과 함께 알아두면 좋을 포함관계

상속처럼 클래스를 재사용할 수 있도록 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것

public class Employee {
    int id;
    String name;
    Address address;  // 참조 변수 !!

    public Employee(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    void showInfo() {
        System.out.println(id + " " + name);
        System.out.println(address.city+ " " + address.country);
    }

    public static void main(String[] args) {
        Address address1 = new Address("서울", "한국"); //Address 할당 후
        Address address2 = new Address("도쿄", "일본");

        Employee e = new Employee(1, "김코딩", address1); // Employee와 함께 재할당
        Employee e2 = new Employee(2, "박해커", address2);

        e.showInfo();
        e2.showInfo();
    }
}

class Address {
    String city, country;

    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
}

 

  • 상속과 포함관계를 어떻게 구분해서 설정할 수 있을까 ?
  1. ~은 ~이다 ( IS-A 관계 ) : "Employee 는 Address이다" 는 성립하지 않는다.
  2. ~은 ~을 가지고 있다. ( HAS-A 관계 ) : "Employee는 Address를 가지고 있다" 는 성립

 이렇게 HAS-A 관계에서는 포함관계를 사용해주는게 적합합니다.

 

 

  • super 키워드

상속 관계에서 상위 클래스의 객체, super() 은 상위 클래스의 생성자를 의미합니다.

public class Super {
    public static void main(String[] args) {
        Lower l = new Lower();
        l.callNum();
    }
}

class Upper {
    int count = 20; // super.count
}

class Lower extends Upper {
    int count = 15; // this.count

    void callNum() {
        System.out.println("count = " + count);
        System.out.println("this.count = " + this.count);
        System.out.println("super.count = " + super.count);
    }
}
  • 위의 예제에서 super을 붙치지 않는다면?

 

자바 컴파일러가 해당 객체의 자신이 속한 인스턴스 객체의 멤버를 먼저 참조합니다.

즉, 자동적으로 this 역할을 하게 되는것입니다.

  • 상위클래스의 멤버 = super 
  • 자신의 멤버 = this

 

캡슐화

특정 객체 안에 관련된 속성과 기능을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것

 

  • 캡슐화를 왜 하는가?
  1. 외부로부터 객체의 속성과 기능이 함부로 변경되지 못하게 데이터를 보호
  2. 데이터가 변경되어도 다른 객체에 영향을 주지 않게 독립성을 확보
  3. 결론적으로 유지보수와 코드 확장시에 오류의 범위를 최소화 할 수 있음

 

  • 접근 제어자

데이터 노출을 방지할 수 있는 핵심적인 방법

  1. private : 동일 클래스에서만 접근 가능
  2. default : 동일 패키지내에서만 접근 가능
  3. protected : 동일 패키지 + 다른 패키지의 하위 클래스에서만 접근 가능
  4. public : 접근 제한 없음

 여기서 사실 private / default / public 은 이해가 되는데 protected가 잘 이해되지 않았습니다.

예제로 살펴보겠습니다.

package package1; // 패키지명 package1 

//파일명: Parent.java
class Test { // Test 클래스의 접근 제어자는 default
    public static void main(String[] args) {
        Parent p = new Parent();

        System.out.println(p.c);
    }
}

public class Parent { // Parent 클래스의 접근 제어자는 public
    protected int c = 3;

    public void printEach() { // 동일 클래스이기 때문에 에러발생하지 않음
        System.out.println(c);
    }
}

package package2; // package2 

//파일명 Test2.java
import package1.Parent;

class Child extends package1.Parent { // package1으로부터 Parent 클래스를 상속
    public void printEach() {
        System.out.println(c); // 다른 패키지의 하위 클래스
    }
}

public class Test2 {
    public static void main(String[] args) {
        Parent p = new Parent();
        
//        System.out.println(p.c); // 호출 에러!
    }
}

다른 패키지에서 prarent를 상속받은 클래스에서는 protect 멤버를 호출이 가능

단, 다른 클래스에서는 상속받지 않은 클래스의 경우 호출 에러가 나옵니다.

 

 * getter, setter메서드  :  private 객체의 데이터 값을 추가, 수정하고 싶은 경우에 사용

 * final 키워드
 - 클래스 : 변경/확장/상속 불가능
 - 메서드 : 오버라이딩 불가능
 - 변수 : 값 변경 불가능

 

 

 

추상화

객체의 공통적인 속성과 기능을 추출하여 정의하는 것

 

  • abstract, interface

- abstract : 추상메서드를 만드는 미완성 설계도입니다. 

- interface : 기본 설계도

 

  • 추상화를 왜 사용하는가?

메서의 내용이 상속 받는 클래스에 따라 종종 달라지기 때문에 상위 클래스에서 선언부만을 작성하고

실제 구현 내용은 상속을 받는 하위 클래스에서 구현하도록 비워둔다면 설계하는 상황이 변하더라도

유연하게 대응할 수 있습니다.

 이때 사용되는 것이 "오버라이딩" 입니다.

 

  • Interface 의 특징

1) class 대신 inteface 키워드를 사용하며 모든 필드가 public static final로 정의됩니다.

2) extends 키워드 대신 implements 키워드를 사용합니다.

3) 구현 클래스에서 는 해당 인터페이스에 정의된 모든 추상메서드를 반드시 구현해야합니다.

4) 하나의 클래스가 여러 인터페이스를 구현할 수 있는 다중 구현이 가능합니다.

    단, 인터페이스는 인터페이스로부터만 상속이 가능합니다.

 

 

* 인터페이스는 왜 다중 구현이 가능할까?
: 클래스에서 다중 상속이 불가능했던 이유는 부모 클래스에 동일한 이름의 필드,메서드가 있는 경우 충돌할 수 있기 때문입니다.
하지만 인터페이스는 애초에 미완성 멤버를 가지고 있기 때문에 충돌할 여지가 없습니다.

 

  • 추상클래스와 인터페이스의 사용의도 차이
  1. 추상클래스 : IS-A "~이다" 의 개념
  2. 인터페이스 : HAS-A "~을 할 수 있는" 의 개념

 

 

 

 

'JAVA' 카테고리의 다른 글

람다식  (0) 2022.07.18
좋은 객체 지향 설계의 5가지 원칙  (0) 2022.07.08
String 중간 공백기준으로 배열 만들기  (0) 2022.07.06
Hamcrest, Matcher란?  (0) 2022.07.03
Optional 이란?  (0) 2022.07.02

ㅇ GET/POST 이해하기

 

1) GET 란?

 - URL에 데이터를 포함시켜 요청

 - 데이터를 헤더에 포함하여 전송

 - URL에 데이터가 노출되어 보안에 취약

 - 캐싱할 수 있음

 => 주로 조회할때만 사용

 

2) POST 란?

 - URL에 데이터를 노출하지 않고 요청

 - 데이터를 바디에 포함

 - URL에 데이터가 노출되지 않아 GET방식보다 보안이 높음

 - 캐싱할 수 없음

=> 주로 노출되면 안되는 데이터를 저장할 때 사용

 

 

HttpMethod

: 다음의 각 메소드들은 HttpMethod에 매칭됩니다.

1) PostMapping

2) GetMapping

3) PutMapping

4) DeleteMapping

5) PatchMapping

 

 

 

ㅇ RequestMapping

1) 스프링부트 애플리케이션이 실행되면 애플리케이션에서 사용할 bean들을 담을 ApplicationContext를 생성하고 초기화합니다.

2) @RequestMapping 이 붙은 메서드들이 핸들러에 등록되는 것은 Application refresh되는 과정에서 일어납니다.

   refresh과정에서 Spring Application 구동을 위해 많은 Bean들이 생성되고,

  그 중 하나가 RequestMappingHandlerMapping 입니다. 이 Bean은 우리가 @RequestMapping 으로 등록한 메서드들을 가지고      있다가 요청이 들어오면 Mapping해주는 역할을 합니다.

3) Bean으로 등록된 HandlerMapping이 변수들을 찾아서 Adapter를 거쳐 실행합니다.

 

 

 

ㅇ Handler

: Spring MVC에서는 핸들러가 @Controller클래스를 의미합니다.

@GetMapping / @PostMapping 을 핸들러 메서드라고 부릅니다.

결국 Handler Mapping 이란 사용자의 요청과 이 요청을 처리하는 Handler를 매핑해주는 역할을 하는 것입니다.

 

 

 

 

ㅇ HandlerAdapter

:  스프링 부트가 아닌 다른 프레임워크의 핸들러를 Spring MVC에 통합하기 위해서는

 HandlerAdapter를 사용할 수 있습니다.

 

 

 

 

 ㅇ @RequestMapping

 

1) value 

: URL값으로 매핑 조건을 부여합니다. 보통 호스트주소와 포트번호를 제외한 url주소를 넣어줍니다.

  이는 다중 요청이 가능하여 @RequestMapping ( {"/hello". "/hello-rule", "/hello/**" } ) 형식으로 사용할 수 있습니다.

 

2) method

: HTTP request 메소드 값을 매핑조건으로 부여하는데 GET,POST,HEAD,OPTIONS,PUT,DELETE,TRACE 메소드가 존재합니다.

 

 

 

 

ㅇ PostMapping / GetMapping

: Post/Get method 로 RequestMapping을 합니다.

 

1) @PostMapping : HTTP Post Method에 해당하는 단축 표현으로 서버에 리소스를 등록(저장)할 때 사용합니다.

2) @GetMapping : HTTP Get Method에 해당하는 단축 표현으로 서버의 리소스를 조회할 때 사용합니다.

3) @DeleteMapping : 서버의 리소스를 삭제

4) @PutMapping : 서버의 리소스를 모두 수정

5) @PatchMapping : 서버의 리소스를 일부 수정

 

 

 

 

ㅇ 현재 저의 소스코드로 설명

@GetMapping("/new")
public String New() {
        return "/Login/회원가입";
}

@PostMapping("/new")
public String create(MemberForm form) {
        Member member = new Member();
        member.setName(form.getName());
        member.setPhoneNumber(form.getPhoneNumber());

        memberService.join(member);

        return "redirect:/";
}
<form action="/new" method="post">

 - 위의 소스 코드에 대한 설명 및 순서도

 

 

 

 

 

 

 

 

* 출처

https://itvillage.tistory.com/33

https://velog.io/@dyunge_100/Spring-%EC%9A%94%EC%B2%AD-%EB%B0%A9%EC%8B%9DRequestMapping-GetMapping-PostMapping

 

 

김영한님의 수업에서 JPA 실습을 해보던 중 아래와 같은 에러가 발생하였습니다.

javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not prepare statement

 

위의 에러 내용을 보면 Column "MEMBER0_.USER_NAME" not found; SQL statement: 라는 문구를 볼 수 있습니다.

 

그래서 몇가지 의심되는 부분에 대해 해결해봤습니다.

 

1. Member class 에 Column을 잘못 설정했는지 확인

: 테이블에 컬럼명을 이미 만들어 둔 상태기때문에 @Column으로 이름을 정해줘서 사용하는데, 이때 오타가 있나 확인해봤습니다.

 

 => 정상인 것으로 확인

 

2. application.properties 에 jpa 설정 제대로 추가해줬는지 확인 ( 해결 )    --> 아래 내용 읽어보기

spring.jpa.hibernate.ddl-auto = create

원래 create가 아닌 none으로 설정해줬었는데 이 부분에서 오류가 나고 있었습니다.

원래 제가 이해한 바로는 create는 객체에 대한 테이블과 테이블의 컬럼명을 자동으로 생성해주기때문에 저는 none을 사용했습니다.

그런데 이 부분에 대해 오류가 나고 있었던걸 보아 다시 개념을 확실히 잡고 넘어가야 할것 같습니다.

https://tjdwns4537.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts%2F 

 

TISTORY

나를 표현하는 블로그를 만들어보세요.

www.tistory.com

 

여기서 또 다시 드는 의문점은

create를 해주고 다시 none을 해준 후 테스트를 해보면 되는 것입니다.

그래서 테이블을 직접 확인해보니 phonenumber이였던 컬럼명이 phone_number이 되어있었습니다.

그래서 테이블을 삭제 후 다시 테스트를 해보던 중 아래와 같은 사실을 알게 되었습니다.

 

Member class에 각 객체에 @Column 으로 이름을 설정해줬는데

이때 @Column(name="phoneNumber") 로 설정을 하니까 PHONE_NUMBER로 읽고 PHONE_NUMER 컬럼을 찾으려고하니

에러가 발생하고 있던것입니다.

그래서 @Column(name="phonenumber")라고 설정하니 ddl-auto = none을 하고도 에러발생하지 않았습니다.

spring.jpa.hibernate.ddl-auto = none

이 외에도 column명을 설정할때 전부 대문자로 해도 에러는 발생하지 않습니다.

단지 소문자뒤에 대문자가 오면 구분자로 (_) 가 들어간다는 것입니다.

 

그래서 다시 확인을 해보니 다음과 같은 규칙이 있었습니다.

 

 

ㅇ JPA의 기본 테이블 DDL Naming 전략

 기본적으로 JPA는 DDL을 진행할 때 Entity 이름과 해당 필드들을 lower_under_score 방식을 이용합니다.

 예를 들어, @Column(name="PARK") 를 하면 Table에는 park로 lowerCarmelCase 를 lower_snake_case로

 변환하여 테이블을 생성합니다.

 

 이는 중요한 문제입니다.

 그 이유는 MySQL 에서 쿼리를 날릴 때에 [ 문자의 대소문자를 확실히 구분하는 설정 ] 과 [ 그렇지 않은 설정 ] 이

 있기 때문에 만약 확실히 구분하는 설정일 경우에 에러가 발생합니다.

 

 

* 해결 방법

SpringPhysicalNamingStrategy를 상속받아 Custom하여 설정값으로 물려주는 방법입니다.

 

1) 상속받는다.

2) 오버라이딩하여 함수를 작성해준다.

public class UpperCaseNamingStrategy extends SpringPhysicalNamingStrategy {

    @Override
    protected Identifier getIdentifier(String name, boolean quoted, JdbcEnvironment jdbcEnvironment) {
        return new Identifier(name.toUpperCase(), quoted); // 파라미터로 넘어온 name값을 대문자화 시킵니다.
    }
}

3) UpperCaseNamingStrategy 클래스를 application.yml에 설정해줍니다.

spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: web.common.strategy.UpperCaseNamingStrategy # 커스텀클래스 패키지

 ==> 확인해보면 UPPER_SNAKE_CASE가 적용된 모습을 볼 수 있습니다.

 

 

 

 

- 결론

1) 애초에 컬럼명을 설정할 때, 대문자나 소문자 하나로 설정한다.

2) UpperCaseNamingStrategy 클래스를 이용한다.

 

 

 

 

* 링크

https://velog.io/@devduhan/Spring%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-JPA-Naming-%EC%A0%84%EB%9E%B5

 

# JPA설정 이해하기

spring.jpa.hibernate.ddl-auto = create

 

- 역할

 1) create : 해당하는 테이블이 있으면 DROP하고 새로 만들어버린다.

 2) create-drop : create와 같은데 종료시점에 테이블 DROP한다.

 3) update : 변경분만 반영

 4) validate : 엔티티와 테이블이 정상매핑되었는지만 확인

 5) none : 사용하지 않음 ( 사실상 없는 값이지만 관례상으로 적는다. )

 

- 주의사항

: create, create-drop, update는 로컬 또는 개발 초기단계 환경에서만 사용해야한다.

   위의 역할에서 처럼 테이블을 자동으로 DROP해버린다는건.. 상상도 하기 싫은 일이다.

 

public String[] test(String str) {
    if (str.isEmpty()) {
      return new String[]{};
    }

    return str.split(" ");
  }

- 여기서 주의깊게 볼것은 str.isEmpty() 의 활용성입니다. 이와 비슷하게 isBlank() 라는 메서드도 존재하는데

둘을 비교해보자면

 

1) isEmpty()

문자열의 길이를 체크하여, 문자열의 길이가 0인 경우에만 true를 리턴하기 때문에

빈 공백이 들어있는 문자열은 false를 리턴합니다.

 

2) isBlank()

문자열이 비어 있거나, 빈 공백(white space)를 포함하고 있는 경우에 true를 리턴하기 때문에

위 케이스의 경우 true를 리턴합니다.

 

즉, if문은 문자열이 빈칸일때에 반환타입인 빈 String[]을 반환하는 것입니다.

 

 

- str.split(" ")

: str에 빈 공백(" ") 을 기준으로 나눈다는 의미입니다.

'JAVA' 카테고리의 다른 글

좋은 객체 지향 설계의 5가지 원칙  (0) 2022.07.08
객체 지향 프로그래밍  (0) 2022.07.08
Hamcrest, Matcher란?  (0) 2022.07.03
Optional 이란?  (0) 2022.07.02
상속을 응용한 스택 구현  (0) 2021.10.07

인프런 김영한 강사님의 수업을 들으면서 실습하던 중 아래와 같은 에러가 발생하였습니다.

org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "INSERT INTO MEMBER(NAME) VALUE[*]()"; expected "DIRECT, SORTED, DEFAULT, VALUES, SET, (, WITH, SELECT, TABLE, VALUES"; SQL statement:

insert into member(name) value() [42001-200]



java.lang.IllegalStateException: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "INSERT INTO MEMBER(NAME) VALUE[*]()"; expected "DIRECT, SORTED, DEFAULT, VALUES, SET, (, WITH, SELECT, TABLE, VALUES"; SQL statement:
insert into member(name) value() [42001-200]



Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Syntax error in SQL statement "INSERT INTO MEMBER(NAME) VALUE[*]()"; expected "DIRECT, SORTED, DEFAULT, VALUES, SET, (, WITH, SELECT, TABLE, VALUES"; SQL statement:
insert into member(name) value() [42001-200]



2022-07-05 17:38:53.021  INFO 96188 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
MemberServiceIntegrationTest > join() FAILED

위와 같은 에러가 발생하는 이유는 강사님은 Member.name 에 대해서만 실습을 진행하였는데

저는 PhoneNumber 라는 변수 하나를 추가해서 실습했기 때문이라고 추정됩니다.

 

그런데 DB를 모르니 뭐가 문젠지 통 알 수가 없네요..

천천히 하나씩 뜯어보도록 하겠습니다.

 

가장 먼저

1) Syntax error in SQL statement "INSERT INTO MEMBER(NAME) VALUE[*]()"

2) insert into member(name) value() [42001-200]

 

이 두 문장을 봤을때 INSERT 쿼리에서 문제가 발생했다는 것 같습니다.

그래서 코드를 뜯어 보겠습니다.

"insert into member(name) value()";

- 해석 : member 테이블에 name 컬럼에 value() 값을 넣어준다.

이 부분에서 에러가 발생하는것 같습니다.. 그렇다면 h2 database 쪽 문제로 보이는데

 

 

1. dependency 버전 문제인가?

 

아래 사진을 보면 인텔리제이의 h2 버전이 제가 실행중인 h2 버전과 다른걸 알 수 있습니다.

 

 

제가 사용중인 버전은 아래 사진처럼 1.4.200입니다.

 

아래 사진과 같이 버전을 직접 적어서 dependency를 추가해줍니다.

 

똑같은 오류가 발생중입니다..

 

2. 예외의 원인을 찾아보자

IllegalStateException

위와 같은 예외가 제일 먼저 발생하는데 그 이유는 무엇일까?

 - 위와 같은 오류가 발생한 이유는 이전에 value 값이 들어가지 않았기 때문에 없는 value 에 접근하려고하니 에러가 발생하는 것이였다.

 

3. 오타 해결...

String sql = "insert into member(name) values(?)";

 1) value 가 아니라 values로 값 목록을 찾아야한다.

 2) values ( ? ) 은 레코드를 다수 넣기위한 하나의 문법이다.

 - 둘 이상의 값을 포함하는 배열을 생성 후

 - 쿼리문에 물음표를 작성하면 해당 자리가 값 배열로 대체됩니다.

String sql = "insert into member(name) values(?)";
PreparedStatement pstmt = null;
Connection conn = null;
pstmt = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);

 * Statement.RETURN_GENERATED_KEYS

 : DB상에 AUTO_INCREMENT로 인해 자동으로 생성되어진 key(=id) 를 가져오는 쿼리

 * Statement class

 : SQL 구문 실행하는 역할

 * PreparedStatement class

 : Statement를 상속받는 인터페이스로 동일한 SQL구문을 반복 실행한다면 향상된 기능으로 실행시키는 객체

 - 예시)

 // Statement class
String name = "홍길동";
String memo = "메모 테스트 입니다. 홍길동's 메모장";
String priority = "1";
String sql = String.format("insert into tblMemo values(memoSeq.nextval,'%s','%s',default,%s)", name, memo, priority); 


// PreparedStatement class
sql = "insert into tblMemo values(memoSeq.nextval,?,?,default,?)";
pstat = conn.prepareStatement(sql);

//매개변수 값 대입 + 매개변수 유효화 처리.
pstat.setString(1, name);
pstat.setString(2, memo);
pstat.setString(3, priority);

INFO: 0 containers and 3 tests were Method or class mismatch 아 같은 에러가 발생하였습니다.

 

이 에러는 테스트 메서드를 3개를 작성해놓고 1개만 실행시킨 경우에 발생하는 에러로 무시하고 진행해도 됩니다.

 

 

 

 

해결하고 싶은경우 맥OS 의 인텔리제이 기준으로

 

preference -> Build, Execution, Deployment -> Build Tools -> Gradle 에서

 

Build and run using Run tests using의 속성을 Intellij IDEA로 해주시면 해결됩니다.

 

스프링 부트 2.4에서 패치가 생겼습니다.

그래서 스프링 부트 2.4부터는 application.properties밑에 `spring.datasource.username=sa`를 꼭 붙쳐줘야합니다.

- 발생한 문제

: 로컬에 있는 메모장을 실수로 업로드 해버렸습니다..

1) 깃헙에서 메모장을 삭제,  로컬에서 메모장을 삭제

2) 두가지 행동을 취한 후 프로젝트 작업 진행

3) 수정된 프로젝트를 다시  깃헙에 업로드 후 아래 오류 발생

저번에 봤던 오류와 같은 오류입니다.

간단히 pull 하고 다시 push 하면 될 것으로 예상이 되는데...

 

 

[ 제가 가진 의문점 ] 

 : pull 하면 제가 수정한 파일이 깃헙에 업로드된 파일로 덮어쓰기 되버리는게 아닌가 ?? 라는 점입니다.

 

 

그래서 복사본을 따로 어디에 넣어두고 pull 하고 push 해보니 덮어쓰기 이런거 없이 잘 해결 되었습니다.

 

* 결론 : pull 은 내가 수정한 파일들을 해치지 않는다 !!

 

 

 

 

그리고 위의 오류에 대한 해결 사항은 아래 글을 보시면 해결할 수 있습니다.

https://tjdwns4537.tistory.com/34

 

깃헙 Everything up-to-date , error: failed to push some refs to 오류

발생 오류: - 현상태 : Repository 에 수정된 부분을 업로드하려는 상황 1. 커밋 후 푸쉬 2. Everything up-to-date 라고 뜨고 아무것도 업데이트되지 않음. 방법1. git clean -f : untracked file 에 .git ignore..

tjdwns4537.tistory.com

 

+ Recent posts