A Guide to JavaLite - Building a RESTful CRUD application

1. Introduzione

JavaLite è una raccolta di framework per semplificare le attività comuni che ogni sviluppatore deve affrontare durante la creazione di applicazioni.

In questo tutorial, daremo uno sguardo alle funzionalità di JavaLite incentrate sulla creazione di una semplice API.

2. Configurazione

In questo tutorial, creeremo una semplice applicazione RESTful CRUD. Per fare ciò, utilizzeremo ActiveWeb e ActiveJDBC , due dei framework con cui si integra JavaLite.

Quindi, iniziamo e aggiungiamo la prima dipendenza di cui abbiamo bisogno:

 org.javalite activeweb 1.15 

L'artefatto ActiveWeb include ActiveJDBC, quindi non è necessario aggiungerlo separatamente. Si prega di notare che l'ultima versione di activeweb può essere trovata in Maven Central.

La seconda dipendenza di cui abbiamo bisogno è un connettore di database . Per questo esempio, utilizzeremo MySQL, quindi dobbiamo aggiungere:

 mysql mysql-connector-java 5.1.45 

Ancora una volta, l'ultima dipendenza mysql-connector-java può essere trovata su Maven Central.

L'ultima dipendenza che dobbiamo aggiungere è qualcosa di specifico di JavaLite:

 org.javalite activejdbc-instrumentation 1.4.13   process-classes  instrument    

L'ultimo plugin activejdbc-instrumentation può essere trovato anche in Maven Central.

Avendo tutto questo a posto e prima di iniziare con entità, tabelle e mappature, ci assicureremo che uno dei database supportati sia attivo e funzionante . Come abbiamo detto prima, useremo MySQL.

Ora siamo pronti per iniziare con la mappatura relazionale degli oggetti.

3. Mappatura relazionale a oggetti

3.1. Mappatura e strumentazione

Cominciamo creando una classe Product che sarà la nostra entità principale :

public class Product {}

E creiamo anche la tabella corrispondente :

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

Infine, possiamo modificare la nostra classe Product per eseguire la mappatura :

public class Product extends Model {}

Abbiamo solo bisogno di estendere la classe org.javalite.activejdbc.Model . ActiveJDBC deduce i parametri dello schema DB dal database . Grazie a questa capacità, non è necessario aggiungere getter e setter o alcuna annotazione .

Inoltre, ActiveJDBC riconosce automaticamente che la classe Product deve essere mappata alla tabella PRODUCTS . Utilizza le inflessioni inglesi per convertire la forma singolare di un modello in una forma plurale di una tabella. E sì, funziona anche con le eccezioni.

C'è un'ultima cosa di cui avremo bisogno per far funzionare la nostra mappatura: la strumentazione. La strumentazione è un passaggio aggiuntivo richiesto da ActiveJDBC che ci consentirà di giocare con la nostra classe Product come se avesse getter, setter e metodi simili a DAO.

Dopo aver eseguito la strumentazione, saremo in grado di fare cose come:

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

o:

List products = Product.findAll();

È qui che entra in gioco il plugin activejdbc-instrumentation . Poiché abbiamo già la dipendenza nel nostro pom, dovremmo vedere le classi strumentate durante la compilazione:

... [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 **************************** ...

Successivamente, creeremo un semplice test per assicurarci che funzioni.

3.2. Test

Infine, per testare la nostra mappatura, seguiremo tre semplici passaggi: aprire una connessione al database, salvare un nuovo prodotto e recuperarlo:

@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")); }

Nota che tutto questo (e altro) è possibile solo avendo un modello e una strumentazione vuoti.

4. Controller

Ora che la nostra mappatura è pronta, possiamo iniziare a pensare alla nostra applicazione e ai suoi metodi CRUD.

Per questo, utilizzeremo controller che elaborano le richieste HTTP.

Creiamo il nostro ProductsController :

@RESTful public class ProductsController extends AppController { public void index() { // ... } }

Con questa implementazione, ActiveWeb mapperà automaticamente il metodo index () al seguente URI:

//:/products

I controller annotati con @RESTful , forniscono un set fisso di metodi mappati automaticamente a URI differenti. Vediamo quelli che saranno utili per il nostro esempio CRUD:

Metodo del controller Metodo HTTP URI
CREARE creare() INVIARE // host: port / prodotti
LEGGI UNO spettacolo() OTTENERE // host: port / products / {id}
LEGGI TUTTO indice() OTTENERE // host: port / prodotti
AGGIORNARE aggiornare() METTERE // host: port / products / {id}
ELIMINA distruggere() ELIMINA // host: port / products / {id}

E se aggiungiamo questo set di metodi al nostro 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 } }

Prima di passare alla nostra implementazione logica, daremo una rapida occhiata ad alcune cose che dobbiamo configurare.

5. Configurazione

ActiveWeb si basa principalmente su convenzioni, la struttura del progetto ne è un esempio. I progetti ActiveWeb devono seguire un layout di pacchetto predefinito :

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

C'è un pacchetto specifico a cui dobbiamo dare un'occhiata: un pp.config .

All'interno di quel pacchetto creeremo tre classi:

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

Questa classe configura le connessioni al database utilizzando un file delle proprietà nella directory principale del progetto contenente i parametri richiesti:

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

Questo creerà la connessione automaticamente sostituendo ciò che abbiamo fatto nella prima riga del nostro test di mappatura.

La seconda classe che dobbiamo includere nel pacchetto app.config è:

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

Questo codice vincolerà la connessione che abbiamo appena configurato al nostro controller.

The third class willconfigure our app's context:

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

After creating the three classes, the last thing regarding configuration is creating our web.xml file under webapp/WEB-INF directory:

   dispatcher org.javalite.activeweb.RequestDispatcher  exclusions css,images,js,ico   encoding UTF-8    dispatcher /*  

Now that configuration is done, we can go ahead and add our logic.

6. Implementing CRUD Logic

With the DAO-like capabilities provided by our Product class, it's super simple to add basic CRUD functionality:

@RESTful public class ProductsController extends AppController { private ObjectMapper mapper = new ObjectMapper(); public void index() { List 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(); // ... } }

Easy, right? However, this isn't returning anything yet. In order to do that, we have to create some views.

7. Views

ActiveWeb uses FreeMarker as a templating engine, and all its templates should be located under src/main/webapp/WEB-INF/views.

Inside that directory, we will place our views in a folder called products (same as our controller). Let's create our first template called _product.ftl:

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

It's pretty clear at this point that this is a JSON response. Of course, this will only work for one product, so let's go ahead and create another template called index.ftl:

[]

This will basically render a collection named products, with each one formatted by _product.ftl.

Finally, we need to bind the result from our controller to the corresponding view:

@RESTful public class ProductsController extends AppController { public void index() { List products = Product.findAll(); view("products", products); render(); } public void show() { String id = getId(); Product p = Product.findById(id); view("product", p); render("_product"); } }

In the first case, we're assigning products list to our template collection named also products.

Then, as we're not specifying any view, index.ftl will be used.

In the second method, we're assigning product p to element product in the view and we're explicitly saying which view to render.

We could also create a view message.ftl:

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

And then call it form any of our ProductsController‘s method:

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

Let's now see our final 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; } }

At this point, our application is done and we're ready to run it.

8. Running the Application

We'll use Jetty plugin:

 org.eclipse.jetty jetty-maven-plugin 9.4.8.v20171121 

Find latest jetty-maven-plugin in Maven Central.

And we're ready, we can run our application:

mvn jetty:run

Let's create a couple of products:

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

.. read them:

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

.. aggiorna uno di loro:

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

... leggi quello che abbiamo appena aggiornato:

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

Infine, possiamo eliminarne uno:

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

9. Conclusione

JavaLite dispone di molti strumenti per aiutare gli sviluppatori a far funzionare un'applicazione in pochi minuti . Tuttavia, sebbene basare le cose sulle convenzioni si traduca in un codice più pulito e più semplice, ci vuole un po 'per comprendere la denominazione e la posizione di classi, pacchetti e file.

Questa era solo un'introduzione ad ActiveWeb e ActiveJDBC, trova altra documentazione sul loro sito web e cerca l'applicazione dei nostri prodotti nel progetto Github.