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

spring-boot-autoconfiguration

spring boot autoconfiguration 정리

ConditionalOnMissingBean

@ConditionalOnMissingBean

Bean 이 존재 하지 않을때 실행되는 어노테이션이다.
bean name(String)으로 설정 할 수 도 있고 class(해보지 않음) 혹은 annotation(이것도 안해봄)등 설정 할 수 있는 모양이다.

예) @ConditionalOnMissingBean(name = "helloConfigSample")
helloConfigSample 존재 하지 않을때 실행하라.

ConditionalOnBean

@ConditionalOnBean

이 전과 반대로 Bean이 존재 할때 실행 되는 어노테이션이다.

예) @ConditionalOnBean(name = "helloConfig")
helloConfig 존재 할때 실행하라.

ConditionalOnProperty

@ConditionalOnProperty

속성중 prefix라는 속성이 있는데 이 속성의 해당되는 프로퍼티 값이 있다면 실행된다.

예) @ConditionalOnProperty(prefix = "autoconfig.sample", name = "id")

EnableConfigurationProperties

@EnableConfigurationProperties

ConditionalOnProperty 같이 사용하는 어노테이션이다. 프로퍼티의(예를 들어) autoconfig.sample.id 가 있다면 id 값을 저장 해놓기 위한 class가 필요 하다. 그용도로 사용한다.
EnableConfigurationProperties 만 있어도 사용은 가능하지만 프로퍼티의 autoconfig.sample.id 설정하지 않았다면 null 로 나온다. 만약 사용한다면 위의 어노테이션이랑 사용하길 권장한다.

예) @EnableConfigurationProperties(SampleProperties.class)

ConditionalOnWebApplication

@ConditionalOnWebApplication

웹인지 아닌지 판단 하는 어노테이션이다. web 일경우 실행된다. 별거 없다.

예) @ConditionalOnWebApplication

ConditionalOnNotWebApplication

@ConditionalOnNotWebApplication

위와 반대로 web이 아닐경우 실행된다.

예) @ConditionalOnNotWebApplication

ConditionalOnJava

@ConditionalOnJava

자바 버전을 설정 하는 어노테이션이다.
현재 버전(자신의 버전)설정한 버전 보다 낮으면 실행된다.
예를 들어 자신의 버전이 1.8 이고 설정한 버전이 1.8 버전보다 작거나 같다면 실행되고 높으면 실행 되지 않는다.

예) @ConditionalOnJava(value = ConditionalOnJava.JavaVersion.SIX)

ConditionalOnResource

@ConditionalOnResource

리소스 경로에 파일이 있으면 실행되는 어노테이션이다. 특정 경로를 지정해주면 된다.

예) @ConditionalOnResource(resources = "classpath:/META-INF/resourcesfile")

ConditionalOnClass

@ConditionalOnClass

처음에 bean과 동일하게 class를 지정 해주면 된다.(해보진 않았다. 동일하다고 예상된다. jar로 묶어야 되는데 귀찮)
name으로 지정시에는 풀 패키지명을 써줘야 된다.

예) @ConditionalOnClass(name = "com.example.annotation.HelloConfig") 이것만 해봐서 예로 달아놨다.

ConditionalOnMissingClass

@ConditionalOnMissingClass

위의 어노테이션이랑 반대되는 어노테이션이다. 이것도 해보지않았다. name 으로만 해보았다.

예) @ConditionalOnMissingClass(name = "com.example.annotation.HelloConfigMissing")

ConditionalOnExpression

@ConditionalOnExpression

표현식으로 나타낼수 있는 어노테이션이다.

예) @ConditionalOnExpression(value = "'${spring.application.name}' == 'spring-autoconfig'")

위의 어노테션 말고도 몇가지가 더 있는데 자주 쓸만한건 없는거 같다.