關(guān)于Spring Data
Spring社區(qū)的一個頂級工程,主要用于簡化數(shù)據(jù)(關(guān)系型&非關(guān)系型)訪問,如果我們使用Spring Data來開發(fā)程序的話,那么可以省去很多低級別的數(shù)據(jù)訪問操作,如編寫數(shù)據(jù)查詢語句、DAO類等,我們僅需要編寫一些抽象接口并定義相關(guān)操作即可,Spring會在運行期間的時候創(chuàng)建代理實例來實現(xiàn)我們接口中定義的操作。
關(guān)于Spring Data子項目
Spring Data擁有很多子項目,除了Spring Data Jpa外,還有如下子項目。
- Spring Data Commons
- Spring Data MongoDB
- Spring Data Redis
- Spring Data Solr
- Spring Data Gemfire
- Spring Data REST
- Spring Data Neo4j
首先了解JPA是什么?
JPA(Java Persistence API)是Sun官方提出的Java持久化規(guī)范。它為Java開發(fā)人員提供了一種對象/關(guān)聯(lián)映射工具來管理Java應(yīng)用中的關(guān)系數(shù)據(jù)。他的出現(xiàn)主要是為了簡化現(xiàn)有的持久化開發(fā)工作和整合ORM技術(shù),結(jié)束現(xiàn)在Hibernate,TopLink,JDO等ORM框架各自為營的局面。值得注意的是,JPA是在充分吸收了現(xiàn)有Hibernate,TopLink,JDO等ORM框架的基礎(chǔ)上發(fā)展而來的,具有易于使用,伸縮性強等優(yōu)點。從目前的開發(fā)社區(qū)的反應(yīng)上看,JPA受到了極大的支持和贊揚,其中就包括了Spring與EJB3.0的開發(fā)團隊。
注意:JPA是一套規(guī)范,不是一套產(chǎn)品,那么像Hibernate,TopLink,JDO他們是一套產(chǎn)品,如果說這些產(chǎn)品實現(xiàn)了這個JPA規(guī)范,那么我們就可以叫他們?yōu)镴PA的實現(xiàn)產(chǎn)品。
關(guān)于Spring Data Jpa
Spring Data JPA 是 Spring 基于ORM 框架、JPA 規(guī)范的基礎(chǔ)上封裝的一套JPA應(yīng)用框架,可使開發(fā)者用極簡的代碼即可實現(xiàn)對數(shù)據(jù)的訪問和操作。它提供了包括增刪改查等在內(nèi)的常用功能,且易于擴展!學(xué)習(xí)并使用 Spring Data JPA 可以極大提高開發(fā)效率!
Spring Data Jpa是Spring Data的一個子項目,主要用于簡化數(shù)據(jù)訪問層的實現(xiàn),使用Spring Data Jpa可以輕松實現(xiàn)增刪改查、分頁、排序等。
示例:Spring Boot + Spring Data Jpa
添加POM.XML文件
<!--Spring Data Jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--Web應(yīng)用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--緩存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--整合Swagger2-->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.0</version>
</dependency>
編寫實體類User
package com.pingkeke.rdf.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import java.io.Serializable;
/**
* User.
*
* 注意下這里的@NamedQuery注解,
* 大致意思就是讓我們在Repository接口中定義的findByName方法不使用默認(rèn)的查詢實現(xiàn),
* 取而代之的是使用這條自定義的查詢語句去查詢,如果這里沒有標(biāo)注的話,會使用默認(rèn)實現(xiàn)的。
*/
@Entity
@NamedQuery(name = "User.findByName", query = "select name,address from User u where u.name=?1")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
long id;
@Column(name = "name")
String name;
@Column(name = "address")
String address;
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getAddress()
{
return address;
}
public void setAddress(String address)
{
this.address = address;
}
}
編寫Repository接口
這里將編寫兩個Repository接口,僅僅用于示例,實際中可以合并成一個:
package com.pingkeke.rdf.repository;
import com.pingkeke.rdf.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* UserJpaRepository.
* 這里的UserJpaRepository接口實現(xiàn)了JpaRepository接口;
*
* 實際上JpaRepository實現(xiàn)了PagingAndSortingRepository接口,
* PagingAndSortingRepository接口實現(xiàn)了CrudRepository接口,
* CrudRepository接口實現(xiàn)了Repository接口;
簡單說明下:
Repository接口是一個標(biāo)識接口,里面是空的;
CrudRepository接口定義了增刪改查方法;
PagingAndSortingRepository接口用于分頁和排序;
由于JpaRepository接口繼承了以上所有接口,所以擁有它們聲明的所有方法;
*/
public interface UserJpaRepository extends JpaRepository<User,Long> {
}
package com.pingkeke.rdf.repository;
import com.pingkeke.rdf.domain.User;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* UserRepository.
*
* 這里的UserRepository接口主要定義了一些查詢方法;
比如這里的findByNameAndAddress和findByName方法,
我們是不需要額外定義其它查詢語句就可以直接執(zhí)行的,
Spring Data Jpa會根據(jù)實體類的屬性名字以及方法名自動實現(xiàn)該方法;
PS:由于我們在實體類中聲明了@NamedQuery注解,
實際上findByName方法會使用@NamedQuery注解標(biāo)注的查詢語句去查詢;
另外這里的findByName1方法使用了HQL語句查詢;
findByName2方法使用了原始的sql語句查詢;
*/
public interface UserRepository extends Repository<User, Long> {
List<User> findByNameAndAddress(String name, String address);
@Query(value = "from User u where u.name=:name")
List<User> findByName1(@Param("name") String name);
@Query(value = "select * from #{#entityName} u where u.name=?1", nativeQuery = true)
List<User> findByName2(String name);
List<User> findByName(String name);
}
編寫Service
package com.pingkeke.rdf.service;
import com.pingkeke.rdf.domain.User;
import org.springframework.data.domain.Page;
import java.util.List;
/**
* IUserService.
*/
public interface IUserService {
List<User> findAll();
void saveUser(User book);
User findOne(long id);
void delete(long id);
List<User> findByName(String name);
Page<User> findPage(int page,int size);
}
package com.pingkeke.rdf.service.impl;
import com.pingkeke.rdf.domain.User;
import com.pingkeke.rdf.repository.UserJpaRepository;
import com.pingkeke.rdf.repository.UserRepository;
import com.pingkeke.rdf.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* UserServiceImpl.
*/
@Service
@Transactional
public class UserServiceImpl implements IUserService {
@Autowired
private UserJpaRepository userJpaRepository;
@Autowired
private UserRepository userRepository;
public List<User> findAll()
{
return userJpaRepository.findAll();
}
public List<User> findByName(String name)
{
List<User> userList1 = userRepository.findByName1(name);
List<User> userList2 = userRepository.findByName2(name);
List<User> userList3 = userRepository.findByNameAndAddress(name, "111");
System.out.println("userList1:" + userList1);
System.out.println("userList2:" + userList2);
System.out.println("userList3:" + userList3);
return userRepository.findByName(name);
}
public void saveUser(User user)
{
userJpaRepository.save(user);
}
@Cacheable("users")
public User findOne(long id)
{
System.out.println("Cached Pages");
return userJpaRepository.findOne(id);
}
public void delete(long id)
{
userJpaRepository.delete(id);
}
/**
* Pageable 是Spring Data庫中定義的一個接口,
* 該接口是所有分頁相關(guān)信息的一個抽象,
* 通過該接口,我們可以得到和分頁相關(guān)所有信息(例如pageNumber、pageSize等),
* 這樣,Jpa就能夠通過pageable參數(shù)來得到一個帶分頁信息的Sql語句。
*
* Page類也是Spring Data提供的一個接口,
* 該接口表示一部分?jǐn)?shù)據(jù)的集合以及其相關(guān)的下一部分?jǐn)?shù)據(jù)、數(shù)據(jù)總數(shù)等相關(guān)信息,
* 通過該接口,我們可以得到數(shù)據(jù)的總體信息(數(shù)據(jù)總數(shù)、總頁數(shù)...)
* 以及當(dāng)前數(shù)據(jù)的信息(當(dāng)前數(shù)據(jù)的集合、當(dāng)前頁數(shù)等)
* @return
*/
public Page<User> findPage(int page,int size) {
// int page=0,size=10;
Sort sort = new Sort(Sort.Direction.DESC, "id");
Pageable pageable = new PageRequest(page, size, sort);
return userJpaRepository.findAll(pageable);
}
}
分頁和排序
Pageable定義了很多方法,但其核心的信息只有兩個:一是分頁的信息(page、size),二是排序的信息。Spring Data Jpa提供了PageRequest的具體實現(xiàn),我們只提供分頁以及排序信息即可。
我們可以看到,我們只需要在方法的參數(shù)中直接定義一個pageable類型的參數(shù),當(dāng)Spring發(fā)現(xiàn)這個參數(shù)時,Spring會自動的根據(jù)request的參數(shù)來組裝該pageable對象,Spring支持的request參數(shù)如下:
- page,第幾頁,從0開始,默認(rèn)為第0頁
- size,每一頁的大小,默認(rèn)為20
- sort,排序相關(guān)的信息,以property,property(,ASC|DESC)的方式組織,例如
sort=firstname&sort=lastname,desc
表示在按firstname
正序排列基礎(chǔ)上按lastname
倒序排列
這樣,我們就可以通過url的參數(shù)來進行多樣化、個性化的查詢,而不需要為每一種情況來寫不同的方法了。
通過url來定制pageable很方便,但唯一的缺點是不太美觀,因此我們需要為pageable設(shè)置一個默認(rèn)配置,這樣很多情況下我們都能夠通過一個簡潔的url來獲取信息了。
Spring提供了@PageableDefault幫助我們個性化的設(shè)置pageable的默認(rèn)配置。例如@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)
表示默認(rèn)情況下我們按照id倒序排列,每一頁的大小為15。
請求返回示例
{
"content": [
{
"id": 102,
"name": "110",
"address": "111"
},
{
"id": 101,
"name": "2",
"address": "111"
}
],
"last": false,
"totalPages": 2,
"totalElements": 3,
"number": 0,
"size": 2,
"sort": [
{
"direction": "DESC",
"property": "id",
"ignoreCase": false,
"nullHandling": "NATIVE",
"ascending": false,
"descending": true
}
],
"first": true,
"numberOfElements": 2
}
怎么樣,信息是不是很豐富,代碼是不是很簡單,快點來嘗試一下Jpa的分頁查詢吧。
編寫Controller
package com.pingkeke.rdf.controller;
import com.pingkeke.rdf.domain.User;
import com.pingkeke.rdf.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* UserController.
* http://localhost:8080/swagger-ui.html
*/
@Api(tags = "用戶管理相關(guān)接口")
@RestController
@RequestMapping(value = "/users")
public class UserController {
@Autowired
private IUserService userService;
@ApiOperation(value="創(chuàng)建用戶", notes="根據(jù)User對象創(chuàng)建用戶")
@RequestMapping(value = "/add/{id}/{name}/{address}", method=RequestMethod.GET)
public User addUser(@PathVariable int id, @PathVariable String name,
@PathVariable String address)
{
User user = new User();
user.setId(id);
user.setName(name);
user.setAddress(address);
userService.saveUser(user);
return user;
}
@ApiOperation(value="刪除用戶", notes="根據(jù)url的id來指定刪除對象")
@ApiImplicitParam(name = "id", value = "用戶ID", required = true, paramType="path", dataType = "Long")
@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
public void deleteUser(@PathVariable Long id)
{
userService.delete(id);
}
@ApiOperation(value="獲取用戶列表", notes="")
@RequestMapping(value={""}, method= RequestMethod.GET)
public List<User> getUsers()
{
return userService.findAll();
}
@ApiOperation(value="分頁獲取用戶列表", notes="")
@RequestMapping(value={"getPageUsers/{page}/{size}"}, method= RequestMethod.GET)
public Page<User> getPageUsers(@PathVariable int page, @PathVariable int size)
{
return userService.findPage(page,size);
}
@ApiOperation(value="獲取用戶詳細(xì)信息", notes="根據(jù)url的id來獲取用戶詳細(xì)信息")
@ApiImplicitParam(name = "id", value = "用戶ID", required = true, paramType="path", dataType = "Long")
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public User getUser(@PathVariable int id)
{
User user = userService.findOne(id);
return user;
}
@ApiOperation(value="獲取用戶列表", notes="根據(jù)用戶名查詢")
@RequestMapping(value = "/search/name/{name}", method=RequestMethod.GET)
public List<User> getUserByName(@PathVariable String name)
{
List<User> users = userService.findByName(name);
return users;
}
}
/**
*
* spring-data-jpa-example
*
* http://localhost:8080/users/
http://localhost:8080/users/add/100/110/111
http://localhost:8080/users/delete/100
http://localhost:8080/users/search/name/2
http://localhost:8080/users/100
*/
配置datasource
spring.jpa.show-sql = true
logging.level.org.springframework.data=DEBUG
spring.jpa.hibernate.ddl-auto=
spring.datasource.url=jdbc:mysql://localhost:3306/tdf_db?characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
示例代碼地址
https://github.com/ChenQianPing/spring-data-jpa-example
整合Thymeleaf模板引擎
簡介
Thymeleaf是一個跟Velocity、FreeMarker類似的模板引擎,它可以完全替代JSP,相較與其他的模板引擎,它有如下三個極吸引人的特點:
- Thymeleaf在有網(wǎng)絡(luò)和無網(wǎng)絡(luò)的環(huán)境下皆可運行,即它可以讓美工在瀏覽器查看頁面的靜態(tài)效果,也可以讓程序員在服務(wù)器查看帶數(shù)據(jù)的動態(tài)頁面效果。這是由于它支持 html 原型,然后在 html 標(biāo)簽里增加額外的屬性來達到模板+數(shù)據(jù)的展示方式。瀏覽器解釋 html 時會忽略未定義的標(biāo)簽屬性,所以thymeleaf的模板可以靜態(tài)地運行;當(dāng)有數(shù)據(jù)返回到頁面時,Thymeleaf 標(biāo)簽會動態(tài)地替換掉靜態(tài)內(nèi)容,使頁面動態(tài)顯示。
- Thymeleaf開箱即用的特性。它提供標(biāo)準(zhǔn)和spring標(biāo)準(zhǔn)兩種方言,可以直接套用模板實現(xiàn)JSTL、OGNL表達式效果,避免每天套模板、改jstl、改標(biāo)簽的困擾。同時開發(fā)人員也可以擴展和創(chuàng)建自定義的方言。
- Thymeleaf提供spring標(biāo)準(zhǔn)方言和一個與SpringMVC完美集成的可選模塊,可以快速的實現(xiàn)表單綁定、屬性編輯器、國際化等功能。
編輯pom文件,引入Thymeleaf
<!--整合Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Thymeleaf配置
# thymeleaf
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.check-template-location=true
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.cache=false
新建模板文件
在resources文件夾下新增templates目錄,用于存放模板文件,新增hello.html。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>springboot-thymeleaf demo</title>
</head>
<body>
<p th:text="'hello, ' + ${name} + '!'" />
</body>
</html>
編輯業(yè)務(wù)代碼HelloController
package com.pingkeke.rdf.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletRequest;
/**
* HelloController.
*/
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(HttpServletRequest request, @RequestParam(value = "name", required = false, defaultValue = "springboot-thymeleaf") String name) {
request.setAttribute("name", name);
return "hello";
}
}
編輯啟動類
package com.pingkeke.rdf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
/**
* RdfApplication
*/
public class RdfApplication extends SpringBootServletInitializer {
/**
* 這個類的作用與在web.xml中配置負(fù)責(zé)初始化Spring應(yīng)用上下文的監(jiān)聽器作用類似,
* 只不過在這里不需要編寫額外的XML文件了。
* @param application
* @return
*/
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(RdfApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(RdfApplication.class, args);
}
}