Spring Boot 中Spring Data JPA的应用

关于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

简介

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);
    }
}

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容