[java 手把手教程][第二季]java 後端部落格系統文章系統——No6

pc859107393發表於2019-02-20

本期主要是文章儲存功能,涉及到草稿、文章釋出、歷史這三個要點。

專案github地址:github.com/pc859107393…

實時專案同步的地址是國內的碼雲:git.oschina.net/859107393/m…

我的簡書首頁是:www.jianshu.com/users/86b79…

上一期是:[手把手教程][第二季]java 後端部落格系統文章系統——No5

[java 手把手教程][第二季]java 後端部落格系統文章系統——No6
行走的java全棧

工具

  • IDE為idea16
  • JDK環境為1.8
  • gradle構建,版本:2.14.1
  • Mysql版本為5.5.27
  • Tomcat版本為7.0.52
  • 流程圖繪製(xmind)
  • 建模分析軟體PowerDesigner16.5
  • 資料庫工具MySQLWorkBench,版本:6.3.7build

本期目標

  • mybatis一些簡單的進階使用
  • 日誌功能的記錄
  • 文章釋出
  • 草稿儲存

實用技巧

  1. 通過物件導向建模後,我們可以逆向工程生成對應的bean甚至一些其他的程式碼
  2. mybatis可以使用動態sql語句執行復雜操作
  3. 使用pojo包裝型別進行資料庫操作
  4. 使用高階對映可以實現複雜的業務場景
  5. 延遲載入和快取開發
  6. mybatis逆向工程工具

文章提交

上期我們通過對資料庫的分析,仔仔細細的探索了一遍WordPress程式執行時候文章提交和草稿提交的區別和共同點,簡略的概括下如下:

  • 文章和草稿區別是postType不同
  • 文章和草稿都有日誌記錄
  • 日誌的postStatus都是closed
  • 日誌的postParent始終是指向其文章的id
  • ···

更為詳細的文章,建議去我的上一篇文章檢視。

既然我們已經大概明白了怎麼去實現,那麼現在我們需要的就是進行實驗。

首先老規矩,先從我們的dao層實現開始,我們爭取把各個功能模組解耦,那麼我們就不能過於在dao層限制,所以我們需要在dao層實現資料(文章、草稿、歷史記錄)插入。所以我們先生成dao介面如下:

@Repository("postDao")
public interface PostDao extends Dao<PostCustom> {

    /**
     * 根據擴充套件類來查詢文章資訊,文章為postBean
     * @param postCustom post的擴充套件類,使用註解來說明物件名稱
     * @return 受影響的行數
     */
    @Override
    int add(@Param(value = "postCustom") PostCustom postCustom);

    //////////////////////////////////////分割線///////////////////////////

    @Override
    int del(PostCustom postBean);

    @Override
    int update(PostCustom postBean);

    @Override
    PostCustom findOneById(Serializable postId);

    @Override
    List<PostCustom> findAll();


    List<PostCustom> findAllNew();

    /**
     * 分頁查詢
     *
     * @param offset 起始位置
     * @param limit  每頁數量
     * @return
     */
    List<PostCustom> findAllPublish(@Param("offset") int offset, @Param("limit") int limit);

    int getAllCount();

    List<PostCustom> getAllPostDateCount();
}

//在上面分割線上面的,就是我們這次提交文章的介面,下面我們接著完成mapper

<insert id="add" parameterType="PostCustom">
        <!--
            selectKey是在insert語句執行的時候可以執行的語句。
            keyProperty是指定需要記錄的key
            order是選擇在insert執行之前還是之後執行selectKey中的語句
            resultType是返回的資料型別

            SELECT LAST_INSERT_ID();是返回最近執行insert後增加的內容的自增主鍵。注意,這個語句僅僅可用於自增主鍵。
        -->
        <selectKey keyProperty="postCustom.id" order="AFTER" resultType="java.lang.String">
            SELECT LAST_INSERT_ID();
        </selectKey>
        <choose>
            <when test="postCustom.id == null">
                #文章id為空說明是剛開始插入新的文章
                INSERT INTO `wp_posts`
                (`post_author`, `post_date`, `post_date_gmt`, `post_content`, `post_title`, `post_excerpt`,
                `post_status`, `comment_status`, `comment_count`, `ping_status`, `post_password`,
                `post_name`, `to_ping`, `pinged`, `post_modified`, `post_modified_gmt`,
                `post_content_filtered`, `post_parent`, `guid`, `menu_order`, `post_type`, `post_mime_type`)
                VALUES (#{postCustom.postAuthor},#{postCustom.postDate},#{postCustom.postDateGmt},#{postCustom.postContent}, #{postCustom.postTitle}, #{postCustom.postExcerpt},
                #{postCustom.postStatus},#{postCustom.commentStatus},#{postCustom.commentCount},#{postCustom.pingStatus},#{postCustom.postPassword},#{postCustom.postName},
                #{postCustom.toPing},#{postCustom.pinged},#{postCustom.postModified},#{postCustom.postModifiedGmt},#{postCustom.postContentFiltered},
                #{postCustom.postParent},#{postCustom.guid},#{postCustom.menuOrder},#{postCustom.postType},#{postCustom.postMimeType})

            </when>
            <when test="postCustom.id != null">
                #id不為空說明現在是更新某個文章
#                 UPDATE wp_posts
#                 SET `post_author`=#{postAuthor},`post_date`=#{postDate}
#                 WHERE ID=#{id}
            </when>
        </choose>
    </insert>複製程式碼

上面的語句中我們可以看到在mapper的 insert語句塊中我們插入了selectKey語句塊,主要是用來返回我們插入的條目的自增id。為什麼這裡我們非要獲取到插入的id呢?主要是文章生成後的日誌記錄都是需要文章id才能生成對應的日誌記錄。

同時在inset語句塊中,我們使用了choose+when語句塊生成了動態sql,這樣就能動態的選擇程式執行。

同時在上面的程式碼中我們可以看到上面的when和下面的when語句塊中關於bean的屬性獲取使用方式不一樣!

重點:擴充套件型別,要使用父類的屬性時,必須是物件名.屬性名的形式,物件名從dao的介面設定。(否則直接使用屬性的時候,會異常提示空指標,且提示物件的屬性中沒有該屬性)

小技巧:

  • 在mapper中使用屬性時,最好是物件名.屬性名的方式,同時在dao層中設定物件的名稱(在我們上面dao層的add介面中有說明)
  • mybatis的動態sql可以減少很多額外的方法和mapper中的sql編寫,所以必須掌握動態sql
  • 當屬性較多的時候,我們一定不能忘記單元測試,單元測試可以簡單直觀的瞭解到哪裡出錯,減少在後續開發中錯誤檢查的時間。

PostDao的單元測試

上面我們也是提到過單元測試,那麼我們現在看看這個dao的單元測試應該如何完成呢?

首先給大家看看我們文章的bean:

public class PostBean implements Serializable {
    /**
     * 版本號
     */
    private static final long serialVersionUID = 4477235816922585138L;

    private String id;

    private String postAuthor;

    private Date postDate;

    private Date postDateGmt;

    private String postContent;

    private String postTitle;

    private String postExcerpt;

    private String postStatus;

    private String commentStatus;

    private String pingStatus;

    private String postPassword;

    private String postName;

    private String toPing;

    private String pinged;

    private Date postModified;

    private Date postModifiedGmt;

    private String postContentFiltered;

    private String postParent = "0";

    private String guid;

    private Integer menuOrder;

    private String postType;

    private String postMimeType;

    private Long commentCount = 0L;

    //省略get、set

    @Override
    public String toString() {
        return "PostBean{" +
                "id=`" + id + ``` +
                ", postAuthor=`" + postAuthor + ``` +
                ", postDate=" + postDate +
                ", postDateGmt=" + postDateGmt +
                ", postContent=`" + postContent + ``` +
                ", postTitle=`" + postTitle + ``` +
                ", postExcerpt=`" + postExcerpt + ``` +
                ", postStatus=`" + postStatus + ``` +
                ", commentStatus=`" + commentStatus + ``` +
                ", pingStatus=`" + pingStatus + ``` +
                ", postPassword=`" + postPassword + ``` +
                ", postName=`" + postName + ``` +
                ", toPing=`" + toPing + ``` +
                ", pinged=`" + pinged + ``` +
                ", postModified=" + postModified +
                ", postModifiedGmt=" + postModifiedGmt +
                ", postContentFiltered=`" + postContentFiltered + ``` +
                ", postParent=`" + postParent + ``` +
                ", guid=`" + guid + ``` +
                ", menuOrder=" + menuOrder +
                ", postType=`" + postType + ``` +
                ", postMimeType=`" + postMimeType + ``` +
                ", commentCount=" + commentCount +
                `}`;
    }
}複製程式碼

哈哈哈哈哈,是不是特別的多呢?又沒有一種蛋疼菊緊的趕腳?
說實話,一看到這裡的時候,我也很惆悵。靈機一動,感覺可以祭出這個系列教程我們自己手寫的第一個設計模式了!開啟我們的javaBean的建造者模式,go!


import cn.acheng1314.utils.StringUtils;
import com.sun.istack.internal.NotNull;

import java.util.Date;

/**
 * Description:文章上傳的擴充套件
 *
 * @author acheng
 * @date
 */
public class PostCustom extends PostBean {
    private Boolean isPublish;

    public PostCustom() {
    }

    public PostCustom(String id, String postAuthor, Date postDate, Date postDateGmt, String postContent, String postTitle, String postExcerpt, String postStatus, String commentStatus, String pingStatus, String postPassword, String postName, String toPing, String pinged, Date postModified, Date postModifiedGmt, String postContentFiltered, String postParent, String guid, Integer menuOrder, String postType, String postMimeType, Long commentCount) {
        setId(id);
        setPostAuthor(postAuthor);
        setPostDate(postDate);
        setPostDateGmt(postDateGmt);
        setPostContent(postContent);
        setPostContentFiltered(postContentFiltered);
        setPostTitle(postTitle);
        setPostExcerpt(postExcerpt);
        setPostStatus(postStatus);
        setCommentStatus(commentStatus);
        setPingStatus(pingStatus);
        setPostPassword(postPassword);
        setPostName(postName);
        setToPing(toPing);
        setPinged(pinged);
        setPostModified(postModified);
        setPostModifiedGmt(postModifiedGmt);
        setPostParent(postParent);
        setGuid(guid);
        setMenuOrder(menuOrder);
        setPostType(postType);
        setPostMimeType(postMimeType);
        setCommentCount(commentCount);
    }

    public Boolean getPublish() {
        return isPublish;
    }

    public void setPublish(Boolean publish) {
        isPublish = publish;
    }

    /**
     * 建造者,引數過於多的時候考慮使用建造者完成。
     * 靜態內部類不使用的時候是不會被建立的。
     */
    public static class Builder {
        private String id;
        private String postAuthor;
        private Date postDate;
        private Date postDateGmt;
        private String postContent;
        private String postTitle;
        private String postExcerpt;
        private String postStatus;
        private String commentStatus;
        private String pingStatus;
        private String postPassword;
        private String postName;
        private String toPing;
        private String pinged;
        private Date postModified;
        private Date postModifiedGmt;
        private String postContentFiltered;
        private String postParent;
        private String guid;
        private Integer menuOrder;
        private String postType;
        private String postMimeType;
        private Long commentCount;


        public Builder id(String id) {
            this.id = id;
            return this;
        }

        public Builder postAuthor(@NotNull String postAuthor) {
            this.postAuthor = postAuthor;
            return this;
        }

        public Builder postDate(Date postDate) {
            this.postDate = postDate;
            return this;
        }

        public Builder postDateGmt(Date postDateGmt) {
            this.postDateGmt = postDateGmt;
            return this;
        }

        public Builder postContent(String postContent) {
            this.postContent = StringUtils.isEmpty(postContent) ? "" : postContent;
            return this;
        }

        public Builder postTitle(String postTitle) {
            this.postTitle = StringUtils.isEmpty(postTitle) ? "" : postTitle;
            return this;
        }

        public Builder postExcerpt(String postExcerpt) {
            this.postExcerpt = postExcerpt;
            return this;
        }

        public Builder postStatus(String postStatus) {
            this.postStatus = postStatus;
            return this;
        }

        public Builder commentStatus(String commentStatus) {
            this.commentStatus = commentStatus;
            return this;
        }

        public Builder pingStatus(String pingStatus) {
            this.pingStatus = pingStatus;
            return this;
        }

        public Builder postPassword(String postPassword) {
            this.postPassword = postPassword;
            return this;
        }

        public Builder postName(String postName) {
            this.postName = postName;
            return this;
        }

        public Builder toPing(String toPing) {
            this.toPing = toPing;
            return this;
        }

        public Builder pinged(String pinged) {
            this.pinged = pinged;
            return this;
        }

        public Builder postModified(Date postModified) {
            this.postModified = postModified;
            return this;
        }

        public Builder postModifiedGmt(Date postModifiedGmt) {
            this.postModifiedGmt = postModifiedGmt;
            return this;
        }

        public Builder postContentFiltered(String postContentFiltered) {
            this.postContentFiltered = postContentFiltered;
            return this;
        }

        public Builder postParent(String postParent) {
            this.postParent = postParent;
            return this;
        }

        public Builder guid(String guid) {
            this.guid = guid;
            return this;
        }

        public Builder menuOrder(Integer menuOrder) {
            this.menuOrder = menuOrder;
            return this;
        }

        public Builder postType(String postType) {
            this.postType = postType;
            return this;
        }

        public Builder postMimeType(String postMimeType) {
            this.postMimeType = postMimeType;
            return this;
        }

        public Builder commentCount(Long commentCount) {
            this.commentCount = commentCount;
            return this;
        }

        public PostCustom build() {
            return new PostCustom(id,
                    postAuthor,
                    postDate,
                    postDateGmt,
                    postContent,
                    postTitle,
                    postExcerpt,
                    postStatus,
                    commentStatus,
                    pingStatus,
                    postPassword,
                    postName,
                    toPing,
                    pinged,
                    postModified,
                    postModifiedGmt,
                    postContentFiltered,
                    postParent,
                    guid,
                    menuOrder,
                    postType,
                    postMimeType,
                    commentCount);
        }
    }
}複製程式碼

那麼我們ben生成好了後,我們就需要開始寫我們的單元測試,如下:

    @Test
    public void testInsertPost() {
        Date atNow = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        formatter.format(atNow);
//        PostCustom postCustom = new PostCustom();
//        postCustom.setId(null);
//        postCustom.setPublish(true);
//        postCustom.setPostAuthor("001");
//        postCustom.setPostDate(atNow);
//        postCustom.setPostDateGmt(atNow);
//        postCustom.setPostContent("1111111");
//        postCustom.setPostTitle("22222");
//        postCustom.setPostExcerpt("11111289489589458");
//        postCustom.setPostStatus("11111289489589458");
//        postCustom.setCommentStatus("11111289489589458");
//        postCustom.setPingStatus("11111289489589458");
//        postCustom.setPostPassword("11111289489589458");
//        postCustom.setPostName("11111289489589458");
//        postCustom.setToPing("11111289489589458");
//        postCustom.setPinged("11111289489589458");
//        postCustom.setPostModified(atNow);
//        postCustom.setPostModifiedGmt(atNow);
//        postCustom.setPostContentFiltered(formatter.format(atNow));
//        postCustom.setGuid(formatter.format(atNow));
//        postCustom.setMenuOrder(11100111);

//        postCustom.setCommentStatus();
        PostCustom postCustom = PostInitUtils.setPostinfo("0", atNow, "我是一個兵999", "我是一個兵2111", "文章內容````嘛", "www.cc1tv.com");
        postDao.add(postCustom);
        System.out.print("id=" + postCustom.getId());
        postCustom = PostInitUtils.insertPostLog(postCustom.getId(), postCustom.getPostAuthor(), postCustom.getPostTitle(), postCustom.getGuid(), postCustom.getPostContent());
        postDao.add(postCustom);
    }複製程式碼

大家可以看到上面我們的PostCustom還是從我們自己手動封裝的PostInitUtils中獲取的,同時我們的PostCustom還是我們的PostBean的子類。所以這裡就用到了傳說中的pojo包裝型別相關的一些知識(我們這裡並沒有包裝,不過已經用到了擴充套件類),讓我們接著看看PostInitUtils的程式碼:


import cn.acheng1314.domain.PostCustom;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Description:文章上傳
 *
 * @author acheng
 * @date 2017/2/27
 */
public class PostInitUtils {
    public final static String PUBLISH_POST_STATUS = "publish";
    public final static String OPEN_COMMENT_STATUS = "open";
    public final static String OPEN_PING_STATUS = "open";
    public final static String POST_TYPE = "post";
    public final static String INHERIT_POST_STATUS = "inherit";
    public final static String CLOSED_COMMENT_STATUS = "closed";
    public final static String REVISION_POST_MIME_TYPE = "revision";

    /**
     * 釋出文章,這時候沒有ID
     *
     * @param postAuthor 文章作者
     * @param postDate  文章提交日期
     * @param postTitle 文章標題
     * @param postName  文章名字一般類說是文章標題進行url轉碼
     * @param postContent   文章內容
     * @param guid  文章的guid
     * @return
     */
    public static PostCustom setPostinfo(
            String postAuthor
            , Date postDate
            , String postTitle
            , String postName
            , String postContent
            , String guid) {
        Date atNow = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        formatter.format(atNow);
        return new PostCustom.Builder()
                .id(null)
                .postAuthor(postAuthor)
                .postDate(postDate)
                .postDateGmt(atNow)
                .postTitle(postTitle)
                .postExcerpt("")
                .postStatus(PUBLISH_POST_STATUS)
                .commentStatus(OPEN_COMMENT_STATUS)
                .commentCount(0L)
                .pingStatus(OPEN_PING_STATUS)
                .postPassword("")
                .postName(postName)
                .toPing("")
                .pinged("")
                .postModified(atNow)
                .postModifiedGmt(atNow)
                .postContentFiltered("")
                .postContent(postContent)
                .postParent("0")
                .guid(guid)
                .menuOrder(0)
                .postType(POST_TYPE)
                .postMimeType("")
                .build();
    }

    /**
     * 文章操作時候,插入日誌
     * @param parentId 修改文章的ID
     * @param postAuthor    文章作者
     * @param postTitle 文章標題
     * @param guid  文章的訪問標記
     * @param postContent   文章內容
     * @return
     */
    public static PostCustom insertPostLog(String parentId, String postAuthor, String postTitle, String guid, String postContent){
        Date atNow = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        formatter.format(atNow);
        return new PostCustom.Builder()
                .id(null)
                .postAuthor(postAuthor)
                .postDate(atNow)
                .postDateGmt(atNow)
                .postTitle(postTitle)
                .postContent(postContent)
                .postExcerpt("")
                .postStatus(INHERIT_POST_STATUS)
                .commentStatus(CLOSED_COMMENT_STATUS)
                .commentCount(0L)
                .pingStatus(CLOSED_COMMENT_STATUS)
                .postPassword("")
                .postName(parentId+"-revision-v1")
                .toPing("")
                .pinged("")
                .postModified(atNow)
                .postModifiedGmt(atNow)
                .postContentFiltered("")
                .postParent(parentId)
                .guid(guid)
                .menuOrder(0)
                .postType(REVISION_POST_MIME_TYPE)
                .postMimeType("")
                .build();
    }
}複製程式碼

這裡我們可以看到我們對外開放的核心也就文章核心資訊相關的一些東西了,那麼這樣我們就能很好的控制資料。

現在我們可以看下資料庫,最新多出來的資料和文章首頁插入的資料如下:

[java 手把手教程][第二季]java 後端部落格系統文章系統——No6
部落格第七章-daoTest寫入文章

我們今天的主幹內容到此基本完成,剩下的從dao到前臺介面適配等等基本上騷年們自己都可以完成至此不在贅述。

核心總結

  1. mapper中的文章寫入處標準的主要資料庫操作程式碼應該是insert,所以文章更新的應該單獨開放介面來完成
  2. pojo實際上是文章的包裝型別,javaBean中不但有擴充套件型別(子類是父類的擴充套件型別)更有包裝型別,包裝型別我們在常用的json輸出的時候有用到,大家可以自己多多動腦。
  3. dao層應該專注於提供單一資料驅動,Service對外提供資料介面,可以適當參與業務場景的資料關係處理。

相關文章