在本教程中,咱們將手把手的演示如何使用guzz,編寫一個能夠並行使用5臺甚至更多數據庫的留言板系統。html
您能夠一邊看一邊跟着作。在實際的教程作,只須要1臺數據庫用來模擬。java
本教程將教會你:如何建立guzz項目,如何用guzz添加數據庫記錄,如何實現讀寫分離,如何進行多數據庫之間分表、切表、分佈式切表等,並對「服務」有個概念。mysql
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臺數據庫,對開發者基本透明。
咱們基於 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 ,這個爲咱們的主配置文件。
咱們定義留言爲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&characterEncoding=UTF-8&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 ,能夠看到分頁顯示的留言列表了:
若是你有多臺數據庫,建立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&characterEncoding=UTF-8&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&characterEncoding=UTF-8&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&characterEncoding=UTF-8&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鏈接池操做。
爲了實現多用戶留言板,首先咱們須要增長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。
多用戶留言板完成。
隨着業務的增加咱們發現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&characterEncoding=UTF-8&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&characterEncoding=UTF-8&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庫中。完成分佈式切表。
隨着留言板應用的深刻,用戶但願可以對留言進行投票,像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>
從新並重啓應用,如今投票功能就可使用了。
雖然投票功能有了,但剛上線就發現了另一個問題:投票的量太多。每次投票都須要進行一次數據庫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&characterEncoding=UTF-8&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表,能夠看到計數操做已經成功寫入到了隊列中。
爲了將計數隊列的數據寫回各個表對應的主庫,還須要啓動計數隊列服務的服務器端。修改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 。