关于Spring Data
Spring社区的一个顶级工程,主要用于简化数据(关系型&非关系型)访问,如果我们使用Spring Data来开发程序的话,那么可以省去很多低级别的数据访问操作,如编写数据查询语句、DAO类等,我们仅需要编写一些抽象接口并定义相关操作即可,Spring会在运行期间的时候创建代理实例来实现我们接口中定义的操作。
关于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持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在Hibernate,TopLink,JDO等ORM框架各自为营的局面。值得注意的是,JPA是在充分吸收了现有Hibernate,TopLink,JDO等ORM框架的基础上发展而来的,具有易于使用,伸缩性强等优点。从目前的开发社区的反应上看,JPA受到了极大的支持和赞扬,其中就包括了Spring与EJB3.0的开发团队。
注意:JPA是一套规范,不是一套产品,那么像Hibernate,TopLink,JDO他们是一套产品,如果说这些产品实现了这个JPA规范,那么我们就可以叫他们为JPA的实现产品。
关于Spring Data Jpa
Spring Data JPA 是 Spring 基于ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
Spring Data Jpa是Spring Data的一个子项目,主要用于简化数据访问层的实现,使用Spring Data Jpa可以轻松实现增删改查、分页、排序等。
示例: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应用-->
<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方法不使用默认的查询实现,
* 取而代之的是使用这条自定义的查询语句去查询,如果这里没有标注的话,会使用默认实现的。
*/
@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接口实现了JpaRepository接口;
*
* 实际上JpaRepository实现了PagingAndSortingRepository接口,
* PagingAndSortingRepository接口实现了CrudRepository接口,
* CrudRepository接口实现了Repository接口;
简单说明下:
Repository接口是一个标识接口,里面是空的;
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方法,
我们是不需要额外定义其它查询语句就可以直接执行的,
Spring Data Jpa会根据实体类的属性名字以及方法名自动实现该方法;
PS:由于我们在实体类中声明了@NamedQuery注解,
实际上findByName方法会使用@NamedQuery注解标注的查询语句去查询;
另外这里的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库中定义的一个接口,
* 该接口是所有分页相关信息的一个抽象,
* 通过该接口,我们可以得到和分页相关所有信息(例如pageNumber、pageSize等),
* 这样,Jpa就能够通过pageable参数来得到一个带分页信息的Sql语句。
*
* Page类也是Spring Data提供的一个接口,
* 该接口表示一部分数据的集合以及其相关的下一部分数据、数据总数等相关信息,
* 通过该接口,我们可以得到数据的总体信息(数据总数、总页数...)
* 以及当前数据的信息(当前数据的集合、当前页数等)
* @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的具体实现,我们只提供分页以及排序信息即可。
我们可以看到,我们只需要在方法的参数中直接定义一个pageable类型的参数,当Spring发现这个参数时,Spring会自动的根据request的参数来组装该pageable对象,Spring支持的request参数如下:
- page,第几页,从0开始,默认为第0页
- size,每一页的大小,默认为20
- sort,排序相关的信息,以property,property(,ASC|DESC)的方式组织,例如
sort=firstname&sort=lastname,desc
表示在按firstname
正序排列基础上按lastname
倒序排列
这样,我们就可以通过url的参数来进行多样化、个性化的查询,而不需要为每一种情况来写不同的方法了。
通过url来定制pageable很方便,但唯一的缺点是不太美观,因此我们需要为pageable设置一个默认配置,这样很多情况下我们都能够通过一个简洁的url来获取信息了。
Spring提供了@PageableDefault帮助我们个性化的设置pageable的默认配置。例如@PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)
表示默认情况下我们按照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 = "用户管理相关接口")
@RestController
@RequestMapping(value = "/users")
public class UserController {
@Autowired
private IUserService userService;
@ApiOperation(value="创建用户", notes="根据User对象创建用户")
@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="根据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="获取用户详细信息", notes="根据url的id来获取用户详细信息")
@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="根据用户名查询")
@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在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以thymeleaf的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
- Thymeleaf开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、OGNL表达式效果,避免每天套模板、改jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
- Thymeleaf提供spring标准方言和一个与SpringMVC完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
编辑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>
编辑业务代码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中配置负责初始化Spring应用上下文的监听器作用类似,
* 只不过在这里不需要编写额外的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);
}
}