Ein Handbuch zu JavaLite - Erstellen einer RESTful CRUD-Anwendung

1. Einleitung

JavaLite ist eine Sammlung von Frameworks zur Vereinfachung allgemeiner Aufgaben , mit denen sich jeder Entwickler beim Erstellen von Anwendungen befassen muss.

In diesem Tutorial werden wir uns die JavaLite-Funktionen ansehen, die sich auf das Erstellen einer einfachen API konzentrieren.

2. Setup

In diesem Tutorial erstellen wir eine einfache RESTful CRUD-Anwendung. Zu diesem Zweck verwenden wir ActiveWeb und ActiveJDBC - zwei der Frameworks, in die JavaLite integriert ist.

Beginnen wir also und fügen die erste Abhängigkeit hinzu, die wir benötigen:

 org.javalite activeweb 1.15 

Das ActiveWeb-Artefakt enthält ActiveJDBC, sodass es nicht separat hinzugefügt werden muss. Bitte beachten Sie, dass die neueste Activeweb-Version in Maven Central verfügbar ist.

Die zweite Abhängigkeit, die wir benötigen, ist ein Datenbankconnector . In diesem Beispiel verwenden wir MySQL und müssen Folgendes hinzufügen:

 mysql mysql-connector-java 5.1.45 

Auch hier kann die neueste MySQL-Connector-Java-Abhängigkeit von Maven Central gefunden werden.

Die letzte Abhängigkeit, die wir hinzufügen müssen, ist etwas Spezifisches für JavaLite:

 org.javalite activejdbc-instrumentation 1.4.13   process-classes  instrument    

Das neueste Plugin für die aktive JDBC-Instrumentierung finden Sie auch in Maven Central.

Nachdem dies alles vorhanden ist und bevor wir mit Entitäten, Tabellen und Zuordnungen beginnen, stellen wir sicher, dass eine der unterstützten Datenbanken betriebsbereit ist . Wie bereits erwähnt, verwenden wir MySQL.

Jetzt können wir mit der objektrelationalen Zuordnung beginnen.

3. Objektrelationale Zuordnung

3.1. Mapping und Instrumentierung

Beginnen wir mit der Erstellung einer Produktklasse , die unsere Hauptentität sein wird :

public class Product {}

Und erstellen wir auch die entsprechende Tabelle dafür :

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

Schließlich können wir unsere Produktklasse ändern , um das Mapping durchzuführen :

public class Product extends Model {}

Wir müssen nur die Klasse org.javalite.activejdbc.Model erweitern . ActiveJDBC leitet DB-Schemaparameter aus der Datenbank ab . Dank dieser Funktion müssen keine Getter und Setter oder Anmerkungen hinzugefügt werden .

Darüber hinaus erkennt ActiveJDBC automatisch , dass Produktklasse Anforderungen abgebildet werden PRODUKTEN Tabelle. Es verwendet englische Beugungen, um die Singularform eines Modells in eine Pluralform einer Tabelle umzuwandeln. Und ja, es funktioniert auch mit Ausnahmen.

Es gibt noch eine letzte Sache, die wir brauchen, damit unser Mapping funktioniert: Instrumentierung. Die Instrumentierung ist ein zusätzlicher Schritt, der von ActiveJDBC benötigt wird, damit wir mit unserer Produktklasse so spielen können , als ob sie Getter, Setter und DAO-ähnliche Methoden hätte.

Nachdem wir die Instrumentierung ausgeführt haben, können wir Folgendes tun:

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

oder:

List products = Product.findAll();

Hier kommt das Plugin activejdbc-instrumentation ins Spiel . Da wir bereits die Abhängigkeit in unserem pom haben, sollten wir sehen, dass Klassen während des Builds instrumentiert werden :

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

Als Nächstes erstellen wir einen einfachen Test, um sicherzustellen, dass dies funktioniert.

3.2. Testen

Um unser Mapping zu testen, führen wir drei einfache Schritte aus: Öffnen Sie eine Verbindung zur Datenbank, speichern Sie ein neues Produkt und rufen Sie es ab:

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

Beachten Sie, dass all dies (und mehr) möglich ist, wenn nur ein leeres Modell und eine leere Instrumentierung vorhanden sind.

4. Controller

Nachdem unser Mapping fertig ist, können wir über unsere Anwendung und ihre CRUD-Methoden nachdenken.

Dafür werden wir Controller verwenden, die HTTP-Anfragen verarbeiten.

Lassen Sie uns unseren ProductsController erstellen :

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

Mit dieser Implementierung ordnet ActiveWeb die index () -Methode automatisch dem folgenden URI zu:

//:/products

Regler mit kommentierten @RESTful , einen festen Satz von Methoden liefern automatisch auf unterschiedliche URIs zugeordnet. Sehen wir uns diejenigen an, die für unser CRUD-Beispiel nützlich sind:

Controller-Methode HTTP-Methode URI
ERSTELLEN erstellen() POST // Host: Port / Produkte
LESEN SIE EINEN Show() BEKOMMEN // Host: Port / Produkte / {ID}
LESE ALLES Index() BEKOMMEN // Host: Port / Produkte
AKTUALISIEREN aktualisieren() STELLEN // Host: Port / Produkte / {ID}
LÖSCHEN zerstören() LÖSCHEN // Host: Port / Produkte / {ID}

Und wenn wir diese Methoden zu unserem ProductsController hinzufügen :

@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 } }

Bevor wir mit unserer Logikimplementierung fortfahren, werfen wir einen kurzen Blick auf einige Dinge, die wir konfigurieren müssen.

5. Konfiguration

ActiveWeb is based mostly on conventions, project structure is an example of that. ActiveWeb projects need to follow a predefined package layout:

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

There's one specific package that we need to take a look at – app.config.

Inside that package we're going to create three classes:

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

This class configures database connections using a properties file in the project's root directory containing the required parameters:

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

This will create the connection automatically replacing what we did in the first line of our mapping test.

The second class that we need to include inside app.config package is:

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

This codewill bind the connection that we just configured to our 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" } ]

.. aktualisieren Sie einen von ihnen:

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

… Lesen Sie die, die wir gerade aktualisiert haben:

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

Schließlich können wir eine löschen:

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

9. Fazit

JavaLite verfügt über zahlreiche Tools, mit denen Entwickler eine Anwendung in wenigen Minuten zum Laufen bringen können . Wenn Sie sich jedoch auf Konventionen stützen, erhalten Sie einen saubereren und einfacheren Code. Es dauert jedoch eine Weile, bis Sie die Benennung und den Speicherort von Klassen, Paketen und Dateien verstanden haben.

Dies war nur eine Einführung in ActiveWeb und ActiveJDBC. Weitere Dokumentationen finden Sie auf ihrer Website. Suchen Sie im Github-Projekt nach unserer Produktanwendung.