sys8论坛:对天乙社区bbscs8实现的详细分析一 2024-04-27 01:24:37 0 0 由于BBSCS8是由数据库设计-bean/hbm.xml-DAO-Service-Web(作者laoer回答)这样的创建过程,因此分析这个系统最好是先查看数据库设计(见http://bbs.laoer.com/main-read-15-ff80808113baa8140113d201333e5274.html下载研究),而我的分析是由Service层开始引出讨论的,所以你需对论坛的常用功能有所体会,知道什么是投票贴,怎么样去用,还要有论坛后台管理使用过等等.如果不知道的话,请先在www.laoer.com处或在自己电脑上本地测试以便先对其功能进行体会,请注意!!! com.laoer.bbscs.service层下有众多的接口: AgreeAgainstService,它有三个方法: AgreeAgainst saveAgreeAgainst(AgreeAgainst agreeAgainst) throws BbscsException; AgreeAgainst findAgreeAgainstByUidPidBid(String userID,String postID,long bid); void removeOutTime(long time)throws BbscsException; AgreeAgainstImp为其实现: 首先定义了一个logger,设置好agreeAgainstDAO;getAgreeAgainstDAO及setAgreeAgainstDAO; public AgreeAgainst saveAgreeAgainst(AgreeAgainst agreeAgainst) throws BbscsException{ try{ return this.getAgreeAgainstDAO().saveAgreeAgainst(agreeAgainst); (注:此处为合写,也可写成: agreeAgainst=this.getAgreeAgainstDAO().saveAgreeAgainst(agreeAgainst); return agreeAgainst;我这里啰嗦了一下,以后源代码中会用到,别问我为什么~~~) }catch(Exception ex) { logger.error(ex); throw new BbscsException(ex); } } 注意的是getAgreeAgainstDAO返回的是AgreeAgainstDAO来自于com.laoer.bbscs.dao包中的interface; 它有三个接口方法: public AgreeAgainst saveAgreeAgainst(AgreeAgainst agreeAgainst); public AgreeAgainst findAgreeAgainstByUidPidBid(String userID,String postID,long bid); public void removeOutTime(long time); 而实现上注入到service方法中的是其实现类: com.laoer.bbscs.dao.hibernate.AgreeAgainstHibernateDAO,其注入了: <property name="sessionFactory"> <ref local="sessionFactory" /> </property> 一个sessinFacotry唯一,用于获得可操作的session,让我们看看其实现: 它将两个HQL语句都重构成private static final String 了.一个是LOAD_BY_UID_PID_BID,一个是ROMOVE_OUTTIME,注意其extends HibernateDAOSupport抽象类(得到了org.springframework.orm.hibernate3.support.HibernateDAOSupport的支持哦~)这样我们就可以不用get和set这个sessionFacotry了,另外可能使用this.getHibernateTemplate()来进行实际操作了. saveorupdate(agreeAgainst);对于查找: 先构造一个Object[] o={postID,userID,new Long(bid)}; List list=this.getHibernateTemplate().find(LOAD_BY_UID_PID_BID,o); if(l==null||l.isEmpty()){ return null; } else{ return (AgreeAgaist) l.get(0); } } 最后removeOutTime(final long time) { getHibernateTemplate().execute(new HibernateCallback(){ public Object doInHibernate(Session session) throws HibernateException, SQLException{ Query query=s.createQuery(REMOVE_OUTTIME); query.setLong(0,time); query.executeUpdate(); return null; } }); }; } 我们看看hbm.xml文件和bean文件,先是bean(AgreeAgainst.java) 它有如下属性: private String id; private String userID; private String postID; private long boardID; private int voteType; private long createTime; 及其set/get方法的一个构造. 看下AgreeAgainst.hbm.xml文件: <hibernate-mapping package="com.laoer.bbscs.bean"> <class name="AgreeAgainst" table="bbscs_agreeagainst"> <id name="id" column="ID" type="string" unsaved-value="null"> <generator class="uuid"/> </id> <property column="UserID" length="40" name="userID" not-null="true" type="string"/> <property column="PostID" length="60" name="postID" not-null="true" type="string"/> <property column="BoardID" length="13" name="boardID" not-null="true" type="long"/> <property column="VoteType" length="1" name="voteType" type="int"/> <property column="CreateTime" name="createTime" not-null="true" type="long"/> </class> </hibernate-mapping> id为主键uuid算法,还要其长度的定义等等!其它不是...... 看下数据库表!!! Null Default ID varchar(40) NO UserID varchar(40) No PostID varchar(40)No BoardID bigint(20)No 0 VoteType tinyint(1)Yes 0 CreateTime bigint(20)No 0 我们来看下实现: 在帖子的支持和反对处选择! Hibernate: insert into bbscs_agreeagainst (UserID, PostID, BoardID, VoteType, CreateTime, ID) values (?, ?, ?, ?, ?, ?) Hibernate: select agreeagain0_.ID as ID24_, agreeagain0_.UserID as UserID24_, agreeagain0_.PostID as PostID24_, agreeagain0_.BoardID as BoardID24_, agreeagain0_.VoteType as VoteType24_, agreeagain0_.CreateTime as CreateTime24_ from bbscs_agreeagainst agreeagain0_ where agreeagain0_.PostID=? and agreeagain0_.UserID=? and agreeagain0_.BoardID=? 数据库中的数据: ID:402881 e513bd c85501 13be01 e70d00 1f(32位) UserID:4028818208ed006b0108ed020bd50001 PostID:402881e513bdc8550113bdefb43c0014 BoardID:2(第2个建的) VoteType:1(反对)0(支持) CreateTime:1184303802125 对比一下,就OK了 BoardAuthUserService(版块授权的用户) 有public BoardAuthUser saveBoardAuthUser(BoardAuthUser boardAuthUser) throws BbscsException; punlic BoardAuthUser findBoardAuthUserById(String id); public BoardAuthUser findBoardAuthUserByBidUid(long bid,String uid); public BoardAuthUser findBoardAuthUserByBidUserName(long bid,String userName); public List findBoardAuthUserByBid(long bid); public void removeBoardAuthUser(BoardAuthUser boardAuthUser); public void removeBoardAuthuserByBidUid(long bid,String uid) throws BbscsException; public void removeBoardAuthUserByBidUserName(long bid,String userName) throws BbscsExeption; 同样,imp里BoardAuthUserServiceImp中,注入DAO 由DAO来完成对应方法的实际工作.注意:有Exception方法的写法,例如: public void removeBoardAuthUserByBidUserName(long bid, String userName) throws BbscsException { try { this.getBoardAuthUserDAO().removeBoardAuthUserByBidUserName(bid, userName); } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } 来到DAO接口层,没有一个Exception方法哦!!!同样实现它的HibernateDAO也没有Ecxeption,可见,Serivce层有Exception.而DAO层没有Exception(一般情况下),除了没有Exception外,接口层与Serivce接口层好象.说说Exception:这里用到的只不过是一个BbscsException而已,在专门的com.laoer.bbscs.exception包中有这个类:原来它也继承了Exception而已,有三个重载的方法: public BbscsException(String message) { super(message); } public BbscsException(String message, Throwable cause) { super(message, cause); } public BbscsException(Throwable cause) { super(cause); } 自定义异常类的主要作用是区分异常发生的位置,当用户遇到异常时,根据异常名就可以知道哪里有异常,根据异常提示信息进行修改。 看其hibernate实现: 整个源码重构过似的,上面为HQL语句字串常量定义: private static final String LOAD_BY_BID_UID = "from BoardAuthUser where boardID = ? and userID = ?"; private static final String LOADS_BY_BID = "from BoardAuthUser where boardID = ? order by createTime asc"; 看看方法吧: public BoardAuthUser findBoardAuthUserById(String id) { return (BoardAuthUser)this.getHibernateTemplate().get(BoardAuthUser.class, id); } 这个ID明显是对象标识! public BoardAuthUser findBoardAuthUserByBidUserName(long bid, String userName) { Object[] o = {new Long(bid), userName}; List l = this.getHibernateTemplate().find(LOAD_BY_BID_USERNAME, o); if (l == null || l.isEmpty()) { return null; } else { return (BoardAuthUser) l.get(0); } } public List findBoardAuthUsersByBid(long bid) { return this.getHibernateTemplate().find(LOADS_BY_BID, new Long(bid)); } 需要注意的是Object数组中的元素必为对象;我们来看看是怎么删除操作的: public void removeBoardAuthUser(BoardAuthUser boardAuthUser) { this.getHibernateTemplate().delete(boardAuthUser); } 而不是一个BoradAuthUser对象,是通过以下代码实现: public void removeBoardAuthUserByBidUid(final long bid, final String uid) { getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException, SQLException { Query query = s.createQuery(REMOVE_BY_BID_UID); query.setLong(1, bid); query.setString(0, uid); query.executeUpdate(); return null; } }); } BoardPermissionService是版区(版块)的权限对应服务;其BEAN BoardPermission如下: private String id; private long boardID; private int groupID; private List permissions = new ArrayList(); //特殊的哦~~~ 再看下其hbm.xml文件: <hibernate-mapping package="com.laoer.bbscs.bean"> <class name="BoardPermission" table="bbscs_boardpermission"> <id name="id" column="ID" type="string" unsaved-value="null"> <generator class="uuid"/> </id> <property column="BoardID" length="20" name="boardID" not-null="true" type="long"/> <property column="GroupID" length="11" name="groupID" not-null="true" type="int"/> <property column="Permissions" name="permissions" type="com.laoer.bbscs.ext.hibernate.SplitList"/> </class> </hibernate-mapping> 注意到:permissions对应于表的Permssions,而其type为com.laoer.bbscs.ext.hibernate.SplitList; 让我们看看SplitList这个类:(它在ext.hibernate包中): [转自网络: 使用Hibernate自定义UserType遇到的一个问题 Hibernate自定义UserType可以使设计更为优雅,逻辑更为清晰。第一次使用遇到了问题。Rule表中有一个字段methods,类型为VARCHAR(30),not null, 允许有多个method,中间用逗号分开,之所以这么设计是不想为此增加一个关联表。为methods实现了一个自定义UserType叫MethodsList,该类对用户隐藏了实现细节,使用户不用处理method的连接和拆分,而使用List来操作就行了,非常直观。在保存Rule实体的时候,Hibernate报methods字段不允许为空,说明methods在持久化的时候还是null,DEBUG发现在调用session.saveOrUpdate()方法的时候methods不为空,但在调用MethodsList的nullSafeSet(PreparedStatement st, Object value, int index)时显示value的值为null,Google了很久仍然没有找到原因 后来发现只要把methods字段的not null属性设为false(即允许为空)问题就不复存在了,很是奇怪... 据此猜测,Hibernate在应用自定义UserType(即MethodsList)之前进行字段是否为空之类的检查,而这时methods字段还是null,所以出现以上错误,把该字段改为允许为空之后自然就没有问题了。个人愚见,仅供参考,有空研究一下Hibernate源码一探究 robbin: 1、UserType不是用来做主键的(虽然也可以,但是那样和复合主键没有区别了,并且复合主键是非常不推荐的做法) 2、UserType比Component更加灵活,适用性更强,封装的更透明。 ] 其中有以下方法:assembledeepCopy(重要)disassmbleequals(重要)hashCodeisMutable ullSafeGet eplace eturnedClasssqlTypes等方法需要实现(不一定)! rs--->Object public Object nullSafeGet(ResultSet resultSet, String[] stringArray, Object object) throws HibernateException, SQLException { String value = (String) Hibernate.STRING.nullSafeGet(resultSet, stringArray[0]); if (value != null) { return parse(value); } else { return new ArrayList(); } } 用了parse方法: private List parse(String value) { String[] strs = StringUtils.split(value, SPLITTER); List set = new ArrayList(); for (int i = 0; i < strs.length; i++) { if (!StringUtils.isBlank(strs[i])) { set.add(Long.valueOf(strs[i])); // System.out.println(strs[i]); // set.add(new Long(Long.parseLong(strs[i]))); } } return set; } object--->rs public void nullSafeSet(PreparedStatement preparedStatement, Object object, int _int) throws HibernateException, SQLException { if (object != null) { String str = assemble((List) object); Hibernate.STRING.nullSafeSet(preparedStatement, str, _int); } else { Hibernate.STRING.nullSafeSet(preparedStatement, "", _int); } } 用了assemble方法: private String assemble(List set) { StringBuffer sb = new StringBuffer(); Iterator it = set.iterator(); while (it.hasNext()) { sb.append(it.next()); sb.append(SPLITTER); } String fs = sb.toString(); if (fs != null && fs.length() > 0 && fs.endsWith(SPLITTER)) { fs = fs.substring(0, fs.length() - 1); } return fs; } 附参考资料:http://blog.csdn.net/ckangtai/archive/2007/05/23/1622396.aspx 好,看完这个后,我们进入到服务内容: public BoardPermission saveBoardPermission(BoardPermission bp) throws BbscsException; public BoardPermission updateBoardPermission(BoardPermission bp) throws BbscsException; public BoardPermission findBoardPermissionByID(String id); public BoardPermission findBoardPermissionByBidGid(long bid, int gid); public List findBoardPermissionsByBid(long bid); public List findBoardPermissionsByGid(int gid); public void removeBoardPermissionsByBid(long bid) throws BbscsException; public void removeBoardPermissionsByGid(int gid) throws BbscsException; 实际是由注入的boardPermissionDAO其实现类完成的. 注意这里引入了 private Cache userPermissionCache;这个缓存类! 我们从applicationContext.xml看看是什么东东: <bean id="userPermissionCache" class="com.laoer.bbscs.service.imp.OsCacheImp"> <constructor-arg> <value>${cacheup.config}</value> </constructor-arg> </bean> 哦,原来是另外一个服务,${cacheup.config}指的是cacheup.config=oscache_up.properties; 在classes下有许多配置文件,是用不同的配置文件是为了方便集群,以区分是不同的缓存。 我们从com.laoer.bbscs.serivce.Cache接口看起,它提供了如下方法: public void add(Object key,Object value); public Object get(Object key); public void remove(Object key); public void removeAll(); 再看imp: 由于spring中的bean带construtctor-arg: 将调用构造方法: public OsCacheImp(String profile) { Properties properties = new Properties(); ClassPathResource classPathResource = new ClassPathResource(profile); //这个类标识从classpath获得的资源 try { logger.info("Init Cache..."); properties.load(classPathResource.getInputStream());//使用Properties的load(InputStream inStream) 来读取配置文件的时候 admin = new GeneralCacheAdministrator(properties); } catch (Exception ex) { logger.error(ex); admin = new GeneralCacheAdministrator(); } } 注意,这个admin对象来自OSCache包,用于管理Cache内容吧.另外,我们看看其对Cache的实现: public void add(Object key, Object value) { logger.debug("Add into cache [Key:" + key + "]"); this.admin.putInCache(String.valueOf(key), value); } public Object get(Object key) { try { logger.debug("Get from cache [Key:" + key + "]"); return this.admin.getFromCache(String.valueOf(key)); } catch (NeedsRefreshException ex) { logger.debug("Object not in cache, return null"); this.admin.cancelUpdate(String.valueOf(key)); return null; } } public void remove(Object key) { logger.debug("Remove from cache [Key:" + key + "]"); this.admin.flushEntry(key.toString()); } public void removeAll() { logger.debug("Remove all"); this.admin.flushAll(); } 这样就可以对缓存内容进行控制操作了(就用这几个方法)!让我们回到BoardPermissionSerivceImp类中: 注入了DAO和Cache(userPermissionCache)后,进行方法实现时,主要还是DAO去做,不过在saveBoardPermission和updateBoardPermission,还有各种remove方法中还用了this.clearPermissionCache();(除了find),让我们来看看它的代码吧: private void clearPermissionCache() { if (Constant.USE_PERMISSION_CACHE) { //public static final boolean USE_PERMISSION_CACHE = true; this.getUserPermissionCache().removeAll();//调用Cache中的方法哦!removeALL看上面this.admin.flushAll();! } } 555555,这个服务类也了Cache服务(与别的服务结合了),下面是DAO实现: private static final String LOAD_BY_BID_GID ="from BoardPermission where boardID = ? and groupID = ?"; private static final String LOADS_BY_BID = "from BoardPermission where boardID = ?"; private static final String LOADS_BY_GID = "from BoardPermission where groupID = ?"; private static final String REMOVE_BY_BID = "delete from BoardPermission where boardID = ?"; private static final String REMOVE_BY_GID = "delete from BoardPermission where groupID = ?"; 我们看update: public BoardPermission updateBoardPermission(BoardPermission bp) { //System.out.println("update bp"); this.getHibernateTemplate().update(bp); return bp; } 再看: /** * 根据GroupID删除BoardPermission对象 * * @param gid int * @todo Implement this com.laoer.bbscs.dao.BoardPermissionDAO method */ public void removeBoardPermissionsByGid(final int gid) { getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException, SQLException { Query query = s.createQuery(REMOVE_BY_GID); query.setLong(0, gid); query.executeUpdate(); return null; } }); } 接下来,是BoardSaveService: 先看bean吧,它实现了implements Serializable...,加入private static final long serialVersionUID = 5390014211916049604L;它有三个属性: private String id; private String userID; private long boardID; [ Java的JavaBeans. Bean的状态信息通常是在设计时配置的。Bean的状态信息必须被存起来,以便当程序运行时能恢复这些状态信息。这也需要serializaiton机制。 总之如果在网络的环境下做类传输,应该还是implements Serializable。 ] 再看其hbm.xml: <hibernate-mapping package="com.laoer.bbscs.bean"> <class name="BoardSave" table="bbscs_boardsave"> <id name="id" column="ID" type="string" unsaved-value="null"> <generator class="uuid"/> </id> <property column="UserID" length="40" name="userID" not-null="true" type="string"/> <property column="BoardID" length="13" name="boardID" not-null="true" type="long"/> </class> </hibernate-mapping> 从这样便知是用于收藏版区的对象模型啊!那接下来看其接口中的方法: public BoardSave saveBoardSave(BoardSave boardSave) throws BbscsException; public BoardSave findBoardSaveById(String id); public BoardSave findBoardSaveByUidBid(String userId, long bid); public List findBoardSavesByUid(String userId); public List findBoardSaveBidsByUid(String userId);//找Bid的List public void removeBoardSave(BoardSave boardSave) throws BbscsException; public void removeBoardSaveByUidBid(String userId, long bid) throws BbscsException; public void removeBoardSaveByBid(long bid) throws BbscsException; public void removeBoardSaveByBidsUid(String userId, List ids) throws BbscsException; 对于imp调用dao-->daoimp我们直接进入dao的imp中: 其中的常量定义如下: private static final String LOAD_BY_UID_BID = "from BoardSave where userID = ? and boardID = ?"; private static final String LOADS_BY_USERID = "from BoardSave where userID = ?"; private static final String LOADS_BOARDID_BY_USERID = "select boardID from BoardSave where userID = ?";//很特别哦!(其实也没什么,相对而已) private static final String REMOVE_BY_UID_BID = "delete from BoardSave where userID = ? and boardID = ?"; private static final String REMOVE_BY_BID = "delete from BoardSave where boardID = ?"; private static final String REMOVE_IN_IDS_BY_UID = "delete from BoardSave where userID = :userID and boardID in (:ids)"; 我们来看方法实现吧. public BoardSave saveBoardSave(BoardSave boardSave) { this.getHibernateTemplate().saveOrUpdate(boardSave); return boardSave; } 可用于保存,也可以用于更新! public List findBoardSaveBidsByUid(String userId) { return this.getHibernateTemplate().find(LOADS_BOARDID_BY_USERID, userId); //一个参数 public void removeBoardSaveByBidsUid(final String userId, final List ids) { getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException, SQLException { Query query = s.createQuery(REMOVE_IN_IDS_BY_UID); query.setString("userID", userId); query.setParameterList("ids", ids); query.executeUpdate(); return null; } }); } OK! 接下来,是BoardService,先看其相关的Bean!它也实现了可序列化操作...它的属性特别多.. 很关键哦! private Long id;//主键 private long parentID;//父级版区ID private List parentIDs;//所有父级版区ID列表 private List childIDs; private String boardName;//版区名称 private String explains;(说明) private String bulletin;(公告) private String boardPic;//图片 private int useStat;(使用状态) private int orders;//序 private int needPasswd;//是否需要密码访问 private String passwd;//访问密码 private int level;---->数据库中的BoardLevel//级别 private int boardType;(类型1,2,3)//版区类型 private int allowHTML;//HTML是否支持 private int allowUBB;//UBB... private int auditPost; (帖子是否需要审核) private int auditAttach;//附件是否需要审核 private int addUserPostNum;//是否增加用户发贴数 private int isHidden; private int isAuth;//是否需要论证用户才能访问 private long mainPostNum;//主贴数 private long postNum;//帖子数量 private Map boardMaster=new HashMap(); private Set boardTag=new HashSet(); 我们看看Board.hbm.xml,错了,我们应该看看applicationCotext.xml中的内容是不是这个,经查看: <value>com/laoer/bbscs/bean/Board-${datasource.type}.hbm.xml</value>,我用mysql当然是Board-mysql.hbm.xml,其内容主要如下: <hibernate-mapping package="com.laoer.bbscs.bean"> <class name="Board" table="bbscs_board"> <id name="id" column="ID" type="long" unsaved-value="null"> <generator class="identity"/> //long类型的identity! </id> <property column="ParentID" length="20" name="parentID" not-null="true" type="long"/> <property column="ParentIDs" name="parentIDs" type="com.laoer.bbscs.ext.hibernate.SplitList"/>//parentIDs有用了userType <property column="ChildIDs" name="childIDs" type="com.laoer.bbscs.ext.hibernate.SplitList"/> <property column="BoardName" length="60" name="boardName" not-null="true" type="string"/> <property column="Explains" name="explains" type="text"/>//text类型 <property column="Bulletin" name="bulletin" type="text"/> <property column="BoardPic" length="200" name="boardPic" type="string"/> <property column="UseStat" length="1" name="useStat" type="int"/>//一个开关的功能 <property column="Orders" length="11" name="orders" type="int"/>//论坛排序 <property column="NeedPasswd" length="1" name="needPasswd" type="int"/>//开关 <property column="Passwd" length="100" name="passwd" type="string"/> <property column="BoardLevel" length="11" name="level" type="int"/> <property column="BoardType" length="2" name="boardType" type="int"/> <property column="AllowHTML" length="1" name="allowHTML" type="int"/> <property column="AllowUBB" length="1" name="allowUBB" type="int"/> <property column="AuditPost" length="1" name="auditPost" type="int"/> <property column="AuditAttach" length="1" name="auditAttach" type="int"/>//不懂??? <property column="AddUserPostNum" length="1" name="addUserPostNum" type="int"/>//应该是一个开关吧 <property column="IsHidden" length="1" name="isHidden" type="int"/> <property column="IsAuth" length="1" name="isAuth" type="int"/> <property column="MainPostNum" length="11" name="mainPostNum" type="long"/> <property column="PostNum" length="11" name="postNum" type="long"/> //关键点,boardMaster和boardTag均为one-to-many类型! <map name="boardMaster" inverse="true" cascade="all-delete-orphan" lazy="false"> <key column="boardID"/> <map-key column="UserName" type="string"/> <one-to-many class="BoardMaster"/> </map> <set name="boardTag" inverse="true" cascade="all-delete-orphan" lazy="false" order-by="orders asc"> <key column="boardID"/> <one-to-many class="BoardTag"/> </set> </class> </hibernate-mapping> 看完这些后,我们就知道这个bean是用于版区操作了,让我们看看方法:(先看接口BoardService) public Board saveBoard(Board board) throws BbscsException;//保存或更新Board对象 public Board createBoard(Board board) throws BbscsException; public Board updateBoard(Board board, long oldParentID) throws BbscsException; public Board getBoardByID(long id); public List findBoardsByParentID(long pid, int useStat, int hidden, int orderType); public List findBoardsAllTree(long pid, List topList, int useStat, int hidden, int orderType); public int getNextOrder(long pid); public int getPostSumNum(int mainorall, int useStat, int hidden); public void removeBoard(Board board) throws BbscsException; public Map[] getBoardPermission(long bid, int groupID); public Map[] getBoardMasterPermission(int roleID); public boolean isBoardMaster(Board board, String userName); public List findBoardsInIDs(List ids); public void removeBoardTag(Board board, String tagID) throws BbscsException; public List getBoardIDs(List boards); public void saveBoardsPostNumCount() throws BbscsException; 这里方法有16个左右,注意我们进行除了查询后的其它CURD操作都可能会产生异常.对于具体方法我们需要去了解其实现才能理清其作用.看下服务实现层:(注BoardServiceCacheImp为其实现类,可能是加入了许多缓存的原因吧): 首先仍是logger(这里发现了原来其实在service层和dao层只有这里需要logger,其它地方都没,对于异常也只有service接口和实现层有BbscsException这个异常处理,而对于DAO层的异常则已经由HibernateTemplate完全抛出了,如: public void saveOrUpdate(final Object entity) throws DataAccessException { execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { checkWriteOperationAllowed(session); session.saveOrUpdate(entity); return null; } } , true); } ) 我们回到正题上,除了logger,还有boardDAO(当然要了) ,userGroupDAO,boardPermissionDAO(讲过的),permissionDAO,roleDAO,forumDAO,forumHistoryDAO,还有一些Cache服务:sysListObjCache,boardCache,userPermissionCache(以前用过),还有一个额外的服务类:sysStatService;对他们进行getter/setter方法一下... 我们分析下sysStatService:(applicationContext.xml) 系统统计服务 <bean id="sysStatService" class="com.laoer.bbscs.service.imp.SysStatServiceImp" scope="prototype"> //每次请求都产生一个对象 <property name="userConfig"> <ref bean="userConfig" /> </property> </bean> userConfig指: <bean id="userConfig" class="com.laoer.bbscs.service.config.UserConfig"> <property name="safePath"> <value>${bbscs.safePath}</value> //安全目录我的指的是D:/safe/ </property> </bean> UserConfig是一个config配置服务,在服务类中首先注入了safePath 这个String对象,它向外提供了String getUserFilePath(String userID)[简单,最终得到一个用户文件Path,见实际目录就可知道其原理]和String getIndexPath()和File getIndexFilePath()以及boolean indexExist()四个对外公共方法.详细看方法实现: public String getIndexPath() { //好象没用到过 StringBuffer sb = new StringBuffer(); sb.append(this.getSafePath()); sb.append("index/"); return sb.toString(); } public File getIndexFilePath() { File indexFilePath = new File(this.getIndexPath());//构造一个File对象 if (!indexFilePath.exists()) { indexFilePath.mkdirs(); } return indexFilePath; } public boolean indexExist() { File file = new File(this.getIndexPath() + "segments"); return file.exists();//使用文件是否存在的方法作判断! } 可见config服务类较简单(没用到logger)!提供一个常用服务!而com.laoer.bbscs.service.imp.SysStatService是怎么用到它的呢?它继承了抽象类SysStatService,在这个抽象类中,有一个东西是私有的: private long onlineNum = 0; private long appearTime = 0; 发表时间long类型 private String appearTimeStr = ""; private long allUserNum = 0; private String lastRegUser = ""; private long postMainNum = 0; private long postNum = 0; (这个东西和safe文件夹下的sysstat.properties好像啊,看看先: #sysstat.properties #Mon Jul 16 11:35:16 CST 2007 //appearTimeStr ?? onlineNum=2 postMainNum=1 allUserNum=2 appearTime=963209825828 postNum=1 lastRegUser=sgwood )下面有其get/set方法,子类可以继承哦!当然,还有一个子类需实现的方法: public abstract void load(); public abstract void saveOnline(long nowonlinenum);//在线数 public abstract void saveAllUserNum(long allusernum, String lastreguser);//所有用户数和最后注册用户名 public abstract void savePostNum(long main, long all); //存入主题贴和总数 接着我们看下SysStatServiceImp.java文件好了:它加入了logger和UserConfig对象先,看下面的load方法: public void load() { Properties prop = new Properties(); File f = new File(this.getUserConfig().getSafePath() + "sysstat.properties"); //getSafePath()当然会暴露出来! if (f.exists()) { try { FileInputStream fis = new FileInputStream(f); prop.load(fis); /**只需传递这个文件的 InputStream 给 load() 方法,就会将每一个键-值对添加到 Properties 实例中。然后用 list() 列出所有属性或者用 getProperty() 获取单独的属性。 list() 方法的输出中键-值对的顺序与它们在输入文件中的顺序不一样。 Properties 类在一个散列表(hashtable,事实上是一个 Hashtable 子类)中储存一组键-值对,所以不能保证顺序。 */ this.setOnlineNum(Long.parseLong(prop.getProperty("onlineNum", "0").trim()));//继承过来的!!! this.setAppearTime(Long.parseLong(prop.getProperty("appearTime", "0").trim())); this.setAllUserNum(Long.parseLong(prop.getProperty("allUserNum", "0").trim())); this.setLastRegUser(prop.getProperty("lastRegUser", "")); this.setPostMainNum(Long.parseLong(prop.getProperty("postMainNum", "0").trim())); this.setPostNum(Long.parseLong(prop.getProperty("postNum", "0").trim())); this.setAppearTimeStr(Util.formatDateTime(new Date(this.getAppearTime())));//写时间用 fis.close(); } catch (NumberFormatException ex) { logger.error(ex); } catch (FileNotFoundException ex) { logger.error(ex); } catch (IOException ex) { logger.error(ex); } } else { save(); //文件不存在时!内部private方法哦 } } private void save() { String path = this.getUserConfig().getSafePath() + "sysstat.properties"; Properties prop = new Properties(); prop.setProperty("onlineNum", String.valueOf(this.getOnlineNum())); prop.setProperty("appearTime", String.valueOf(this.getAppearTime())); prop.setProperty("allUserNum", String.valueOf(this.getAllUserNum())); prop.setProperty("lastRegUser", this.getLastRegUser()); prop.setProperty("postNum", String.valueOf(this.getPostNum())); prop.setProperty("postMainNum", String.valueOf(this.getPostMainNum())); try { FileOutputStream fos = new FileOutputStream(path);//写入新文件中 prop.store(fos, "sysstat.properties"); fos.close(); } catch (FileNotFoundException ex) { logger.error(ex); } catch (IOException ex) { logger.error(ex); } } 我们看下对抽象类的实现吧:(先load一下资源文件,再set相关的key值,最后用私有的save一下) public void saveAllUserNum(long allusernum, String lastreguser) { this.load(); this.setAllUserNum(allusernum); this.setLastRegUser(lastreguser); this.save(); } public void saveOnline(long nowonlinenum) { this.load(); if (nowonlinenum > this.getOnlineNum()) { //好象不太对,不然只有多没有少! long atime = System.currentTimeMillis(); this.setOnlineNum(nowonlinenum); this.setAppearTime(atime); this.setAppearTimeStr(Util.formatDateTime(new Date(atime))); this.save(); } } public void savePostNum(long main, long all) { this.load(); this.setPostMainNum(main); this.setPostNum(all); this.save(); } OK!终于可以回到BoardServiceCachImp实现类中了,绕了好大一个弯!由于还有许多东西没用过,只能推测一下其用法了哦~~~还是先看几个,createBoard(Board board)先: @SuppressWarnings("unchecked") public Board createBoard(Board board) throws BbscsException { try { Board pboard = this.getBoardDAO().getBoardByID(board.getParentID()); // 取得父级版区 if (pboard != null) { // 父级版区存在 List pboards = new ArrayList(); pboards.addAll(pboard.getParentIDs());//父的祖先 pboards.add(pboard.getId());//父 /** addAll(Collection c) add(int index,Elelemt e) 注意parentIDs和childIDs均为List是由自定义userType的 public Class returnedClass() { return List.class; } 决定返回是List类型的! */ board.setParentIDs(pboards); // 设置父级版区列表字段 board.setLevel(pboard.getLevel() + 1); // 设置级别,在父级别上+1 } board = this.getBoardDAO().saveBoard(board);//这里的其它参数由添加时决定,不需要改变,或由其它因素相关!由DAO完成实质的工作 if (pboard != null) { List pcboards = this.getBoardDAO().findBoardsByParentID(board.getParentID(), 1, -1, Constant.FIND_BOARDS_BY_ORDER); /** 取得父级半区的所有子版区列表pcboard,1是useStat,-1是hidden,FIND_BOARD_BY_ORDER=0另外有, public static final int FIND_BOARDS_BY_MAINPOSTNUM = 1; public static final int FIND_BOARDS_BY_POSTNUM = 2; /* List cids = this.getBoardIDs(pcboards);//得到子版ID的List /** public List getBoardIDs(List boards) { List<Long> l = new ArrayList<Long>(); for (int i = 0; i < boards.size(); i++) { Board b = (Board) boards.get(i); l.add(b.getId()); } return l; } */ pboard.setChildIDs(cids); // 设置父级版区的所有子版区列表字段 this.getBoardDAO().saveBoard(pboard);//更新父信息 this.getBoardCache().remove(pboard.getId()); /**从Board Cache中清除, 我们看下BoardCache的bean定义: <bean id="boardCache" class="com.laoer.bbscs.service.imp.OsCacheImp"> 仍然是这个,以前讲过! <constructor-arg> <value>${cache.config}</value> cache.config=oscache.properties </constructor-arg> </bean> public void remove(Object key) { logger.debug("Remove from cache [Key:" + key + "]"); this.admin.flushEntry(key.toString()); } 从oscache.properties配置处的Cache内容中去掉key=pboard.getID()的对象,让OSCache自动缓存内容吧! */ } this.clearBoradListSysListCache(board.getParentID()); /**它其实是一私有方法哦!需要注意的是实质用了SysListOjbCache,它其中的对象标识竟是[][][][][],5555,可能有些对象后三者可不用: private void clearBoradListSysListCache(long pid) { String[] useStats = { "-1", "0", "1" }; String[] hiddens = { "-1", "0", "1" }; String[] orderTypes = { "0", "1", "2" }; for (int i = 0; i < useStats.length; i++) { for (int j = 0; j < hiddens.length; j++) { for (int x = 0; x < orderTypes.length; x++) { this.getSysListObjCache().remove( "[B][" + pid + "][" + useStats[i] + "][" + hiddens[j] + "][" + orderTypes[x] + "]"); } } } } */ // 为版区增加用户组版区权限 List gl = this.getUserGroupDAO().findUserGroupsAll(); /**取得用户组列表 public List findUserGroupsAll() { return this.getHibernateTemplate().find(LOADS_ALL); } private static final String LOADS_ALL = "from UserGroup order by id"; 原始数据为6个记录的表(见数据库),id1---6,以GroupName区分之,TypeID表示代表类型,默认是系统类型的,不能删除,用户自己建的就是另外一个类型了。 */ BoardPermission bp; for (int i = 0; i < gl.size(); i++) { UserGroup ug = (UserGroup) gl.get(i); bp = new BoardPermission(); bp.setBoardID(board.getId().longValue());//冗余字段 bp.setGroupID(ug.getId().intValue());//冗余字段 switch (ug.getId().intValue()) { case 1: bp.setPermissions(Constant.BOARD_PERMISSION_GROUP_LIST_1); /**<property column="Permissions" name="permissions" type="com.laoer.bbscs.ext.hibernate.SplitList"/> break; case 2: bp.setPermissions(Constant.BOARD_PERMISSION_GROUP_LIST_2); break; case 3: bp.setPermissions(Constant.BOARD_PERMISSION_GROUP_LIST_3); break; case 4: bp.setPermissions(Constant.BOARD_PERMISSION_GROUP_LIST_4); break; case 5: bp.setPermissions(Constant.BOARD_PERMISSION_GROUP_LIST_5); break; case 6: bp.setPermissions(Constant.BOARD_PERMISSION_GROUP_LIST_6); break; default: bp.setPermissions(Constant.BOARD_PERMISSION_GROUP_LIST_1); } this.getBoardPermissionDAO().saveBoardPermission(bp); /**而在Constant.java中有段static段: for (int i = 0; i < BOARD_PERMISSION_GROUP_1.length; i++) { BOARD_PERMISSION_GROUP_LIST_1.add(new Long(BOARD_PERMISSION_GROUP_1[i])); } for (int i = 0; i < BOARD_PERMISSION_GROUP_2.length; i++) { BOARD_PERMISSION_GROUP_LIST_2.add(new Long(BOARD_PERMISSION_GROUP_2[i])); } for (int i = 0; i < BOARD_PERMISSION_GROUP_3.length; i++) { BOARD_PERMISSION_GROUP_LIST_3.add(new Long(BOARD_PERMISSION_GROUP_3[i])); } for (int i = 0; i < BOARD_PERMISSION_GROUP_4.length; i++) { BOARD_PERMISSION_GROUP_LIST_4.add(new Long(BOARD_PERMISSION_GROUP_4[i])); } for (int i = 0; i < BOARD_PERMISSION_GROUP_5.length; i++) { BOARD_PERMISSION_GROUP_LIST_5.add(new Long(BOARD_PERMISSION_GROUP_5[i])); } */ } return board; } catch (Exception e) { logger.error(e); throw new BbscsException(e); } } 从创建中可以知道,它调用了两个cache(sysListObjCache和boardCache,对父级和本级的相关项进行更新(或产生),再保存.还有就是对此分版区(论坛)的用户权限的写入.总的来说,原则上,要数据服务(保存和查找)从DAO层来,要Cache从服务层来,要常用服务也从服务层来(因为它不参与DAO工作) 接下来,我们继续看findBoardAllTree: @SuppressWarnings("unchecked")//类型安全问题 public List findBoardsAllTree(long pid, List topList, int useStat, int hidden, int orderType) { List l = this.getBoardDAO().findBoardsByParentID(pid, useStat, hidden, orderType);//这个pid指的是当前论坛,与前面的pid不一样哦 for (int i = 0; i < l.size(); i++) { Board b = (Board) l.get(i); topList.add(b); this.findBoardsAllTree(b.getId().longValue(), topList, useStat, hidden, orderType);//由于topList是一个引用类型,可递归得到All子论坛及子论坛的子论坛... } return topList; } 而这个服务类的findBoardByParentID(非DAO)它首先是查询SysListObjCache里面有没有,若没有的话再由DAO从数据库中找,且放入到SysListObjCache中,用了add方法,需注意的是其命名有点怪哦! public List findBoardsByParentID(long pid, int useStat, int hidden, int orderType) { List l = (List) this.getSysListObjCache().get( "[B][" + pid + "][" + useStat + "][" + hidden + "][" + orderType + "]"); if (l == null) { // l = this.getBoardDAO().findBoardsByParentID(pid, useStat, hidden, // orderType); l = this.getBoardDAO().findBoardIdsByParentID(pid, useStat, hidden, orderType); this.getSysListObjCache().add("[B][" + pid + "][" + useStat + "][" + hidden + "][" + orderType + "]", l); } List<Board> bl = new ArrayList<Board>(); if (l != null && !l.isEmpty()) { for (int i = 0; i < l.size(); i++) { // Board b = this.getBoardByID(((Long)l.get(i)).longValue()); Board b = this.getBoardByID((Long) l.get(i)); if (b != null) { bl.add(b); } } } 由于Cache中不一定是Borad对象了,由于add进的是list,便由List<Board>类型的bl重新加载一次为Board的List.findBoardInIDs根据IDs一个取一个,边放入到l中! public List findBoardsInIDs(List ids) { List<Board> l = new ArrayList<Board>(); if (ids != null && !ids.isEmpty()) { for (int i = 0; i < ids.size(); i++) { // Board b = this.getBoardByID(((Long) ids.get(i)).longValue()); Board b = this.getBoardByID((Long) ids.get(i)); if (b != null) { l.add(b); } } } return l; } return bl; } 与其相反的方法是: public List getBoardIDs(List boards) { List<Long> l = new ArrayList<Long>(); for (int i = 0; i < boards.size(); i++) { Board b = (Board) boards.get(i); l.add(b.getId()); } return l; } 注意以上都有this.getBoardByID(),它是带Cache的哦!从BoardCache中取! public Board getBoardByID(long id) { Board board = (Board) this.getBoardCache().get(new Long(id)); if (board == null) { board = this.getBoardDAO().getBoardByID(id); if (board != null) { this.getBoardCache().add(board.getId(), board); } } return board; } 另外,对于Permission,有以下几个方法: public Map[] getBoardMasterPermission(int roleID) public Map[] getBoardPermission(long bid, int groupID) private Map[] getPermissionMaps(long bid, int groupID) private Map[] getPermissionMaps(int roleID) 前面2个是公开的方法,后2个是被调用的.. public Map[] getBoardMasterPermission(int roleID) { if (Constant.USE_PERMISSION_CACHE) { Map[] mapPermission = (Map[]) this.getUserPermissionCache().get("R_" + String.valueOf(roleID)); if (mapPermission == null) { mapPermission = this.getPermissionMaps(roleID); this.getUserPermissionCache().add("R_" + String.valueOf(roleID), mapPermission); } return mapPermission; } else { return this.getPermissionMaps(roleID); } } private Map[] getPermissionMaps(int roleID) { Map[] mapPermission = { new HashMap(), new HashMap() }; //MAP数组,555 Role role = this.getRoleDAO().findRoleByID(roleID);//得到id为roleID的角色对象 List permissions = role.getPermissions(); // 取得角色的权限ID列表(ID的List) if (permissions != null && !permissions.isEmpty()) { List permissionList = this.getPermissionDAO().findPermissionnIDs(permissions); // 取得权限列表Permission对象的List for (int i = 0; i < permissionList.size(); i++) { Permission permission = (Permission) permissionList.get(i); if (permission.getTypeID() == 2) { mapPermission[0].put(permission.getResource() + "," + permission.getAction(), permission); } if (permission.getTypeID() == 3) { mapPermission[1].put(permission.getId(), permission); } //这段需理解!!! } } return mapPermission; } 另外,除了版主外,还有版区权限: public Map[] getBoardPermission(long bid, int groupID) { if (Constant.USE_PERMISSION_CACHE) { Map[] mapPermission = (Map[]) this.getUserPermissionCache().get( "BG_" + String.valueOf(bid) + "_" + String.valueOf(groupID)); if (mapPermission == null) { mapPermission = this.getPermissionMaps(bid, groupID); this.getUserPermissionCache().add("BG_" + String.valueOf(bid) + "_" + String.valueOf(groupID), mapPermission); } return mapPermission; } else { return this.getPermissionMaps(bid, groupID); } } 同样,它用了类似的重载方法: private Map[] getPermissionMaps(long bid, int groupID) { Map[] boardPermission = { new HashMap(), new HashMap() }; BoardPermission bp = this.getBoardPermissionDAO().findBoardPermissionByBidGid(bid, groupID);//用的是BoardPermissionDAO List permissions = bp.getPermissions(); // 取得权限ID列表 if (permissions != null && !permissions.isEmpty()) { List permissionList = this.getPermissionDAO().findPermissionnIDs(permissions); // 取得权限列表Permission对象的List for (int i = 0; i < permissionList.size(); i++) { Permission permission = (Permission) permissionList.get(i); if (permission.getTypeID() == 2) { boardPermission[0].put(permission.getResource() + "," + permission.getAction(), permission); } if (permission.getTypeID() == 3) { boardPermission[1].put(permission.getId(), permission); } } } return boardPermission; } 接下来,看看getNextOrder(long pid),getPostSumNum(int mainorall,int usStat,int hidden),它们完全由DAO去查询数据库!我们看下remove系列: public void removeBoard(Board board) throws BbscsException { try { Long lbid = board.getId(); long pbid = board.getParentID(); Board pboard = this.getBoardDAO().getBoardByID(board.getParentID()); // 取得父版区 this.getBoardDAO().removeBoard(board); // 删除版区 this.getBoardPermissionDAO().removeBoardPermissionsByBid(board.getId().longValue());//删除对应版区的权限 if (pboard != null) { // 父版区存在,对ChildIDs字段做矫正 List pcboards = this.getBoardDAO().findBoardsByParentID(pboard.getId().longValue(), 1, 0, Constant.FIND_BOARDS_BY_ORDER);//pcboards是子对象 List cids = this.getBoardIDs(pcboards);//cids是子IDs pboard.setChildIDs(cids); this.getBoardDAO().saveBoard(pboard); } this.getBoardCache().remove(lbid); this.clearBoradListSysListCache(pbid); //清理本id的BoardCache,清理父id的SysListObjectCache,而userPermission中出现过去2类:R_ BG_的..都不好清理! } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } public void removeBoardTag(Board board, String tagID) throws BbscsException { BoardTag bt = null; Iterator it = board.getBoardTag().iterator(); //由Iterator遍历查找,找到后remove掉 while (it.hasNext()) { bt = (BoardTag) it.next(); if (bt.getId().equals(tagID)) { board.getBoardTag().remove(bt); break; } } try { board = this.getBoardDAO().saveBoard(board); //保存修改结果 this.getForumDAO().updateForumsTag(tagID, "0", "");//帖子TAG this.getForumHistoryDAO().updateForumsTag(tagID, "0", "");//历史贴TAG this.getBoardCache().remove(board.getId());//BoardCache清理一次 } catch (Exception e) { logger.error(e); throw new BbscsException(e); } } 最后再看两个方法: public Board updateBoard(Board board, long oldParentID) throws BbscsException { try { Board pboard = this.getBoardDAO().getBoardByID(board.getParentID()); if (pboard != null) { List pboards = new ArrayList(); pboards.addAll(pboard.getParentIDs()); pboards.add(pboard.getId()); board.setParentIDs(pboards); board.setLevel(pboard.getLevel() + 1); } else { board.setParentIDs(new ArrayList());//hbm.xml决定需List对象 board.setLevel(0); } /** 数据库中的数据id /parentID/ParentIDs/ChildIDs 1/ 0/ /2,3 2/ 1/ 1/ 3/ 1/ 1/ */ board = this.getBoardDAO().saveBoard(board); if (pboard != null) { List pcboards = this.getBoardDAO().findBoardsByParentID(board.getParentID(), 1, -1, Constant.FIND_BOARDS_BY_ORDER); List cids = this.getBoardIDs(pcboards); pboard.setChildIDs(cids); this.getBoardDAO().saveBoard(pboard); this.getBoardCache().remove(pboard.getId()); } this.clearBoradListSysListCache(board.getParentID()); if (oldParentID != -1) { // 父级版区改变。修正父级版区数据 关键点,由传入的oldParentID决定是否改变ParentID Board pboardOld = this.getBoardDAO().getBoardByID(oldParentID); if (pboardOld != null) { List pcboards = this.getBoardDAO().findBoardsByParentID(pboardOld.getId().longValue(), 1, -1, Constant.FIND_BOARDS_BY_ORDER); List cids = this.getBoardIDs(pcboards); pboardOld.setChildIDs(cids); this.getBoardDAO().saveBoard(pboardOld); this.getBoardCache().remove(pboardOld.getId()); this.clearBoradListSysListCache(oldParentID); } } this.getBoardCache().remove(board.getId()); // 从Cache中清除 return board; } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } 最后一个方法了: public void saveBoardsPostNumCount() throws BbscsException { long totalNum = 0; long totalMainNum = 0; List bl = findBoardsByParentID(0, 1, -1, Constant.FIND_BOARDS_BY_ORDER);//从0继承的版区有显示主题数和贴子数的需求 for (int i = 0; i < bl.size(); i++) { Board b = (Board) bl.get(i); if (b.getBoardType() == 3) { //可发贴的版区吧! b.setMainPostNum(this.getForumDAO().getForumNum(b.getId(), 1, 0, 0, -1) + this.getForumHistoryDAO().getForumNum(b.getId(), 1, 0, 0, -1)); b.setPostNum(this.getForumDAO().getForumNum(b.getId(), -1, 0, 0, -1) + this.getForumHistoryDAO().getForumNum(b.getId(), -1, 0, 0, -1)); //isNew=-1表示非主题贴,而1则相反! try { b = this.getBoardDAO().saveBoard(b); totalNum = totalNum + b.getPostNum(); totalMainNum = totalMainNum + b.getMainPostNum(); // if (Constant.USE_CLUSTER) { this.getBoardCache().remove(b.getId()); // 从Cache中清除 // } else { // this.getBoardCache().add(b.getId(), b); // } } catch (Exception ex1) { logger.error(ex1); throw new BbscsException(ex1); } } List bl2 = findBoardsByParentID(b.getId(), 1, -1, Constant.FIND_BOARDS_BY_ORDER); //子论坛的更新 if (!bl2.isEmpty()) { for (int j = 0; j < bl2.size(); j++) { Board b2 = (Board) bl2.get(j); if (b2.getBoardType() == 3) { b2.setMainPostNum(this.getForumDAO().getForumNum(b2.getId(), 1, 0, 0, -1) + this.getForumHistoryDAO().getForumNum(b2.getId(), 1, 0, 0, -1)); b2.setPostNum(this.getForumDAO().getForumNum(b2.getId(), -1, 0, 0, -1) + this.getForumHistoryDAO().getForumNum(b2.getId(), -1, 0, 0, -1)); try { b2 = this.getBoardDAO().saveBoard(b2); totalNum = totalNum + b2.getPostNum(); totalMainNum = totalMainNum + b2.getMainPostNum(); // if (Constant.USE_CLUSTER) { this.getBoardCache().remove(b2.getId()); // 从Cache中清除 // } else { // this.getBoardCache().add(b2.getId(), b2); // } } catch (Exception ex1) { logger.error(ex1); throw new BbscsException(ex1); } } } } } logger.info("postMainNum:" + totalMainNum + " postNum:" + totalNum);//打印 this.getSysStatService().savePostNum(totalMainNum, totalNum);//SysStatService服务终于用上了! } OK!我们直接进入DAO层:先看接口,发现与service接口层的方法并不相同,主要是由于service层有些方法是业务处理用,而不是用于数据处理,如createBoard,updateBoard,findBoardsAllTree,getBoardPermission,getBoardMasterPermission,isBoardMaster,当然DAO接口层也有一些方法是没有的:findBoardsNeedCount,findBoardsInIDsfindBoardsIdByParentIDInUse等等,我们看其实现吧: 进入com.laoer.bbscs.BoardHibernateDAO中首先是一些字符常量: private static final String LOADS_BY_PARENTID_BY_ORDER = "from Board where parentID = ? order by orders"; private static final String LOADS_ALL = "from Board"; public static final String[] FIND_BOARDS = new String[3]; public static final String LOAD_NEXT_ORDER = "select max(orders) from Board where parentID = ?"; private static final String LOAD_IDS_IN_USE = "select id from Board where parentID = ? and useStat = 1"; 我们看几个重点的实现方法: public List findBoardsByParentID(long pid) { return this.getHibernateTemplate().find(LOADS_BY_PARENTID_BY_ORDER, new Long(pid)); } 其重载方法: public List findBoardsByParentID(final long pid, final int useStat, final int hidden, final int orderType) { return getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException { Criteria c = s.createCriteria(Board.class); c.add(Restrictions.eq("parentID", new Long(pid))); if (useStat != -1) { c.add(Restrictions.eq("useStat", new Integer(useStat))); } if (hidden != -1) { c.add(Restrictions.eq("isHidden", new Integer(hidden))); } if (orderType != -1) { if (orderType == Constant.FIND_BOARDS_BY_ORDER) { c.addOrder(Order.asc("orders")); } if (orderType == Constant.FIND_BOARDS_BY_MAINPOSTNUM) { c.addOrder(Order.desc("mainPostNum")); } if (orderType == Constant.FIND_BOARDS_BY_POSTNUM) { c.addOrder(Order.desc("postNum")); } } return c.list(); } }); } 其中用到了条件查询(Criteria Query),参考http://blog.sina.com.cn/u/4a5e7dc401000878 和http://hi.baidu.com/yaolihui/blog/item/7c77b58286a56792f703a663.html,而public List findBoardIdsByParentID(final long pid, final int useStat, final int hidden, final int orderType)采用了构造HQL语句的方式来完成查询!根据parentID预取得Board序列: public int getNextOrder(long pid) { List l = getHibernateTemplate().find(LOAD_NEXT_ORDER, new Long(pid)); if (l != null && !l.isEmpty()) { if (l.get(0) == null) { return 5; } else { return ((Integer) l.get(0)).intValue() + 5; } } else { return 5; } } public int getPostSumNum(final int mainorall, final int useStat, final int hidden) { int sum = 0; List list = getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException { Criteria c = s.createCriteria(Board.class); if (mainorall == 0) { c.setProjection(Projections.projectionList().add(Projections.sum("mainPostNum"))); //求mainPostNum的和为结果 } if (mainorall == 1) { c.setProjection(Projections.projectionList().add(Projections.sum("postNum")));//求postNum的和为结果 } if (useStat != -1) { c.add(Restrictions.eq("useStat", new Integer(useStat))); } if (hidden != -1) { c.add(Restrictions.eq("isHidden", new Integer(hidden))); } return c.list(); } }); if (!list.isEmpty()) { Object obj = (Object) list.get(0);//注:结果集只有一个字段 if (obj != null) { sum = ((Integer) obj).intValue(); } } return sum; } public List findBoardsInIDs(final List ids, final int useStat, final int hidden) { return getHibernateTemplate().executeFind(new HibernateCallback() { @SuppressWarnings("unchecked") public Object doInHibernate(Session s) throws HibernateException { Criteria c = s.createCriteria(Board.class); if (ids == null) { //ids为空 List idss = new ArrayList(); idss.add(new Long(0)); c.add(Restrictions.in("id", idss)); } else if (ids.isEmpty()) {//ids为empty ids.add(new Long(0)); c.add(Restrictions.in("id", ids)); } else { c.add(Restrictions.in("id", ids));//Restrictions.in } if (useStat != -1) { c.add(Restrictions.eq("useStat", new Integer(useStat))); } if (hidden != -1) { c.add(Restrictions.eq("isHidden", new Integer(hidden))); } c.addOrder(Order.asc("orders")); return c.list(); } }); } public List findBoardsNeedCount(final int useStat, final int hidden) { return getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException { Criteria c = s.createCriteria(Board.class); c.add(Restrictions.or(Restrictions.eq("boardType", new Integer(3)), Restrictions.eq("boardType", new Integer(4))));//Restrictions.or 3,4的都需要count if (useStat != -1) { c.add(Restrictions.eq("useStat", new Integer(useStat))); } if (hidden != -1) { c.add(Restrictions.eq("isHidden", new Integer(hidden))); } c.addOrder(Order.desc("mainPostNum")); c.addOrder(Order.desc("postNum")); return c.list(); } }); } public List findBoardsByParentID(final long pid, final int useStat, final int hidden) { return getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException { Criteria c = s.createCriteria(Board.class); c.add(Restrictions.eq("parentID", new Long(pid)));//条件! if (useStat != -1) { c.add(Restrictions.eq("useStat", new Integer(useStat)));//附加条件! } if (hidden != -1) { c.add(Restrictions.eq("isHidden", new Integer(hidden))); } return c.list(); } }); } public List findBoardsIdByParentIDInUse(long pid) { return this.getHibernateTemplate().find(LOAD_IDS_IN_USE, new Long(pid)); } OK!分析完毕!当然,对于具体的方法是怎么被用上的,要看web层了. 接下来,看下BookMarkFactory接口,是个工厂接口.它只有一个一个公有方法, public BookMark getInstance(String userId);其实现为BookMarkFactoryImp,它产生一个BookMark bean,不过是同步的! public synchronized BookMark getInstance(String userId) { return new BookMark();//返回的是com.laoer.bbscs.bean.BookMark } 另有一个实现:BookMarksFactoryImp:(有个私有属性:int modNum及其get/set方法) public synchronized BookMark getInstance(String userId) { try { return (BookMark) Class.forName(BBSCSUtil.getClassName("BookMark", userId, this.getModNum())). newInstance(); } catch (ClassNotFoundException ex) { logger.error(ex); return null; } catch (IllegalAccessException ex) { logger.error(ex); return null; } catch (InstantiationException ex) { logger.error(ex); return null; } } 这里用到了com.laoer.bbscs.common包中的工具类:BBSCSUtil.java public static String getClassName(String className, String userID) { int num = Math.abs(userID.hashCode()); className = Constant.BEANPERFIX + className + (num % 10);//public static String BEANPERFIX = "com.laoer.bbscs.bean."; return className; } public static String getClassName(String className, String userID, int modnum) { int num = Math.abs(userID.hashCode()); className = Constant.BEANPERFIX + className + (num % modnum); return className; //应该返回是就是com.laoer.bbscs.bean.BookMark0~~~9之间的class了 } public static String getClassName(String className, long bid, int modnum) { className = Constant.BEANPERFIX + className + (bid % modnum); return className; } 而BookMarkService则完全负责这个业务!先看BEAN: private String id; private String userID; private String bookMarkName; private String url; private String alt; private int isShare; private Date createTime; <hibernate-mapping package="com.laoer.bbscs.bean"> <class name="BookMark" table="bbscs_bookmark"> <id name="id" column="ID" type="string" unsaved-value="null"> <generator class="uuid"/> </id> <property column="UserID" length="40" name="userID" not-null="true" type="string"/> <property column="BookMarkName" length="255" name="bookMarkName" not-null="true" type="string"/> <property column="Url" length="255" name="url" type="string"/> <property column="Alt" length="255" name="alt" type="string"/> <property column="IsShare" length="1" name="isShare" type="int"/> <property column="CreateTime" name="createTime" not-null="true" type="timestamp"/>//timestamp类型! </class> </hibernate-mapping> 看service接口中的方法: public BookMark findBookMarkByIDUserID(String id, String userID); public BookMark saveBookMark(BookMark bm) throws BbscsException; public long getBookMarkNumByUserID(String userID); public PageList findBookMarks(String userID, Pages pages); public PageList findBookMarksByUserIDShare(String userID, int isShare, Pages pages); public void removeBookMark(BookMark bm) throws BbscsException; public void removeBookMarkByIDUserID(String id, String userID) throws BbscsException; 我们看实现层:先注入BookMarkDAO对象.特别的是: public PageList findBookMarks(String userID, Pages pages) { PageList pl = new PageList(); if (pages.getTotalNum() == -1) { pages.setTotalNum(this.getBookMarkDAO().getBookMarkNumByUserID(userID)); } pages.executeCount(); List l = this.getBookMarkDAO().findBookMarks(userID, pages.getSpage(), pages.getPerPageNum()); //DAO层方法,Service层没有 pl.setObjectList(l); pl.setPages(pages); return pl; } 和 public PageList findBookMarksByUserIDShare(String userID, int isShare, Pages pages) { PageList pl = new PageList(); if (pages.getTotalNum() == -1) { pages.setTotalNum(this.getBookMarkDAO().getBookMarkNumByUserIDShare(userID, isShare)); } pages.executeCount(); List l = this.getBookMarkDAO().findBookMarksByUserIDShare(userID, isShare, pages.getSpage(), pages.getPerPageNum());//DAO层方法,Service层没有 pl.setObjectList(l); pl.setPages(pages); return pl; } 这里用到了2个分页功能的类:它们都在com.laoer.bbscs.service.web包中,一个Page一个PageList; 两个都是javaBEAN,不过可以带少量的业务逻辑处理功能.先看Pages: int page=1;//页号 long totalNum=-1;//记录总数 int perPageNum=1;//每页显示记录数 int allPage=1;//总页数 int cpage=1;//当前页 int spage=1;//开始记录数 String fileName=""; boolean useUrlRewirte=false; public Pages(int page, long totalNum, int perPageNum) { this.page = page; this.totalNum = totalNum; this.perPageNum = perPageNum; this.executeCount(); } public void executeCount() { this.allPage = (int) Math.ceil((this.totalNum + this.perPageNum - 1) / this.perPageNum); int intPage = this.page; if (intPage > this.allPage) { // pages == 0 this.cpage = 1; } else { this.cpage = intPage; } this.spage = (this.cpage - 1) * this.perPageNum; } 而PageList包括一个pages和List类型的objectList 及其set/get方法 public PageList() { } 我们回到findBookMarks,先得到totalNum,再执行executeCount(),设置其值后,用this.getBookMarkDAO().findBookMark(userID,pages.getspage,pages.getPerPageNum())得到后赋给PageList的objectList和page对象.这样,就可以给web层用List去遍历了,当然也要配合page. 我们直接进入DAO接口层:(其实它完全为service层服务,整体上差不多不过有些方法却不一样) public BookMark saveBookMark(BookMark bm); public BookMark findBookMarkByIDUserID(String id,String userID); public long getBookMarkNumByUserID(String userID); public List findBookMark(final String userID,final int firstResult,final int maxResults); public long getBookMarkNumByUserIDShare(String userID, int isShare); public List findBookMarksByUserIDShare(final String userID, final int isShare, final int firstResult,final int maxResults);//final int类型,方法体里不可改变 public void removeBookMark(BookMark bm); public void removeBookMarkByIDUserID(String id, String userID); 看它的实现BookMarkHibernateDAO.java:(private static final String类型) private static final String LOAD_BY_ID_USERID = "from BookMark where id = ? and userID = ?"; private static final String GET_NUM_BY_USERID = "select count(*) from BookMark where userID = ?"; private static final String LOADS_BY_USERID = "from BookMark where userID = ? order by createTime desc"; private static final String REMOVE_BY_ID_USERID = "delete from BookMark where id = ? and userID = ?"; private static final String GET_NUM_BY_USERID_ISSHARE = "select count(*) from BookMark where userID = ? and isShare = ?"; private static final String LOADS_BY_USERID_ISSHARE = "from BookMark where userID = ? and isShare = ? order by createTime desc"; 我们看其中的一些方法(根据UserID取得BookMark数量) public long getBookMarkNumByUserID(String userID) { List l = this.getHibernateTemplate().find(GET_NUM_BY_USERID, userID); if (l == null || l.isEmpty()) { return 0; } else { return ( (Long) l.get(0)).longValue(); } } 另外一个方法:(根据UserID和isShare取得BookMark列表) public List findBookMarksByUserIDShare(final String userID, final int isShare, final int firstResult, final int maxResults) { return getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException, SQLException { Query query = s.createQuery(LOADS_BY_USERID_ISSHARE); query.setString(0, userID); query.setInteger(1, isShare); query.setFirstResult(firstResult); query.setMaxResults(maxResults); List list = query.list(); return list; } }); } 我们看下CommendService:(推荐) <bean id="commendServiceTarget" class="com.laoer.bbscs.service.imp.CommendServiceImp"> <property name="commendDAO"> <ref local="commendDAO" /> </property> <property name="forumDAO"> <ref local="forumMainDAO" /> </property> <property name="commendFileIO"> <ref local="commendFileIO" /> </property> <property name="sysListObjCache"> <ref local="sysListObjCache" /> </property> </bean> 先看bean: private String id; private long boardID;//版区ID private String boardName; private String postID; private String postMainID; private String userID; private String userName; private long commendBoardID;//推荐顶层版区ID private int commendTop;//是否推荐到首页 private String title;//帖子标题 private long createTime;//创建时间 <hibernate-mapping package="com.laoer.bbscs.bean"> <class name="Commend" table="bbscs_commend"> <id name="id" column="ID" type="string" unsaved-value="null"> <generator class="uuid"/> </id> <property column="BoardID" length="13" name="boardID" not-null="true" type="long"/> <property column="BoardName" length="60" name="boardName" not-null="true" type="string"/> <property column="PostID" length="40" name="postID" not-null="true" type="string"/> <property column="PostMainID" length="40" name="postMainID" not-null="true" type="string"/> <property column="UserID" length="40" name="userID" not-null="true" type="string"/> <property column="UserName" length="20" name="userName" not-null="true" type="string"/> <property column="CommendBoardID" length="13" name="commendBoardID" not-null="true" type="long"/> <property column="CommendTop" length="1" name="commendTop" not-null="true" type="int"/> <property column="Title" length="150" name="title" type="string"/> <property column="BoardCategory" length="40" name="boardCategory" type="string"/> <property column="TopCategory" length="40" name="topCategory" type="string"/> <property column="CreateTime" name="createTime" not-null="true" type="long"/> </class> </hibernate-mapping> CommendServie接口中有以下主要方法: public Commend saveCommend(Commend commend) throws BbscsException; public int getCommendNumByCommendBoardID(long commendBoardID); public PageList findCommendsByCommendBoardID(long commendBoardID, Pages pages); public int getCommendNumByCommendTop(int commendTop); public void removeCommend(long commendBoardID, List ids) throws BbscsException; public void createCommendTopFile(int num) throws BbscsException; public List findCommendsByCommendTopCache(int commendTop, int num); 而其实现层大多方法给DAO去实现之.下面为其中的几个方法: public PageList findCommendsByCommendBoardID(long commendBoardID, Pages pages) { PageList pl = new PageList(); if (pages.getTotalNum() == -1) { pages.setTotalNum(this.getCommendDAO().getCommendNumByCommendBoardID(commendBoardID));//DAO实现 } pages.executeCount(); List l = this.getCommendDAO().findCommendsByCommendBoardID(commendBoardID, pages.getSpage(), pages.getPerPageNum()); pl.setObjectList(l); pl.setPages(pages); return pl; } 下面是从顶层版区删除推荐.... public void removeCommend(long commendBoardID, List ids) throws BbscsException { List l = this.getCommendDAO().findCommendsInIds(ids); try { for (int i = 0; i < l.size(); i++) { Commend c = (Commend) l.get(i);//取出 Forum f = this.getForumDAO().findForumByID(c.getPostID(), c.getBoardID()); f.setCommend(0);//修改是否推荐标志 this.getForumDAO().saveOrUpdateForum(f); this.getCommendDAO().removeCommend(c); } List commendList = this.getCommendDAO().findCommendsByCommendBoardID(commendBoardID, 0, 10); this.getCommendFileIO().saveCommendInReadPageFile(commendBoardID, commendList);//写入推荐文件中!!!! } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } public void createCommendTopFile(int num) throws BbscsException { List l = this.getCommendDAO().findCommendsByCommendTop(0, 0, num); try { this.getCommendFileIO().saveCommendInReadPageFile(0, l); } catch (IOException ex) { logger.error(ex); throw new BbscsException(ex); } } 而findCommendByCommendTopCache为从SysListObjCache中获得10条推荐信息! public List findCommendsByCommendTopCache(int commendTop, int num) { List l = (List) this.getSysListObjCache().get(Constant.COMMEND_CACHE_NAME);//public static final String COMMEND_CACHE_NAME = "CommendSceipt"; if (l == null) { l = this.getCommendDAO().findCommendsByCommendTop(commendTop, 0, num); this.getSysListObjCache().add(Constant.COMMEND_CACHE_NAME, l); } return l; } 我们来分析下commendFileIO: <bean id="commendFileIO" class="com.laoer.bbscs.fio.imp.CommendFileIOImp" /> 由于service实现层用的是fio接口层,实际注入的却是fil.imp里面的东西: public interface CommendFileIO { public void saveCommendInReadPageFile(long commendid, List commendList) throws IOException;//只有一个方法 } 看其实现,里面用了BBSCSUtilTextUtils工具类,它其实写了2个文件!!!前一个在贴子显示时用到,后一个可见www.laoer.com斑主推荐部分! public void saveCommendInReadPageFile(long commendid, List commendList) throws IOException { StringBuffer sb = new StringBuffer(); for (int i = 0; i < commendList.size(); i++) { Commend commend = (Commend) commendList.get(i); sb.append("·"); sb.append("<a href=""); if (Constant.USE_URL_REWRITE) { sb.append("read-topic-" + commend.getBoardID() + "-" + commend.getPostMainID() + "-0-1-index-1.html"); } else { sb.append(BBSCSUtil.getActionMappingURLWithoutPrefix("read?action=topic&id=" + commend.getPostMainID() + "&bid=" + commend.getBoardID())); } sb.append("">"); sb.append(TextUtils.htmlEncode(commend.getTitle())); sb.append("</a><BR/>"); } File commendFile = new File(BBSCSUtil.getIncludePath() + "Commend_" + commendid + ".html"); FileUtils.writeStringToFile(commendFile, sb.toString(), Constant.CHARSET); commendFile = null; sb = null; sb = new StringBuffer(); //int counter = 0; for (int i = 0; i < commendList.size(); i++) { Commend c = (Commend) commendList.get(i); sb.append("<tr>"); sb.append("<td>"); sb.append("<a href=""); if (Constant.USE_URL_REWRITE) { sb.append("read-topic-" + c.getBoardID() + "-" + c.getPostMainID() + "-0-1-index-1.html"); } else { sb.append(BBSCSUtil.getActionMappingURLWithoutPrefix("read?action=topic&id=" + c.getPostMainID() + "&bid=" + c.getBoardID())); } sb.append("" title=""); sb.append(c.getTitle()); sb.append("">"); sb.append(c.getTitle()); sb.append("</a>"); sb.append("[<a href=""); if (Constant.USE_URL_REWRITE) { sb.append("forum-index-" + c.getBoardID() + ".html"); } else { sb.append(BBSCSUtil.getActionMappingURLWithoutPrefix("forum?action=index&bid=" + c.getBoardID())); } sb.append("">"); sb.append(c.getBoardName()); sb.append("</a>]"); sb.append("</td>"); sb.append("</tr>"); } commendFile = new File(BBSCSUtil.getIncludePath() + "ForumCover_Commend_" + commendid + ".html"); FileUtils.writeStringToFile(commendFile, sb.toString(), Constant.CHARSET); } BBSCSUtil中的方法:(前面的一些的分析) getUserWebFilePath()返回一个本地的用户资料文件路径,绝对的(加了ROOTPATH) getUserWebPath()相对的... getWebRealPath()返回URL(一级) getUpFilePath和getUpFileWebPath类似与上getUserWebFilePath getIncludePath()返回本地的路径/include/ 对于BBSCSUtil.getActionMappingURLWithoutPrefix("forum?action=index&bid=" + c.getBoardID()),用了工具类封装了真实的网页后缀! public static String getActionMappingURLWithoutPrefix(String action) { //action="forum?action=index&bid=" + c.getBoardID() StringBuffer value = new StringBuffer(); // Use our servlet mapping, if one is specified String servletMapping = Constant.SERVLET_MAPPING;//*.bbscs if (servletMapping != null) { String queryString = null; int question = action.indexOf("?");//6 if (question >= 0) { queryString = action.substring(question);//"action=index&bid="+c.getBoardID() } String actionMapping = getActionMappingNameWithoutPrefix(action);//forum if (servletMapping.startsWith("*.")) { value.append(actionMapping);//forum value.append(servletMapping.substring(1));//forum.bbscs } else if (servletMapping.endsWith("/*")) { value.append(servletMapping.substring(0, servletMapping.length() - 2)); value.append(actionMapping); } else if (servletMapping.equals("/")) { value.append(actionMapping); } if (queryString != null) { value.append(queryString);//value="forum.bbscs?action=index&bid="+c.getBoardID() } } return (value.toString()); } public static String getActionMappingNameWithoutPrefix(String action) { String value = action;action="forum?action=index&bid=" + c.getBoardID() int question = action.indexOf("?");//6 if (question >= 0) { value = value.substring(0, question);//value="forum" } int slash = value.lastIndexOf("/");//slash=0;如果是main/forum.bbscs的话slash=4 int period = value.lastIndexOf(".");//period=0;.....................period=9 if ((period >= 0) && (period > slash)) { value = value.substring(0, period);//value=0....perod main/forum } return (value); //forum } 接下来,我们看CommendDAO,这个接口中提供了如下方法: public Commend saveCommend(Commend commend); public Commend findCommendByID(String id); public Commend findCommendByPostID(String postID); public int getCommendNumByCommendBoardID(long commendBoardID); public List findCommendsByCommendBoardID(long commendBoardID, final int firstResult, final int maxResults); public int getCommendNumByCommendTop(int commendTop); public List findCommendsByCommendTop(int commendTop, final int firstResult, final int maxResults); public List findCommendsInIds(List ids); public void removeCommend(Commend commend); public void removeCommend(String postID); 看实现: private static final String LOADS_IN_IDS = "from Commend where id in (:ids)"; public List findCommendsInIds(final List ids) { return getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException, SQLException { Query query = s.createQuery(LOADS_IN_IDS); query.setParameterList("ids", ids);//List!!!final List ids List list = query.list(); return list; } }); } ConfigService用于配置服务:(它有两个属性:id,confContext其hbm.xml <hibernate-mapping package="com.laoer.bbscs.bean"> <class name="Config" table="bbscs_config"> <id name="id" column="ID" type="string" unsaved-value="undefined"> <generator class="assigned"/> </id> <property column="ConfContext" name="confContext" type="text"/> </class> </hibernate-mapping> 有public Config updateConfig(Config config) throws BbscsException; public Config findConfigByID(String id); public List findConfigs(); public void updateAllConfigs(HashMap configs) throws BbscsException; 其实现: public void updateAllConfigs(HashMap configs) throws BbscsException { Iterator it = configs.values().iterator(); try { while (it.hasNext()) { Config config = (Config) it.next(); this.getConfigDAO().updateConfig(config); } } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } 由于比较简单,直接PASS: public List findConfigs() { return this.getHibernateTemplate().find(LOAD_ALL); } 看EliteService,其对应的BEAN有以下属性: private Long id; private long boardID;//版区ID private long parentID; //父ID private List parentIDs;//你级ID列表,用","分开 private String eliteName;//精华目录名称 private String createUser;//创建者 private long eliteTime;//创建时间 private int orders;//序 <hibernate-mapping package="com.laoer.bbscs.bean"> <class name="Elite" table="bbscs_elite"> <id name="id" column="ID" type="long" unsaved-value="null"> <generator class="identity"/>//自动增长 </id> <property column="BoardID" length="20" name="boardID" not-null="true" type="long"/> <property column="ParentID" length="20" name="parentID" not-null="true" type="long"/> <property column="ParentIDs" name="parentIDs" type="com.laoer.bbscs.ext.hibernate.SplitList"/>//自定义类型 <property column="EliteName" length="90" name="eliteName" type="string"/> <property column="CreateUser" length="60" name="createUser" type="string"/> <property column="EliteTime" length="20" name="eliteTime" type="long"/> <property column="Orders" length="11" name="orders" type="int"/> </class> </hibernate-mapping> 进行方法中: public Elite createElite(Elite elite) throws BbscsException; public Elite saveElite(Elite elite) throws BbscsException; public Elite findEliteByID(long id); public List findElitesByPidBid(long pid, long bid); public void removeElite(Elite elite) throws BbscsException; public List findElitesInIds(List ids); 它首先注入了DAO:eliteDAO和forumDAO;需要注意的是: public Elite createElite(Elite elite) throws BbscsException { Elite pElite = this.getEliteDAO().findEliteByID(elite.getParentID()); if (pElite != null) { List pElites = new ArrayList(); pElites.addAll(pElite.getParentIDs()); pElites.add(pElite.getId()); elite.setParentIDs(pElites);//构造一下其ParentIDs } try { return this.getEliteDAO().saveElite(elite); } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } public void removeElite(Elite elite) throws BbscsException { try { List l = this.getForumDAO().findForumsElite(elite.getBoardID(), elite.getBoardID(), elite.getId().longValue()); for (int i = 0; i < l.size(); i++) { Forum forum = (Forum) l.get(i); forum.setEliteID(elite.getParentID());//改变之 this.getForumDAO().saveOrUpdateForum(forum); } this.getEliteDAO().removeElite(elite); } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } 我们直接看DAO: public Elite saveElite(Elite elite); public Elite findEliteByID(long id); public List findElitesByPidBid(long pid, long bid); public void removeElite(Elite elite); public List findElitesInIds(List ids); 对于具体地实现我们PASS. 现在,让我们看一下最复杂的一个业务对象,forum,它是一个论坛的核心部分,贴子(其实这个单词是论坛的意思)当然其关系到的业务逻辑及其它东东也最多了!我们从BEAN开始研究:(右边为一条数据库表的记录,注意其中有许多Default值) private String id; //主键 402881e513bdc8550113bdefb43c0014 private String parentID; //父级ID NULL值 private String mainID; //MainID 402881e513bdc8550113bdefb43c0014 private long boardID; //版区ID 2 private String boardName; java private int reNum; //回复数 0 private int face; //表情 0 private String userID; //用户ID 4028818208ed006b0108ed020bd50001 private String userName; webmaster private String nickName; webmaster private String title; //标题 dffdsfds private String detail;//文件名称 P_2_402881e513bdc8550113bdefb43c0014.txt private String sign;//签名 NULL private int artSize;//字数 58 private int click;//点击数 5 private long postTime;//发贴时间 1184302609453 private long lastTime;//最后回复时间 1184302609453 private String ipAddress;//IP地址 127.0.0.1 private int isNew;//是否是主贴(这个取名有点问题) 1 private long elite;//是否精华 0 private long eliteID;//精华目录ID 0 private int agree;//赞成 0 private int beAgainst;//反对 1 private int canNotDel;不是水贴标志 0 private int delSign;//删除标志 0 private String delUserID;//删除者 NULL private String delUserName; NULL private long delTime; 0 private String delIP; NULL private String amend;//删除附加信息 NULL private String doEliteName;//加入精华者用户名 NULL private long doEliteTime;//加入精华时间 0 private int haveAttachFile;//是否有附件 0 private List attachFileName = new ArrayList();//附件文件名列表 NULL private String lastPostUserName;//最后回复用户名 --- private String lastPostTitle;//最后回复帖子标题 NULL private String lastPostNickName; --- private long isTop;//置顶标志 0 private int isLock;//锁定标志 0 private int auditing;//审核标志 0 private int auditingAttachFile;//附件审核标志 0 private int isVote;//是否投票贴标志 0 private int isHidden;//是否隐藏贴标志 0 private int editType;//编辑器类型 0 private String quoteText;//引用文字 NULL private int postType;//发贴类型 0 private int titleColor;//标题color 5 private int canNotRe;//已经是否回复标志 0 private long commend;//推荐标志 0 private int isHiddenValue;//隐藏贴参数 0 private int userBlog;//加入个人文集标志 0 private int indexStatus;//索引标志 0 private int quoteEditType;//引用文字编辑器类型 0 private int emailInform;//通知发信到哪里(不用) 0 private int msgInform;//消息到谁(不用) 0 private String voteID;//投票ID NULL private String tagID;//版区Tag 0 private String tagName;//版区Tag名称 NULL private int isGuest;//是否游客发贴 0 private int previewAttach;//附件预览标志 1 对于ForumMain-mysql.hbm.xml, <property column="AttachFileName" name="attachFileName" type="com.laoer.bbscs.ext.hibernate.SplitStringList"/> 我们看下这个另外的hibernate自定义类型类,它与SplitList类似...... 而在数据库中:(请多看看数据库中的数据) PRIMARY KEY (`ID`), UNIQUE KEY `ID` (`ID`), KEY `MainID` (`MainID`), KEY `BoardID` (`BoardID`), KEY `LastTime` (`LastTime`), KEY `PostTime` (`PostTime`), KEY `UserID` (`UserID`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 进入到forumservice中,有一大堆方法:(不加javadoc的话应该不会有100个吧!吓人!) public Forum saveOrUpdateForum(Forum forum) throws BbscsException; public Forum saveForum(Forum forum) throws BbscsException; public Forum updateForum(Forum forum) throws BbscsException; public Forum findForumByID(String id); public Forum findForumByID(String id, long bid);//根据ID,Bid取得Forum对象 public long getForumNum(long bid, int isNew, int delSign, int auditing, int auditingAttachFile);//取得帖子数量 public long getForumNum(long bid);//取得正常帖子数量 public long getForumMainNum(long bid);//取得主帖数量 public long getForumDelNum(long bid);//取得已删除帖子数量 public long getForumAuditingNum(long bid);//取得未审核帖子数量 public long getForumTopicNum(long bid, String mainID, int delSign, int auditing);//取得同一主题帖子数量 public long getForumTopicNum(long bid, String mainID);//取得一个主题的帖子数量(正常的) public long getForumTopicDelNum(long bid, String mainID);//取得一个主题已删除帖子数量 public long getForumTopicAuditingNum(long bid, String mainID);//取得一个主题未审核帖子数量 public List findForums(long bid, int isNew, int delSign, int auditing, OrderObj[] oo);//取得帖子列表 public PageList findForums(long bid, int isNew, int delSign, int auditing, int auditingAttachFile, OrderObj[] oo, Pages pages); public PageList findForumsMainWWW(long bid, Pages pages);//取得主帖分页列表(WWW方式,发帖时间排序) public PageList findForumsMainLastRe(long bid, Pages pages);//取得主帖分页列表(顶帖方式,回复时间排序) public PageList findForumsAll(long bid, Pages pages);//取得正常帖子分页列表,不分主从 public PageList findForumsDel(long bid, Pages pages);// 取得已删除帖子分页列表 public PageList findForumsAuditing(long bid, Pages pages);// 取得未审核帖子分页列表 public PageList findForumsTopic(long bid, String mainID, int delSign, int auditing, OrderObj[] oo, Pages pages);//取得同一主题帖子分页列表 public PageList findForumsTopic(long bid, String mainID, Pages pages); public List findForumsTopicAll(long bid, String mainID, int delSign, int auditing, OrderObj[] oo); public List findForumsTopicDel(long bid, String mainID);//取得已删除同一主题帖子列表 public List findForumsTopicAuditing(long bid, String mainID);取得未审核同一主题帖子列表 public List findForumsElite(long bid, long elite, long eliteId);取得精华帖列表 public long getSearchNum(long bid, String con, String text, int delSign, int auditing);取得搜索帖子数量 public PageList getSearchList(long bid, String con, String text, int delSign, int auditing, String orderby, int ascOrDesc, Pages pages);取得搜索结果分页列表 public long getSearchNum(long bid, String con, String text);取得搜索帖子数量(不带条件) public PageList getSearchList(long bid, String con, String text, Pages pages); public long getForumOwnerNum(String userID);取得自己的帖子数量 public PageList findForumsOwner(String userID, int isNew, Pages pages);取得自己的帖子分页列表 public void removeForum(String id, long bid) throws BbscsException;删除Forum对象 public void removeForum(String id) throws BbscsException; public void removeForum(Forum forum) throws BbscsException; public Forum createForum(Forum forum) throws BbscsException;发帖 public Forum createForum(Forum forum, Board board, UserInfo ui, UploadFile uploadFile) throws BbscsException; public Forum createReForum(Forum forum, Forum mainForum, Board board, UserInfo ui, UploadFile uploadFile, boolean isQuote) throws BbscsException;回帖 public Forum createForumUpFile(Forum forum, UploadFile uploadFile) throws BbscsException; public Forum editForum(Forum forum) throws BbscsException;帖子修改 public long getForumNumCommend(long bid, long commen); public long getForumNumCommend(long commend); public PageList findForumsCommend(long bid, long commen, Pages pages); public PageList findForumsCommend(long commend, Pages pages); public boolean isReedUser(long bid, String mainID, String userID); public void delaPost(Forum forum, Board board, UserInfo ui) throws BbscsException;删除一个帖子 public void savePostElite(Forum forum, UserInfo ui) throws BbscsException; public void saveForums(List forums) throws BbscsException; public void saveForumsEliteDel(List forums) throws BbscsException; public Forum createVoteForum(Forum forum, Board board, Vote vote, UserInfo ui,String voteItem) throws BbscsException; public long getForumOwnerNum(long bid, String userID, String mainID);搂主帖子数 public PageList findForumsOwner(long bid, String userID, String mainID, Pages pages);只看楼主 public void removeAttachFile(Forum forum, List fileNames) throws BbscsException;删除附件 public void removeAllAttachFile(Forum forum) throws BbscsException;删除某个帖子所有附件 public void delForumsNotAuditingAttachFile(List ids) throws BbscsException;删除未通过审核的帖子附件 public long getForumNumHotTopic(long bid, int reNum, int click); public PageList findForumsHotTopic(long bid, int reNum, int click, Pages pages); public void removeToHistory(long atime) throws BbscsException; public void delPosts(List forums, Board board) throws BbscsException; public List findForumsInIds(long bid, List ids); public List findForumsInIds(List ids); public void delPostReal(Forum forum) throws BbscsException; public void delPostsReal(List ids) throws BbscsException; public void delWastePost(long bid) throws BbscsException; public PageList findForumsAllManage(long bid, Pages pages); public void saveForumsResume(List ids, Board board) throws BbscsException; public void saveForumsAuditingAttachFile(List ids) throws BbscsException; public long getForumsAuditingAttachFileNum(long bid); public PageList findForumsAuditingAttachFile(long bid, Pages pages); public List findForumsTopicAuditingAttachFile(long bid, String mainID); public void saveForumChangeUser(Forum forum, int[] titleType, int[] values) throws BbscsException; public void saveForumChangeUser(Forum forum, int titleType, int values) throws BbscsException; public void createCommendPage(long commend) throws BbscsException; public void saveForumCommend(int createOrDel, Board board, Forum forum) throws BbscsException; public List findForumsByIndexStatus(int indexStatus); public List findForumsAllNew(int num); public List findForumsAllNewCache(int num); public PageList findForumsAll(long bid, String tagID, Pages pages); public PageList findForumsMainWWW(long bid, String tagID, Pages pages); public PageList findForumsMainLastRe(long bid, String tagID, Pages pages); public void saveForumBuy(long bid, String postId, Forum f, UserInfo buyFromUi) throws BbscsException; public long getForumBuyNumByPostId(String postId); public ForumBuy findForumBuyByPostIdFromId(String postId, String fromId); public Forum saveEditForum(Forum forum) throws BbscsException; public void saveMoveForum(Forum forum, Board toboard) throws BbscsException; public void saveMoveForum(long frombid, String mainid, Board toboard) throws BbscsException; public String getForumDetail(Forum forum, boolean forcefromfile); 我们进入ForumServiceImp类: 首先它注入了许多DAO和其它服务,如:forumDAO,userInfoDAO,forumUploadFile,sysListObjCache,sysConfig,userInfoFileIO,subscibeFactory,subscibeDAO,boardDAO,subscibeQueue,forumBuyDAO,voteDAO,voteItemDAO,commendDAO,commendFileIO,forumHistoryDAO,forumConfig,postCache....当然,还有logger,真得太吓人了,太多东东了! public Forum createForum(Forum forum) throws BbscsException { Board board = this.getBoardDAO().getBoardByID(forum.getBoardID());//得到版区 UserInfo ui = this.getUserInfoDAO().findUserInfoById(forum.getUserID());//得到用户信息 return createForum(forum, board, ui, null);//让别人去完成! } @SuppressWarnings("unchecked") //因为有些非JDK5.0的开源库如hibernate, 函数返回的一定是List,而不会是List<User>,这时候IDE就会爆出很多warning。用SuppressWarning("unchecked")可以让IDE安静一些。 public Forum createForum(Forum forum, Board board, UserInfo ui, UploadFile uploadFile) throws BbscsException { try { if (Constant.POST_STORAGE_MODE == 0) {//贴子存储方式,其实在web.xml中有设定 forum = this.getForumDAO().saveOrUpdateForum(forum); } else { String detail = forum.getDetail();//现在用这种方式 forum = this.getForumDAO().saveOrUpdateForum(forum); String postFileName = "P_" + forum.getBoardID() + "_" + forum.getId() + ".txt";//可见safe文件夹下的post下的.... File postFile = new File(this.getForumConfig().getForumPath(forum.getBoardID(), forum.getPostTime()) + postFileName); FileUtils.writeStringToFile(postFile, detail, Constant.CHARSET);//写入文件 forum.setDetail(postFileName);//而写入数据库的只是文件名 } if (uploadFile != null) { String fileName = "File_" + forum.getId() + "_" + System.currentTimeMillis()//当前时间 + "." + FilenameUtils.getExtension(uploadFile.getFileName());//文件后缀 String toFilePath = BBSCSUtil.getUpFilePath(forum.getBoardID(), forum.getPostTime());//以前讲过的,应该生成safe/upload/... /** public static String getUpFilePath(long bid, long adate) { StringBuffer sb = new StringBuffer(); sb.append(Constant.ROOTPATH); sb.append(getUpFileWebPath(bid, adate)); File ft = new File(sb.toString()); if (!ft.exists()) { ft.mkdirs(); } return sb.toString(); } public static String getUpFileWebPath(long bid, long adate) { StringBuffer sb = new StringBuffer(); sb.append("upload/"); sb.append((bid % 20)); sb.append("/"); sb.append(bid); sb.append("/"); sb.append(Util.formatDate4(new Date(adate))); sb.append("/"); return sb.toString(); } */ this.getForumUploadFile().saveUploadFile(toFilePath + fileName, uploadFile, this.getSysConfig());//帖子上传文件 forum.setHaveAttachFile(1);//有附件 forum.getAttachFileName().add(fileName);//List里加入内容! if (board.getAuditAttach() == 1) { forum.setAuditingAttachFile(1); } } forum.setMainID(forum.getId()); forum = this.getForumDAO().saveOrUpdateForum(forum);//刷新一下 if (board.getAuditPost() == 0 && board.getAddUserPostNum() == 1) { // 不需要审核,并且版区为增加用户发帖数量 ui.setArticleNum(ui.getArticleNum() + 1); ui.setExperience(ui.getExperience() + 2); // 发帖增加经验值2点。 ui = this.getUserInfoDAO().saveUserInfo(ui);//用户信息更新 this.getUserInfoFileIO().writeUserFile(ui);//用户信息写入文件!见safe/user } if (forum.getEmailInform() != 0 || forum.getMsgInform() != 0) { if (this.getSubscibeDAO().findSubscibeByPostID(forum.getId(), ui.getId(), forum.getBoardID()) == null) { Subscibe subs = this.getSubscibeFactory().getInstance(forum.getBoardID()); subs.setBoardID(forum.getBoardID()); subs.setCreateTime(new Date()); subs.setEmailinform(forum.getEmailInform()); subs.setMsginform(forum.getMsgInform()); subs.setNickName(ui.getNickName()); subs.setPostID(forum.getId()); subs.setPostTitle(forum.getTitle()); subs.setUserEmail(ui.getEmail()); subs.setUserID(ui.getId()); subs.setUserName(ui.getUserName()); subs.setUserLocale(ui.getUserLocale()); this.getSubscibeDAO().saveSubscibe(subs);//订阅服务! } } this.getSysListObjCache().remove(Constant.FORUM_NEW_CACHE_NAME);//文章Cache,remove方法来自Cache接口! return forum; } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } 我们看下ForumConfig的getForumPath:(注意:forum.getBoardID()--->bid,forum.getPostTime()-->adate) 这个服务类注入的是private String safePath; public String getSafePath() { return safePath; } public void setSafePath(String safePath) { this.safePath = safePath; } 它有两个方法:getForumPathOld与getForumPath其实完全一样!555 public String getForumPath(long bid, long adate) { StringBuffer sb = new StringBuffer(); sb.append(this.getSafePath()); if (!this.getSafePath().endsWith("/")) { sb.append("/"); } sb.append("post/"); sb.append(bid % 20); //bid%20的余数 2 sb.append("/"); sb.append(bid); //2 sb.append("/"); sb.append(Util.formatDate4(new Date(adate)));//20070713 sb.append("/"); sb.append(adate % 100);//余数 53 sb.append("/"); File ft = new File(sb.toString()); if (!ft.exists()) { ft.mkdirs(); } return sb.toString(); } 这里有个Util:来自com.laoer.bbscs.comm包,里面有许多格式时间的方法: public static String formatDateTime(Date date) { SimpleDateFormat outFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return outFormat.format(date); } public static String formatDate2(Date myDate) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd"); String strDate = formatter.format(myDate); return strDate; } public static String formatDate3(Date myDate) { SimpleDateFormat formatter = new SimpleDateFormat("MM-dd HH:mm"); String strDate = formatter.format(myDate); return strDate; } public static String formatDate4(Date myDate) { SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd"); String strDate = formatter.format(myDate); return strDate; }//用的是这个啰! public static String formatDate5(Date myDate) { String strDate = getYear(myDate) + "-" + getMonth(myDate) + "-" + getDay(myDate); return strDate; } public static String formatDate6(Date myDate) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm"); String strDate = formatter.format(myDate); return strDate; } 而另外一个是文件上传操作!贴子附件上传和用户信息写入文件两种:它们都在com.laoer.bbscs.fio中,有三个接口:CommendFileIO,ForumUploadFile,UserInfoFileIO,另外,这个包里也提供了一个javabean:UploadFile,有两个上传文件需要用到的属性fileName和InputStream类型的inputStream,它们提供了对外实用方法..我们一个一个看它们的实现过程,CommendFileIO是推荐内容,我们曾讲过,safe/include/...两个文件,而本forum用到的是另外两个: ForumUploadFile中有 public void saveUploadFile(String toFileName, UploadFile uploadFile, SysConfig sysConfig) throws IOException; //sysConfig用于获得系统的配置参数!,这里也用到了UploadFile这个javabean! public void delUploadFile(Forum f) throws IOException; public List delUploadFile(Forum f, List fileNames) throws IOException; public void moveUploadFile(Forum forum, long tobid) throws IOException; public void delDetailFile(Forum forum) throws IOException; 我们看它的实现,由于实现它是通过spring注入的! <bean id="userInfoFileIO" class="com.laoer.bbscs.fio.imp.UserInfoFileIOImp"> <property name="userConfig"> <ref bean="userConfig" /> </property> <property name="sysConfig"> <ref bean="sysConfig" /> </property> </bean> <bean id="forumUploadFile" class="com.laoer.bbscs.fio.imp.ForumUploadFileImp" /> <bean id="commendFileIO" class="com.laoer.bbscs.fio.imp.CommendFileIOImp" /> 这个类也先使用了forumConfig及其getter/setter!(不过怎么没注入呢)我们看其: public void saveUploadFile(String toFileName, UploadFile uploadFile, SysConfig sysConfig) throws IOException { OutputStream bos = new FileOutputStream(toFileName); IOUtils.copy(uploadFile.getInputStream(), bos); if (sysConfig.isAttachImg(uploadFile.getFileName()) && sysConfig.getReduceAttachImg() == 1) { ImgUtil.reduceImg(toFileName, toFileName + Constant.IMG_SMALL_FILEPREFIX, sysConfig .getReduceAttachImgSize(), sysConfig.getReduceAttachImgSize(),1);//缩微图的生成,同目录内!public static String IMG_SMALL_FILEPREFIX = "_Small"; } ----------> public boolean isAttachImg(String fileName) { return FilenameUtils.isExtension(fileName, getAttachImgTypes()); } public String[] getAttachImgTypes() { String[] types = getAttachImgType().split(","); if (types == null || types.length == 0) { //提供默认值! types = new String[3]; types[0] = "gif"; types[1] = "jpg"; types[2] = "jpeg"; } return types; } public String getAttachImgType() { return this.getStringValue("AttachImgType"); } public int getReduceAttachImg() { return this.getIntValue("ReduceAttachImg", 1); } 是图片且系统允许生成! public int getReduceAttachImgSize() { return this.getIntValue("ReduceAttachImgSize", 200); }//长宽都用这个 注意我们将继续深入下去: FilenameUtils是org.apache.common.io下的!而ImgUtil.reduceImg则是com.laoer.bbscs.comm包里的工具类:(这个类就只有一个方法),我们摘入主要的一段: public static void reduceImg(String imgsrc, String imgdist, int widthdist, int heightdist, int benchmark) { // int benchmark说明:0,长宽哪个长,以哪个为标准;1,以宽为基准;2,以高为基准 try { File srcfile = new File(imgsrc); if (!srcfile.exists()) { return; } Image src = javax.imageio.ImageIO.read(srcfile); int width = src.getWidth(null); int height = src.getHeight(null); if (width <= widthdist && height <= heightdist) { // SysUtil.cpoyFile(imgsrc, imgdist); FileUtils.copyFile(new File(imgsrc), new File(imgdist)); return; } // 宽度除以高度的比例 float wh = (float) width / (float) height; if(benchmark==0) { ... } if (benchmark == 1) { float tmp_heigth = (float) widthdist / wh; BufferedImage tag = new BufferedImage(widthdist, (int) tmp_heigth, BufferedImage.TYPE_INT_RGB); tag.getGraphics().drawImage(src, 0, 0, widthdist, (int) tmp_heigth, null); FileOutputStream out = new FileOutputStream(imgdist); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out); encoder.encode(tag); out.close(); } ...主要还是BufferedImage和JPEGCodec.createJPEGEncoder! 对于ForumUploadFileImp中的其它方法我们在用到的时候在来考虑之. 好,我们看看UserInfoFileIO的writeUserInfo(ui),对于这个文件的效果见:safe/user目录下!我们首先注入的是userConfig和sysConfig两个配置服务!下面是某用户文件中的内容: 402881e513bdc8550113bdf70a1b0019|1184303089000|1184303117000|0|0|0|0|100|0|0|0|0|-|本机地址| 用户ID|注册时间|登录时间|文章数|精华文章数|用户头衔|用户生命值(不一定)|积分|......|社区币|是否有相片|(有的话,图片名,无 为"-")|(有的话,用户来自)| 我们看其中的一段代码: if (StringUtils.isBlank(userInfo.getUserFrom())) { sb.append("-"); } else { sb.append(userInfo.getUserFrom()); } sb.append("|"); File usrfile = new File(this.getUserConfig().getUserFilePath(userInfo.getId()) + Constant.USER_PROFILE);//public static final String USER_PROFILE = "UserProFile.txt"; FileUtils.writeStringToFile(usrfile, sb.toString(), Constant.CHARSET); //关于userConfig其实与sysConfig和ForumConfig类似,提供的是简单的配置方法: 注入了safePath:(以前讲过,这里重复一次) public String getUserFilePath(String userID) { StringBuffer sb = new StringBuffer(); int num = Math.abs(userID.hashCode()); sb.append(this.getSafePath()); if (!this.getSafePath().endsWith("/")) { sb.append("/"); } sb.append("user/"); sb.append(num % 100); sb.append("/"); sb.append(userID); sb.append("/"); File ft = new File(sb.toString()); if (!ft.exists()) { ft.mkdirs(); } return sb.toString(); } 好了,看下面的方法:(较长) public Forum createReForum(Forum forum, Forum mainForum, Board board, UserInfo ui, UploadFile uploadFile, boolean isQuote) throws BbscsException { try { String detail = forum.getDetail(); if (board.getAuditPost() == 0) { // 不需要审核 mainForum.setLastPostNickName(forum.getNickName()); mainForum.setLastPostTitle(forum.getTitle()); mainForum.setLastPostUserName(forum.getUserName()); mainForum.setLastTime(forum.getPostTime()); mainForum.setReNum(mainForum.getReNum() + 1);//加入回复数 if (forum.getParentID().equals(forum.getMainID())) { // 回复的是主帖 if (mainForum.getCanNotRe() == 0) { mainForum.setCanNotRe(1);//已经回复 } if (isQuote) { forum.setQuoteEditType(mainForum.getEditType());//引用编辑类型! if (this.getSysConfig().getQuoteMaxSize() > 0) {// public int getQuoteMaxSize() { return this.getIntValue("QuoteMaxSize", 300); } forum.setQuoteText(BBSCSUtil.getSpeShortString(this.getForumDetail(mainForum, false), this .getSysConfig().getQuoteMaxSize(), "...")); /** public String getForumDetail(Forum forum, boolean forcefromfile) { if (Constant.POST_STORAGE_MODE == 0) { return forum.getDetail();//直接从对象得到! } else { if (forcefromfile) { return this.getForumDetailFromFile(forum);//从文件来 } else {//从Cache来 String detail = (String) this.getPostCache().get(forum.getId()); if (detail == null) {//没有的话,从文件来 detail = this.getForumDetailFromFile(forum); if (StringUtils.isNotBlank(detail)) { postCache.add(forum.getId(), detail); } } return detail; } } } 注意这里用了一个私有方法: private String getForumDetailFromFile(Forum forum) { File postFile = new File(forumConfig.getForumPath(forum.getBoardID(), forum.getPostTime()) + forum.getDetail()); try { return FileUtils.readFileToString(postFile, Constant.CHARSET);//把文件写成字符串! } catch (IOException e) { logger.error(e); return ""; } } 这里用了BBSCSUtil工具类,用于截取一定长度的引用文本内容! public static String getSpeShortString(String s, int len, String fillstr) { int ilen = Math.max(0, len - fillstr.length());300-3=297!0与它比较之 char ch = ' ' int reallen = 0; for (int i = 0; i < s.length(); i++) { ch = s.charAt(i); if (((int) ch > 32) && ((int) ch < 128)) { //Ascill reallen++; } else { reallen += 2; } } if (reallen <= len) { return s; } StringBuffer buf = new StringBuffer(); reallen = 0; for (int i = 0; i < s.length(); i++) { ch = s.charAt(i); buf.append(ch); if (((int) ch > 32) && ((int) ch < 128)) { reallen++; } else { reallen += 2; } if (reallen >= ilen) { return buf.append(fillstr).toString(); } } return buf.toString(); } */ } else { forum.setQuoteText(this.getForumDetail(mainForum, false)); } } } } else { //需要审核帖子!!!! mainForum.setLastPostTitle(forum.getTitle()); mainForum.setLastTime(forum.getPostTime());//不加入回复数 if (forum.getParentID().equals(forum.getMainID())) { // 回复的是主帖 if (isQuote) { forum.setQuoteEditType(mainForum.getEditType()); if (this.getSysConfig().getQuoteMaxSize() > 0) { forum.setQuoteText(BBSCSUtil.getSpeShortString(this.getForumDetail(mainForum, false), this .getSysConfig().getQuoteMaxSize(), "...")); } else { forum.setQuoteText(this.getForumDetail(mainForum, false)); } } } }//结束了大的if!注意两者由于主贴有特别的edittype,才对mainForum进行操作! mainForum = this.getForumDAO().saveOrUpdateForum(mainForum);//更新 if (mainForum.getReNum() == this.getSysConfig().getForumHotRes()) { // 回复次数达到热贴标准,增加发帖人人缘系数 UserInfo mui = this.getUserInfoDAO().findUserInfoById(mainForum.getUserID()); if (mui != null) { mui.setUserKnow(mui.getUserKnow() + 1); this.getUserInfoDAO().saveUserInfo(mui); this.getUserInfoFileIO().writeUserFile(mui); }//更新用户信息 } Forum reForum = mainForum;//假的! if (!forum.getParentID().equals(forum.getMainID())) { // 回复的不是主帖 reForum = this.getForumDAO().findForumByID(forum.getParentID(), forum.getBoardID()); if (reForum != null) { if (reForum.getCanNotRe() == 0) { reForum.setCanNotRe(1); reForum = this.getForumDAO().saveOrUpdateForum(reForum);//保存真正所回复的帖子信息! } if (isQuote) { forum.setQuoteEditType(reForum.getEditType()); String reDetail = this.getForumDetail(reForum, false); if (this.getSysConfig().getQuoteMaxSize() > 0) { forum.setQuoteText(BBSCSUtil.getSpeShortString(reDetail, this.getSysConfig() .getQuoteMaxSize(), "...")); } else { forum.setQuoteText(reDetail); } } } } // 处理文章标题 if (forum.getTitle().equalsIgnoreCase(Constant.RE)) { if (reForum != null) { if (reForum.getTitle().startsWith(Constant.RE)) { forum.setTitle(reForum.getTitle());//如果主贴有re:,则回贴一致有! } else { if (BBSCSUtil.getSysCharsetStrLength(Constant.RE + reForum.getTitle()) > 90) /** public static int getSysCharsetStrLength(String txt) { try { return txt.getBytes(Constant.CHARSET).length; } catch (UnsupportedEncodingException ex) { return txt.length(); } } */ { forum.setTitle(reForum.getTitle()); } else { forum.setTitle(Constant.RE + reForum.getTitle()); } } } } forum = this.getForumDAO().saveOrUpdateForum(forum);//更新下回贴! //下面是附件的操作! if (Constant.POST_STORAGE_MODE == 0) { if (uploadFile != null) { String fileName = "File_" + forum.getId() + "_" + System.currentTimeMillis() + "." + FilenameUtils.getExtension(uploadFile.getFileName()); String toFilePath = BBSCSUtil.getUpFilePath(forum.getBoardID(), forum.getPostTime()); this.getForumUploadFile().saveUploadFile(toFilePath + fileName, uploadFile, this.getSysConfig()); forum.setHaveAttachFile(1); forum.getAttachFileName().add(fileName); forum = this.getForumDAO().saveOrUpdateForum(forum); } } else { if (uploadFile != null) { String fileName = "File_" + forum.getId() + "_" + System.currentTimeMillis() + "." + FilenameUtils.getExtension(uploadFile.getFileName()); String toFilePath = BBSCSUtil.getUpFilePath(forum.getBoardID(), forum.getPostTime()); this.getForumUploadFile().saveUploadFile(toFilePath + fileName, uploadFile, this.getSysConfig()); forum.setHaveAttachFile(1); forum.getAttachFileName().add(fileName); } String postFileName = "P_" + forum.getBoardID() + "_" + forum.getId() + ".txt"; File postFile = new File(this.getForumConfig().getForumPath(forum.getBoardID(), forum.getPostTime()) + postFileName); FileUtils.writeStringToFile(postFile, detail, Constant.CHARSET); forum.setDetail(postFileName); forum = this.getForumDAO().saveOrUpdateForum(forum); } if (board.getAuditPost() == 0 && board.getAddUserPostNum() == 1) { // 不需要审核,并且版区为增加用户发帖数量 ui.setArticleNum(ui.getArticleNum() + 1); ui.setExperience(ui.getExperience() + 1); // 回帖增加经验值1点。 ui = this.getUserInfoDAO().saveUserInfo(ui); this.getUserInfoFileIO().writeUserFile(ui);//写入用户文件! } if (forum.getEmailInform() != 0 || forum.getMsgInform() != 0) { Subscibe subs = this.getSubscibeFactory().getInstance(forum.getBoardID()); subs.setBoardID(forum.getBoardID()); subs.setCreateTime(new Date()); subs.setEmailinform(forum.getEmailInform()); subs.setMsginform(forum.getMsgInform()); subs.setNickName(ui.getNickName()); subs.setPostID(forum.getId()); subs.setPostTitle(forum.getTitle()); subs.setUserEmail(ui.getEmail()); subs.setUserID(ui.getId()); subs.setUserName(ui.getUserName()); subs.setUserLocale(ui.getUserLocale()); this.getSubscibeDAO().saveSubscibe(subs); } this.getSubscibeQueue().add(forum); /**加入队列中!是一个同步访问方法: public synchronized void add(Object o) { aVector.add(o); } */ return forum; } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } 这个时候,让我们回顾一下applicationContext.xml中的内容: <bean id="forumServiceTarget" class="com.laoer.bbscs.service.imp.ForumServiceImp"> <property name="forumDAO"> <ref local="forumMainDAO" /> </property> <property name="userInfoDAO"> <ref local="userInfoDAO" /> </property> <property name="forumUploadFile"> <ref local="forumUploadFile" /> </property> <property name="sysListObjCache"> <ref local="sysListObjCache" /> </property> <property name="sysConfig"> <ref local="sysConfig" /> </property> <property name="userInfoFileIO"> <ref local="userInfoFileIO" /> </property> <property name="subscibeFactory"> <ref local="subscibeFactory" /> </property> <property name="subscibeDAO"> <ref local="subscibeDAO" /> </property> <property name="boardDAO"> <ref local="boardDAO" /> </property> <property name="subscibeQueue"> <ref local="subscibeQueue" /> </property> <property name="forumBuyDAO"> <ref local="forumBuyDAO" /> </property> <property name="voteDAO"> <ref local="voteDAO" /> </property> <property name="voteItemDAO"> <ref local="voteItemDAO" /> </property> <property name="commendDAO"> <ref local="commendDAO" /> </property> <property name="commendFileIO"> <ref local="commendFileIO" /> </property> <property name="forumHistoryDAO"> <ref local="forumHistoryDAO" /> </property> <property name="forumConfig"> <ref local="forumConfig" /> </property> <property name="postCache"> <ref local="postCache" /> </property> </bean> OK!Go On!下面是创建投票帖子的,省略了后面的部分! public Forum createVoteForum(Forum forum, Board board, Vote vote, UserInfo ui, String voteItem) throws BbscsException { try { String[] details = voteItem.split(" ");//投票项字符串数组! vote = this.getVoteDAO().saveVote(vote);//Vote保存! for (int i = 0; i < details.length; i++) { VoteItem vi = new VoteItem(); vi.setItem(details[i]); vi.setItemValue(0); vi.setVoteID(vote.getId()); this.getVoteItemDAO().saveVoteItem(vi);//VoteItem保存 } forum.setVoteID(vote.getId()); forum = this.getForumDAO().saveOrUpdateForum(forum); forum.setMainID(forum.getId()); forum = this.getForumDAO().saveOrUpdateForum(forum);//帖子保存 .... 下面这个是删除真实帖子的文件及从数据库中把数据删除之! public void delPostReal(Forum forum) throws BbscsException { try { if (Constant.POST_STORAGE_MODE == 1) { this.getForumUploadFile().delDetailFile(forum); } this.getForumUploadFile().delUploadFile(forum); this.getForumDAO().removeForum(forum); } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } 其中,由delDetailFile删除帖子内容文件,delUploadFile删除附件文件! public void delDetailFile(Forum forum) throws IOException { File postFile = new File(this.getForumConfig().getForumPath(forum.getBoardID(), forum.getPostTime()) + forum.getDetail()); if (postFile.exists()) { FileUtils.forceDelete(postFile); } } public void delUploadFile(Forum f) throws IOException { if (f.getHaveAttachFile() == 1 && f.getAttachFileName() != null && f.getAttachFileName().size() > 0) { List fl = f.getAttachFileName(); String filePath = BBSCSUtil.getUpFilePath(f.getBoardID(), f.getPostTime()); for (int i = 0; i < fl.size(); i++) { File attachFile = new File(filePath + (String) fl.get(i)); if (attachFile.exists()) { FileUtils.forceDelete(attachFile); } File attachFileSmall = new File(filePath + (String) fl.get(i) + Constant.IMG_SMALL_FILEPREFIX); if (attachFileSmall.exists()) { FileUtils.forceDelete(attachFileSmall); } } } } 接下来,看下复杂的方法:(delaPost(Forum forum,Board board,UserInfo ui)差不多! public void delPosts(List forums, Board board) throws BbscsException { for (int i = 0; i < forums.size(); i++) { try { Forum forum = (Forum) forums.get(i); if (forum.getAuditing() == 0) { // 如果已近是通过审核的帖子 forum = this.getForumDAO().saveOrUpdateForum(forum); // 保存标注删除,包括删除人、时间等信息 ???? if (forum.getIsNew() != 1) { // 如果不是主帖 Forum mainForum = this.getForumDAO().findForumByID(forum.getMainID(), forum.getBoardID()); // 取主帖 if (mainForum != null) { mainForum.setReNum(mainForum.getReNum() - 1); // 减少主帖回复数 if (mainForum.getLastTime() == forum.getPostTime()) { // 如果删除的是最后一个回复帖,要修正主帖表中最后回复人和时间 OrderObj[] oo = { new OrderObj("postTime", Constant.ORDER_DESC) };//查询条件吧!OrderObj是com.laoer.bbscs.comm里面的一个javaBean类,它有两个属性orderBy和ascOrDesc=Constant.ORDER_DESC; /** public OrderObj(String orderBy, int ascOrDesc) { this.orderBy = orderBy; this.ascOrDesc = ascOrDesc; } */ List l = this.getForumDAO().findForumsTopic(mainForum.getBoardID(), mainForum.getId(), 0, 0, oo, 0, 1);//参见DAO!!! if (l != null && !l.isEmpty()) { Forum lastF = (Forum) l.get(0); mainForum.setLastPostNickName(lastF.getNickName()); mainForum.setLastPostTitle(lastF.getTitle()); mainForum.setLastPostUserName(lastF.getUserName()); mainForum.setLastTime(lastF.getPostTime()); } } this.getForumDAO().saveOrUpdateForum(mainForum); // 保存主帖 } } UserInfo ui = this.getUserInfoDAO().findUserInfoById(forum.getUserID()); if (board.getAddUserPostNum() == 1 && ui != null) { // 版区为增加用户发帖数量 if (ui.getArticleNum() > 0) { ui.setArticleNum(ui.getArticleNum() - 1); // 减少用户发帖数 ui = this.getUserInfoDAO().saveUserInfo(ui); this.getUserInfoFileIO().writeUserFile(ui); } if (forum.getIsNew() == 1) { ui.setExperience(ui.getExperience() - 2); // 主帖,扣除发帖人2点经验值 } else { ui.setExperience(ui.getExperience() - 1); // 回帖,扣除发帖人1点经验值 } } if (forum.getElite() != 0 || forum.getCommend() != 0) { // 如果是精华或推荐 if (forum.getElite() != 0) { if (ui.getArticleEliteNum() >= 1) { ui.setArticleEliteNum(ui.getArticleEliteNum() - 1); } else { ui.setArticleEliteNum(0); } ui.setLiterary(ui.getLiterary() - 3); } if (forum.getCommend() != 0) { ui.setLiterary(ui.getLiterary() - 1); this.getCommendDAO().removeCommend(forum.getId()); } ui = this.getUserInfoDAO().saveUserInfo(ui); this.getUserInfoFileIO().writeUserFile(ui); } if (forum.getIsNew() == 1) { // 如果是主帖,尝试从新帖Cache中清除,只有主贴才从Cache去之 this.getSysListObjCache().remove(Constant.FORUM_NEW_CACHE_NAME); } } else { // 如果是尚未审核 forum = this.getForumDAO().saveOrUpdateForum(forum); // 保存标注删除,包括删除人、时间等信息 } } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } } 另外: public void delPostsReal(List ids) throws BbscsException { List forums = this.getForumDAO().findForumsInIds(ids); for (int i = 0; i < forums.size(); i++) { Forum forum = (Forum) forums.get(i); if ((System.currentTimeMillis() - forum.getDelTime()) > 7 * 24 * 3600 * 1000) {//大于7天就真正删除哦! try { if (Constant.POST_STORAGE_MODE == 1) { this.getForumUploadFile().delDetailFile(forum); } this.getForumUploadFile().delUploadFile(forum); this.getForumDAO().removeForum(forum); } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } } } 看个查找生成分页列表的两个方法: public PageList findForums(long bid, int isNew, int delSign, int auditing, int auditingAttachFile, OrderObj[] oo, Pages pages) { PageList pl = new PageList(); if (pages.getTotalNum() == -1) { pages.setTotalNum(this.getForumDAO().getForumNum(bid, isNew, delSign, auditing, auditingAttachFile)); } pages.executeCount(); List l = this.getForumDAO().findForums(bid, isNew, delSign, auditing, auditingAttachFile, oo, pages.getSpage(), pages.getPerPageNum()); pl.setObjectList(l); pl.setPages(pages); return pl; } public PageList findForumsAll(long bid, Pages pages) { OrderObj oo0 = new OrderObj("isTop", Constant.ORDER_DESC); OrderObj oo1 = new OrderObj("lastTime", Constant.ORDER_DESC); OrderObj[] oo = { oo0, oo1 }; return this.findForums(bid, -1, 0, 0, -1, oo, pages); } 还有重载的方法: public List findForumsAllNew(int num) { OrderObj[] oo = { new OrderObj("postTime", Constant.ORDER_DESC) }; return this.getForumDAO().findForums(-1, 1, 0, 0, -1, oo, 0, num); } public List findForumsAllNewCache(int num) { List l = (List) this.getSysListObjCache().get(Constant.FORUM_NEW_CACHE_NAME); if (l == null) { OrderObj[] oo = { new OrderObj("postTime", Constant.ORDER_DESC) }; l = this.getForumDAO().findForums(-1, 1, 0, 0, -1, oo, 0, num); this.getSysListObjCache().add(Constant.FORUM_NEW_CACHE_NAME, l); } return l; } 下面是恢复帖子方法: public void saveForumsResume(List ids, Board board) throws BbscsException { List forums = this.getForumDAO().findForumsInIds(ids); for (int i = 0; i < forums.size(); i++) { Forum forum = (Forum) forums.get(i); if (forum.getAuditing() == 0) { // 已通过审核的帖子恢复 forum.setDelSign(0); if (forum.getIndexStatus() == Constant.INDEX_STATUS_NO_INDEX_TO_DEL) { forum.setIndexStatus(Constant.INDEX_STATUS_NO_INDEX); } else if (forum.getIndexStatus() == Constant.INDEX_STATUS_DELED) { forum.setIndexStatus(Constant.INDEX_STATUS_NO_INDEX); } try { this.getForumDAO().saveOrUpdateForum(forum); if (!forum.getId().equals(forum.getMainID())) { Forum mainForum = this.getForumDAO().findForumByID(forum.getMainID(), forum.getBoardID()); if (mainForum != null) { //mainForum修改 mainForum.setReNum(mainForum.getReNum() + 1); this.getForumDAO().saveOrUpdateForum(mainForum); } } if (board.getAddUserPostNum() == 1) { UserInfo ui = this.getUserInfoDAO().findUserInfoById(forum.getUserID()); if (ui != null) {//用户信息修改 ui.setArticleNum(ui.getArticleNum() + 1); if (forum.getIsNew() == 1) { ui.setExperience(ui.getExperience() + 2); // 主帖增加2点经验值 } else { ui.setExperience(ui.getExperience() + 1); // 回帖增加1点经验值 } ui = this.getUserInfoDAO().saveUserInfo(ui); this.getUserInfoFileIO().writeUserFile(ui); } } } catch (Exception ex) { logger.error(ex); throw new BbscsException(ex); } } else { // 未通过审核的被删除的帖子恢复 forum.setDelSign(0); try { this.getForumDAO().saveOrUpdateForum(forum); } catch (Exception ex1) { logger.error(ex1); throw new BbscsException(ex1); } } } } public static final int INDEX_STATUS_NO_INDEX = 0; public static final int INDEX_STATUS_INDEXED = 1; public static final int INDEX_STATUS_NEED_UPDTAE = 2; public static final int INDEX_STATUS_NEED_DEL = 3; public static final int INDEX_STATUS_DELED = 4; public static final int INDEX_STATUS_NO_INDEX_TO_DEL = 5; public static final int INDEX_STATUS_UPDATE_TO_DEL = 6; 对于其它类似,不在继续下去了!我们将直接看DAO的实现层:(由于DAO层简单,下面只是列出我个人认为较有代表性的方法) public List findForums(final long bid, final int isNew, final int delSign, final int auditing, final OrderObj[] oo) {//参数为-1,表示此项忽略,否则为增加相应的条件 return getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException { Criteria c = s.createCriteria(getForumClass()); if (bid != -1) { c.add(Restrictions.eq("boardID", new Long(bid))); } if (isNew != -1) { c.add(Restrictions.eq("isNew", new Integer(isNew))); } if (delSign != -1) { c.add(Restrictions.eq("delSign", new Integer(delSign))); } if (auditing != -1) { c.add(Restrictions.eq("auditing", new Integer(auditing))); } if (oo != null && oo.length > 0) { for (int i = 0; i < oo.length; i++) { if (StringUtils.isNotBlank(oo[i].getOrderBy())) { if (oo[i].getAscOrDesc() == Constant.ORDER_ASC) { c.addOrder(Order.asc(oo[i].getOrderBy()));//Order序!!! } if (oo[i].getAscOrDesc() == Constant.ORDER_DESC) { c.addOrder(Order.desc(oo[i].getOrderBy())); } } } } return c.list(); } }); } public List findForumsElite(long bid, long elite, long eliteId) { Object[] o = { new Long(bid), new Long(elite), new Long(eliteId), new Integer(0) }; String sql = "from " + this.getObjName() + " where boardID = ? and elite = ? and eliteID = ? and delSign = ?"; return this.getHibernateTemplate().find(sql, o); } /** private String getObjName() { return "Forum" + this.flag; } private String flag = "Main"; */ public List findForumsRealDelAll(long bid, long atime) { Object[] o = { new Long(bid), new Long(atime) }; String sql = "from " + this.getObjName() + " where boardID = ? and delSign = 1 and delTime < ?"; return this.getHibernateTemplate().find(sql, o); } public List findForumsToHistory(long atime) { String sql = "from ForumMain where isNew = 1 and elite = 0 and lastTime <= ?"; return this.getHibernateTemplate().find(sql, new Long(atime)); } public long getForumNumHotTopic(long bid, int reNum, int click) { Object[] o = { new Long(bid), new Integer(1), new Integer(0), new Integer(0), new Integer(reNum), new Integer(click) }; String sql = "select count(id) from " + this.getObjName() + " where boardID = ? and isNew = ? and delSign = ? and auditing = ? and (reNum >= ? or click >= ?)"; List l = this.getHibernateTemplate().find(sql, o); if (l == null || l.isEmpty()) { return 0; } else { return ((Long) l.get(0)).longValue(); } } 下面是一个关于搜索帖子的方法: public List getSearchList(final long bid, final String con, final String text, final int delSign, final int auditing, final String orderby, final int ascOrDesc, final int firstResult, final int maxResults) { return getHibernateTemplate().executeFind(new HibernateCallback() { public Object doInHibernate(Session s) throws HibernateException { Criteria c = s.createCriteria(getForumClass()); c.add(Restrictions.eq("boardID", new Long(bid))); c.add(Restrictions.like(con, "%" + text + "%")); if (delSign != -1) { c.add(Restrictions.eq("delSign", new Integer(delSign))); } if (auditing != -1) { c.add(Restrictions.eq("auditing", new Integer(auditing))); } if (StringUtils.isNotBlank(orderby)) { if (ascOrDesc == Constant.ORDER_ASC) { c.addOrder(Order.asc(orderby)); } if (ascOrDesc == Constant.ORDER_DESC) { c.addOrder(Order.desc(orderby)); } } c.setFirstResult(firstResult); c.setMaxResults(maxResults); return c.list(); } }); } 其它的省略之!已经将主要的Forum服务给分析完了.OK! 收藏(0)