1. 소개

JavaLite는 모든 개발자가 애플리케이션을 구축할 때 처리해야 하는 일반적인 작업을 단순화하기 위한 프레임워크 모음입니다 .

이 사용방법(예제)에서는 간단한 API 구축에 중점을 둔 JavaLite 기능을 살펴보겠습니다.

2. 설정

이 예제 전체에서 우리는 간단한 RESTful CRUD 애플리케이션을 만들 것입니다. 이를 위해 JavaLite와 통합되는 프레임워크 중 두 가지인 ActiveWeb 및 ActiveJDBC를 사용합니다 .

이제 필요한 첫 번째 의존성을 시작하고 추가해 보겠습니다.

<dependency>
    <groupId>org.javalite</groupId>
    <artifactId>activeweb</artifactId>
    <version>1.15</version>
</dependency>

ActiveWeb 아티팩트에는 ActiveJDBC가 포함되어 있으므로 별도로 추가할 필요가 없습니다. 최신 activeweb 버전은 Maven Central에서 찾을 수 있습니다.

두 번째로 필요한 의존성은 데이터베이스 커넥터 입니다 . 이 예에서는 MySQL을 사용하므로 다음을 추가해야 합니다.

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.45</version>
</dependency>

다시 말하지만 최신 mysql-connector-java 의존성은 Maven Central에서 찾을 수 있습니다.

추가해야 하는 마지막 의존성은 JavaLite에 특정한 것입니다.

<plugin>
    <groupId>org.javalite</groupId>
    <artifactId>activejdbc-instrumentation</artifactId>
    <version>1.4.13</version>
    <executions>
        <execution>
            <phase>process-classes</phase>
            <goals>
                <goal>instrument</goal>
            </goals>
        </execution>
    </executions>
</plugin>

최신 activejdbc-instrumentation 플러그인은 Maven Central에서도 찾을 수 있습니다.

이 모든 것이 준비되고 엔터티, 테이블 및 매핑으로 시작하기 전에 지원되는 데이터베이스 중 하나가 실행 중인지 확인합니다 . 앞에서 말했듯이 MySQL을 사용할 것입니다.

이제 개체-관계형 매핑을 시작할 준비가 되었습니다.

3. 개체-관계형 매핑

3.1. 매핑 및 계측

기본 엔터티가 될 Product 클래스를 만들어 시작하겠습니다 .

public class Product {}

그리고 이에 해당하는 테이블도 생성해 보겠습니다 .

CREATE TABLE PRODUCTS (
    id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
    name VARCHAR(128)
);

마지막으로 매핑을 수행하도록 Product 클래스를 수정할 수 있습니다 .

public class Product extends Model {}

org.javalite.activejdbc.Model 클래스 만 확장하면 됩니다 . ActiveJDBC는 데이터베이스에서 DB 스키마 매개변수를 유추합니다 . 이 기능 덕분에 getter 및 setter 또는 어노테이션을 추가할 필요가 없습니다 .

또한 ActiveJDBC는 Product 클래스가 PRODUCTS 테이블 에 매핑되어야 함 을 자동으로 인식합니다 . 영어 굴절을 사용하여 모델의 단수형을 테이블의 복수형으로 변환합니다. 예, 예외가 있는 경우에도 작동합니다.

매핑 작업을 수행하는 데 필요한 마지막 항목이 하나 있습니다. 계측입니다. 계측은 ActiveJDBC에 필요한 추가 단계로, 마치 게터, 세터 및 DAO와 같은 메서드가 있는 것처럼 Product 클래스를 사용할 수 있습니다 .

계측을 실행한 후 다음과 같은 작업을 수행할 수 있습니다.

Product p = new Product();
p.set("name","Bread");
p.saveIt();

또는:

List<Product> products = Product.findAll();

이것은 activejdbc-instrumentation 플러그인이 들어오는 곳입니다. pom에 이미 의존성이 있으므로 빌드 중에 클래스가 계측되는 것을 볼 수 있습니다.

...
[INFO] --- activejdbc-instrumentation:1.4.11:instrument (default) @ javalite ---
**************************** START INSTRUMENTATION ****************************
Directory: ...\tutorials\java-lite\target\classes
Instrumented class: .../tutorials/java-lite/target/classes/app/models/Product.class
**************************** END INSTRUMENTATION ****************************
...

다음으로 이것이 작동하는지 확인하기 위해 간단한 테스트를 생성합니다.

3.2. 테스트

마지막으로 매핑을 테스트하기 위해 데이터베이스에 대한 연결을 열고 새 제품을 저장하고 검색하는 세 가지 간단한 단계를 따릅니다.

@Test
public void givenSavedProduct_WhenFindFirst_ThenSavedProductIsReturned() {
    
    Base.open(
      "com.mysql.jdbc.Driver",
      "jdbc:mysql://localhost/dbname",
      "user",
      "password");

    Product toSaveProduct = new Product();
    toSaveProduct.set("name", "Bread");
    toSaveProduct.saveIt();

    Product savedProduct = Product.findFirst("name = ?", "Bread");

    assertEquals(
      toSaveProduct.get("name"), 
      savedProduct.get("name"));
}

이 모든 것(그리고 그 이상)은 빈 모델과 계측을 통해서만 가능합니다.

4. 컨트롤러

이제 매핑이 준비되었으므로 응용 프로그램과 해당 CRUD 메서드에 대해 생각할 수 있습니다.

이를 위해 HTTP 요청을 처리하는 컨트롤러를 사용할 것입니다.

ProductsController를 만들어 봅시다 .

@RESTful
public class ProductsController extends AppController {

    public void index() {
        // ...
    }

}

이 구현으로 ActiveWeb은 자동으로 index() 메서드를 다음 URI에 매핑합니다 .

http://<host>:<port>/products

@RESTful 어노테이션이 달린 컨트롤러는 다른 URI에 자동으로 매핑되는 고정 메서드 집합을 제공합니다. CRUD 예제에 유용한 것을 살펴보겠습니다.

컨트롤러 방식 HTTP 방식 URI
만들다 만들다() 우편 http://호스트:포트/제품
하나를 읽으십시오 보여주다() 얻다 http://호스트:포트/제품/{id}
모두 읽기 색인() 얻다 http://호스트:포트/제품
업데이트 업데이트() 놓다 http://호스트:포트/제품/{id}
삭제 파괴하다() 삭제 http://호스트:포트/제품/{id}

그리고 이 메소드 세트를 ProductsController 에 추가하면 :

@RESTful
public class ProductsController extends AppController {

    public void index() {
        // code to get all products
    }

    public void create() {
        // code to create a new product
    }

    public void update() {
        // code to update an existing product
    }

    public void show() {
        // code to find one product
    }

    public void destroy() {
        // code to remove an existing product 
    }
}

논리 구현으로 이동하기 전에 구성해야 하는 몇 가지 사항을 빠르게 살펴보겠습니다.

5. 구성

ActiveWeb은 대부분 규칙을 기반으로 하며 프로젝트 구조가 그 예입니다. ActiveWeb 프로젝트는 미리 정의된 패키지 레이아웃을 따라야 합니다 .

src
 |----main
       |----java.app
       |     |----config
       |     |----controllers
       |     |----models
       |----resources
       |----webapp
             |----WEB-INF
             |----views

살펴봐야 할 특정 패키지가 하나 있습니다. 바로 pp.config 입니다 .

해당 패키지 안에 세 가지 클래스를 만들 것입니다.

public class DbConfig extends AbstractDBConfig {
    @Override
    public void init(AppContext appContext) {
        this.configFile("/database.properties");
    }
}

이 클래스는 필수 매개변수가 포함된 프로젝트의 루트 디렉터리에 있는 속성 파일을 사용하여 데이터베이스 연결을 구성합니다 .

development.driver=com.mysql.jdbc.Driver
development.username=user
development.password=password
development.url=jdbc:mysql://localhost/dbname

이렇게 하면 매핑 테스트의 첫 번째 줄에서 수행한 작업을 자동으로 대체하는 연결이 생성됩니다.

app.config 패키지 에 포함해야 하는 두 번째 클래스는 다음 과 같습니다.

public class AppControllerConfig extends AbstractControllerConfig {
 
    @Override
    public void init(AppContext appContext) {
        add(new DBConnectionFilter()).to(ProductsController.class);
    }
}

이 코드는 방금 구성한 연결을 컨트롤러에 바인딩합니다.

세 번째 클래스는 앱의 컨텍스트를 구성 합니다 .

public class AppBootstrap extends Bootstrap {
    public void init(AppContext context) {}
}

세 개의 클래스를 생성한 후 구성과 관련된 마지막 작업은 webapp/WEB-INF 디렉토리 아래에 web.xml 파일을 생성하는 것입니다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns=...>

    <filter>
        <filter-name>dispatcher</filter-name>
        <filter-class>org.javalite.activeweb.RequestDispatcher</filter-class>
        <init-param>
            <param-name>exclusions</param-name>
            <param-value>css,images,js,ico</param-value>
        </init-param>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>dispatcher</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

이제 구성이 완료되었으므로 로직을 추가할 수 있습니다.

6. CRUD 로직 구현

Product 클래스 에서 제공하는 DAO와 같은 기능을 사용하면 기본 CRUD 기능을 추가하는 것이 매우 간단합니다 .

@RESTful
public class ProductsController extends AppController {

    private ObjectMapper mapper = new ObjectMapper();    

    public void index() {
        List<Product> products = Product.findAll();
        // ...
    }

    public void create() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        Product p = new Product();
        p.fromMap(payload);
        p.saveIt();
        // ...
    }

    public void update() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        String id = getId();
        Product p = Product.findById(id);
        p.fromMap(payload);
        p.saveIt();
        // ...
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        // ...
    }

    public void destroy() {
        String id = getId();
        Product p = Product.findById(id);
        p.delete();
        // ...
    }
}

쉽죠? 그러나 이것은 아직 아무것도 반환하지 않습니다. 그러기 위해서는 몇 가지 보기를 만들어야 합니다.

7. 조회수

ActiveWeb은 FreeMarker를 템플릿 엔진으로 사용하며 모든 템플릿은 src/main/webapp/WEB-INF/views 아래에 있어야 합니다 .

해당 디렉토리 내에서 뷰를 제품 이라는 폴더 (컨트롤러와 동일) 에 배치합니다 . _product.ftl 이라는 첫 번째 템플릿을 만들어 보겠습니다 .

{
    "id" : ${product.id},
    "name" : "${product.name}"
}

이 시점에서 이것이 JSON 응답이라는 것이 매우 분명합니다. 물론 이것은 하나의 제품에만 작동하므로 계속해서 index.ftl 이라는 다른 템플릿을 생성해 보겠습니다 .

[<@render partial="product" collection=products/>]

이렇게 하면 기본적으로 products 라는 컬렉션이 렌더링되며 각 컬렉션은 _product.ftl 형식으로 지정됩니다 .

마지막으로 컨트롤러의 결과를 해당 뷰에 바인딩해야 합니다 .

@RESTful
public class ProductsController extends AppController {

    public void index() {
        List<Product> products = Product.findAll();
        view("products", products);
        render();
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        view("product", p);
        render("_product");
    }
}

첫 번째 경우에는 products 라는 템플릿 컬렉션에 제품 List을 할당합니다 .

그런 다음 뷰를 지정하지 않으므로 index.ftl이 사용됩니다.

두 번째 방법에서는 제품 p를 보기의 제품 요소에 할당하고 렌더링할 보기를 명시적으로 지정합니다.

message.ftl 보기를 만들 수도 있습니다 .

{
    "message" : "${message}",
    "code" : ${code}
}

그런 다음 ProductsController 의 메서드 중 하나에서 호출합니다 .

view("message", "There was an error.", "code", 200);
render("message");

이제 최종 ProductsController를 살펴보겠습니다 .

@RESTful
public class ProductsController extends AppController {

    private ObjectMapper mapper = new ObjectMapper();

    public void index() {
        view("products", Product.findAll());
        render().contentType("application/json");
    }

    public void create() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        Product p = new Product();
        p.fromMap(payload);
        p.saveIt();
        view("message", "Successfully saved product id " + p.get("id"), "code", 200);
        render("message");
    }

    public void update() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        p.fromMap(payload);
        p.saveIt();
        view("message", "Successfully updated product id " + id, "code", 200);
        render("message");
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        view("product", p);
        render("_product");
    }

    public void destroy() {
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        p.delete();
        view("message", "Successfully deleted product id " + id, "code", 200);
        render("message");
    }

    @Override
    protected String getContentType() {
        return "application/json";
    }

    @Override
    protected String getLayout() {
        return null;
    }
}

이 시점에서 애플리케이션이 완료되었으며 실행할 준비가 되었습니다.

8. 애플리케이션 실행

Jetty 플러그인을 사용합니다.

<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>9.4.8.v20171121</version>
</plugin>

Maven Central에서 최신 jetty-maven-plugin을 찾으십시오 .

이제 준비가 되었습니다. 애플리케이션을 실행할 수 있습니다 .

mvn jetty:run

몇 가지 제품을 만들어 보겠습니다.

$ curl -X POST http://localhost:8080/products 
  -H 'content-type: application/json' 
  -d '{"name":"Water"}'
{
    "message" : "Successfully saved product id 1",
    "code" : 200
}
$ curl -X POST http://localhost:8080/products 
  -H 'content-type: application/json' 
  -d '{"name":"Bread"}'
{
    "message" : "Successfully saved product id 2",
    "code" : 200
}

.. 읽어보세요:

$ curl -X GET http://localhost:8080/products
[
    {
        "id" : 1,
        "name" : "Water"
    },
    {
        "id" : 2,
        "name" : "Bread"
    }
]

.. 그들 중 하나를 업데이트하십시오.

$ curl -X PUT http://localhost:8080/products/1 
  -H 'content-type: application/json' 
  -d '{"name":"Juice"}'
{
    "message" : "Successfully updated product id 1",
    "code" : 200
}

… 방금 업데이트한 내용을 읽어보세요.

$ curl -X GET http://localhost:8080/products/1
{
    "id" : 1,
    "name" : "Juice"
}

마지막으로 하나를 삭제할 수 있습니다.

$ curl -X DELETE http://localhost:8080/products/2
{
    "message" : "Successfully deleted product id 2",
    "code" : 200
}

9. 결론

JavaLite에는 개발자가 몇 분 안에 응용 프로그램을 시작하고 실행하는 데 도움이 되는 많은 도구가 있습니다 . 그러나 규칙을 기반으로 하면 코드가 더 깨끗하고 단순해지지만 클래스, 패키지 및 파일의 이름과 위치를 이해하는 데 시간이 걸립니다.

이것은 ActiveWeb 및 ActiveJDBC에 대한 소개일 뿐이며 해당 웹 사이트 에서 더 많은 문서를 찾고 Github 프로젝트 에서 제품 애플리케이션을 찾으십시오 .

 

res – REST (eBook) (cat=REST)