Monday, 5 June 2017

CRUD Java Application with Couchbase, Java EE and WildFly

Couchbase is an open-source, NoSQL, document database. It allows to access, index, and query JSON documents while taking advantage of integrated distributed caching for high performance data access.

Developers can write applications to Couchbase using different languages (Java, Go, .NET, Node, PHP, Python, C) multiple SDKs. This blog will show how you can easily create a CRUD application using Java SDK for Couchbase.

REST with Couchbase


The application will use curl to issue REST commands to a JAX-RS endpoint deployed on WildFly. These commands will then perform CRUD operations on travel-sample bucket in Couchbase. N1QL (SQL query language for JSON) will be used to communicate with Couchbase to retrieve results. Both the “builder pattern” and raw N1QL commands will be used.

TL;DR

Lets get started!

Run Couchbase Server

Couchbase server can be easily downloaded from Couchbase Server Downloads page. In a containerized world, its a lot easier to fire up a Couchbase server using Docker.

If Docker is configured on your machine then the easiest way is to use Docker Compose for Couchbase:

mycouchbase:
  name: mycouchbase
  image: couchbase/server
  volumes:
    - ~/couchbase:/opt/couchbase/var
  ports:
    - 8091:8091
    - 8092:8092 
    - 8093:8093 
    - 11210:11210

Starting up the application server shows:

> docker-compose up -d
Creating couchbaseserver_mycouchbase_1

And then the logs can be seen as:

> docker-compose logs
Attaching to couchbaseserver_mycouchbase_1
mycouchbase_1 | Starting Couchbase Server -- Web UI available at http://<ip>:8091

The database needs to be configured and is explained at Configure Couchbase Server. Make sure to install travel-sample bucket.

Deploy the Java EE Application on WildFly

  • Download WildFly 9.0.2 , unzip, and start WildFly application server as ./wildfly-9.0.0.Final/bin/standalone.sh.
  • Git clone the repo: git clone https://github.com/arun-gupta/couchbase-javaee.git
  • Change directory cd couchbase-javaee
  • Deploy the application to WildFly: mvn install -Pwildfly.
The application uses Java SDK for Couchbase by importing the following Maven coordinates:

<dependency>
    <groupId>com.couchbase.client</groupId>
    <artifactId>java-client</artifactId>
    <version>2.2.1</version>
</dependency>

Invoke the REST Endpoints Using cURL


GET Airline resources (limit to 10)

Lets query the database to list 10 Airline resources.

Request

~ > curl -v http://localhost:8080/couchbase-javaee/resources/airline
* Hostname was NOT found in DNS cache
*   Trying ::1...
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /couchbase-javaee/resources/airline HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
>

Response

< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
* Server WildFly/9 is not blacklisted
< Server: WildFly/9
< Content-Type: application/octet-stream
< Content-Length: 1415
< Date: Wed, 18 Nov 2015 21:19:15 GMT
* Connection #0 to host localhost left intact
[{"travel-sample":{"country":"France","iata":"SB","callsign":"AIRCALIN","name":"Air Caledonie International","icao":"ACI","id":139,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"WQ","callsign":null,"name":"PanAm World Airways","icao":"PQW","id":13633,"type":"airline"}}, {"travel-sample":{"country":"United Kingdom","iata":"BA","callsign":"SPEEDBIRD","name":"British Airways","icao":"BAW","id":1355,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"FL","callsign":"CITRUS","name":"AirTran Airways","icao":"TRS","id":1316,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"-+","callsign":null,"name":"U.S. Air","icao":"--+","id":13391,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"Q5","callsign":"MILE-AIR","name":"40-Mile Air","icao":"MLA","id":10,"type":"airline"}}, {"travel-sample":{"country":"France","iata":"AF","callsign":"AIRFRANS","name":"Air France","icao":"AFR","id":137,"type":"airline"}}, {"travel-sample":{"country":"United States","iata":"K5","callsign":"SASQUATCH","name":"SeaPort Airlines","icao":"SQH","id":10765,"type":"airline"}}, {"travel-sample":{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlinair","icao":"RLA","id":1203,"type":"airline"}}, {"travel-sample":{"country":"United Kingdom","iata":"5W","callsign":"FLYSTAR","name":"Astraeus","icao":"AEU","id":112,"type":"airline"}}]

The N1QL query for this is written as:

N1qlQuery query = N1qlQuery
                .simple(select("*")
                .from(i(database.getBucket().name()))
                .limit(10));
And can also be alternatively written as:

SELECT * FROM `travel-sample` LIMIT 10

You may optionally update the code to include ORDER BY clause as shown in N1QL Tutorial.

GET one Airline resource


Use id attribute to query a single Airline resource

Request

~ > curl -v http://localhost:8080/couchbase-javaee/resources/airline/139
* Hostname was NOT found in DNS cache
*   Trying ::1...
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /couchbase-javaee/resources/airline/139 HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
>

POST a new Airline resource


Learn how to run N1QL queries from the CLI using CBQ tool and verify the existing sample data:

bin > ./cbq -engine=http://192.168.99.100:8093
Couchbase query shell connected to http://192.168.99.100:8093/ . Type Ctrl-D to exit.
cbq> select * from `travel-sample` where name="Airlinair" limit 10;
{
    "requestID": "ce2de67b-2c05-47df-afbe-343cb7409d2b",
    "signature": {
        "*": "*"
    },
    "results": [
        {
            "travel-sample": {
                "callsign": "AIRLINAIR",
                "country": "France",
                "iata": "A5",
                "icao": "RLA",
                "id": 1203,
                "name": "Airlinair",
                "type": "airline"
            }
        }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "3.418285894s",
        "executionTime": "3.418232688s",
        "resultCount": 1,
        "resultSize": 294
    }
}

This query retrieve documents where airline’s name is Airlinair. The count is shown in metrics.resultCount.

Create a new document using POST.

Request

~ > curl -v -H "Content-Type: application/json" -X POST -d '{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlinair","icao":"RLA","type":"airline"}' http://localhost:8080/couchbase-javaee/resources/airline
* Hostname was NOT found in DNS cache
*   Trying ::1...
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /couchbase-javaee/resources/airline HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 104
>

Response

* upload completely sent off: 104 out of 104 bytes
< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
* Server WildFly/9 is not blacklisted
< Server: WildFly/9
< Content-Type: application/octet-stream
< Content-Length: 117
< Date: Wed, 18 Nov 2015 21:42:51 GMT
* Connection #0 to host localhost left intact
{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlinair","icao":"RLA","id":"19810","type":"airline"}

Query again using CBQ and now the results are shown as:


cbq> select * from `travel-sample` where name="Airlinair" limit 10;
{
    "requestID": "5e79031a-f7ee-4cc9-8c87-4e3b7484f09f",
    "signature": {
        "*": "*"
    },
    "results": [
        {
            "travel-sample": {
                "callsign": "AIRLINAIR",
                "country": "France",
                "iata": "A5",
                "icao": "RLA",
                "id": 1203,
                "name": "Airlinair",
                "type": "airline"
            }
        },
        {
            "travel-sample": {
                "callsign": "AIRLINAIR",
                "country": "France",
                "iata": "A5",
                "icao": "RLA",
                "id": "19810",
                "name": "Airlinair",
                "type": "airline"
            }
        }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "3.342391947s",
        "executionTime": "3.342343455s",
        "resultCount": 2,
        "resultSize": 591
    }
}

Note that two JSON documents are returned instead of one as before the POST command was issued.

PUT an existing Airline resource


Update an existing resource using HTTP POST.

The data model for travel-sample bucket requires to include “id” attribute in the payload and in the URI as well.

Request

~ > curl -v -H "Content-Type: application/json" -X PUT -d '{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlin Air","icao":"RLA","type":"airline","id": "19810"}' http://localhost:8080/couchbase-javaee/resources/airline/19810
* Hostname was NOT found in DNS cache
*   Trying ::1...
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> PUT /couchbase-javaee/resources/airline/19810 HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
> Content-Type: application/json
> Content-Length: 118
* upload completely sent off: 118 out of 118 bytes

Name of the airline is updated from “Airlinair” to “Airlin Air”, all other attributes stay the same.

Response

< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
* Server WildFly/9 is not blacklisted
< Server: WildFly/9
< Content-Type: application/octet-stream
< Content-Length: 117
< Date: Wed, 18 Nov 2015 21:46:16 GMT
* Connection #0 to host localhost left intact
{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlin Air","icao":"RLA","id":"19810","type":"airline"}

The updated record is shown in the response.

Querying for Airlinair gives:


cbq> select * from `travel-sample` where name="Airlinair" limit 10;
{
    "requestID": "a8d72427-9f4b-49ab-a77a-17cd99cdce5f",
    "signature": {
        "*": "*"
    },
    "results": [
        {
            "travel-sample": {
                "callsign": "AIRLINAIR",
                "country": "France",
                "iata": "A5",
                "icao": "RLA",
                "id": 1203,
                "name": "Airlinair",
                "type": "airline"
            }
        }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "3.372603693s",
        "executionTime": "3.37256091s",
        "resultCount": 1,
        "resultSize": 294
    }
}

So the previously added record is now updated and thus does not appear in query results. Querying for Airlin Airgives:

cbq> select * from `travel-sample` where name="Airlin Air" limit 10;
{
    "requestID": "a3797a73-d879-4ca1-be90-e07179aae118",
    "signature": {
        "*": "*"
    },
    "results": [
        {
            "travel-sample": {
                "callsign": "AIRLINAIR",
                "country": "France",
                "iata": "A5",
                "icao": "RLA",
                "id": "19810",
                "name": "Airlin Air",
                "type": "airline"
            }
        }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "3.393649025s",
        "executionTime": "3.393530368s",
        "resultCount": 1,
        "resultSize": 298
    }
}

This shows the newly updated document.

DELETE an existing Airline resource


Query for a unique id:

cbq> select * from `travel-sample` where id="19810" limit 10;
{
    "requestID": "47a315cd-afe4-45a8-8814-5ab3034e0d0f",
    "signature": {
        "*": "*"
    },
    "results": [
        {
            "travel-sample": {
                "callsign": "AIRLINAIR",
                "country": "France",
                "iata": "A5",
                "icao": "RLA",
                "id": "19810",
                "name": "Airlin Air",
                "type": "airline"
            }
        }
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "3.006863656s",
        "executionTime": "3.006821997s",
        "resultCount": 1,
        "resultSize": 298
    }
}

Notice that one document is returned.

Lets delete this document.

Request

~ > curl -v -X DELETE http://localhost:8080/couchbase-javaee/resources/airline/19810
* Hostname was NOT found in DNS cache
*   Trying ::1...
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> DELETE /couchbase-javaee/resources/airline/19810 HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:8080
> Accept: */*
>

Response

< HTTP/1.1 200 OK
< Connection: keep-alive
< X-Powered-By: Undertow/1
* Server WildFly/9 is not blacklisted
< Server: WildFly/9
< Content-Type: application/octet-stream
< Content-Length: 136
< Date: Wed, 18 Nov 2015 21:52:47 GMT
* Connection #0 to host localhost left intact
{"travel-sample":{"country":"France","iata":"A5","callsign":"AIRLINAIR","name":"Airlin Air","icao":"RLA","id":"19810","type":"airline"}}

The deleted document is shown in the response.

Query again for the deleted id:


cbq> select * from `travel-sample` where id="19810" limit 10;
{
    "requestID": "972b0bbd-ba25-4f6c-a30e-ed188bf43588",
    "signature": {
        "*": "*"
    },
    "results": [
    ],
    "status": "success",
    "metrics": {
        "elapsedTime": "3.261481199s",
        "executionTime": "3.261431917s",
        "resultCount": 0,
        "resultSize": 0
    }
}

And no results are returned!