javahih
V2EX  ›  Java

基于 Spring Data JPA 框架的文章归档实现

  •  
  •   javahih · Jan 15, 2018 · 4221 views
    This topic created in 3074 days ago, the information mentioned may be changed or developed.

    目录

    [TOC]

    前言

    最近在写自己的个人博客系统,框架采用 SpringMVC、Spring4.0、Spring Data/JPA 组合,本博客就文档归档功能在 Spring Data JPA 框架下是如何实现的进行记录。 现在可以 star(收藏),watch(关注),但是还在开发中,所以还是还在先别下载 项目 github: https://github.com/u014427391/myblog

    文章信息设计

    数据暂时这样设计,仅供学习参考,对于文章评论回复,栏目之间的关联还没设计,不过本博客的目的是记录文档归档功能的实现,这个并不会影响 这里写图片描述

    VO 类:全部采用注解,注意因为我数据库表名为 article,所以不需要写 @Table 注解,表名为其它的话,就需要自己添加 @Table 注解了

    package net.myblog.entity;
    
    import java.util.Date;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.Temporal;
    import javax.persistence.TemporalType;
    
    /**
     * 博客系统文章信息的实体类
     * @author Nicky
     */
    @Entity
    public class Article {
    	
    	/** 文章 Id,自增**/
    	private int articleId;
    	
    	/** 文章名称**/
    	private String articleName;
    	
    	/** 文章发布时间**/
    	private Date articleTime;
    	
    	/** 图片路径,测试**/
    	private String imgPath;
    	
    	/** 文章内容**/
    	private String articleContent;
    	
    	/** 查看人数**/
    	private int articleClick;
    	
    	/** 是否博主推荐。0 为否; 1 为是**/
    	private int articleSupport;
    	
    	/** 是否置顶。0 为; 1 为是**/
    	private int articleUp;
    	
    	/** 文章类别。0 为私有,1 为公开,2 为仅好友查看**/
    	private int articleType;
    	
    	@GeneratedValue
    	@Id
    	public int getArticleId() {
    		return articleId;
    	}
    
    	public void setArticleId(int articleId) {
    		this.articleId = articleId;
    	}
    
    	@Column(length=100, nullable=false)
    	public String getArticleName() {
    		return articleName;
    	}
    
    	public void setArticleName(String articleName) {
    		this.articleName = articleName;
    	}
    
    	@Temporal(TemporalType.DATE)
    	@Column(nullable=false, updatable=false)
    	public Date getArticleTime() {
    		return articleTime;
    	}
    
    	public void setArticleTime(Date articleTime) {
    		this.articleTime = articleTime;
    	}
    
    	@Column(length=100)
    	public String getImgPath() {
    		return imgPath;
    	}
    
    	public void setImgPath(String imgPath) {
    		this.imgPath = imgPath;
    	}
    
    	@Column(nullable=false)
    	public String getArticleContent() {
    		return articleContent;
    	}
    
    	public void setArticleContent(String articleContent) {
    		this.articleContent = articleContent;
    	}
    
    	public int getArticleClick() {
    		return articleClick;
    	}
    
    	public void setArticleClick(int articleClick) {
    		this.articleClick = articleClick;
    	}
    
    	public int getArticleSupport() {
    		return articleSupport;
    	}
    
    	public void setArticleSupport(int articleSupport) {
    		this.articleSupport = articleSupport;
    	}
    
    	public int getArticleUp() {
    		return articleUp;
    	}
    
    	public void setArticleUp(int articleUp) {
    		this.articleUp = articleUp;
    	}
    
    	@Column(nullable=false)
    	public int getArticleType() {
    		return articleType;
    	}
    
    	public void setArticleType(int articleType) {
    		this.articleType = articleType;
    	}
    }
    
    

    代码实现步骤

    文章表里有很多数据,要按照年月获取文章进行归档的话,我们可以使用如下 SQL 对数据进行分组

    SELECT YEAR(articleTime) AS 'year',MONTH(articleTime) AS 'month',COUNT(*) AS 'count' FROM article 
    	GROUP BY YEAR(articleTime) DESC,MONTH(articleTime);
    

    然后编写数据库层的 Repository 类,类实现 Spring Data JPA 提供的接口

    package net.myblog.repository;
    
    import java.util.Date;
    import java.util.List;
    
    import net.myblog.entity.Article;
    
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.data.repository.PagingAndSortingRepository;
    import org.springframework.data.repository.query.Param;
    
    public interface ArticleRepository extends PagingAndSortingRepository<Article,Integer>{
    	/**
    	 * 文章归档信息获取
    	 * @return
    	 */
    	@Query(value="select year(a.articleTime) as year,month(a.articleTime) as month,"
    			+ "count(a) as count from Article a group by year(a.articleTime),month(a.articleTime)",
    			countQuery="select count(1) from (select count(1) from Article a group by year(a.articleTime),month(a.articleTime))")
    	public List<Object[]> findArticleGroupByTime();
    	
    }
    

    然后在 Service 类,用 @Autowired 注解调用

    package net.myblog.service;
    
    import java.util.Date;
    import java.util.List;
    
    import net.myblog.entity.Article;
    import net.myblog.repository.ArticleRepository;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.PageRequest;
    import org.springframework.data.domain.Sort.Direction;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service
    public class ArticleService {
    	
    	@Autowired ArticleRepository articleRepository;
    	/**
    	 * 文章归档信息获取
    	 * @return
    	 */
    	@Transactional
    	public List<Object[]> findArticleGroupByTime(){
    		return articleRepository.findArticleGroupByTime();
    	}
    	
    }
    
    

    然后在 Controller 里调用,用的是 SpringMVC 框架

    package net.myblog.web.controller;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Vector;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import net.myblog.service.ArticleService;
    import net.sf.json.JSONArray;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Sort.Direction;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    public class BlogIndexController extends BaseController{
    	
    	@Autowired
    	ArticleService articleService;
    	
    	/**
    	 * 访问博客主页
    	 * @return
    	 */
    	@RequestMapping(value="/toblog",produces="text/html;charset=UTF-8")
    	public ModelAndView toBlog(HttpServletRequest request, HttpServletResponse response, Model model)throws ClassNotFoundException{
    		
    		//获取归档文章信息
    		List<Object[]> archiveArticles = articleService.findArticleGroupByTime();
    		
    		model.addAttribute("archiveArticles", archiveArticles);
    		mv.setViewName("myblog/frame/index");
    		return mv;
    	}
    	
    }
    
    

    在 JSP 页面调用显示:

    <h2><p>文章归档</p></h2>
    <ul class="news">
      <c:choose>
    	<c:when test="${not empty archiveArticles }">
    		<c:forEach items="${archiveArticles }" var="ac">
    		<li><a href="getArchiveArticles.do?yearmonth=${ac[0]}-${ac[1]}">
    		${ac[0]}年${ac[1]}月(${ac[2] })</a></li>
    		</c:forEach>
    	</c:when>
    	<c:otherwise>
    		<li><a href="#">没有相关数据</a></li>
    	</c:otherwise>  
      </c:choose>
    </ul>
    

    效果如图所示: 这里写图片描述

    文档归档信息查询

    然后介绍点击文档归档信息后,获取文章信息的实现,其实也就是按年月查询文档信息

    在 Repository 类里添加方法:

    /**
    	 * 按月份获取文章信息
    	 * @param month
    	 * 			月份数
    	 * @return
    	 */
    	@Query("from Article a where date_format(a.articleTime,'%Y%m')=date_format((:yearmonth),'%Y%m') "
    			+ "order by articleTime desc")
    	public List<Article> findArticleByMonth(@Param("yearmonth")Date yearmonth);
    
    

    Service 类里调用:

    	/**
    	 * 按月份获取文章信息
    	 * @param month
    	 * @return
    	 */
    	@Transactional
    	public List<Article> findArticleByMonth(Date month){
    		return articleRepository.findArticleByMonth(month);
    	}
    

    在 JSP 页面写入,getArchiveArticles.do 就是要访问的 url,传入 yearmonth 参数就可以

    <h2><p>文章归档</p></h2>
    <ul class="news">
      <c:choose>
    	<c:when test="${not empty archiveArticles }">
    		<c:forEach items="${archiveArticles }" var="ac">
    		<li><a href="getArchiveArticles.do?yearmonth=${ac[0]}-${ac[1]}">
    		${ac[0]}年${ac[1]}月(${ac[2] })</a></li>
    		</c:forEach>
    	</c:when>
    	<c:otherwise>
    		<li><a href="#">没有相关数据</a></li>
    	</c:otherwise>  
      </c:choose>
    </ul>
    

    在 Controller 类里进行处理:

    @RequestMapping("/getArchiveArticles")
    	public ModelAndView getArticleByMonth(HttpServletRequest request){
    		String yearMonthString = request.getParameter("yearmonth");
    		System.out.println("month:"+yearMonthString);
    		ModelAndView mv = this.getModelAndView();
    		
    		Date yearmonth = DateUtils.parse("yyyy-MM", yearMonthString);
    		
    		List<Article> articles = articleService.findArticleByMonth(yearmonth);
    
    		mv.addObject("articles", articles);
    		mv.setViewName("myblog/article/archive_articles");
    		return mv;
    	}
    

    这里写图片描述

    附录(工具类、公共类代码)

    DateUtils.java、BaseController.java 类 DateUtil.java

    package net.myblog.utils;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Locale;
    
    import net.myblog.core.Constants;
    
    public class DateUtils {
    	
    	public static String formatDate(Date date) throws ParseException{
    		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    		return dateFormat.format(date);
    	}
    	
    	 /**
    	   * 解析日期,注:此处为严格模式解析,即 20151809 这样的日期会解析错误
    	   * 
    	   * @param pattern
    	   * @param date
    	   * @return
    	   */
    	  public static Date parse(String pattern, String date){
    	    return parse(pattern, date, Constants.LOCALE_CHINA);
    	  }
    
    	  /**
    	   * 解析日期,注:此处为严格模式解析,即 20151809 这样的日期会解析错误
    	   * 
    	   * @param pattern
    	   * @param date
    	   * @param locale
    	   * @return
    	   */
    	  public static Date parse(String pattern, String date, Locale locale){
    	    SimpleDateFormat format = new SimpleDateFormat(pattern, locale);
    	    format.setLenient(false);
    	    Date result = null;
    	    try{
    	      result = format.parse(date);
    	    }catch(Exception e){
    	      e.printStackTrace();
    	    }
    
    	    return result;
    	  }
    
    }
    
    

    BaseController.java:

    package net.myblog.web.controller;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.servlet.ModelAndView;
    
    public class BaseController {
    	
    	/**
    	 * 得到 request 对象
    	 */
    	public HttpServletRequest getRequest() {
    		HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
    		
    		return request;
    	}
    	
    	/**
    	 * 得到 ModelAndView
    	 */
    	public ModelAndView getModelAndView(){
    		return new ModelAndView();
    	}
    
    
    }
    
    
    15 replies    2018-12-20 06:00:30 +08:00
    cuqk
        1
    cuqk  
       Jan 15, 2018
    看到 DateUtils.formatDate()每次方法调用会生成一个 SimpleDataFormat...
    badbye
        2
    badbye  
       Jan 15, 2018
    同 1l,试试 java8 时间 api 或 joda-time
    pelloz
        3
    pelloz  
       Jan 15, 2018
    @cuqk SimpleDataFormat 线程不安全,每次使用 new 一个新的出来是正确的。
    如果是 java8 的话应该使用新的时间 API。
    wangmm
        4
    wangmm  
       Jan 15, 2018
    不错学习了
    cuqk
        5
    cuqk  
       Jan 15, 2018 via Android
    @pelloz 所以为什么不用 ThreadLocal
    pelloz
        6
    pelloz  
       Jan 15, 2018
    @cuqk 我觉得这里使用 ThreadLocal 增加了不必要的复杂度,new 一个 SimpleDataFormat 并不是一个很费的操作。
    letitbesqzr
        7
    letitbesqzr  
       Jan 15, 2018
    我来吐槽下 Spring data jpa 几点。
    Query creation from method names 这个功能简直难用,稍微条件多一点点的查询,那函数名看着真想打人。。
    用 Query 注解调用 jpql 或者 原生 sql 管理起来并没有 mybatis 方便。

    比较好用的还是 JpaSpecificationExecutor 和 JpaRepository 里的接口,非常灵活
    Charkey
        8
    Charkey  
       Jan 15, 2018
    @letitbesqzr 我是写惯了 mybatis
    letitbesqzr
        9
    letitbesqzr  
       Jan 15, 2018
    @Charkey
    我们一般对于性能要求比较高,系统逻辑不是那么复杂的用 mybatis。
    如果是关系特别复杂并且内部系统,迸发各方面不是那么多要求的会用 jpa。毕竟 jpa 在动态生成条件方面的确方便点,像很多内部系统为了开发速度,很多 sql 根本就不去后台写了,根据前台的表单名称就去自动的和 entity 对应修改了。。但互联网项目不敢这么来。。。
    cuqk
        10
    cuqk  
       Jan 15, 2018 via Android
    @pelloz 与其说是复杂度,倒不如说 ThreadLocal 比直接 new 的上手难度大一些些
    StevenTong
        11
    StevenTong  
       Jan 15, 2018
    entity 不用 lombok 看着难受
    chencn
        12
    chencn  
       Jan 15, 2018 via iPhone
    我倒是很喜欢 jpa 那个写一串又臭又长的方法名就把 sql 省了的设计
    carakan
        13
    carakan  
       Feb 22, 2018
    @letitbesqzr 为什么我觉得 Specification 也很难用啊。。。一查全字段都查出来了
    someonedeng
        14
    someonedeng  
       Feb 27, 2018
    写裸的 sql 挺难受。。
    Allianzcortex
        15
    Allianzcortex  
       Dec 20, 2018
    最近刚好在用,非感谢+收藏不足以表达对楼主的感激之情( D
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1007 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 58ms · UTC 22:20 · PVG 06:20 · LAX 15:20 · JFK 18:20
    ♥ Do have faith in what you're doing.