spring-boot-rest를 해보자!(1)

Spring boot rest 를 이용하여 API 서버를 개발해보자!

Boot에 대해 알아봤으니 다음은 spring-boot-rest 대해 알아보자.
모르는분은 링크참조
rest중 우리는 jpa를 살펴볼것이다.

프로젝트 생성후 처음 할일은 메이븐에 디펜던시를 추가 하는일이다.
아래와같이 추가를 해보자.

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-rest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.6</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

살펴보면 spring-boot-starter-data-restweb, jackson과 관련된 라이브러리가 디펜던시 되어있다.
spring-boot-starter-data-jpa 아시다시피 jpa 관련 라이브러리다.
간단하게 살펴보는거니까 메모리 디비를 사용하겠다.
귀찮은 작업이 있으니 아주 좋은 lombok을 사용하겠다. 이놈은 선택사항이다. 모르시는분은 인터넷을 찾아보면 아주 자세히 나와있다.

메인 클래스를 만들자.

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

다음으론 entity(domain) 클래스를 만들자.

@Entity
@Data
public class Account {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String email;

    private String password;

}

다음으론 Repository 인터페이스를 생성해보자

@RepositoryRestResource(collectionResourceRel = "account", path = "account")
public interface AccountRepository extends JpaRepository<Account, Long> {

    Page<Account> findByname(@Param("name") String name, Pageable pageable);
}

기본 데이터가 있어야 하기 때문에 resourcesimport.sql을 추가 한다. 아래와 같이 입력해보자

insert into account(id, name, email, password) values(1,'wonwoo','wonwoo@test.com','qwer')
insert into account(id, name, email, password) values(2,'kevin','kevin@test.com','asdf')
insert into account(id, name, email, password) values(3,'wonwoo1','kevin@test.com','qwqw')

혹은 java8을 이용한다면 좀더 멋지게 해보자

    @Bean
    CommandLineRunner runner(AccountRepository accountRepository) {
        return args -> {
            Arrays.asList(
                    new Account(1L, "wonwoo", "wonwoo@test.com", "qwer"),
                    new Account(2L, "kevin", "kevin@test.com", "asdf"),
                    new Account(3L, "wonwoo", "kevin@test.com", "qwqw")
            )
                    .forEach(account -> accountRepository.save(account));
            accountRepository.findAll().forEach(System.out::println);
        };
    }

아 물론 Account 클래스를 수정해야된다.

@Entity
@Data
@AllArgsConstructor //추가 모든 필드가 있는 생성자를 만든다.
@NoArgsConstructor  //추가 디폴트 생성자를 만든다. 이놈이 필요한이유는 JPA 덕분 
public class Account {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    private String email;

    private String password;

}

그럼 이제 메인클래스를 실행해보자!
실행이 완료되었다면 http://localhost:8080/

{
  _links: {
    account: {
      href: "http://localhost:8080/account{?page,size,sort}",
      templated: true
    },
    profile: {
      href: "http://localhost:8080/profile"
    }
  }
}

웹브라우저에 이런 화면을 볼수 있다. 깔끔하게 볼라면 크롬 확장 프로그램을 설치하자.(JSONView) 였던걸로 기억한다.
이번엔 아래와 같이 계정 정보를 봐보자!
http://localhost:8080/account

{
  "_embedded": {
    "account": [
      {
        "name": "wonwoo",
        "email": "wonwoo@test.com",
        "password": "qwer",
        "_links": {
          "self": {
            "href": "http://localhost:8080/account/1"
          },
          "account": {
            "href": "http://localhost:8080/account/1"
          }
        }
      },
      {
        "name": "kevin",
        "email": "kevin@test.com",
        "password": "asdf",
        "_links": {
          "self": {
            "href": "http://localhost:8080/account/2"
          },
          "account": {
            "href": "http://localhost:8080/account/2"
          }
        }
      },
      {
        "name": "wonwoo1",
        "email": "kevin@test.com",
        "password": "qwqw",
        "_links": {
          "self": {
            "href": "http://localhost:8080/account/3"
          },
          "account": {
            "href": "http://localhost:8080/account/3"
          }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/account"
    },
    "profile": {
      "href": "http://localhost:8080/profile/account"
    },
    "search": {
      "href": "http://localhost:8080/account/search"
    }
  },
  "page": {
    "size": 20,
    "totalElements": 3,
    "totalPages": 1,
    "number": 0
  }
}

흠. 잘 나온다. 만족하는 결과다.

이번엔 http://localhost:8080/account/search 브라우저에 띄우자

{
  "_links": {
    "findByname": {
      "href": "http://localhost:8080/account/search/findByname{?name,page,size,sort}",
      "templated": true
    },
    "self": {
      "href": "http://localhost:8080/account/search"
    }
  }
}

url 중 search 는 자동으로 생성되는듯하다. findByname 메소드 명이다.

마지막으로 이름으로 검색을 해보자
http://localhost:8080/account/search/findByname?name=wonwoo

{
  "_embedded": {
    "account": [
      {
        "name": "wonwoo",
        "email": "wonwoo@test.com",
        "password": "qwer",
        "_links": {
          "self": {
            "href": "http://localhost:8080/account/1"
          },
          "account": {
            "href": "http://localhost:8080/account/1"
          }
        }
      }
    ]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/account/search/findByname?name=wonwoo"
    }
  },
  "page": {
    "size": 20,
    "totalElements": 1,
    "totalPages": 1,
    "number": 0
  }
}

이렇게 나왔다면 성공!!

한가지 살펴 볼 것이 있다.
바로 @RepositoryRestResource(collectionResourceRel = "account", path = "account") 이거다.
RepositoryRestResource 이 애노테이션은 선택인거 같다. 없어도 잘 된다(필자는 잘된다.). 물론 스캔 범위에 있다는 가정이다. 하지만 명시적인게 좋으니 써주는것도 나쁘지 않다. 없으면 엔티티 복수형으로 자동생성하는듯 하다.
애노테이션 속성중 collectionResourceRel은 key 속성이다. 다시말해

"account": { //이 속성
      "href": "http://localhost:8080/account{?page,size,sort}",
      "templated": true
},

그리고 path는 url path를 말하는거다.
마지막으로 findByname 메소드 안에 @Param("key") 어노테이션은 파라미터의 키이다. 없으면 에러가 난다. 감지를 할수 없다는 듯하다.
key를 변경해보자.

Page<Account> findByname(@Param("first_name") String name, Pageable pageable);

그럼 키가 변경된 것을 알 수있다.

{
  "_links": {
    "findByname": {
      "href": "http://localhost:8080/account/search/findByname{?first_name,page,size,sort}",
      "templated": true
    },
    "self": {
      "href": "http://localhost:8080/account/search"
    }
  }
}

다음편은 조금더 세부적으로 살펴보자!

참고 : 아래와 같이 maven에 추가해보자.

    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-rest-hal-browser</artifactId>
    </dependency>

그리고 다시 http://localhost:8080 브라우저를 열어보자!
쉽게 테스트 할 수 있다.

Spring boot 빠르게 시작해보자

Spring boot를 이용하여 API 서버를 개발해보자!

start.spring.io 에서 설정해서 시작해도 된다.

maven을 이용하여 시작해보자.
일단 메이븐 프로젝트를 생성한다.
그럼 pom.xml 파일이 있을 것이다.
pom파일에 아래와 같이 추가한다.

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.3.RELEASE</version>
        <relativePath/>
    </parent>

위의 아이는 버전관리 및 플러그인, 인코딩 자바 버전 등이 설정 되어있다.
참으로 좋은 녀석이다.
다음으론 아래와 같이 dependencies 들을 추가한다.


<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

첫 번째 있는 spring-boot-starter 에는 스프링 부트의 핵심인?(필자기준 아닐수도 있음) autoconfigure가 포함되어 있다.
그리고 스프링 부트는 기본으로 logback을 사용하고 있다.
그런데 가만보면 이상한점이 있다. 버전을 정보가 없다. 그 이유는 parent에서 관리는 해주기 때문이다.
니가 뭔데 버전을 관리하냐 나는 내가 할거다 하는 사람은 버전을 명시적으로 써주면 된다.

흠. 설정이 끝났다.
메인 클래스 만들자.

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

메인 클래스 작성도 끝났다. 실행시켜보자. 아무 에러가 없다면 준비는 끝났다.
이 클래스에 중요한건 @SpringBootApplication 어노테이션이다.
저 안에는 @Configuration ,@EnableAutoConfiguration,@ComponentScan 애노테이션이 있다.
아주 쓸모있는 아이다.

이제 API를 서버를 만들자.
서버를 띄우기 위해선 아래와 같이 추가를 한다.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

이놈도 무시무시한놈이다. 내장톰캣을 사용하고 웹에 필요한 라이브러리들을 추가해준다.
다음으론 메인 클래스를 조금 수정하자. 아니 추가하자.

@SpringBootApplication
@RestController
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @RequestMapping("hello")
    public String hello(){
        return "hello world";
    }
}

이제 모든 준비는 끝났다.
메인 클래스를 실행시키자!
서버가 시작되었면 http://localhost:8080/hello로 접속을 해보자
hello world가 웹페이지에 있다면 성공적으로 API서버를 만들었다.

참고 : Starter POMs

순열이란

순열

  • 서로 다른 n개에서 r개를 택하여 일렬로 배열하는 것을 n개에서 r개를 택하는 순열
  • nPr로 나타낸다.

문제) 1,2,3,4,5의 다섯개 숫자중에서 서로 다른 세 개의 숫자를 사용하여 만들 수 있는 세 자리의 자연수의 개수를 구하여라

  • 5P3 = 5×4×3 = 60

특수한 경우

문제) m,a,t,h의 네 개 문자중 m,t 이웃하도록 배열

  • (mt), a, h를 한덩어리로 본다. 그럼 3!
  • 또한 mt, tm 도 가능하기 때문에 2!
  • 3!× 2!

문제) 1~5 5장 숫자카드중 세자리 짝수의 경우의수는?

  • 조건을 보면 짝수 조건이다.
  • 그럼 마지막의 올 수 있는 카드는 2 또는 4 밖에 올 수 없다. XX2 또는 XX4
  • 그럼 첫번째 자리에 올 수있는 카드의 개수는 4 두번째는 3 그리고 마지막 자리엔 2개
  • 4 × 3 × 2

중복 순열

  • 순열과 마찬가지로 n개중에 r개를 택하는데 중복을 허용하는 순열이다.
  • n∏r로 나타낸다.

문제) 1~5 숫자카드중 세자리 자연수를 만들수있는 개수는? (중복허락)

  • 첫 번째자리에 올 수있는 카드의 개수는 5개 두 번째도 5개 세 번쨰도 5개
  • 5∏3 으로 나태낸다.
  • 5 × 5 × 5 개수이다.

문제) 1 2 두개의 카드중 중복을 허용해서 세자리 자연수로 나타낼수 있는 개수는

  • 첫 번째자리에 2개 두 번째도 2개 세번쨰도 2개
  • 2∏3
  • 2 × 2 × 2

n∏r중에 n은 중복되는 것이 온다.
쉽게 설명에서 n는 집 r은 사람이라 생각하면 된다.

문제) 5명의 유권자중에 2명의 후보에게 투표 방법은?

  • A라는 후보에 5명이 모두 투표 할 수 있다. 이건 A A A A A 라 볼 수 있다.
  • 또는 A에 두명 B엔 3명이 투표를 할 수 있다. 이건 A A B B B 라 볼 수 있다.
  • 그럼 2∏5가 정답이다.

같은 것이 있는 순열

  • n개 중에서 서로 같은 것이 각각 p개,q개 … r개씩 있을 때 이들 n개를 모두 일렬로 배열하는 순열

문제) s, o, c, c, e, r 6개 문자를 일렬로 배열하는 경우의 수

  • 일단 6개 숫자중 같은 것이 없다고 생각하고 배열 6!
  • 그리고 같은 것이 c 두개가 있으므로 2!
  • 두개를 나눠주면 된다.
  • 6!/2!

문제) a,b,c,d,e,f 6개의 문자중 일렬로 배열하는 경우의 수 하지만 b가 d 보다 항상 앞에 있어야 된다.

  • 예로 abcdef 와 adcbef가 배열로 있을 때 두번째는 d가 b보다 앞에 있기 때문에 1개로 취급한다.
  • 1개로 취급 즉 같은 것으로 취급하면 된다.
  • 6!/2!이다.

문제) 숫자 1이 적힌 카드가 3장, 숫자 2가 적힌 카드 2장, 숫자 0이 적힌 카드 1장이 있다. 이 6장의 카드를 일렬로 배열하여 6자리의 자연수를 만들 때, 서로 다른 자연수의 개수는?

  • 일단 0은 맨 앞자리로 못온다.
  • 맨 앞자리에 올수 있는 카드는 1 또는 2
  • 1이 맨 앞에 올 때 올 수 있는 카드는 1 1 2 2 0 5!/2!2! = 30
  • 2가 맨 앞에 올 때 올 수 있는 카드는 1 1 1 2 0 5!/3! = 20
  • 두개를 더하면 된다. 50