SpringBoot之认识Spring Data Jpa

很早之前就想写关于JPA的文章了,但是基于到时很赖,写不下来,那今天我们就慢慢的开始写,写多少就多少吧!

什么是JPA
JPA是Java Persistence API的简称,JPA是一个基于O/R映射的标准规范,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。而JPA是需要Provider来实现其功能的,Hibernate就是JPA Provider中很强的一个。
Hibernate 从3.2开始,就开始兼容JPA。Hibernate3.2获得了Sun TCK的JPA(Java Persistence API) 兼容认证,在Hibernate有自由、持久、游离三种,JPA里有new,managed,detached,removed,明眼人一看就知道,这些状态都是一一对应的。再如flush方法,都是对应的,而其他的再如说Query query = manager.createQuery(sql),它在Hibernate里写法上是session,而在JPA中变成了manager,所以从Hibernate到JPA的代价应该是非常小的同样,JDO,也开始兼容JPA。在ORM的领域中,看来JPA已经是王道,规范就是规范。在各大厂商的支持下,JPA的使用开始变得广泛。


使用JPA
创建实体类NBArticle

那么我们可以这样定义一个Dao层

  1. public interface ArticleRepository extends JpaRepository<NBArticle, Long>, JpaSpecificationExecutor<NBArticle> {
  2. }

就短短的这几行代码,我们就有了以下这几个功能

  1. public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
  2. List<T> findAll(); //查询所有
  3. List<T> findAll(Sort var1);
  4. List<T> findAllById(Iterable<ID> var1);
  5. <S extends T> List<S> saveAll(Iterable<S> var1);
  6. void flush();
  7. <S extends T> S saveAndFlush(S var1);
  8. void deleteInBatch(Iterable<T> var1);
  9. void deleteAllInBatch();
  10. T getOne(ID var1);
  11. <S extends T> List<S> findAll(Example<S> var1);
  12. <S extends T> List<S> findAll(Example<S> var1, Sort var2);
  13. }

看是不是很方便啊,如果以上这些方法还不能满足你的要求,那么我们可以自己定义方法啊

查询总数

  1. public interface ArticleRepository extends JpaRepository<NBArticle, Long>, JpaSpecificationExecutor<NBArticle> {
  2. /**
  3. * 查询满足UrlSequence相关的总数
  4. *
  5. * @param urlSequence
  6. * @return
  7. */
  8. long countByUrlSequence(String urlSequence);
  9. /**
  10. * 根据满足UrlSequence和draft变量计算文章数量
  11. *
  12. * @param draft
  13. * @return
  14. */
  15. long countByUrlSequenceAndDraft(String urlSequenceboolean draft);
  16. }

查找对象

  1. //根据文章自定义链接查找文章对象
  2. List<NBArticle> findNBArticleByUrlSequence(String urlSeq);
  3. //通过自定义链接like查询
  4. List<NBArticle> findNBArticleByUrlSequenceLike(String urlSeq);
  5. //
  6. //通过自定义链接和dreaft查询
  7. List<NBArticle> findNBArticleByUrlSequenceAndDraft(String urlSeq,boolean draft);

关键字
从上面的代码我们可以看出我们可以用一些关键字代表一些语句,表格如下

关键字 示例 JPQL同等功能
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = 1?
Between findByStartDateBetween … where x.startDate between 1? and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

限制数量查询(top或者frist)
那么有时我们只要查询几条,如我只要查询5条文章

  1. //查询满足UrlSequence的5条记录(First)
  2. List<NBArticle> findFirst5ByUrlSequence(String urlSeq);
  3. //top
  4. List<NBArticle> findTop5ByUrlSequence(String urlSeq);

Sql语句操作
有人说,我习惯用SQL查询,当然,这里也可以用sql,用到注解@Query,在这个注解里有个参数nativeQuery,为true是,表示是原生的操作表,如果为false,则操作的是实体类。表名是nb_article

查询
/**

  1. * 查找出最大的 top
  2. *
  3. * @return
  4. */
  5. @Query(nativeQuery = true, value = "SELECT MAX(top)FROM nb_article ")
  6. int findMaxTop();
  7. /**
  8. * 查找相似的文章 参数是一一对应的
  9. *
  10. * @param cateId
  11. * @param limit
  12. * @return
  13. */
  14. @Query(nativeQuery = true, value = "SELECT * FROM nb_article WHERE cate_id = ?1 ORDER BY rand() LIMIT ?2")
  15. List<NBArticle> findSimilarArticles(long cateId, int limit);

update
更新时,我们还要在加个注解,就是@Modifying 我们还可以加个事务的注解 @Transactional(rollbackOn = Exception.class)

  1. /**
  2. * 更新 appreciable 状态 默认不是原生的
  3. *
  4. * @param appreciable
  5. * @param id
  6. * @return
  7. */
  8. @Modifying
  9. @Query("update NBArticle a set a.appreciable = ?1 where a.id = ?2")
  10. @Transactional(rollbackOn = Exception.class)
  11. int updateAppreciableById(boolean appreciable, long id);
  12. //原生的
  13. /**
  14. * 更新文章点赞数
  15. *
  16. * @param articleId
  17. * @return
  18. * @throws Exception
  19. */
  20. @Query(nativeQuery = true, value = "UPDATE nb_article SET approve_cnt = approve_cnt + 1 WHERE id = ?1")
  21. @Modifying
  22. @Transactional(rollbackOn = Exception.class)
  23. int updateApproveCntById(long articleId);

ExampleMatcher实例查询
这个是个好东西,对应那些对sql语句不好的程序员,可以用这个查询

实体对象:在ORM框架中与Table对应的域对象,一个对象代表数据库表中的一条记录,如上例中User对象,对应user表。在构建查询条件时,一个实体对象代表的是查询条件中的“数值”部分。如:要查询姓“X”的客户,实体对象只需要存储条件值“X”。

匹配器:ExampleMatcher对象,它是匹配“实体对象”的,表示了如何使用“实体对象”中的“值”进行查询,它代表的是“查询方式”,解释了如何去查的问题。如:要查询姓“X”的客户,即姓名以“X”开头的客户,该对象就表示了“以某某开头的”这个查询方式,如上例中:withMatcher(“userName”, GenericPropertyMatchers.startsWith())

实例:即Example对象,代表的是完整的查询条件。由实体对象(查询条件值)和匹配器(查询方式)共同创建。最终根据实例来findAll即可

  1. NBArticle prob = NBArticle.builder().textContent(searchStr)
  2. .title(searchStr).build(); //创建查询实体
  3. if (articleQueryBO.getCateId() != null) {
  4. prob.setCateId(articleQueryBO.getCateId());
  5. }
  6. prob.setDraft(false); //查询实体 就是我们要查询的条件
  7. ExampleMatcher matcher = ExampleMatcher.matching()
  8. .withMatcher("title", ExampleMatcher.GenericPropertyMatcher::contains)
  9. .withMatcher("textContent", ExampleMatcher.GenericPropertyMatcher::contains)//包含,就是like
  10. .withIgnorePaths("post", "modify", "view", "approveCnt", "commented", "mdContent", "appreciable", "top", "draft")//忽略属性列表,忽略的属性不参与查询过滤。
  11. .withIgnoreNullValues(); //匹配器
  12. Example<NBArticle> articleExample = Example.of(prob, matcher);//实例
  13. List<NBArticle> pages = articleRepository.findAll(articleExample);
  14. //Page page = articleRepository.findAll(articleExample, pageable);分页查询

分页查询
在分页查询中,我们要extends接口 JpaSpecificationExecutor,这样我们就有以下的功能

  1. public interface JpaSpecificationExecutor<T> {
  2. Optional<T> findOne(@Nullable Specification<T> var1);
  3. List<T> findAll(@Nullable Specification<T> var1);
  4. Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
  5. List<T> findAll(@Nullable Specification<T> var1, Sort var2);
  6. long count(@Nullable Specification<T> var1);
  7. }
  8. page对象
  9. public interface Page<T> extends Slice<T> {
  10. static <T> Page<T> empty() {
  11. return empty(Pageable.unpaged());
  12. }
  13. static <T> Page<T> empty(Pageable pageable) {
  14. return new PageImpl(Collections.emptyList(), pageable, 0L);
  15. }
  16. int getTotalPages(); //总页数
  17. long getTotalElements(); //元素的总数
  18. <U> Page<U> map(Function super T, ? extends U> var1);
  19. }
  20. 还有其他方法
  21. /**
  22. * 分页信息 第一页从1开始
  23. *
  24. */
  25. public class Page<T> implements Serializable, Iterable<T> {
  26. protected List<T> result;
  27. protected int pageSize=10;
  28. protected int pageNumber=1;
  29. /**
  30. * 总记录数
  31. */
  32. protected long totalCount = 0;
  33. protected boolean isPagination = true;
  34. private int pageNum;//总页数
  35. private int startIndex;
  36. private int limit;
  37. public int getPageNum() {
  38. int totalPageNum = (int) ((totalCount % pageSize == 0) ? totalCount / pageSize : totalCount / pageSize + 1);
  39. return totalPageNum;
  40. }
  41. public Page() {
  42. }
  43. public Page(int pageNumber, int pageSize, long totalCount) {
  44. this(pageNumber, pageSize, totalCount, new ArrayList(0));
  45. }
  46. public Page(int pageNumber, int pageSize, long totalCount, List<T> result) {
  47. if (pageSize <= 0)
  48. throw new IllegalArgumentException("[pageSize] must great than zero");
  49. this.pageSize = pageSize;
  50. this.pageNumber = PageUtils.computePageNumber(pageNumber, pageSize, totalCount);
  51. this.totalCount = totalCount;
  52. setResult(result);
  53. }
  54. public void setResult(List<T> elements) {
  55. if (elements == null)
  56. throw new IllegalArgumentException("'result' must be not null");
  57. this.result = elements;
  58. }
  59. /**
  60. * 当前页包含的数据
  61. *
  62. * @return 当前页数据源
  63. */
  64. public List<T> getResult() {
  65. return result;
  66. }
  67. /**
  68. * 是否是首页(第一页),第一页页码为1
  69. *
  70. * @return 首页标识
  71. */
  72. public boolean isFirstPage() {
  73. return getThisPageNumber() == 1;
  74. }
  75. /**
  76. * 是否是最后一页
  77. *
  78. * @return 末页标识
  79. */
  80. public boolean isLastPage() {
  81. return getThisPageNumber() >= getLastPageNumber();
  82. }
  83. /**
  84. * 是否有下一页
  85. *
  86. * @return 下一页标识
  87. */
  88. public boolean isHasNextPage() {
  89. return getLastPageNumber() > getThisPageNumber();
  90. }
  91. /**
  92. * 是否有上一页
  93. *
  94. * @return 上一页标识
  95. */
  96. public boolean isHasPreviousPage() {
  97. return getThisPageNumber() > 1;
  98. }
  99. /**
  100. * 获取最后一页页码,也就是总页数
  101. *
  102. * @return 最后一页页码
  103. */
  104. public int getLastPageNumber() {
  105. return PageUtils.computeLastPageNumber(totalCount, pageSize);
  106. }
  107. /**
  108. * 总的数据条目数量,0表示没有数据
  109. *
  110. * @return 总数量
  111. */
  112. public long getTotalCount() {
  113. return totalCount;
  114. }
  115. public void setTotalCount(long totalCount) {
  116. this.totalCount = totalCount;
  117. }
  118. /**
  119. * 获取当前页的首条数据的行编码
  120. *
  121. * @return 当前页的首条数据的行编码
  122. */
  123. public int getThisPageFirstElementNumber() {
  124. return (getThisPageNumber() - 1) * getPageSize() + 1;
  125. }
  126. /**
  127. * 获取当前页的末条数据的行编码
  128. *
  129. * @return 当前页的末条数据的行编码
  130. */
  131. public long getThisPageLastElementNumber() {
  132. int fullPage = getThisPageFirstElementNumber() + getPageSize() - 1;
  133. return getTotalCount() < fullPage ? getTotalCount() : fullPage;
  134. }
  135. /**
  136. * 获取下一页编码
  137. *
  138. * @return 下一页编码
  139. */
  140. public int getNextPageNumber() {
  141. return getThisPageNumber() + 1;
  142. }
  143. /**
  144. * 获取上一页编码
  145. *
  146. * @return 上一页编码
  147. */
  148. public int getPreviousPageNumber() {
  149. return getThisPageNumber() - 1;
  150. }
  151. /**
  152. * 每一页显示的条目数
  153. *
  154. * @return 每一页显示的条目数
  155. */
  156. public int getPageSize() {
  157. return pageSize;
  158. }
  159. public void setPageSize(int pageSize) {
  160. this.pageSize = pageSize;
  161. }
  162. /**
  163. * 当前页的页码
  164. *
  165. * @return 当前页的页码
  166. */
  167. public int getThisPageNumber() {
  168. return pageNumber;
  169. }
  170. public void setPageNumber(int pageNumber) {
  171. this.pageNumber = pageNumber;
  172. }
  173. /**
  174. * 得到用于多页跳转的页码
  175. *
  176. * @return
  177. */
  178. public List<Integer> getLinkPageNumbers() {
  179. return PageUtils.generateLinkPageNumbers(getThisPageNumber(), getLastPageNumber(), 10);
  180. }
  181. /**
  182. * 得到数据库的第一条记录号
  183. *
  184. * @return
  185. */
  186. public int getFirstResult() {
  187. return PageUtils.getFirstResult(pageNumber, pageSize);
  188. }
  189. public Iterator<T> iterator() {
  190. return (Iterator<T>) (result == null ? Collections.emptyList().iterator() : result.iterator());
  191. }
  192. public boolean isPagination() {
  193. return isPagination;
  194. }
  195. public void setPagination(boolean isPagination) {
  196. this.isPagination = isPagination;
  197. }
  198. }

排序(Sort类)
Sort sort=new Sort(Direction.ASC,”id”);
其中第一个参数表示是降序还是升序(此处表示升序),第二个参数表示你要按你的实体类的,不是数据库的中的那个变量进行排序。如果要多个排序呢

  1. //多个排序,用Map,再创建对象
  2. Map<String, String> orders = new HashMap<>(2);
  3. orders.put("top", "desc");
  4. orders.put("post", "desc");
  5. Sort sort = getJpaSortWithOther(pagination, orders);

有了这个类,我们可以放在分页中查询,也可以放在普通的查询:

  1. //根据文章自定义链接查找文章对象
  2. List<NBArticle> findNBArticleByUrlSequence(String urlSeqSort sort);

配置文件(pom.xml)
说了那么多,那我们怎么配置jpa呢,在SpringBoot中,我们引用下面的文件,其他配置SpringBoot会帮我们自动配置

  1. org.springframework.boot
  2. spring-boot-starter-data-jpa

配置文件(yaml)

  1. #数据访问配置
  2. jpa:
  3. database: mysql
  4. hibernate:
  5. ddl-auto: update
  6. show-sql: true
  7. database-platform: me.wuwenbin.noteblogv4.config.configuration.LocalMySQL57InnoDBDialect
  8. jackson:
  9. date-format: yyyy-MM-dd HH:mm:ss
  10. #最大上传文件大小
  11. servlet:
  12. multipart:
  13. max-file-size: 20MB
  14. cache:
  15. #使用默认的ConcurrentMap
  16. type: simple
  17. #程序启动时创建的缓存名称
  18. cache-names: paramCache,authCache