摘要:數據模型的首次迭代接下來我們要開始完成我們的博客引擎的模型部分。一個普遍的選擇是使用關系型數據庫。不要認為生成的成員變量是函數變量,其實它是技術變量。當你在中運行應用時,會自動切換到框架并加載對應的。再次運行測試并檢查是否一切安好。
數據模型的首次迭代
接下來我們要開始完成我們的博客引擎的模型部分。
JPA入門模型層是一個Play應用的核心(對于其他Web框架也同樣成立)。它是一個對應用操作的資源的領域特定的表示。因為我們想要創建一個博客引擎,模型層就包括User,Post和Comment(用戶,博文和評論)。
因為大多數模型對象需要在應用停止運行時保留下來,我們需要把它們存儲在持久性數據庫中。一個普遍的選擇是使用關系型數據庫。因為Java是一個面向對象的語言,我們將使用一個ORM來減少一些繁瑣的工作。
JPA是一個給ORM定義一套標準API的Java規范。作為一個JPA的實現,Play使用猿媛皆知的Hibernate框架。之所以使用JPA而不是原生的Hibernate API,是因為這樣所有的映射都可以用Java對象直接完成。
如果之前用過Hibernate或JPA,你將驚訝于Play所添加的包裝。不再需要配置什么了;JPA與Play框架合一。
如果你不知道JPA,你可以在繼續之前閱讀一些JPA實現的介紹
User類我們首先來完成User類。創建新文件/yabe/app/models/User.java,并寫入下面的內容:
package models; import java.util.*; import javax.persistence.*; import play.db.jpa.*; @Entity public class User extends Model { public String email; public String password; public String fullname; public boolean isAdmin; public User(String email, String password, String fullname) { this.email = email; this.password = password; this.fullname = fullname; } }
@Entity注解(annotation)標記該類成為托管的JPA實體(managed JPA Entity),而Model父類將自動提供一些接下來將會用到的有用的JPA輔助函數。這個類的所有成員變量都會被持久化到數據庫中。
默認情況下,對應的表就是"User"。如果想要使用一個"user"是保留關鍵字的數據庫,你需要給JPA映射指定一個不同的表名。要想這么做,使用@Table(name="blog_user")注解User類。
你的模型對象不一定得繼承自play.db.jpa.Model類。你也可以使用原生JPA。但繼承自該類往往是個更好的選擇,因為它使得運用JPA變得更為簡單。
如果之前用過JPA,你知道每個JPA實體都需要提供一個@Id屬性。在這里,Model父類已經提供了一個自動生成的ID,在大多數情況下,這樣就行了。
不要認為生成的id成員變量是函數變量(functional identifier),其實它是技術變量(technical identifier)。區分這兩概念通常是個好主意,記住自動生成的ID是一個技術變量(譯注:這里我弄不懂,以下附上原文)
Don’t think about this provided id field as a functional identifier but as a technical identifier. It is generally a good idea to keep both concepts separated and to keep an automatically generated numeric ID as a technical identifier.
如果你寫過Java,心中可能已經敲起了警鐘,因為我們居然大量使用公有成員!在Java(一如其他面向對象語言),最佳實踐通常是盡量保持各成員私有,并提供getter和setter。這就是封裝,面向對象設計的基本概念之一。事實上,Play已經考慮到這一點,在自動生成getter和setter的同時保持封裝;等下我們將看到它是怎么做到的。
現在你可以刷新主頁面,看一下結果。當然,除非你犯錯,否則應該什么變化都看不到:D。Play自動編譯并加載了User類,不過這沒有給應用添加任何新特性。
寫下第一個測試測試新增的User類的一個好方法是寫下JUnit測試用例。它會允許你增量開發的同時保證一切安好。
要運行一個測試用例,你需要在"test"模式下運行應用。停止當前正在運行的應用,打開命令行并輸入:
~$ play test
play test命令就像play run,不過它加載的是一個測試運行器模塊,使得你可以直接在瀏覽器中運行測試套件。
當你在test mode中運行Play應用時,Play會自動切換到test框架ID并加載對應的application.conf。閱讀框架ID文檔來了解更多。
在瀏覽器打開http://localhost:9000/@tests頁面來看看測試運行器。嘗試選擇所有的默認測試并運行;應該全部都會是綠色……但是默認的測試其實什么都沒測:D
我們將使用JUnit測試來測試模型部分。如你所見,已經存在一個默認的BasicTests.java,所以讓我們打開它(/yabe/test/BasicTest.java):
import org.junit.*; import play.test.*; import models.*; public class BasicTest extends UnitTest { @Test public void aVeryImportantThingToTest() { assertEquals(2, 1 + 1); } }
刪除沒用的默認測試(aVeryImportantThingToTest),創建一個注冊新用戶并進行檢查的測試:
@Test public void createAndRetrieveUser() { // Create a new user and save it new User("bob@gmail.com", "secret", "Bob").save(); // Retrieve the user with e-mail address bob@gmail.com User bob = User.find("byEmail", "bob@gmail.com").first(); // Test assertNotNull(bob); assertEquals("Bob", bob.fullname); }
如你所見,Model父類給我們提供了兩個非常有用的方法:save()和find()。
你可以在Play文檔中的JPA支持閱讀到Model類的更多方法。
在test runner中選擇BasicTests.java,點擊開始,看一下是不是全都變綠了。
我們將需要在User類中添加一個方法,來檢查給用戶的用戶名和密碼是否存在了。讓我們完成它,并且測試它。
在User.java中,添加connect()方法:
public static User connect(String email, String password) { return find("byEmailAndPassword", email, password).first(); }
如今測試用例成這樣:
@Test public void tryConnectAsUser() { // Create a new user and save it new User("bob@gmail.com", "secret", "Bob").save(); // Test assertNotNull(User.connect("bob@gmail.com", "secret")); assertNull(User.connect("bob@gmail.com", "badpassword")); assertNull(User.connect("tom@gmail.com", "secret")); }
每次修改之后,你都可以從Play測試運行器運行所有的測試,來確保沒有什么被破壞了。
Post類Post類表示博客文章。讓我們寫下代碼:
package models; import java.util.*; import javax.persistence.*; import play.db.jpa.*; @Entity public class Post extends Model { public String title; public Date postedAt; @Lob public String content; @ManyToOne public User author; public Post(User author, String title, String content) { this.author = author; this.title = title; this.content = content; this.postedAt = new Date(); } }
這里我們使用@Lob注解告訴JPA來使用字符大對象類型(clob)來存儲文章內容。我們也聲明跟User類的關系是@ManyToOne。這意味著每個Post對應一個User,而每個User可以有多個Post。
PostgreSQL的最近版本不會將@Lob注解的String成員存儲成字符大對象類型,除非你額外用@Type(type = "org.hibernate.type.TextType")注解該成員。
我們將寫一個新的測試用例來檢查Post類能否正常工作。但在寫下更多測試之前,我們需要修改下JUnit測試類。在當前測試中,數據庫的內容永不刪除,所以每次運行測試都會創建越來越多的對象。假如將來我們需要測試對象的數目是否正確,這將會是一個問題。
所以先寫一個JUnit的setup()方法在每次測試之前清空數據庫:
public class BasicTest extends UnitTest { @Before public void setup() { Fixtures.deleteDatabase(); } … }
@Before是JUnit測試工具的一個核心概念
如你所見,Fixtures類是一個在測試時幫助處理數據庫的類。再次運行測試并檢查是否一切安好。之后接著下下一個測試:
@Test public void createPost() { // Create a new user and save it User bob = new User("bob@gmail.com", "secret", "Bob").save(); // Create a new post new Post(bob, "My first post", "Hello world").save(); // Test that the post has been created assertEquals(1, Post.count()); // Retrieve all posts created by Bob ListbobPosts = Post.find("byAuthor", bob).fetch(); // Tests assertEquals(1, bobPosts.size()); Post firstPost = bobPosts.get(0); assertNotNull(firstPost); assertEquals(bob, firstPost.author); assertEquals("My first post", firstPost.title); assertEquals("Hello world", firstPost.content); assertNotNull(firstPost.postedAt); }
添加Comment類不要忘記導入java.util.List,否則你會得到一個編譯錯誤。
最后,我們需要給博文添加評論功能。
創建Comment類的方式十分簡單直白。
package models; import java.util.*; import javax.persistence.*; import play.db.jpa.*; @Entity public class Comment extends Model { public String author; public Date postedAt; @Lob public String content; @ManyToOne public Post post; public Comment(Post post, String author, String content) { this.post = post; this.author = author; this.content = content; this.postedAt = new Date(); } }
讓我們寫下第一個測試用例:
@Test public void postComments() { // Create a new user and save it User bob = new User("bob@gmail.com", "secret", "Bob").save(); // Create a new post Post bobPost = new Post(bob, "My first post", "Hello world").save(); // Post a first comment new Comment(bobPost, "Jeff", "Nice post").save(); new Comment(bobPost, "Tom", "I knew that !").save(); // Retrieve all comments ListbobPostComments = Comment.find("byPost", bobPost).fetch(); // Tests assertEquals(2, bobPostComments.size()); Comment firstComment = bobPostComments.get(0); assertNotNull(firstComment); assertEquals("Jeff", firstComment.author); assertEquals("Nice post", firstComment.content); assertNotNull(firstComment.postedAt); Comment secondComment = bobPostComments.get(1); assertNotNull(secondComment); assertEquals("Tom", secondComment.author); assertEquals("I knew that !", secondComment.content); assertNotNull(secondComment.postedAt); }
你可以看到Post和Comments之間的聯系并不緊密:我們不得不通過查詢來獲得所有跟某一個Post關聯的評論。通過在Post和Comment類之間建立新的關系,我們可以改善這一點。
在Post類添加comments成員:
... @OneToMany(mappedBy="post", cascade=CascadeType.ALL) public Listcomments; public Post(User author, String title, String content) { this.comments = new ArrayList (); this.author = author; this.title = title; this.content = content; this.postedAt = new Date(); } ...
注意現在我們用mappedBy屬性來告訴JPAComment類的post成員是維持這個關系的一方。當你用JPA定義一個雙向關系時,需要指定哪一方來維持這個關系。在這個例子中,因為Comment示例依賴于Post,我們按Comment.post的反向來定義關系。
我們也設置了cascade屬性來告訴JPA,我們希望Post的刪除將級聯影響到comments。也即是,如果你刪除一個博文時,所有相關的評論也將一并刪除。
由于有了這個新關系,我們可以給Post類添加一個輔助方法來簡化評論的添加:
public Post addComment(String author, String content) { Comment newComment = new Comment(this, author, content).save(); this.comments.add(newComment); this.save(); return this; }
讓我們寫多一個測試檢查它能否工作:
@Test public void useTheCommentsRelation() { // Create a new user and save it User bob = new User("bob@gmail.com", "secret", "Bob").save(); // Create a new post Post bobPost = new Post(bob, "My first post", "Hello world").save(); // Post a first comment bobPost.addComment("Jeff", "Nice post"); bobPost.addComment("Tom", "I knew that !"); // Count things assertEquals(1, User.count()); assertEquals(1, Post.count()); assertEquals(2, Comment.count()); // Retrieve Bob"s post bobPost = Post.find("byAuthor", bob).first(); assertNotNull(bobPost); // Navigate to comments assertEquals(2, bobPost.comments.size()); assertEquals("Jeff", bobPost.comments.get(0).author); // Delete the post bobPost.delete(); // Check that all comments have been deleted assertEquals(1, User.count()); assertEquals(0, Post.count()); assertEquals(0, Comment.count()); }
這次全綠了么?
使用Fixtures來寫更復雜的測試當你開始寫更加復雜的測試,你通常需要一些測試數據。Fixtures允許你在一個YAML文件中描述你的模型,并在測試開始前加載。
編輯/yabe/test/data.yml并開始描述一個User:
User(bob): email: bob@gmail.com password: secret fullname: Bob ...
呃,因為data.yml有點大,你可以在這里下載它。
現在我們可以創建一個加載數據并對它運行一些斷言的測試用例:
@Test public void fullTest() { Fixtures.loadModels("data.yml"); // Count things assertEquals(2, User.count()); assertEquals(3, Post.count()); assertEquals(3, Comment.count()); // Try to connect as users assertNotNull(User.connect("bob@gmail.com", "secret")); assertNotNull(User.connect("jeff@gmail.com", "secret")); assertNull(User.connect("jeff@gmail.com", "badpassword")); assertNull(User.connect("tom@gmail.com", "secret")); // Find all of Bob"s posts ListbobPosts = Post.find("author.email", "bob@gmail.com").fetch(); assertEquals(2, bobPosts.size()); // Find all comments related to Bob"s posts List bobComments = Comment.find("post.author.email", "bob@gmail.com").fetch(); assertEquals(3, bobComments.size()); // Find the most recent post Post frontPost = Post.find("order by postedAt desc").first(); assertNotNull(frontPost); assertEquals("About the model layer", frontPost.title); // Check that this post has two comments assertEquals(2, frontPost.comments.size()); // Post a new comment frontPost.addComment("Jim", "Hello guys"); assertEquals(3, frontPost.comments.size()); assertEquals(4, Comment.count()); }
你可以在YAML manual page中閱讀更多關于Play和YAML的內容。
保存你的成果現在我們已經完成了博客引擎的大部分模型層。既然已經創建并測試好了模型層,我們可以開始開發這個Web應用了。
不過在繼續前進之前,是時候用Bazaar保存你的成果。打開命令行,輸入bzr st來看看在前一個提交之后做的修改:
$ bzr st
如你所見,一些新文件不在版本控制之中。test-result目錄不需要加入到版本控制,所以就忽略它。
$ bzr ignore test-result
通過bzr add向版本控制加入其他文件。
$ bzr add
你現在可以提交你的改動了。
$ bzr commit -m "The model layer is ready"
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/64103.html
摘要:對的詳細配置已經超出了本教程的范圍,但大體上看上去像這樣然后在中加入下面一行,讓本地的反向代理能夠連接上你的應用這才只是個開始如果一路上你一直跟著本教程,你應該已經懂得如何開發一個應用了。 部署應用 如今我們已經完成了博客引擎了。讓我們來看一下一些部署Play應用的步驟。 定義一個框架ID 一般,你需要部署你的應用到一臺跟開發時不一樣的電腦。這臺電腦(很有可能是臺服務器)上面的P...
摘要:通過來實現一個基本的管理面板目前,我們還沒法使用博客的來寫新的文章,或修改評論。提供了一個即開即用的模塊,可以快速生成一個基本的管理面板。這是因為默認是以的輸出來得到一個模型對象的表示。在本教程的最后一章,你會學到關于本地化信息的更多東西。 通過CRUD來實現一個基本的管理面板 目前,我們還沒法使用博客的UI來寫新的文章,或修改評論。Play提供了一個即開即用的CRUD模塊,可以快速...
摘要:國際化和本地化完成了博客引擎后,我們來考慮額外的一件事應用的國際化和語言的本地化。國際化和本地化我們將分兩步討論,先是國際化,再是本地化。實際上,兩者是同步進行的你在國際化的同時,往往也是在本地化。 國際化和本地化 完成了博客引擎后,我們來考慮額外的一件事:Web應用的國際化和語言的本地化。雖然我們可以一開始就做這件事,但是最好還是先完成該應用的單一語言版本,然后再添加其他語言的支持...
摘要:確保你的文本編輯器已經做了相應的配置。第一個,會自動監測源代碼的改變并在運行時自動重載。檢查下面的一行是否出現在應用日志中使用版本控制系統來追蹤變化當你開發一個項目時,最好使用版本控制系統來存儲你的源代碼。 Play是一個Java Web敏捷開發的框架http://www.playframework.com/documentation/1.2.7/home 之所以要翻譯這個教程,是因...
摘要:完成應用測試我們已經完成了我們想要創建的博客引擎。當然我們已經完成了測試所有模型層的功能。評估代碼覆蓋率當然我們還沒有完成應用所需的所有測試用例。如你所見,我們遠遠沒有完成對應用的全面測試。 完成應用測試 我們已經完成了我們想要創建的博客引擎。不過這個項目尚未完全結束。為了保證代碼的質量,我們需要添加更多的測試。 當然我們已經完成了測試所有模型層的功能。所以博客引擎的核心功能已經被...
閱讀 2686·2021-09-22 15:58
閱讀 2230·2019-08-29 16:06
閱讀 896·2019-08-29 14:14
閱讀 2810·2019-08-29 13:48
閱讀 2451·2019-08-28 18:01
閱讀 1495·2019-08-28 17:52
閱讀 3318·2019-08-26 14:05
閱讀 1610·2019-08-26 13:50