1. 簡介
JavaLite 是一系列框架,用於簡化開發者在構建應用程序時需要處理的常見任務。
在本教程中,我們將重點介紹 JavaLite 的功能,重點在於構建一個簡單的 API。
2. 設置
在本文教程中,我們將創建一個簡單的 RESTful CRUD 應用程序。為此,我們將使用 ActiveWeb 和 ActiveJDBC – JavaLite 集成的兩個框架。
因此,讓我們開始並添加我們需要的第一個依賴項:
<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. Object-Relational Mapping
Object-Relational Mapping (ORM) 是一種技術,它允許你使用編程語言與關係數據庫交互,而無需編寫大量的 SQL 語句。 換句話説,ORM 提供了在關係數據庫和麪向對象編程語言之間進行數據映射的橋樑。
核心概念
- 對象 (Object): 在面向對象編程中,對象是具有屬性和方法的實例。
- 關係數據庫 (Relational Database): 一種使用表來存儲和組織數據的數據庫系統。
- 映射 (Mapping): 將關係數據庫中的表和列映射到面向對象編程語言中的類和屬性的過程。
ORM 的優勢
- 簡化開發: ORM 隱藏了數據庫交互的複雜性,使開發者能夠更專注於業務邏輯。
- 提高代碼可讀性: ORM 使用面向對象編程的概念,使代碼更易於理解和維護。
- 跨平台兼容性: ORM 允許你在不同的數據庫系統之間共享代碼。
常見 ORM 框架
- Hibernate (Java)
- Entity Framework (C#)
- Django ORM (Python)
- SQLAlchemy (Python)
3.1. 映射與插樁
讓我們從創建用於作為我們主要實體的 產品 類開始:
public class Product {}而且,我們還需要創建相應的表格:
CREATE TABLE PRODUCTS (
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
name VARCHAR(128)
);最後,我們可以修改我們的 產品 類以進行映射:
public class Product extends Model {}我們只需要擴展 org.javalite.activejdbc.Model 類。 ActiveJDBC 從數據庫推斷數據庫參數。 憑藉這一功能,無需添加 getter 和 setter 或任何註解。
此外,ActiveJDBC 自動識別 Product 類需要映射到 PRODUCTS 表。 它利用英語後綴將模型的單數形式轉換為表的複數形式。 並且,它也支持異常處理。
還有一件我們需要做的事情才能使映射生效:Instrumentation。 Instrumentation 是 ActiveJDBC 所要求的額外步驟,它允許我們像操作具有 getter、setter 和 DAO 樣方法的 Product 類一樣操作。
在運行 Instrumentation 後,我們可以執行諸如以下操作:
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"));
}請注意,僅使用空模型和儀器(Instrumentation)就能實現所有這些(以及更多)的功能。
4. 控制器
現在我們的映射已準備就緒,我們可以開始思考我們的應用程序及其 CRUD 方法。
為此,我們將利用控制器來處理 HTTP 請求。
讓我們創建一個 ProductsController:
@RESTful
public class ProductsController extends AppController {
public void index() {
// ...
}
}通過此實現,ActiveWeb 將自動將 index() 方法映射到以下 URI:
http://<host>:<port>/products控制器標註了 <em >@RESTful</em>,將一組方法自動映射到不同的 URI。以下是一些對 CRUD 示例有用的方法:
| 控制器方法 | HTTP 方法 | URI | |
|---|---|---|---|
| 創建 | create() |
POST | http://host:port/products |
| 讀取單個 | show() |
GET | http://host:port/products/{id} |
| 讀取所有 | index() |
GET | http://host:port/products |
| 更新 | update() |
PUT | http://host:port/products/{id} |
| 刪除 | destroy() |
DELETE | http://host:port/products/{id} |
如果將這些方法添加到我們的 <em >ProductsController</em> 中:
@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) {}
}創建完這三個類之後,關於配置的最後一步是 創建我們的 web.xml文件,該文件位於 webapp/WEB-INF目錄下:
<?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 邏輯
藉助我們的 產品 類提供的 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 目錄下。
在該目錄下,我們將視圖放在一個名為 products 的文件夾中(與我們的控制器相同)。 讓我們創建一個名為 _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");
}
}在第一個案例中,我們正在將 產品列表 賦值給名為 產品 的模板集合。
然後,由於我們沒有指定任何視圖,因此 index.ftl 將被使用。
在第二個方法中,我們正在將產品 p 賦值給視圖中的 產品 元素,並且明確指定了要渲染的視圖。
我們還可以創建一個視圖 message.ftl:
{
"message" : "${message}",
"code" : ${code}
}然後,從我們任何 <em 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 提供了大量的工具,可以幫助開發者在幾分鐘內啓動應用程序。然而,雖然基於約定俗成的做法可以產生更簡潔、更易懂的代碼,但理解類、包和文件的命名和位置,需要花費一些時間。