- Java EE項目應用開發(fā)
- 劉勇軍 王電鋼編著
- 3502字
- 2018-12-30 08:37:21
1.5 JSP Model2開發(fā)模式應用樣例
JSP Model2是JSP+JavaBean+Servlet的結合。這種開發(fā)模式完全遵循MVC設計模式,充分利用JSP和Servlet兩種技術原有的優(yōu)點,能更加明顯地把顯示和邏輯分離,使得代碼比JSP Model1開發(fā)模式的容易管理,適用于大型項目的開發(fā)。
為了幫助獲得JSP Model2開發(fā)模式開發(fā)Java EE的Web應用經(jīng)驗,本節(jié)介紹一個基于JSP+JavaBean+Servlet實現(xiàn)的例子:在線購物的B2C電子商務網(wǎng)站中用戶注冊登錄應用。
1.5.1 電子商務網(wǎng)站說明
電子商務是運用現(xiàn)代通信技術、計算機和網(wǎng)絡技術進行的一種社會經(jīng)濟形態(tài),其目的是通過減低社會經(jīng)營成本、提高社會生產(chǎn)效率、優(yōu)化社會資源配置,從而實現(xiàn)社會財富的最大化利用。
電子商務按照經(jīng)濟活動的類別可分為兩大類別。
B2B(Business to Business):企業(yè)間的電子商務,即企業(yè)與企業(yè)之間,通過網(wǎng)絡進行產(chǎn)品或服務的經(jīng)營活動。
B2C(Business to Customer):企業(yè)與消費者之間的電子商務,即企業(yè)通過網(wǎng)絡為消費者提供一個產(chǎn)品或者服務的經(jīng)營活動。
本小節(jié)要開發(fā)的案例就是一個微型的在線購物的B2C電子商務網(wǎng)站。主要包括以下幾個模塊:
● 用戶注冊、登錄
● 商品瀏覽、搜索
● 購物車
● 結賬、支付
● 訂單處理
● 查看訂單狀態(tài)、詳細信息
1.5.2 JSP Model2應用:在線購物的B2C電子商務網(wǎng)站—用戶注冊登錄應用
我們將用JSP Model2開發(fā)模式實現(xiàn)在線購物的B2C電子商務網(wǎng)站—用戶注冊登錄應用。幾乎任何一個Web應用網(wǎng)站都提供了用戶注冊登錄功能。
1.電子商務網(wǎng)站—用戶注冊登錄應用需求
用戶注冊登錄應用需求非常簡單。對新用戶提供注冊功能、已注冊用戶提供登錄功能。
按照JSP Model2模式,可以分析出用戶注冊登錄應用中各組件之間關系如圖1-38所示。

圖1-38 用戶注冊登錄應用組件關系
新用戶注冊流程:
(1)用戶單擊首頁中的注冊鏈接(newUser.jsp)。
(2)添加注冊信息。
(3)使用JavaScript初步校驗填寫數(shù)據(jù)是否完整、合法,如果合法,繼續(xù)下一步;如果不合法,轉到(2)。
(4)控制器組件AccountServlet接收注冊表單中的參數(shù),封裝注冊參數(shù)到模型組件Account。創(chuàng)建模型組件AccountDAO對象,通過DAO對象中定義的業(yè)務接口把注冊數(shù)據(jù)保存到數(shù)據(jù)庫中。
(5)如果成功,把頁面轉發(fā)到userLogin.jsp;否則把頁面轉發(fā)到newUser.jsp,讓用戶重新添加信息,并且提示錯誤信息。
用戶登錄流程:
(1)進入登錄頁面。
(2)用戶輸入用戶名和密碼。
(3)使用JavaScript校驗用戶是否已經(jīng)輸入了用戶名和密碼,如果輸入完整,繼續(xù)下一步;否則,提示用戶重新輸入。
(4)控制器組件AccountServlet接收登錄表單中的參數(shù)。通過模型組件AcccountDAO校驗用戶名和密碼是否正確。
(5)如果校驗正確,把用戶登錄信息保存在session,然后把視圖轉發(fā)到目標;否則,返回登錄頁面,要求重新登錄。
2.分析數(shù)據(jù)
用戶注冊登錄應用中所涉及到實體就只有系統(tǒng)賬戶,包含用戶ID、用戶密碼、用戶真實姓名、用戶E-mail、用戶狀態(tài)、用戶地址1、用戶地址2、用戶所在城市、用戶所在省、郵編、用戶所在國家或地區(qū)、聯(lián)系電話等基本字段,系統(tǒng)賬戶實體對象設計如圖1-39所示。

圖1-39 系統(tǒng)賬戶實體對象
采用MS Sql Server 2005作為數(shù)據(jù)庫服務器,構建電子商務網(wǎng)站系統(tǒng)數(shù)據(jù)庫為topebusdb,創(chuàng)建系統(tǒng)賬戶數(shù)據(jù)表eb_account,SQL語句代碼如下所示:
create table eb_account ( userid char(10) not null, password varchar(25) not null, email varchar(80) not null, chinaname varchar(80) not null, status char(2), address1 varchar(80) not null, address2 varchar(80), city varchar(80) not null, state varchar(80) not null, zip varchar(20) not null, country varchar(20) not null, phone varchar(80) not null, constraint pk_account primary key (userid) );
3.項目初始化
(1)在MyEclipse下構建Web Project項目,定義電子商務網(wǎng)站項目名稱為TopEBus。
(2)在Web應用根目錄下構建JS目錄,編寫常用的JavaScript文件存放其中。
(3)在Web應用根目錄下建立images目錄,導入相關圖片。
(4)建立通用的頁頭(header)和頁腳(footer)文件,放入Web應用根目錄下的include文件目錄中。可以通過<%@include>指令或者<jsp:include>動作把header和footer文件包含入其他頁面中,詳見【代碼1-3】和【代碼1-4】。
【代碼1-3】 header.jsp文件。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" errorPage="/include/error.jsp" %> <html> <head><title>top_ebus電子商務網(wǎng)站</title> <meta content=”text/html; charset=gb2312" http-equiv=”Content-Type" /> </head><body> <table width=980 height=100 background="/images/duffersden001ba800.gif"> <tr height=80% width=100%> <td height=80% width=100% align=center><font size=+2 color=990099> TOP-EBusiness B2C電子商務網(wǎng)站</font></td> </tr> <tr height=20%><td align=right> <form action="#" method=post> <img src="/images/i_org.gif"border=0><a href="/index.jsp">商品類別 </a> <img src="/images/i_org.gif" border=0><a href="/order/viewOrders. do">我的訂單 </a> <img src="/images/i_org.gif"border=0><a href="/cart/eb_c_cart.jsp"> 購物車 </a> <img src="/images/i_org.gif" border=0><a href="/account/userLogin. jsp">登錄 </a> <img src="/images/i_org.gif" border=0><a href="/account/newUser. jsp">注冊 </a> <img src="/images/i_org.gif" border=0><a href="/account/logout. jsp">注銷</a> <INPUT id=keyword maxlength="10" name=keyword style="BORDER-RIGHT: #999999 1px solid; BORDER-TOP: #999999 1px solid; FONT-SIZE: 9pt; BORDER-LEFT:#999999 1px solid;WIDTH:100px;BORDER-BOTTOM:#999999 1px solid; BACKGROUND-COLOR: #f6f6f6"> < select id=searchType name=searchType> <option value="productid">產(chǎn)品ID</option> <option value="name">產(chǎn)品名稱</option> <option value="category">產(chǎn)品類別</option> <option value="descn">產(chǎn)品描述</option> </select> <INPUT type=image height=21 alt="Go SEARCH" width=21 src="/images/ search.gif" align=absMiddle border=0> </form> </td></tr></table>
【代碼1-4】 footer.jsp文件。
<table width="100%"cellspacing="0"cellpadding="0"border="0"bgcolor="#d6d6d6"> <tr><td valign="top" class="footer" height="20"> <center>@2011.四川托普信息技術職業(yè)學院.計算機系<br> <font size=2>成都市高新西區(qū)西區(qū)大道2000號</font></center> </td></tr> </table> </body> </html>
(5)建立通用的錯誤處理頁面文件error.jsp。當JSP在運行出錯時,就需要轉向錯誤處理頁面執(zhí)行。在開發(fā)階段,錯誤處理頁面主要用于提供開發(fā)的錯誤調(diào)試信息,到產(chǎn)品實施階段,錯誤處理頁面應該進行替換,不能把原始的Java錯誤代碼提供給用戶。需要注意的是,錯誤處理頁面的page指令中,必須包含isErrorPage="true"的聲明。該文件同樣放入include文件目錄中。詳細如【代碼1-5】所示。
【代碼1-5】 error.jsp文件。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isErrorPage="true" import="java.io.*"%> <HTML> <HEAD><META http-equiv="Content-Type" content="text/html; charset=UTF- 8"> <title>出錯了!</title></HEAD> <BODY>發(fā)生了以下的錯誤:<br><hr><font color=red> <% String message = (String) request.getAttribute("message"); out.println("error message:<br>"); out.println(message+"<br>"); try { exception.printStackTrace(); StringWriter sout = new StringWriter(); PrintWriter pout = new PrintWriter(sout); exception.printStackTrace(pout); %> <pre><%=sout.toString()%> </pre> <%} catch (Exception e) {}%> </font> </BODY> </HTML>
(6)構建通用數(shù)據(jù)訪問組件及字符轉碼工具組件。在src目錄下建立包com.etop.topebus. common,在該包下創(chuàng)建數(shù)據(jù)庫訪問類EBusDb.java,該類主要用于封裝獲取數(shù)據(jù)庫的連接的方法,執(zhí)行各種SQL語句的方法,從而完成與數(shù)據(jù)庫之間的交互;此外在該包下創(chuàng)建數(shù)據(jù)庫屬性文件ebusdb.properties,用于封裝各類數(shù)據(jù)庫連接的基礎信息,包括數(shù)據(jù)庫驅(qū)動、數(shù)據(jù)庫URL、數(shù)據(jù)庫用戶名及密碼等,在EBusDb文件可以讀取其中信息從而構建數(shù)據(jù)庫連接;在該包下建立字符轉碼工具組件類CharEncoding.java,該類提供將字符編碼從ISO8859-1轉換為UTF-8的方法接口。CharEncoding.java、ebusdb.properties、EBusDb.java文件詳細如【代碼1-6】~【代碼1-8】所示。
【代碼1-6】 CharEncoding.java文件。
public class CharEncoding { static public String toUTF(String input){ String output=""; try { output=new String(input.getBytes("iso8859-1"),"UTF-8"); } catch (UnsupportedEncodingException e) {e.printStackTrace(); } return output; } }
【代碼1-7】 ebusdb.properties文件。
#MS SQL Server2005數(shù)據(jù)庫連接信息 dirverclass=com.microsoft.sqlserver.jdbc.SQLServerDriver dburl=jdbc:sqlserver://localhost:1433;databaseName=topebusdb dbuser=sa dbpwd=mssqladmin #MySQL數(shù)據(jù)庫連接信息 #dirverclass=com.mysql.jdbc.Driver #dburl=jdbc:mysql://127.0.0.1:3306/topebusdb #dbuser=root #dbpwd=mysqladmin
【代碼1-8】 EBusDb.java文件。
public class EBusDb { private Connection conn=null; private Properties prop=null; InputStream ins=null; public Connection getCon(){//獲取數(shù)據(jù)庫連接對象接口 String driverclass,dburl,dbuser,dbpwd; prop=new Properties(); ins=getClass().getResourceAsStream("ebusdb.properties");// 讀取屬性文件 try { prop.load(ins); //加載屬性文件 driverclass=prop.getProperty("dirverclass");//獲取屬性信息 dburl=prop.getProperty("dburl"); dbuser=prop.getProperty("dbuser"); dbpwd=prop.getProperty("dbpwd"); Class.forName(driverclass); //加載數(shù)據(jù)庫驅(qū)動 conn=DriverManager.getConnection(dburl,dbuser,dbpwd); // 獲取數(shù)據(jù)庫連接對象 } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace();} return conn; } //關閉數(shù)據(jù)庫操作對象接口 public void closedb(ResultSet rs,Statement stmt,PreparedStatement pstmt,Connection conn){ try { if(rs!=null) rs.close(); if(stmt!=null) stmt.close(); if(pstmt!=null)pstmt.close(); if(conn!=null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
4.構建Model組件
定義包com.etop.topebus.model.pojo,在該包下創(chuàng)建賬戶信息表對應JavaBean類Account.java,該類實現(xiàn)java.io.Serializable接口,用于在組件之間數(shù)據(jù)的傳遞。詳細如【代碼1-9】所示。
【代碼1-9】 Account.java文件。
public class Account implements Serializable{ private String userid; //屬性:對應數(shù)據(jù)表字段 private String password; …//略去部分屬性定義 public Account() {}//無參構造函數(shù) //對應屬性的公開的getter和setter方法,略去 }
定義包com.etop.topebus.model.dao,在該包下創(chuàng)建賬戶處理組件類AccountDAO.java,該類用于實現(xiàn)對數(shù)據(jù)庫的操作,包括用戶登錄校驗以及用戶注冊等業(yè)務方法。詳細如【代碼1-10】所示。
【代碼1-10】 AccountDAO.java文件。
public class AccountDAO { private Connection conn = null; private Statement stmt = null; private PreparedStatement pstmt = null; private ResultSet rs = null; public boolean loginValidate(String userid,String password){ //登錄校驗 boolean bl=false; conn = new EBusDb ().getCon(); // 獲取數(shù)據(jù)庫連接對象 String loginstr="select * from eb_account where userid=? and password=?";//構建sql語句 try { pstmt=conn.prepareStatement(loginstr); //建立預處理對象 pstmt.setString(1, userid); //給占位符賦值 pstmt.setString(2,password); rs=pstmt.executeQuery(); //執(zhí)行查詢 //迭代結果,是否存在符合條件記錄,如果存在,設置標志為true if(rs.next()){ bl=true; } } catch (SQLException e) { e.printStackTrace(); } finally{ ebusdb.closedb(rs, stmt, pstmt, conn); } return bl; } public Account findByAccountid (String userid){ //根據(jù)用戶ID獲取用戶信息 Account account=null; conn = new EBusDb ().getCon(); String findbyidstr="select * from eb_account where userid=?"; try { pstmt=conn.prepareStatement(findbyidstr); pstmt.setString(1, userid); rs=pstmt.executeQuery(); if(rs.next()){//如果有ID對應信息 account=new Account(); account.setUserid(rs.getString("userid")); account.setPassword(rs.getString("password")); //此處略去部分代碼,將數(shù)據(jù)庫表對應字段獲取值封裝在account對象中 } } catch (SQLException e) { e.printStackTrace(); } finally{ ebusdb.closedb(rs, stmt, pstmt, conn); } } return account; public void createNewAccount(Account account){ //增加新用戶 conn = new EBusDb ().getCon(); String createnewastr="insert into account values(?,?,?,?,?,?,?,?,?,?, ?,?)"; try { pstmt=conn.prepareStatement(createnewastr); pstmt.setString(1, account.getUserid()); pstmt.setString(2,account.getPassword()); //此處略去部分代碼,從account對象中獲取屬性值賦予占位符 pstmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally{ ebusdb.closedb(rs, stmt, pstmt, conn); } } }
5.構建Control組件
定義包com.etop.topebus.control,在該包下創(chuàng)建控制器類AccountServlet.java,用于執(zhí)行用戶注冊及登錄的流程控制,一方面獲取用戶注冊及用戶登錄操作的表單數(shù)據(jù),另一方面調(diào)用模型組件AccountDAO中對應業(yè)務方法進行處理,根據(jù)處理結果轉發(fā)視圖。詳細如【代碼1-11】所示。
【代碼1-11】 AccountServlet.java文件。
public class AccountServlet extends HttpServlet { String defaultPage = "/index.jsp"; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void init(ServletConfig config) throws ServletException { super.init(config); try { defaultPage = config.getInitParameter("defaultPage"); if (defaultPage == null) defaultPage = "/index.jsp"; } catch (Exception e) {defaultPage = "/index.jsp";} } public void doPost(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 獲取客戶端請求操作類型參數(shù),確定是登錄操作還是注冊操作 String operation = request.getParameter("op").trim(); AccountDAO adao = new AccountDAO();// 構建用戶操作業(yè)務對象 // 判斷客戶端請求操作類型 if (operation != null && operation.equals("dologin")) {// 登錄 String userid=request.getParameter("userid").trim();//獲取登錄參數(shù) String password = request.getParameter("password").trim(); // 調(diào)用用戶操作業(yè)務對象中登錄校驗接口 if (adao.loginValidate(userid, password)) {// 成功 Account loginaccount = adao.findByID(userid); //找到登錄賬戶對象 HttpSession usersession = request.getSession(false); if (usersession != null) usersession.invalidate(); usersession = request.getSession(); usersession.setAttribute("loginaccount",loginaccount);//保存 在session中 } else {// 失敗 defaultPage="/account/login.jsp"; request.setAttribute("errorMessage","請輸入正確的用戶名和密碼!"); } } else {// 注冊 String userid=request.getParameter("userid").trim();//獲取注冊賬戶ID Account registaccount=new Account();// 構建注冊賬戶 registaccount.setUserid(userid); registaccount.setPassword(request.getParameter("password").trim()); //此處省略部分代碼,將表單參數(shù)獲取并封裝在注冊賬戶對象registaccount中 //判斷是否已經(jīng)存在該賬戶 if(adao.findByID(userid)!=null){//存在 defaultPage="/account/newUser.jsp"; request.setAttribute("nowaccount",registaccount); request.setAttribute("errorMessage","已經(jīng)存在該賬戶名,請重 新注冊!"); }else{//不存在 try {//調(diào)用用戶操作業(yè)務對象創(chuàng)建新賬戶接口 adao.createNewAccount(registaccount); defaultPage="/account/userLogin.jsp"; } catch (RuntimeException e) { defaultPage="/account/newUser.jsp"; request.setAttribute("nowaccount",registaccount); request.setAttribute("errorMessage","在創(chuàng)建新用戶時出 錯!請重新再試!"); } } } getServletContext().getRequestDispatcher(defaultPage).forward(request ,response); //請求轉發(fā) } }
將該控制器組件在Web部署描述符web.xml文件中注冊并加入映射,如【代碼1-12】所示。
【代碼1-12】 web.xml中控制器部署片段。
<servlet> <servlet-name>AccountServlet</servlet-name> <servlet-class>com.etop.topebus.control.AccountServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>AccountServlet</servlet-name> <url-pattern>/servlet/AccountServlet.do</url-pattern> </servlet-mapping>
6.構建View組件
新用戶注冊頁面newUser.jsp,該頁面采用Post方法提交表單數(shù)據(jù),從而有助于數(shù)據(jù)的保密性。頁面要求采用JavaScript對用戶輸入信息的完整性進行校驗,如【代碼1-13】所示。
【代碼1-13】 newUser.jsp文件。
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="com.etop.topebus.model.pojo.Account" %> <html> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <head><title>新用戶注冊</title> <jsp:include page="/include/header.jsp" flush="true" /> <script language="JavaScript"> function check(){ //略去輸入校驗邏輯 } </script> <% String errorMessage = (String) request.getAttribute("errorMessage"); Account nowaccount = (Account) request.getAttribute("nowaccount"); if(nowaccount==null) nowaccount=new Account(); %> <% if (errorMessage != null) { %> <br><span><font color=red> <%=errorMessage%></font></span><br> <%}%> <form method="post" action="../servlet/AccountServlet.do" name="form1"> <input type=hidden name="op" value="doreigst"> <TABLE class=font1 cellSpacing=0 borderColorDark=#ffffff cellPadding=2 width=750 align=center bgColor=#ffffff borderColorLight=#003686 border=1 align="center"> <tr><td colspan=2><IMG height=35 alt=""src="../images/new_user.gif" width=160 border=0><br></td></tr> <tr><td colspan="6"><IMG height=10 alt=""src="../images/spacer.gif" width=1 border=0></td></tr> <tr><td>用戶名:</td><td><input size="15" name="userid" value="<%=nowaccount.getUserid() %>"></input></td></tr> <tr><td colspan="6"><IMG height=3 alt=""src="../images/spacer.gif" width=1 border=0></td></tr> <tr><td>密碼:</td><td><input type="password" size="15" name="password"></input></td></tr> <tr><td>確認密碼:</td><td><input type="password" size="15" name="confirmpassword"></input></td></tr> <tr><td>真實姓名:</td><td><input size="15" name="chinaname" value="<%=nowaccount.getChinaname() %>"></input></td></tr> <tr><td>電話:</td><td><input size="15" name="phone" value="<%=nowaccount.getPhone() %>"></input></td></tr> <tr><td>email:</td><td><input size="15" name="email" value="<%=nowaccount.getEmail() %>"></input></td></tr> <tr><td>地址1:</td><td><input size="15" name="address1" value="<%=nowaccount.getAddress1() %>"></input></td></tr> <tr><td>地址2:</td><td><input size="15" name="address2" value="<%=nowaccount.getAddress2() %>"></input></td></tr> <tr><td>城市:</td><td><input size="15" name="city" value="<%=nowaccount.getCity() %>"></input></td></tr> <tr><td>省:</td><td><input size="15" name="state" value="<%= nowaccount.getState() %>"></input></td></tr> <tr><td>郵編:</td><td><input size="15" name="zip" value="<%= nowaccount.getZip() %>"></input></td></tr> <tr><td>國別/地區(qū):</td><td> <select name="country"> <option value="中國大陸">中國大陸</option> <option value="中國香港">中國香港</option> <option value="美國">美國</option> </select> </td> </tr> <tr><td colspan=2 align=center><input type="image" src="../ images/submit.gif" name="submit" onClick="return check()"></input></td></tr> </table> </form> <jsp:include page="/include/footer.jsp" flush="true" />
用戶登錄頁面userLogin.jsp,該頁面仍然需要采用JavaScript對用戶輸入信息的完整性進行校驗,如【代碼1-14】所示。
【代碼1-14】 userLogin.jsp文件。
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%> <html> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <head><title>::用戶登錄::</title> <script language="JavaScript"> //略去輸入校驗函數(shù)</script> <% String errorMessage=(String)request.getAttribute("errorMessage");%> <jsp:include page="/include/header.jsp" flush="true" /> <% if (errorMessage != null) {%> <center><font color=red> <%=errorMessage%></font></center><br> <%}%> <form action="../servlet/AccountServlet.do" method="POST"> <input type=hidden name="op" value="dologin"> <table align="center" border="0"> <tr><td colspan="2">請輸入用戶名和密碼. <br /> </td></tr> <tr><td> 用 戶 名 :</td><td><input type="text" name="userid" value="ebusaccount" /></td> </tr> <tr><td>密碼:</td><td><input type="password" name="password" value="ebusaccount" /></td></tr> <tr><td> </td><td><input type="image" border="0" src="../images/button_submit.gif" /></td></tr> </table> </form> <jsp:include page="/include/footer.jsp" flush="true" />
7.部署應用
至此,已經(jīng)使用JSP Model2開發(fā)模式完成在線購物的B2C電子商務網(wǎng)站中注冊登錄應用的實現(xiàn),現(xiàn)在將項目TopEBus進行部署應用。由于部署應用相對較簡單,本書略去此過程。
- 現(xiàn)代C++編程:從入門到實踐
- 軟件界面交互設計基礎
- Spring Boot+Spring Cloud+Vue+Element項目實戰(zhàn):手把手教你開發(fā)權限管理系統(tǒng)
- Backbone.js Blueprints
- Hands-On Microservices with Kotlin
- Functional Kotlin
- Instant RubyMotion App Development
- 編程數(shù)學
- App Inventor創(chuàng)意趣味編程進階
- Xcode 6 Essentials
- SQL Server 入門很輕松(微課超值版)
- Apache Solr PHP Integration
- 超好玩的Scratch 3.5少兒編程
- Groovy 2 Cookbook
- Jakarta EE Cookbook