Guzz入門教程

在本教程中,咱們將手把手的演示如何使用guzz,編寫一個能夠並行使用5臺甚至更多數據庫的留言板系統。html

您能夠一邊看一邊跟着作。在實際的教程作,只須要1臺數據庫用來模擬。java

本教程將教會你:如何建立guzz項目,如何用guzz添加數據庫記錄,如何實現讀寫分離,如何進行多數據庫之間分表、切表、分佈式切表等,並對「服務」有個概念。mysql

什麼是guzz?何時用?

guzz[ˈɡuzi]唸作穀子。稻穀的谷,意爲種子、穀物等。web

guzz和hibernate,ibatis同屬於數據持久層框架,在應用架構上用於代替hibernate或者ibatis。使用guzz後,傳統的ssh(spring + struts + hibernate)程序就變成了ssg(spring + struts + guzz)。同時guzz也能夠和hibernate/ibatis並存,同時使用。spring

guzz適合於大型系統使用,包括訪問量較大的系統和數據量較大的系統。當你的程序須要同時使用多臺數據庫,或者存在大表須要分切成小表時,guzz是目前通用持久層框架中最好的選擇。guzz提供了面向將來的配置化分表、多庫、表分切、讀寫分離,以及多臺數據庫之間透明的分佈式事務支持等特性,使得多數據庫編程和普通編程同樣簡單,對開發者透明。sql

同時guzz提供一些服務定義,幫助團隊從零開始建立雲服務計算平臺。若是您計劃將多個系統間的通用計算(功能)作成服務,而且尚未動手,guzz service將是個很好的起步點。數據庫

guzz框架免費開源,項目地址:http://code.google.com/p/guzz/編程

準備開發環境

在本教程中,咱們使用MyEclipse IDE作開發演示。留言板將運行在1臺Mysql5.0 + 1臺Tomcat6上。所以您只須要本身的開發機器就能夠跟上。數組

固然,若是你有5臺或者更多數據庫,會更好。tomcat

雖然教程中只有1臺數據庫,但效果同樣。你能夠更清楚的看到,guzz使用多臺數據庫仍是隻用1臺數據庫,對開發者基本透明。

建立MessageBoard工程

咱們基於 springIOC + springMVC + guzz 架構建立留言板,所以先從guzz網站下載提供的空工程 a empty sample project buildxxxxxxx.zip :http://code.google.com/p/guzz/downloads/list

解壓工程,並導入到Eclipse中(File -> Import -> General/Existing Projects into Workspace)。 這時工做區中多了一個名爲「GuzzEmpty」的項目。選擇項目,右鍵 -> Refactor -> Rename。輸入MessageBoard,肯定。

從 http://code.google.com/p/guzz/downloads/list 下載最新版的guzz發佈包,如「guzz1.2.9 buildxxxxxx.zip」,解壓文件,將最新的guzz.jar覆蓋到剛剛建立的MessageBoard工程的 /WebRoot/WEB-INF/lib/ 下。

修改 /WebRoot/WEB-INF/ 下的 fms.properties 爲 messageBoard.properties ,這個爲咱們的主配置文件。

配置工程,插入第1條留言

配置工程

咱們定義留言爲Message,在src源代碼目錄下建立域對象example/business/Message.java:

 1 package example.business;
 2 
 3 import java.util.Date;
 4 
 5 public class Message implements java.io.Serializable {
 6         
 7         private int id ;
 8 
 9         private String content ;
10         
11         private Date createdTime ;
12         
13         public int getId() {
14                 return id;
15         }
16 
17         public void setId(int id) {
18                 this.id = id;
19         }
20 
21         public String getContent() {
22                 return content;
23         }
24 
25         public void setContent(String content) {
26                 this.content = content;
27         }
28 
29         public Date getCreatedTime() {
30                 return createdTime;
31         }
32 
33         public void setCreatedTime(Date createdTime) {
34                 this.createdTime = createdTime;
35         }
36         
37 }

同時在 example/business/ 目錄下建立域對象的映射文件 Message.hbm.xml:

 1 <?xml version="1.0"?>
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 4 <hibernate-mapping>
 5         <class name="example.business.Message" table="tb_message">
 6                 <id name="id" type="int" column="id">
 7                         <generator class="native" />
 8                 </id>
 9                 <property name="content" type="string" column="content" />
10                 <property name="createdTime" type="datetime" column="createdTime" />
11         </class>
12 </hibernate-mapping>

在映射文件中,留言存儲在tb_message表中。tb_message用content字段存儲留言內容。

啓動並連上Mysql5.0,建立測試用數據庫mb_main,並在其中建立表tb_message。sql語句以下:

1 create database mb_main default character set utf8 ;
2 
3 use mb_main ;
4 
5 create table tb_message(
6         id int not null auto_increment primary key, 
7         content text, 
8         createdTime datetime
9 )engine=Innodb ;

好啦,如今須要配置數據庫鏈接池,讓mb_main數據庫和MessageBoard工程對接上。

打開 /WebRoot/WEB-INF/guzz.xml 文件,選擇源代碼模式編輯。右鍵選擇 /WebRoot/WEB-INF/messageBoard.properties -> Open With -> Properties File Editor(注意:不能用高級可視化編輯器打開),打開主配置文件。

修改guzz.xml:刪除默認配置的3組數據庫組中的updateDB和logDB;刪除2個service服務;在business中增長Message域對象聲明。修改後的guzz.xml爲:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE guzz-configs PUBLIC "-//GUZZ//DTD MAIN CONFIG//EN" "http://www.guzz.org/dtd/guzz.dtd">
 3 
 4 <guzz-configs>
 5         
 6         <dialect class="org.guzz.dialect.Mysql5Dialect" />
 7         
 8         <tran>
 9                 <dbgroup name="default" masterDBConfigName="masterDB" />
10         </tran>
11         
12         <config-server>
13                 <server class="org.guzz.config.LocalFileConfigServer">
14                         <param name="resource" value="messageBoard.properties" />
15                 </server>
16         </config-server>
17         
18         <!-- business starts -->
19         <business dbgroup="default" name="message" file="classpath:example/business/Message.hbm.xml" />
20         <!-- business ends -->
21 
22 </guzz-configs>

在新的guzz.xml中,留言對象使用default數據庫組,default數據庫組的master數據庫的配置組名稱爲「masterDB」,沒有slave數據庫。修改剛剛打開的messageBoard.properties,修改masterDB配置組的鏈接池,指向咱們剛纔建立的數據庫mb_main。其餘數據庫組配置刪除。新的messageBoard.properties內容以下:

 1 #guzz app config file.
 2  
 3 #master db
 4 [masterDB]
 5 guzz.identifer=defaultMasterDB1
 6 guzz.IP=localhost
 7 guzz.maxLoad=120
 8 driverClass=com.mysql.jdbc.Driver
 9 jdbcUrl=jdbc:mysql://localhost:3306/mb_main?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true
10 user=root
11 password=root
12 acquireIncrement=10
13 idleConnectionTestPeriod=60
14  
15 #debug settings
16 [guzzDebug]
17 #runMode=debug/production
18 runMode=debug
19 #onError=halt/log/ignore
20 onError=halt
21 printSQL=true
22 printSQLParams=true
23 ignoreDemonThreadSQL=true
24 #print out how many nano-seconds a sql takes to execute.
25 measureTime=true
26 #only print out slow sqls that takes over xxx mill-seconds to execute. 0 means print out all.
27 onlySlowSQLInMillSeconds=0
28 
29 ############################### fundamental services #####################

打開 /WebRoot/WEB-INF/applicationContext.xml,刪除bean:insertQueueService 。

部署工程到Tomcat6上,而後啓動。若是沒有錯誤,則配置已經成功。若是報錯,請檢查數據庫是否給予了訪問受權等。

中止tomcat6.

插入留言

下面咱們開始插入第一條留言。

在/WebRoot/下建立jsp文件:messagesList.jsp,用於顯示留言填寫表單,內容以下:

 1 <%@ page language="java" pageEncoding="UTF-8" errorPage="/WEB-INF/jsp/include/defaultException.jsp"%>
 2 <%@include file="/WEB-INF/jsp/include/tags.jsp"%>
 3 
 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 5 <html>
 6   <head>    
 7         <title>Message List</title>
 8   </head>
 9   
10   <body>
11         
12         Leave a message:<br>
13         
14         <form method="POST" action="./newMessage.do">
15                 <textarea name="content" cols="80" rows="10"></textarea>
16                 
17                 <br/>
18                 <input type="submit" />
19         </form>
20   </body>
21 </html>

建立java文件 example.view.action.NewMessageAction.java 用於處理添加新留言的Form表單提交,內容以下:

 1 package example.view.action;
 2 
 3 import javax.servlet.http.HttpServletRequest;
 4 import javax.servlet.http.HttpServletResponse;
 5 
 6 import org.guzz.GuzzContext;
 7 import org.guzz.transaction.WriteTranSession;
 8 import org.springframework.web.servlet.ModelAndView;
 9 import org.springframework.web.servlet.mvc.Controller;
10 
11 import example.business.Message;
12 
13 public class NewMessageAction implements Controller {
14         
15         private GuzzContext guzzContext ;
16 
17         public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
18                 String content = request.getParameter("content") ;
19                 
20                 WriteTranSession write = guzzContext.getTransactionManager().openRWTran(true) ;
21                 
22                 Message msg = new Message() ;
23                 msg.setContent(content) ;
24                 msg.setCreatedTime(new java.util.Date()) ;
25                 
26                 try{
27                         write.insert(msg) ;
28                 }finally{
29                         write.close() ;
30                 }
31                 
32                 return new ModelAndView("redirect:/messageList.jsp");
33         }
34 
35         public GuzzContext getGuzzContext() {
36                 return guzzContext;
37         }
38 
39         public void setGuzzContext(GuzzContext guzzContext) {
40                 this.guzzContext = guzzContext;
41         }
42 
43 }

修改/WebRoot/WEB-INF/dispatcher-servlet.xml文件,增長NewMessageAction的映射bean:

1 <bean name="/newMessage.do" class="example.view.action.NewMessageAction">
2         <property name="guzzContext" ref="guzzContext" />
3 </bean>

部署程序,啓動Tomcat6,訪問:http://localhost:8080/guzz/messageList.jsp ,輸入留言「I am the first message. What about you?」,提交。

打開數據tb_message表,能夠看到1條記錄已經成功插入。

分頁列出發表的留言

咱們在messageList.jsp中列出全部發表的留言,每頁30條,分頁顯示。對於數據庫簡單的讀操做,咱們使用guzz taglib實現。新的 messageList.jsp 內容以下;

 1 <%@ page language="java" pageEncoding="UTF-8" errorPage="/WEB-INF/jsp/include/defaultException.jsp"%>
 2 <%@include file="/WEB-INF/jsp/include/tags.jsp"%>
 3 <g:page business="message" var="m_messages" pageNo="${param.pageNo}" pageSize="30" orderBy="id desc" scope="request" />
 4 
 5 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
 6 <html>
 7   <head>    
 8     <title>Message List</title>
 9   </head>
10   
11   <body> 
12          
13         Leave a message:<br>
14         
15         <form method="POST" action="./newMessage.do">
16                 <textarea name="content" cols="80" rows="10"></textarea>
17                 
18                 <br/>
19                 <input type="submit" />
20         </form>
21         
22         <hr>
23         <table width="96%" border="1">
24                 <tr>
25                         <th>No.</th>
26                         <th>Content</th>
27                         <th>Date</th>
28                 </tr>
29                 
30                 <c:forEach items="${m_messages.elements}" var="m_msg">
31                 <tr>
32                         <td>${m_messages.index}</td>
33                         <td><g:out value="${m_msg.content}" escapeXml="false" escapeScriptCode="true" /></td>
34                         <td>${m_msg.createdTime}</td>
35                 </tr>
36                 </c:forEach>
37         </table>
38         
39         <table width="96%" border="1">
40                 <tr>
41                         <c:import url="/WEB-INF/jsp/include/console_flip.jsp" />
42                 </tr>
43         </table>
44         
45   </body>
46 </html>

在這個jsp中,

1 <g:page business="message" var="m_messages" pageNo="${param.pageNo}" pageSize="30" orderBy="id desc" scope="request" />

根據當前傳入的頁碼查詢message,並按照id desc排序。而後咱們用1個c:forEach循環,打印出結果。用m_messages.index打印出當前記錄從1開始的排序位置,用g:out標籤打印出容許html元素但不容許script執行的正文。最後引用通用的分頁文件 /WEB-INF/jsp/include/console_flip.jsp 顯示分頁狀況。

再次訪問:http://localhost:8080/guzz/messageList.jsp ,能夠看到分頁顯示的留言列表了:

實現讀寫分離(啓用第2臺數據庫)

若是你有多臺數據庫,建立mb_main數據庫的從庫(具體可參看http://guzz.javaeye.com/blog/366508)。修改guzz.xml的

1 <dbgroup name="default" masterDBConfigName="masterDB" />

增長從數據庫配置屬性slaveDBConfigName,咱們設置值爲slaveDB。以下:

1 <dbgroup name="default" masterDBConfigName="masterDB" slaveDBConfigName="slaveDB" />

在messageBoard.properties文件增長slaveDB配置項的具體信息,以下:

 1 [slaveDB]
 2 guzz.identifer=defaultSlaveDB1
 3 guzz.IP=localhost
 4 guzz.maxLoad=80
 5 driverClass=com.mysql.jdbc.Driver
 6 jdbcUrl=jdbc:mysql://localhost:3306/mb_main?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true
 7 user=root
 8 password=root
 9 acquireIncrement=10
10 idleConnectionTestPeriod=60

固然,若是你有單獨的從數據庫,jdbcUrl處修改成從庫地址。

此時完成的 messageBoard.properties 文件內容爲:

 1 #guzz app config file.
 2  
 3 #master db
 4 [masterDB]
 5 guzz.identifer=defaultMasterDB1
 6 guzz.IP=localhost
 7 guzz.maxLoad=120
 8 driverClass=com.mysql.jdbc.Driver
 9 jdbcUrl=jdbc:mysql://localhost:3306/mb_main?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true
10 user=root
11 password=root
12 acquireIncrement=10
13 idleConnectionTestPeriod=60
14 
15 [slaveDB]
16 guzz.identifer=defaultSlaveDB1
17 guzz.IP=localhost
18 guzz.maxLoad=80
19 driverClass=com.mysql.jdbc.Driver
20 jdbcUrl=jdbc:mysql://localhost:3306/mb_main?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true
21 user=root
22 password=root
23 acquireIncrement=10
24 idleConnectionTestPeriod=60
25  
26 #debug settings
27 [guzzDebug]
28 #runMode=debug/production
29 runMode=debug
30 #onError=halt/log/ignore
31 onError=halt
32 printSQL=true
33 printSQLParams=true
34 ignoreDemonThreadSQL=true
35 #print out how many nano-seconds a sql takes to execute.
36 measureTime=true
37 #only print out slow sqls that takes over xxx mill-seconds to execute. 0 means print out all.
38 onlySlowSQLInMillSeconds=0
39 
40 ############################### fundamental services #####################

至此,即完成了讀寫分離的配置。系統啓動後,將建立兩個鏈接池,上面留言讀取的g:page將自動從slave數據庫鏈接操做。在調用guzz持久化API時,全部選擇容許延遲的讀操做也將從slave鏈接池操做。

實現1個多用戶留言板

爲了實現多用戶留言板,首先咱們須要增長User域對象。User對象只有三個屬性,用戶編號、用戶名和留言數。建立example.business.User.java:

 1 package example.business;
 2 
 3 public class User implements java.io.Serializable {
 4         
 5         private int id ;
 6 
 7         private String userName ;
 8         
 9         private int messageCount ;
10         
11         public int getId() {
12                 return id;
13         }
14 
15         public void setId(int id) {
16                 this.id = id;
17         }
18 
19         public String getUserName() {
20                 return userName;
21         }
22 
23         public void setUserName(String userName) {
24                 this.userName = userName;
25         }
26 
27         public int getMessageCount() {
28                 return messageCount;
29         }
30 
31         public void setMessageCount(int messageCount) {
32                 this.messageCount = messageCount;
33         }
34         
35 }

建立映射文件User.hbm.xml:

 1 <?xml version="1.0"?>
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 4 <hibernate-mapping>
 5     <class name="example.business.User" table="tb_user">
 6         <id name="id" type="int" column="id">
 7                 <generator class="native" />
 8         </id>
 9         <property name="userName" type="string" column="userName" />
10         <property name="messageCount" type="int" column="messageCount" />
11     </class>
12 </hibernate-mapping>

同時在Message.java、Message.hbm.xml 中增長userId屬性對應User對象的id屬性。新的Message.java和Message.hbm.xml爲:

 1 package example.business;
 2 
 3 import java.util.Date;
 4 
 5 public class Message implements java.io.Serializable {
 6         
 7         private int id ;
 8 
 9         private String content ;
10         
11         private int userId ;
12         
13         private Date createdTime ;
14         
15         public int getId() {
16                 return id;
17         }
18 
19         public void setId(int id) {
20                 this.id = id;
21         }
22 
23         public String getContent() {
24                 return content;
25         }
26 
27         public void setContent(String content) {
28                 this.content = content;
29         }
30 
31         public Date getCreatedTime() {
32                 return createdTime;
33         }
34 
35         public void setCreatedTime(Date createdTime) {
36                 this.createdTime = createdTime;
37         }
38 
39         public int getUserId() {
40                 return userId;
41         }
42 
43         public void setUserId(int userId) {
44                 this.userId = userId;
45         }
46         
47 }
 1 <?xml version="1.0"?>
 2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 3 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
 4 <hibernate-mapping>
 5     <class name="example.business.Message" table="tb_message">
 6         <id name="id" type="int" column="id">
 7                 <generator class="native" />
 8         </id>
 9         <property name="userId" type="int" column="userId" />
10         <property name="content" type="string" column="content" />
11         <property name="createdTime" type="datetime" column="createdTime" />
12     </class>
13 </hibernate-mapping>

在mb_main數據庫中增長tb_user表,並調整tb_message表:

 1 use mb_main ;
 2 
 3 create table tb_user(
 4         id int not null auto_increment primary key, 
 5         userName varchar(64) not null, 
 6         messageCount int(11)default 0
 7 )engine=Innodb ;
 8 
 9 alter table tb_message add column userId int(11) default 1 ;
10 create index idx_msg_uid on tb_message(userId) ;
11 
12 insert into tb_user(userName) values('Lucy') ;
13 insert into tb_user(userName) values('Lily') ;
14 insert into tb_user(userName) values('Cathy') ;
15 insert into tb_user(userName) values('Polly, The Bird') ;
16 
17 update tb_user set messageCount = (select count(*) from tb_message) where id = 1 ;

修改guzz.xml,增長User域對象的聲明:

1 <business dbgroup="default" name="user" file="classpath:example/business/User.hbm.xml" />

因爲是多用戶留言板,所以 messageList.jsp 中咱們須要增長一個參數用來區分用戶。此參數定義爲userId。不管是訪問留言板,仍是發表留言,咱們都用userId標記當前留言板所屬用戶。修改 messageList.jsp 增長對userId參數的支持:

 1 <%@ page language="java" pageEncoding="UTF-8" errorPage="/WEB-INF/jsp/include/defaultException.jsp"%>
 2 <%@include file="/WEB-INF/jsp/include/tags.jsp"%>
 3 <g:get business="user" var="m_user" limit="id=${param.userId}" />
 4 
 5 <g:boundary>
 6         <g:addLimit limit="userId=${m_user.id}" />
 7         <g:page business="message" var="m_messages" tableCondition="${m_user.id}" pageNo="${param.pageNo}" pageSize="30" orderBy="id desc" scope="request" />
 8 </g:boundary>
 9 
10 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
11 <html>
12   <head>    
13     <title>${m_user.userName}'s Message List</title>
14   </head>
15   
16   <body>         
17         Leave a message:<br>    
18 
19         <form method="POST" action="./newMessage.do">
20                 <input type="hidden" name="userId" value="${m_user.id}" />
21                 
22                 <textarea name="content" cols="80" rows="10"></textarea>                
23                 ....

在新的 messageList.jsp 中,g:page增長了1個查詢條件。而在form表單提交時,增長了隱藏的userId參數。

修改 NewMessageAction.java 增長對userId參數的支持:

 1  public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2                 int userId = RequestUtil.getParameterAsInt(request, "userId", 0) ;
 3                 String content = request.getParameter("content") ;
 4 
 5                 Message msg = new Message() ;
 6                 msg.setContent(content) ;
 7                 msg.setCreatedTime(new java.util.Date()) ;
 8                 
 9                 //close auto-commit
10                 WriteTranSession write = guzzContext.getTransactionManager().openRWTran(false) ;
11                 
12                 try{
13                         User user = (User) write.findObjectByPK(User.class, userId) ;
14                         user.setMessageCount(user.getMessageCount() + 1) ;
15                         
16                         msg.setUserId(userId) ;
17                         
18                         write.insert(msg) ;
19                         write.update(user) ;
20                         
21                         write.commit() ;
22                 }catch(Exception e){
23                         write.rollback() ;
24                         
25                         throw e ;
26                 }finally{
27                         write.close() ;
28                 }
29                 
30                 return new ModelAndView("redirect:/messageList.jsp", "userId", userId);
31         }

由於要同時更新兩張表,所以咱們選擇手工控制事務的提交。

從新部署並啓動應用,訪問:http://localhost:8080/guzz/messageList.jsp?userId=1 ,看到Lucy's Message List;訪問http://localhost:8080/guzz/messageList.jsp?userId=1 看到Lily's Message List。

多用戶留言板完成。

使用2組數據庫(啓用第三、4臺數據庫)

隨着業務的增加咱們發現mb_main數據庫負載過高,須要將tb_user表挪出去,部署到另一組數據庫中。這組數據庫有1臺主庫(第3臺數據庫)和1臺從庫(第4臺數據庫)。

咱們給這組新數據庫取名爲userDB。首先,很顯然須要將現有的tb_user表導出並裝載到userDB數據庫組中,並建好數據庫主從。假設此步驟已經完成。

爲了演示,咱們在本機上建立 mb_user 數據庫做爲userDB。

1 create database mb_user default character set utf8 ;
2 
3 create table mb_user.tb_user select * from mb_main.tb_user ;
4 
5 alter table mb_user.tb_user modify column id int(11) not null auto_increment primary key ;
6 
7 drop table mb_main.tb_user ;

這時咱們有2組數據庫:mb_main存放tb_message, mb_user存放tb_user。

爲了讓應用知道mb_user庫的存在,修改guzz.xml的tran,增長一個userDB dbgroup: 

1 <dbgroup name="userDB" masterDBConfigName="userMasterDB" slaveDBConfigName="userSlaveDB" />

修改User域對象使用userDB: 

1 <business dbgroup="userDB" name="user" file="classpath:example/business/User.hbm.xml" />

修改messageBoard.properties主配置文件,增長userDB主從數據庫鏈接池的配置信息userMasterDB和userSlaveDB:

 1 [userMasterDB]
 2 guzz.identifer=userMasterDB1
 3 guzz.IP=localhost
 4 guzz.maxLoad=120
 5 driverClass=com.mysql.jdbc.Driver
 6 jdbcUrl=jdbc:mysql://localhost:3306/mb_user?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true
 7 user=root
 8 password=root
 9 acquireIncrement=10
10 idleConnectionTestPeriod=60
11 
12 [userSlaveDB]
13 guzz.identifer=userSlaveDB1
14 guzz.IP=localhost
15 guzz.maxLoad=80
16 driverClass=com.mysql.jdbc.Driver
17 jdbcUrl=jdbc:mysql://localhost:3306/mb_user?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true
18 user=root
19 password=root
20 acquireIncrement=10
21 idleConnectionTestPeriod=60

若是你有第三、4臺獨立數據庫,將上面的jdbcUrl改到具體的數據庫地址便可。

從新部署並啓動應用,這時您的應用已經開始使用4臺數據庫。當你發表新留言時,更新tb_user和tb_message表的操做將自動在一個分佈式事務中完成。代碼不須要作任何調整。

注意: 1. 經過create table ..select..的方式,索引將不復存在,真實環境下注意從新建立索引。2. 上面例子中的sql語句經過db.table的方式操做數據庫,mysql主從複製可能會忽略這類操做的複製;若是從庫沒有執行,須要手工在從庫執行sql。下同。

將留言表分切成小表

隨着業務的進一步發展,很快留言數突破千萬,tb_message查詢緩慢,已經成爲系統的性能瓶頸。咱們決定將留言表按照用戶分紅多張小表。

分切的規則是:每一個用戶1張表,表名規則爲tb_message${userId}。

切表的詳細說明文檔,請參看:TutorialShadowTable 。

爲了實現表分切,首先須要實現分切規則,建立一個新的java類 example.business.MessageShadowTableView 定義規則:

 1 package example.business;
 2 
 3 import org.guzz.exception.GuzzException;
 4 import org.guzz.orm.AbstractShadowTableView;
 5 
 6 public class MessageShadowTableView extends AbstractShadowTableView {
 7 
 8         public String toTableName(Object tableCondition) {
 9                 if (tableCondition = null) { // 強制要求必須設置表分切條件,避免編程時疏忽。
10                         throw new GuzzException("null table conditon is not allowed.");
11                 }
12 
13                 Integer userId = (Integer) tableCondition;
14 
15                 //tb_message_${userId}
16                 return "tb_message_" + userId.intValue() ;
17         }
18 
19 }

可見,分切條件爲用戶編號userId。咱們有4個用戶,所以將tb_message分切成4張小表,分別爲:tb_message_1, tb_message_2, tb_message_3, tb_message_4.

 1 use mb_main ;
 2 
 3 create table tb_message_1 select * from tb_message where userId = 1 ;
 4 create table tb_message_2 select * from tb_message where userId = 2 ;
 5 create table tb_message_3 select * from tb_message where userId = 3 ;
 6 create table tb_message_4 select * from tb_message where userId = 4 ;
 7 
 8 alter table tb_message_1 modify column id int(11) not null auto_increment primary key ;
 9 alter table tb_message_2 modify column id int(11) not null auto_increment primary key ;
10 alter table tb_message_3 modify column id int(11) not null auto_increment primary key ;
11 alter table tb_message_4 modify column id int(11) not null auto_increment primary key ;
12 
13 drop table tb_message ;

爲了讓guzz知道表分切規則,修改Message.hbm.xml映射文件,在class元素中增長屬性 shadow="example.business.MessageShadowTableView" ,同時將dtd定義改爲guzz的。修改後的文件爲:

 1 <?xml version="1.0"?>
 2 <!DOCTYPE guzz-mapping PUBLIC "-//GUZZ//GUZZ MAPPING DTD//EN" "http://www.guzz.org/dtd/guzz-mapping.dtd">
 3 <guzz-mapping>
 4     <class name="example.business.Message" table="tb_message" shadow="example.business.MessageShadowTableView">
 5         <id name="id" type="int" column="id">
 6                 <generator class="native" />
 7         </id>
 8         <property name="userId" type="int" column="userId" />
 9         <property name="content" type="string" column="content" />
10         <property name="createdTime" type="datetime" column="createdTime" />
11     </class>
12 </guzz-mapping>

這時guzz已經知道Message對象須要根據 example.business.MessageShadowTableView 進行切表。在全部操做 Message 的地方,須要傳入userId做爲切表條件(稱做:tableCondition)。

對於留言讀取的地方,修改 messageList.jsp 的g:page調用爲:

1 <g:page business="message" var="m_messages" tableCondition="${param.userId}" pageNo="${param.pageNo}" pageSize="30" orderBy="id desc" scope="request" />

經過tableCondition屬性傳入切表條件。

修改 NewMessageAction.java的 write.insert(msg) 爲 write.insert(msg, userId) 經過insert方法的第二個參數傳入切表條件。

從新部署並啓動應用,能夠看到咱們的留言板已經在4個小表中工做了。

分佈式切表以應對超級大表

隨着業務的更進一步發展,咱們發現tb_message即便分紅了小表,都存儲在一臺數據庫中也過於龐大。在實際的應用中,多是您的數據已經分切成了上百張表,總量很是大。如今,咱們須要將這些分切後的小表,存儲到不一樣的數據庫機器中,減小單臺數據庫數據量過大的問題,同時也減小1臺數據庫上表的總個數。

爲了解決這個問題,guzz提供了分佈式切表功能,稱做VirtualDB。詳細介紹請參看:TutorialVirtualDB

爲了演示,咱們個性化的小表存儲規則爲:編號爲1的用戶,留言繼續存儲在default數據庫組中不變(mb_main庫中);其餘的存儲到userDB中(mb_user庫中)。固然在實際系統中,您可能不會這麼切分,更可能引入第五、六、七、8甚至更多臺的數據庫機器。這裏只是演示靈活的切分規則。

如今咱們開始挪表,把tb_message_2, tb_message_3, tb_message_4從mb_main挪到mb_user中:

 1 create table mb_user.tb_message_2 select * from mb_main.tb_message_2 ;
 2 create table mb_user.tb_message_3 select * from mb_main.tb_message_3 ;
 3 create table mb_user.tb_message_4 select * from mb_main.tb_message_4 ;
 4 
 5 alter table mb_user.tb_message_2 modify column id int(11) not null auto_increment primary key ;
 6 alter table mb_user.tb_message_3 modify column id int(11) not null auto_increment primary key ;
 7 alter table mb_user.tb_message_4 modify column id int(11) not null auto_increment primary key ;
 8 
 9 drop table mb_main.tb_message_2 ;
10 drop table mb_main.tb_message_3 ;
11 drop table mb_main.tb_message_4 ;

建立一個新的java類,定義VirtualDB的表分佈規則:

 1 package example.business;
 2 
 3 import org.guzz.connection.AbstractVirtualDBView;
 4 import org.guzz.exception.GuzzException;
 5 
 6 public class MessageVirtualDBView extends AbstractVirtualDBView {
 7     
 8     public String getPhysicsDBGroupName(Object tableCondition) {
 9                 if (tableCondition = null) {
10                         throw new GuzzException("null table conditon is not allowed.");
11                 }
12                 
13                 int userId = (Integer) tableCondition;
14                 
15                 if(userId = 1){
16                          //store lucy's messages in the default database.
17                         return "default" ;
18                 }else{
19                          //store others in the userDB database.
20                         return "userDB" ;
21                 }
22     }
23 
24 }

爲了讓 guzz 知道分佈式切表,對於每一個使用VirtualDB的領域對象,咱們須要在guzz.xml中聲明一個virtualdbgroup。對於Message,修改guzz.xml,在tran元素下增長virtualdbgroup:

1 <virtualdbgroup name="messageDB" shadow="example.business.MessageVirtualDBView" />

同時,修改Message領域對象的dbgroup到新的messageDB。修改好的guzz.xml爲:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE guzz-configs PUBLIC "-//GUZZ//DTD MAIN CONFIG//EN" "http://www.guzz.org/dtd/guzz.dtd">
 3 
 4 <guzz-configs>
 5         
 6         <dialect class="org.guzz.dialect.Mysql5Dialect" />
 7         
 8         <tran>
 9                 <dbgroup name="default" masterDBConfigName="masterDB" slaveDBConfigName="slaveDB" />
10                 <dbgroup name="userDB" masterDBConfigName="userMasterDB" slaveDBConfigName="userSlaveDB" />
11                 
12                 <virtualdbgroup name="messageDB" shadow="example.business.MessageVirtualDBView" />
13         </tran>
14         
15         <config-server>
16                 <server class="org.guzz.config.LocalFileConfigServer">
17                         <param name="resource" value="messageBoard.properties" />
18                 </server>
19         </config-server>
20         
21         <!-- business starts -->
22         <business dbgroup="messageDB" name="message" file="classpath:example/business/Message.hbm.xml" />
23         <business dbgroup="userDB" name="user" file="classpath:example/business/User.hbm.xml" />
24         <!-- business ends -->
25 
26 </guzz-configs>

從新部署並啓動應用。此時的留言板將自動將lucy的留言存儲到mb_main,其餘人的留言存儲到mb_user庫中。完成分佈式切表。

用計數器服務實現支持投票(啓用第3組,第5臺數據庫)

 

實現投票功能

隨着留言板應用的深刻,用戶但願可以對留言進行投票,像digg同樣支持優秀的留言。咱們須要給留言增長vote功能。Vote分爲支持和反對,支持增長10分,反對減小8分。在Message中增長一個voteScore記錄得票的總分數。咱們給Message.java對象和對應的Message.hbm.xml文件增長3個int類型屬性:

1 private int voteYes ;
2 
3 private int voteNo ;
4 
5 private int voteScore ;

調整數據庫表結構,增長這3個字段:

 1 alter table mb_main.tb_message_1 add column voteYes int(11) default 0 ;
 2 alter table mb_main.tb_message_1 add column voteNo int(11) default 0 ;
 3 alter table mb_main.tb_message_1 add column voteScore int(11) default 0 ;
 4 
 5 alter table mb_user.tb_message_2 add column voteYes int(11) default 0 ;
 6 alter table mb_user.tb_message_2 add column voteNo int(11) default 0 ;
 7 alter table mb_user.tb_message_2 add column voteScore int(11) default 0 ;
 8 
 9 alter table mb_user.tb_message_3 add column voteYes int(11) default 0 ;
10 alter table mb_user.tb_message_3 add column voteNo int(11) default 0 ;
11 alter table mb_user.tb_message_3 add column voteScore int(11) default 0 ;
12 
13 alter table mb_user.tb_message_4 add column voteYes int(11) default 0 ;
14 alter table mb_user.tb_message_4 add column voteNo int(11) default 0 ;
15 alter table mb_user.tb_message_4 add column voteScore int(11) default 0 ;

爲支持用戶投票,增長一個action處理此操做,example.view.action.VoteMessageAction.java:

 1 package example.view.action;
 2 
 3 import javax.servlet.http.HttpServletRequest;
 4 import javax.servlet.http.HttpServletResponse;
 5 
 6 import org.guzz.Guzz;
 7 import org.guzz.GuzzContext;
 8 import org.guzz.transaction.WriteTranSession;
 9 import org.guzz.util.Assert;
10 import org.guzz.util.RequestUtil;
11 import org.springframework.web.servlet.ModelAndView;
12 import org.springframework.web.servlet.mvc.Controller;
13 
14 import example.business.Message;
15 
16 public class VoteMessageAction implements Controller {
17         
18         private GuzzContext guzzContext ;
19 
20         public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
21                 int userId = RequestUtil.getParameterAsInt(request, "userId", 0) ;
22                 int msgId = RequestUtil.getParameterAsInt(request, "msgId", 0) ;
23                 String type = request.getParameter("type") ;
24 
25                 //auto-commit
26                 WriteTranSession write = guzzContext.getTransactionManager().openRWTran(true) ;
27                 
28                 try{
29                         //set tableCondition
30                         Guzz.setTableCondition(userId) ;
31                         Message msg = (Message) write.findObjectByPK(Message.class, msgId) ;
32                         Assert.assertNotNull(msg, "msg not found!") ;
33                         
34                         if("yes".equals(type)){
35                                 msg.setVoteYes(msg.getVoteYes() + 1) ;
36                                 msg.setVoteScore(msg.getVoteScore() + 10) ;
37                         }else{
38                                 msg.setVoteNo(msg.getVoteNo() + 1) ;
39                                 msg.setVoteScore(msg.getVoteScore() - 8) ;
40                         }
41                         
42                         write.update(msg) ;
43                 }finally{
44                         write.close() ;
45                 }
46                 
47                 return new ModelAndView("redirect:/messageList.jsp", "userId", userId);
48         }
49 
50         public GuzzContext getGuzzContext() {
51                 return guzzContext;
52         }
53 
54         public void setGuzzContext(GuzzContext guzzContext) {
55                 this.guzzContext = guzzContext;
56         }
57 
58 }

在這個Action中,咱們經過 Guzz.setTableCondition(userId) 設置當前線程全部數據庫操做默認的分表條件,而後獲取要投票的留言,增長計數後更新。

將這個Action的配置添加到dispatcher-servlet.xml中:

1 <bean name="/voteMessage.do" class="example.view.action.VoteMessageAction">
2         <property name="guzzContext" ref="guzzContext" />
3 </bean>

修改 messageList.jsp ,列出投票數和投票地址:

 1 <%@ page language="java" pageEncoding="UTF-8" errorPage="/WEB-INF/jsp/include/defaultException.jsp"%>
 2 <%@include file="/WEB-INF/jsp/include/tags.jsp"%>
 3 
 4 <g:get business="user" var="m_user" limit="id=${param.userId}" />
 5 
 6 <g:boundary>
 7         <g:addLimit limit="userId=${m_user.id}" />
 8         <g:page business="message" var="m_messages" tableCondition="${m_user.id}" pageNo="${param.pageNo}" pageSize="30" orderBy="id desc" scope="request" />
 9 </g:boundary>
10 
11 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
12 <html>
13   <head>    
14     <title>${m_user.userName}'s Message List</title>
15   </head>
16   
17   <body>         
18         Leave a message:<br>
19         
20         <form method="POST" action="./newMessage.do">
21                 <input type="hidden" name="userId" value="${m_user.id}" />
22                 
23                 <textarea name="content" cols="80" rows="10"></textarea>
24                 
25                 <br/>
26                 <input type="submit" />
27         </form>
28         
29         <hr>
30         <table width="96%" border="1">
31                 <tr>
32                         <th>No.</th>
33                         <th>Vote</th>
34                         <th>Content</th>
35                         <th>Date</th>
36                 </tr>
37                 
38                 <c:forEach items="${m_messages.elements}" var="m_msg">
39                 <tr>
40                         <td>${m_messages.index}</td>
41                         <td>
42                                 voteYes: <a href="./voteMessage.do?type=yes&userId=${m_msg.userId}&msgId=${m_msg.id}">${m_msg.voteYes}</a><br>
43                                 voteNo: <a href="./voteMessage.do?type=no&userId=${m_msg.userId}&msgId=${m_msg.id}">${m_msg.voteNo}</a><br>
44                                 voteScore: ${m_msg.voteScore}
45                         </td>
46                         <td>vote<g:out value="${m_msg.content}" escapeXml="false" escapeScriptCode="true" /></td>
47                         <td>${m_msg.createdTime}</td>
48                 </tr>
49                 </c:forEach>
50         </table>
51         
52         <table width="96%" border="1">
53                 <tr>
54                         <c:import url="/WEB-INF/jsp/include/console_flip.jsp" />
55                 </tr>
56         </table>
57         
58   </body>
59 </html>

從新並重啓應用,如今投票功能就可使用了。

SlowUpdateService(使用第5臺數據庫)

雖然投票功能有了,但剛上線就發現了另一個問題:投票的量太多。每次投票都須要進行一次數據庫update操做,頻繁的update操做對數據庫的消耗太大。

咱們但願將投票操做隊列化,最好可以合併對同1條記錄的多個update操做,而後用批操做的方式寫入數據庫,最大程度的減小數據庫消耗,而不要每次都單獨update。

這種場景出現的比較多,所以guzz內置了 SlowUpdateService 服務來完成這種隊列化計數更新操做。詳細的配置參數和工做原理,請參看:AppendCoreService

下面咱們開始配置。爲了減小對主庫的影響,計數對列咱們存儲在第5臺臨時數據庫中。建立數據庫mb_temp,並建立計數隊列存儲的表tb_guzz_su:

 1 create database mb_temp default character set utf8 ;
 2 
 3 use mb_temp ;
 4 
 5 create table tb_guzz_su(
 6         gu_id bigint not null auto_increment primary key, 
 7         gu_db_group varchar(32) not null, 
 8         gu_tab_name varchar(64) not null, 
 9         gu_inc_col varchar(64) not null ,
10         gu_tab_pk_col varchar(64) not null,
11         gu_tab_pk_val varchar(64) not null ,
12         gu_inc_count int(11) not null
13 )engine=Innodb ;

在guzz.xml中增長mb_temp對應的數據庫組tempDB,並增長臨時表對應域對象的聲明:

1 <tran>
2                 ....
3                 <dbgroup name="tempDB" masterDBConfigName="tempMasterDB" />
4                 ....
5 </tran>
6 
7 <business dbgroup="tempDB" name="guzzSlowUpdate" file="classpath:example/business/IncUpdateBusiness.hbm.xml" />

將工程src中的 fms/business/IncUpdateBusiness.hbm.xml 挪到 example/business/ 下。fms/business目錄能夠刪掉了。

默認的 SlowUpdateService 服務爲guzz內置服務。guzz在啓動時會自動建立此服務,名稱爲guzzSlowUpdate,配置組的名稱也爲guzzSlowUpdate。修改messageBoard.properties主配置文件,增長此服務配置信息,同時增長tempDB數據庫組主數據庫的鏈接池配置:

[tempMasterDB]
guzz.identifer=tempMasterDB1
guzz.IP=localhost
guzz.maxLoad=120
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/mb_temp?useUnicode=true&amp;characterEncoding=UTF-8&amp;useServerPrepStmts=true
user=root
password=root
acquireIncrement=10
idleConnectionTestPeriod=60

[guzzSlowUpdate]
#max size of cached queue
queueSize=20480

#batch size for updating to the temporary database.
batchSize=2048

至此,計數隊列的配置完成。爲了方便spring使用,咱們將此服務導出爲spring bean,在applicationContext.xml中添加bean:

1 <bean id="guzzSlowUpdateService" class="org.guzz.web.context.spring.GuzzServiceFactoryBean"> 
2         <property name="serviceName" value="guzzSlowUpdate" /> 
3     </bean>

修改 VoteMessageAction.java 使用計數器服務:

 1 package example.view.action;
 2 
 3 import javax.servlet.http.HttpServletRequest;
 4 import javax.servlet.http.HttpServletResponse;
 5 
 6 import org.guzz.service.core.SlowUpdateService;
 7 import org.guzz.util.RequestUtil;
 8 import org.springframework.web.servlet.ModelAndView;
 9 import org.springframework.web.servlet.mvc.Controller;
10 
11 import example.business.Message;
12 
13 public class VoteMessageAction implements Controller {
14         
15         private SlowUpdateService slowUpdateService ;
16 
17         public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
18                 int userId = RequestUtil.getParameterAsInt(request, "userId", 0) ;
19                 int msgId = RequestUtil.getParameterAsInt(request, "msgId", 0) ;
20                 String type = request.getParameter("type") ;
21 
22                 if("yes".equals(type)){
23                         //public void updateCount(Class domainClass, Object tableCondition, String propToUpdate, Serializable pkValue, int countToInc) ;
24                         this.slowUpdateService.updateCount(Message.class, userId, "voteYes", msgId, 1) ;
25                         this.slowUpdateService.updateCount(Message.class, userId, "voteScore", msgId, 10) ;
26                 }else{
27                         this.slowUpdateService.updateCount(Message.class, userId, "voteNo", msgId, 1) ;
28                         this.slowUpdateService.updateCount(Message.class, userId, "voteScore", msgId, -8) ;
29                 }
30                 
31                 return new ModelAndView("redirect:/messageList.jsp", "userId", userId);
32         }
33 
34         public SlowUpdateService getSlowUpdateService() {
35                 return slowUpdateService;
36         }
37 
38         public void setSlowUpdateService(SlowUpdateService slowUpdateService) {
39                 this.slowUpdateService = slowUpdateService;
40         }
41 
42 }

在新的Action中,不在直接操做數據庫,而是調用updateCount將計數操做寫入隊列。

修改 dispatcher-servlet.xml ,在bean:/voteMessage.do 中注入guzzSlowUpdateService:

<bean name="/voteMessage.do" class="example.view.action.VoteMessageAction">
        <property name="slowUpdateService" ref="guzzSlowUpdateService" />
</bean>

從新部署並啓動應用。投票。此時票數沒有變化,沒有關係,打開mb_temp數據庫的tb_guzz_su表,能夠看到計數操做已經成功寫入到了隊列中。

SlowUpdateServerService

爲了將計數隊列的數據寫回各個表對應的主庫,還須要啓動計數隊列服務的服務器端。修改guzz.xml文件,添加服務器端服務:

1 <service name="slowUpdateServer" configName="guzzSlowUpdateServer" class="org.guzz.service.db.impl.SlowUpdateServerImpl" />

修改messageBoard.properties文件,添加slowUpdateServer服務的詳細配置信息:

 1 [guzzSlowUpdateServer]
 2 #batch size for updating the main database
 3 batchSize=50
 4 
 5 #page size for reading from the temporary table
 6 pageSize=40
 7 
 8 #how many pages to read from the temporary table for one loop updating
 9 combinePageCount=10
10 
11 #millseconds to wait for the next round of updates checking
12 updateInterval=500

從新部署並啓動應用。查看tb_guzz_su表,隊列已經被處理,訪問:http://localhost:8080/guzz/messageList.jsp?userId=1 ,看到計數已經正確更新了。

刪除留言,批量刪除留言

單條留言刪除

建立刪除留言的Action:example.view.action.DeleteMessageAction.java:

 1 package example.view.action;
 2 
 3 import javax.servlet.http.HttpServletRequest;
 4 import javax.servlet.http.HttpServletResponse;
 5 
 6 import org.guzz.Guzz;
 7 import org.guzz.GuzzContext;
 8 import org.guzz.service.core.SlowUpdateService;
 9 import org.guzz.transaction.WriteTranSession;
10 import org.guzz.util.RequestUtil;
11 import org.springframework.web.servlet.ModelAndView;
12 import org.springframework.web.servlet.mvc.Controller;
13 
14 import example.business.Message;
15 import example.business.User;
16 
17 public class DeleteMessageAction implements Controller {
18         
19         private GuzzContext guzzContext ;
20         
21         private SlowUpdateService slowUpdateService ;
22 
23         public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
24                 int userId = RequestUtil.getParameterAsInt(request, "userId", 0) ;
25                 int msgId = RequestUtil.getParameterAsInt(request, "msgId", 0) ;
26                 
27                 //auto-commit
28                 WriteTranSession write = guzzContext.getTransactionManager().openRWTran(true) ;
29                 
30                 try{
31                         Guzz.setTableCondition(userId) ;
32                         Message msg = (Message) write.findObjectByPK(Message.class, msgId) ;
33                         
34                         if(msg != null){
35                                 write.delete(msg) ;
36                                 
37                                 //dec the message count
38                                 this.slowUpdateService.updateCount(User.class, null, "messageCount", userId, -1) ;
39                         }
40                 }finally{
41                         write.close() ;
42                 }
43                 
44                 return new ModelAndView("redirect:/messageList.jsp", "userId", userId);
45         }
46 
47         public GuzzContext getGuzzContext() {
48                 return guzzContext;
49         }
50 
51         public void setGuzzContext(GuzzContext guzzContext) {
52                 this.guzzContext = guzzContext;
53         }
54 
55         public SlowUpdateService getSlowUpdateService() {
56                 return slowUpdateService;
57         }
58 
59         public void setSlowUpdateService(SlowUpdateService slowUpdateService) {
60                 this.slowUpdateService = slowUpdateService;
61         }
62 
63 }

配置到 dispatcher-servlet.xml 中:

1 <bean name="/deleteMessage.do" class="example.view.action.DeleteMessageAction">
2         <property name="guzzContext" ref="guzzContext" />
3         <property name="slowUpdateService" ref="guzzSlowUpdateService" />
4 </bean>

修改 messageList.jsp,增長delete連接到 ./deleteMessage.do?userId=${m_msg.userId}&msgId=${m_msg.id} 便可刪除消息。在消息刪除時,咱們同時用slowUpdateService減小用戶的留言數。

批量刪除

咱們在 messageList.jsp 中增長一個Form表單,提交表單時提交全部選擇的留言編號,服務器端根據編號批量刪除。messageList.jsp:

 1 <%@ page language="java" pageEncoding="UTF-8"%>
 2 <%@include file="/WEB-INF/jsp/include/tags.jsp"%>
 3 
 4 <g:get business="user" var="m_user" limit="id=${param.userId}" />
 5 
 6 <g:boundary>
 7         <g:addLimit limit="userId=${m_user.id}" />
 8         <g:page business="message" var="m_messages" tableCondition="${m_user.id}" pageNo="${param.pageNo}" pageSize="30" orderBy="id desc" scope="request" />
 9 </g:boundary>
10 
11 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
12 <html>
13   <head>    
14     <title>${m_user.userName}'s Message List</title>
15   </head>
16   
17   <body>          
18         Leave a message:<br>
19         
20         <form method="POST" action="./newMessage.do">
21                 <input type="hidden" name="userId" value="${m_user.id}" />
22                 
23                 <textarea name="content" cols="80" rows="10"></textarea>
24                 
25                 <br/>
26                 <input type="submit" />
27         </form>
28         
29         <hr>
30         <form method="POST" action="./deleteMessage.do">
31                 <input type="hidden" name="userId" value="${m_user.id}" />
32                 
33                 <table width="96%" border="1">
34                         <tr>
35                                 <th>No.</th>
36                                 <th>Vote</th>
37                                 <th>Content</th>
38                                 <th>Date</th>
39                                 <th>OP</th>
40                         </tr>
41                         
42                         <c:forEach items="${m_messages.elements}" var="m_msg">
43                         <tr>
44                                 <td><input type="checkbox" name="ids" value="${m_msg.id}" />${m_messages.index}</td>
45                                 <td>
46                                         voteYes: <a href="./voteMessage.do?type=yes&userId=${m_msg.userId}&msgId=${m_msg.id}">${m_msg.voteYes}</a><br>
47                                         voteNo: <a href="./voteMessage.do?type=no&userId=${m_msg.userId}&msgId=${m_msg.id}">${m_msg.voteNo}</a><br>
48                                         voteScore: ${m_msg.voteScore}
49                                 </td>
50                                 <td>vote<g:out value="${m_msg.content}" escapeXml="false" escapeScriptCode="true" /></td>
51                                 <td>${m_msg.createdTime}</td>
52                                 <td><a href="./deleteMessage.do?userId=${m_msg.userId}&msgId=${m_msg.id}">Delete</a></td>
53                         </tr>
54                         </c:forEach>
55                 </table>                
56                 <table width="96%" border="1">
57                         <tr>
58                                 <c:import url="/WEB-INF/jsp/include/console_flip.jsp" />
59                         </tr>
60                 </table>
61                 
62                 <table width="96%" border="1">
63                         <tr>
64                                 <td><input type="submit" value="Delete All Selected Messages" /></td>
65                         </tr>
66                 </table>        
67         </form>
68         
69   </body>
70 </html>

批量刪除咱們依然提交到 deleteMessage.do 中,只是採用POST方法提交。修改 DeleteMessageAction.java 以支持批量操做:

  1 package example.view.action;
  2 
  3 import java.util.List;
  4 
  5 import javax.servlet.http.HttpServletRequest;
  6 import javax.servlet.http.HttpServletResponse;
  7 
  8 import org.guzz.Guzz;
  9 import org.guzz.GuzzContext;
 10 import org.guzz.jdbc.ObjectBatcher;
 11 import org.guzz.orm.se.SearchExpression;
 12 import org.guzz.orm.se.Terms;
 13 import org.guzz.service.core.SlowUpdateService;
 14 import org.guzz.transaction.ReadonlyTranSession;
 15 import org.guzz.transaction.WriteTranSession;
 16 import org.guzz.util.RequestUtil;
 17 import org.springframework.web.servlet.ModelAndView;
 18 import org.springframework.web.servlet.mvc.Controller;
 19 
 20 import example.business.Message;
 21 import example.business.User;
 22 
 23 public class DeleteMessageAction implements Controller {
 24         
 25         private GuzzContext guzzContext ;
 26         
 27         private SlowUpdateService slowUpdateService ;
 28 
 29         public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
 30                 int userId = RequestUtil.getParameterAsInt(request, "userId", 0) ;
 31                 
 32                 if("POST".equals(request.getMethod())){//Batch delete
 33                         int[] ids = RequestUtil.getParameterAsIntArray(request, "ids", 0) ;
 34                         
 35                         if(ids.length = 0){
 36                                 return new ModelAndView("redirect:/messageList.jsp", "userId", userId);
 37                         }
 38 
 39                         List<Message> msgs = null ;
 40                         
 41                         //load the Messages to delete.
 42                         SearchExpression se = SearchExpression.forLoadAll(Message.class) ;
 43                         se.setTableCondition(userId) ;
 44                         se.and(Terms.in("id", ids)) ;
 45                         
 46                         //read from slave db.
 47                         ReadonlyTranSession read = guzzContext.getTransactionManager().openDelayReadTran() ;
 48                         try{
 49                                 msgs = read.list(se) ;
 50                         }finally{
 51                                 read.close() ;
 52                         }
 53                         
 54                         //Open write connections to the master db.
 55                         WriteTranSession write = guzzContext.getTransactionManager().openRWTran(false) ;
 56                         try{
 57                                 //Perform Batch operation.
 58                                 ObjectBatcher batcher = write.createObjectBatcher() ;
 59                                 batcher.setTableCondition(userId) ;
 60                         
 61                                 for(Message msg : msgs){
 62                                         batcher.delete(msg) ;                                   
 63                                 }
 64                                 
 65                                 batcher.executeUpdate() ;
 66                                 
 67                                 write.commit() ;
 68                         }catch(Exception e){
 69                                 write.rollback() ;
 70                                 
 71                                 throw e ;
 72                         }finally{
 73                                 write.close() ;
 74                         }
 75                         
 76                         //dec the message count
 77                         this.slowUpdateService.updateCount(User.class, null, "messageCount", userId, -msgs.size()) ;
 78                 }else{
 79                         //Delete one message
 80                         int msgId = RequestUtil.getParameterAsInt(request, "msgId", 0) ;
 81                         
 82                         //auto-commit
 83                         WriteTranSession write = guzzContext.getTransactionManager().openRWTran(true) ;
 84                         
 85                         try{
 86                                 Guzz.setTableCondition(userId) ;
 87                                 Message msg = (Message) write.findObjectByPK(Message.class, msgId) ;
 88                                 
 89                                 if(msg != null){
 90                                         write.delete(msg) ;
 91                                         
 92                                         //dec the message count
 93                                         this.slowUpdateService.updateCount(User.class, null, "messageCount", userId, -1) ;
 94                                 }
 95                         }finally{
 96                                 write.close() ;
 97                         }
 98                 }
 99                 
100                 return new ModelAndView("redirect:/messageList.jsp", "userId", userId);
101         }
102 
103         public GuzzContext getGuzzContext() {
104                 return guzzContext;
105         }
106 
107         public void setGuzzContext(GuzzContext guzzContext) {
108                 this.guzzContext = guzzContext;
109         }
110 
111         public SlowUpdateService getSlowUpdateService() {
112                 return slowUpdateService;
113         }
114 
115         public void setSlowUpdateService(SlowUpdateService slowUpdateService) {
116                 this.slowUpdateService = slowUpdateService;
117         }
118 
119 }

對於POST提交的批量操做,咱們獲取全部提交的消息編號數組,經過 SearchExpression 查詢符合的留言,建立批操做一次刪除。guzz提供了兩種批操做API,一種爲基於對象化操做的 ObjectBatcher ,一種爲基於直接SQL語句的SQLBatcher。本例子使用的是 ObjectBatcher 。

相關文章
相關標籤/搜索