1. 개요

이 사용방법(예제)의 목적은 Play 프레임워크를 탐색하고 Java를 사용하여 REST 서비스를 빌드하는 방법을 배우는 것입니다.

REST API를 함께 사용하여 학생 기록을 생성, 검색, 업데이트 및 삭제할 것입니다.

이러한 응용 프로그램에서는 일반적으로 학생 기록을 저장할 데이터베이스가 있습니다. Play 프레임워크에는 Hibernate 및 기타 지속성 프레임워크를 통한 JPA 지원과 함께 H2 데이터베이스가 내장되어 있습니다.

그러나 일을 단순하게 유지하고 가장 중요한 일에 집중하기 위해 간단한 Map를 사용하여 고유한 ID로 학생 개체를 저장합니다.

2. 새 애플리케이션 만들기

Play 프레임워크 소개에 설명된 대로 Play 프레임워크를 설치했으면 애플리케이션을 만들 준비가 된 것입니다.

sbt 명령을 사용하여 play-java-seed 를 사용하여 student-api 라는 새 애플리케이션을 생성해 보겠습니다 .

sbt new playframework/play-java-seed.g8

3. 모델

애플리케이션 스캐폴딩이 준비되면 student-api/app/models로 이동하여 학생 정보를 처리하기 위한 Java bean을 생성합니다.

public class Student {
    private String firstName;
    private String lastName;
    private int age;
    private int id;

    // standard constructors, getters and setters
}

이제 CRUD 작업을 수행하는 헬퍼 메서드를 사용하여 학생 데이터용 으로 HashMap 이 지원하는 간단한 데이터 저장소를 생성합니다.

public class StudentStore {
    private Map<Integer, Student> students = new HashMap<>();

    public Optional<Student> addStudent(Student student) {
        int id = students.size();
        student.setId(id);
        students.put(id, student);
        return Optional.ofNullable(student);
    }

    public Optional<Student> getStudent(int id) {
        return Optional.ofNullable(students.get(id));
    }

    public Set<Student> getAllStudents() {
        return new HashSet<>(students.values());
    }

    public Optional<Student> updateStudent(Student student) {
        int id = student.getId();
        if (students.containsKey(id)) {
            students.put(id, student);
            return Optional.ofNullable(student);
        }
        return null;
    }

    public boolean deleteStudent(int id) {
        return students.remove(id) != null;
    }
}

4. 컨트롤러

student-api/app/controllers 로 이동하여 StudentController.java 라는 새 컨트롤러를 생성해 보겠습니다 . 점진적으로 코드를 단계별로 살펴보겠습니다.

먼저 HttpExecutionContext 를 구성  해야 합니다 . 비동기식 비 차단 코드를 사용하여 작업을 구현합니다. 이는 우리의 작업 메서드가 Result 대신 CompletionStage<Result>를 반환함 의미  합니다  . 이는 차단 없이 장기 실행 작업을 작성할 수 있는 이점이 있습니다.

Play Framework 컨트롤러에서 비동기 프로그래밍을 처리할 때 한 가지 주의할 점이 있습니다. HttpExecutionContext를 제공해야 합니다. HTTP 실행 컨텍스트를 제공하지 않으면 작업 메서드를 호출할 때 "여기에서 사용할 수 있는 HTTP 컨텍스트가 없습니다"라는 악명 높은 오류가 발생합니다.

그것을 주입하자:

private HttpExecutionContext ec;
private StudentStore studentStore;

@Inject
public StudentController(HttpExecutionContext ec, StudentStore studentStore) {
    this.studentStore = studentStore;
    this.ec = ec;
}

또한 StudentStore  를 추가  하고 @Inject  어노테이션 을 사용하여 컨트롤러의 생성자에 두 필드를 모두 삽입했습니다 . 이 작업을 완료하면 이제 작업 메서드 구현을 진행할 수 있습니다.

Play 는 데이터 처리를 위해 Jackson과 함께 제공 되므로 외부 의존성 없이 필요한 모든 Jackson 클래스를 가져올 수 있습니다.

반복 작업을 수행하는 유틸리티 클래스를 정의해 보겠습니다. 이 경우 HTTP 응답을 작성합니다.

따라서 student-api/app/utils 패키지를 생성하고 Util.java 를 추가해 보겠습니다.

public class Util {
    public static ObjectNode createResponse(Object response, boolean ok) {
        ObjectNode result = Json.newObject();
        result.put("isSuccessful", ok);
        if (response instanceof String) {
            result.put("body", (String) response);
        } else {
            result.putPOJO("body", response);
        }
        return result;
    }
}

이 메서드를 사용하여 부울 isSuccessful 키와 Response body을 사용하여 표준 JSON 응답을 생성합니다.

이제 컨트롤러 클래스의 작업을 단계별로 실행할 수 있습니다.

4.1. 만들기 작업 _

POST  작업 으로 매핑된  이 메서드는 Student 개체 생성을 처리합니다.

public CompletionStage<Result> create(Http.Request request) {
    JsonNode json = request.body().asJson();
    return supplyAsync(() -> {
        if (json == null) {
            return badRequest(Util.createResponse("Expecting Json data", false));
        }

        Optional<Student> studentOptional = studentStore.addStudent(Json.fromJson(json, Student.class));
        return studentOptional.map(student -> {
            JsonNode jsonObject = Json.toJson(student);
            return created(Util.createResponse(jsonObject, true));
        }).orElse(internalServerError(Util.createResponse("Could not create data.", false)));
    }, ec.current());
}

주입된 Http.Request  클래스 의 호출을 사용  하여 요청 본문을 Jackson의 JsonNode 클래스로 가져옵니다. 본문이 null 인 경우 유틸리티 메서드를 사용하여 응답을 생성하는 방법에 유의하십시오 .

또한 CompletedFuture.supplyAsync  메서드 를 사용하여 비차단 코드를 작성할 수  있는 CompletionStage<Result> 를 반환합니다 .

상태를 나타내는 부울 플래그 와 함께 String 또는 JsonNode 에 전달할 수 있습니다 .

또한 Json.fromJson() 을 사용하여 들어오는 JSON 객체를 Student 객체로 변환하고 응답을 위해 다시 JSON으로 변환하는 방법에 주목하십시오.

마지막으로, 익숙한 ok() 대신 play.mvc.results 패키지 에서 생성된 도우미 메서드를 사용하고 있습니다. 아이디어는 특정 컨텍스트 내에서 수행 중인 작업에 대해 올바른 HTTP 상태를 제공하는 메서드를 사용하는 것입니다. 예를 들어 HTTP OK 200 상태의 경우 ok() , HTTP CREATED 201일 때의 created() 는 위에서 사용된 결과 상태입니다. 이 개념은 나머지 작업 전반에 걸쳐 나타납니다.

4.2. 업데이트 작업 _

http://localhost:9000/ 에 대한 PUT  요청 StudentController에 도달합니다. StudentStore 의 updateStudent  메소드를 호출하여 학생 정보를 업데이트하는  update 메소드 :

public CompletionStage<Result> update(Http.Request request) {
    JsonNode json = request.body().asJson();
    return supplyAsync(() -> {
        if (json == null) {
            return badRequest(Util.createResponse("Expecting Json data", false));
        }
        Optional<Student> studentOptional = studentStore.updateStudent(Json.fromJson(json, Student.class));
        return studentOptional.map(student -> {
            if (student == null) {
                return notFound(Util.createResponse("Student not found", false));
            }
            JsonNode jsonObject = Json.toJson(student);
            return ok(Util.createResponse(jsonObject, true));
        }).orElse(internalServerError(Util.createResponse("Could not create data.", false)));
    }, ec.current());
}

4.3. 검색 작업 _

학생을 검색하기 위해 http://localhost:9000/:id 에 대한 GET  요청 의 경로 매개변수로 학생의 ID를 전달합니다 . 검색  작업 에 도달합니다  .

public CompletionStage<Result> retrieve(int id) {
    return supplyAsync(() -> {
        final Optional<Student> studentOptional = studentStore.getStudent(id);
        return studentOptional.map(student -> {
            JsonNode jsonObjects = Json.toJson(student);
            return ok(Util.createResponse(jsonObjects, true));
        }).orElse(notFound(Util.createResponse("Student with id:" + id + " not found", false)));
    }, ec.current());
}

4.4. 삭제 작업 _

삭제  작업 은  http://localhost:9000/:id 에 매핑됩니다 . 삭제할 레코드를 식별하기 위해 ID 를 제공합니다 .

public CompletionStage<Result> delete(int id) {
    return supplyAsync(() -> {
        boolean status = studentStore.deleteStudent(id);
        if (!status) {
            return notFound(Util.createResponse("Student with id:" + id + " not found", false));
        }
        return ok(Util.createResponse("Student with id:" + id + " deleted", true));
    }, ec.current());
}

4.5. List 학생 활동

마지막으로  listStudents 작업은 지금까지 저장된 모든 학생의 List을 반환합니다. GET 요청 으로 http://localhost:9000/ 에 매핑됩니다 .

public CompletionStage<Result> listStudents() {
    return supplyAsync(() -> {
        Set<Student> result = studentStore.getAllStudents();
        ObjectMapper mapper = new ObjectMapper();
        JsonNode jsonData = mapper.convertValue(result, JsonNode.class);
        return ok(Util.createResponse(jsonData, true));
    }, ec.current());
}

5. 매핑

컨트롤러 작업을 설정했으면 이제 student-api/conf/routes 파일을 열고 다음 경로를 추가하여 매핑할 수 있습니다.

GET     /                           controllers.StudentController.listStudents()
GET     /:id                        controllers.StudentController.retrieve(id:Int)
POST    /                           controllers.StudentController.create(request: Request)
PUT     /                           controllers.StudentController.update(request: Request)
DELETE  /:id                        controllers.StudentController.delete(id:Int)
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

정적 리소스를 다운로드하려면 /assets 엔드포인트가 항상 있어야 합니다 .

이 후 학생 API 구축이 완료되었습니다.

경로 매핑 정의에 대해 자세히 알아보려면 Play 애플리케이션의 라우팅 사용방법(예제)를 방문하세요.

6. 테스트

이제 http://localhost:9000/ 에 요청을 보내고 적절한 컨텍스트를 추가하여 API에서 테스트를 실행할 수 있습니다. 브라우저에서 기본 경로를 실행하면 다음이 출력되어야 합니다.

{
     "isSuccessful":true,
     "body":[]
}

보시다시피 아직 레코드를 추가하지 않았기 때문에 본문이 비어 있습니다. curl 을 사용하여 몇 가지 테스트를 실행해 보겠습니다(또는 Postman과 같은 REST 클라이언트를 사용할 수 있음).

터미널 창을 열고 curl 명령을 실행하여 학생을 추가해 보겠습니다 .

curl -X POST -H "Content-Type: application/json" \
 -d '{"firstName":"John","lastName":"Baeldung","age": 18}' \
 http://localhost:9000/

그러면 새로 생성된 학생이 반환됩니다.

{ 
    "isSuccessful":true,
    "body":{ 
        "firstName":"John",
        "lastName":"Baeldung",
        "age":18,
        "id":0
    }
}

위의 테스트를 실행한 후 브라우저에서 http://localhost:9000 을 로드하면 이제 다음이 제공됩니다.

{ 
    "isSuccessful":true,
    "body":[ 
        { 
            "firstName":"John",
            "lastName":"Baeldung",
            "age":18,
            "id":0
        }
    ]
}

id 속성 은 우리가 추가하는 모든 새 레코드에 대해 증가합니다.

레코드를 삭제 하려면 DELETE 요청 을 보냅니다 .

curl -X DELETE http://localhost:9000/0
{ 
    "isSuccessful":true,
    "body":"Student with id:0 deleted"
}

위의 테스트에서는 첫 번째 테스트에서 생성한 레코드를 삭제하고 이제 업데이트 메서드 를 테스트할 수 있도록 다시 생성해 보겠습니다 .

curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"John","lastName":"Baeldung","age": 18}' \
http://localhost:9000/
{ 
    "isSuccessful":true,
    "body":{ 
        "firstName":"John",
        "lastName":"Baeldung",
        "age":18,
        "id":0
    }
}

이제 이름을 "Andrew"로 설정하고 나이를 30으로 설정하여 레코드를 업데이트 하겠습니다.

curl -X PUT -H "Content-Type: application/json" \
-d '{"firstName":"Andrew","lastName":"Baeldung","age": 30,"id":0}' \
http://localhost:9000/
{ 
    "isSuccessful":true,
    "body":{ 
        "firstName":"Andrew",
        "lastName":"Baeldung",
        "age":30,
        "id":0
    }
}

위의 테스트는  레코드를 업데이트한 후 firstName age 필드 값의 변경 사항을 보여줍니다.

몇 가지 추가 더미 레코드를 생성하고 John Doe와 Sam Baeldung이라는 두 개를 추가합니다.

curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"John","lastName":"Doe","age": 18}' \
http://localhost:9000/
curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"Sam","lastName":"Baeldung","age": 25}' \
http://localhost:9000/

이제 모든 레코드를 가져오겠습니다.

curl -X GET http://localhost:9000/
{ 
    "isSuccessful":true,
    "body":[ 
        { 
            "firstName":"Andrew",
            "lastName":"Baeldung",
            "age":30,
            "id":0
        },
        { 
            "firstName":"John",
            "lastName":"Doe",
            "age":18,
            "id":1
        },
        { 
            "firstName":"Sam",
            "lastName":"Baeldung",
            "age":25,
            "id":2
        }
    ]
}

위의 테스트를 통해 listStudents 컨트롤러 작업이 제대로 작동하는지 확인하고 있습니다.

7. 결론

이 기사에서는 Play Framework를 사용하여 완전한 REST API를 빌드하는 방법을 보여주었습니다.

늘 그렇듯이 이 예제의 소스 코드는 GitHub 에서 사용할 수 있습니다 .

REST footer banner