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

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

1편은 여기

검색을 할때 url에 메소드명이 마음에 들지 않는다. 또한 json 키도 마음에 들지 않는다. 그래서 바꾸고싶다.
그러기 위해선 아래와같이 추가해보자

    @RestResource(path = "nameStartsWith", rel = "name")
    Page<Account> findByNameStartsWith(@Param("name") String name, Pageable pageable);

브라우저로 열어보자
http://localhost:8080/account/search

{
  "_links": {
    "name": { //name으로 변경
      "href": "http://localhost:8080/account/search/nameStartsWith{?name,page,size,sort}", //nameStartsWith 으로 변경
      "templated": true
    },
    "findByname": {
      "href": "http://localhost:8080/account/search/findByname{?first_name,page,size,sort}",
      "templated": true
    },
    "self": {
      "href": "http://localhost:8080/account/search"
    }
  }
}

이렇게 나온걸 확인할 수 있다. 그리고 http://localhost:8080/account/search/nameStartsWith?name=wonwoo 로 들어가보면 wonwoo로 시작하는 데이터가 두개 나올 것이다.
아니 근데 나는 이메일을 안보여주고 싶은 경우도 있다. 흠!
그렇담 아래와 같이 인터페이스 추가하자

@Projection(name = "noEmail", types = { Account.class })
public interface NoEmailAccount {

    String getId();

    String getName();

    String getPassword();
}

그런다음 http://localhost:8080/account/1?projection=noEamil url로 브라우저를 열어보자
그러면 아래와 같이 email이 빠진걸 확인 할 수 있다.

{
  "name": "wonwoo",
  "id": "1",
  "password": "qwer",
  "_links": {
    "self": {
      "href": "http://localhost:8080/account/1"
    },
    "account": {
      "href": "http://localhost:8080/account/1{?projection}",
      "templated": true
    }
  }
}

다음은 Address라는 엔티티를 추가해서 Account 에 주소로 사용할 예정이다.

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
    @Id
    @GeneratedValue
    @Column(name = "address_id")
    private Long id;

    private String street;

    private String state;

    private String country;
}

그리고 Account 엔티티에 다음과 같이 추가 하자

    @OneToOne
    @JoinColumn(name="address_id")
    private Address address;

그리고 초기화 데이터를 넣어주자!

insert into address(address_id, street, state, country) values(1L, '분당구', '경기도', '대한민국');
insert into address(address_id, street, state, country) values(2L, '강남구', '서울특별시', '대한민국');
insert into account(id, name, email, password, address_id) values(1,'wonwoo','wonwoo@test.com','qwer', 1L);
insert into account(id, name, email, password, address_id) values(2,'kevin','kevin@test.com','asdf', 2L);
insert into account(id, name, email, password, address_id) values(3,'wonwoo1','kevin@test.com','qwqw',1L);

그리고 나서 http://localhost:8080/account/1 확인해보자

{
  "name": "wonwoo",
  "email": "wonwoo@test.com",
  "password": "qwer",
  "address": {
    "street": "분당구",
    "state": "경기도",
    "country": "대한민국"
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/account/1"
    },
    "account": {
      "href": "http://localhost:8080/account/1{?projection}",
      "templated": true
    }
  }
}

이렇게 제대로 나올 것이다. 흠. 근데 address가 마음에 들지 않는다. 한줄로도 나왔으면 좋겠다.
그럼 아까 했던 @Projection을 응용해서 보자
아래와 같이 FullAddress라는 인터페이스 생성한다.

@Projection(name = "fullAddress", types = { Account.class })
public interface FullAddress {

    @Value("#{target.address.country} #{target.address.state} #{target.address.street}")
    String getFullAddress();

    String getName();

    String getEmail();

    String getPassword();
}

그리고 나서 http://localhost:8080/account/1?projection=fullAddress 확인해보면 아래와 같이 한줄로 나왔다.

{
  "name": "wonwoo",
  "password": "qwer",
  "email": "wonwoo@test.com",
  "fullAddress": "대한민국 경기도 분당구",
  "_links": {
    "self": {
      "href": "http://localhost:8080/account/1"
    },
    "account": {
      "href": "http://localhost:8080/account/1{?projection}",
      "templated": true
    }
  }
}

그럼 spring-data-rest-jpa 여기서 마치겠다.
소스는 https://github.com/wonwoo/spring-data-rest-jpa 다운 받을 수 있다
다음엔 몽고도 알아보겠다.

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