Spring Boot简明教程之数据访问(二):JPA(超详细)

Spring Boot简明教程之数据访问(二):JPA(超详细)

创建项目

创建的过程和我们的第一篇文章:SpringBoot简明教程之快速创建第一个SpringBoot应用大致相同,差别只是我们在挑选所需要的组件时,除了Web组件外,我们需要添加如下三个组件:JPA、MySQL、JDBC

在这里插入图片描述

或者,我们按照第一次创建完成后,手动在pom.xml文件中加入以下配置:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>

我们发现,与我们直接使用JDBC template不同的是,这次我们引入了一个新的包:spring-boot-starter-data-jpa,我们进一步查看,就会发现,其主要是引入了一个spring-data-jpa的包。

在这里插入图片描述

Spring Data简介

Spring Data是为了简化构建基于 Spring 框架应用的数据访问技术,包括关系、非关系数据库、
云数据服务等等。SpringData为我们提供使用统一的API来对数据访问层进行操作,让我们在使用关系型或者非关系型数据访问技术时都基于Spring提供的统一标准,标准包含了CRUD(创建、获取、更新、删除)、查询、
排序和分页的相关操作,同时为我们提供了统一的数据访问类的模板,例如:MongoTemplate、RedisTemplate等。

JPA简介

JPA(Java Persistence API)定义了一系列对象持久化的标准,它的出现主要是为了简化现有的持久化开发工作和整合 ORM 技术,目前实现了这一规范的有 Hibernate、TopLink、JDO 等 ORM 框架。

Spring Data 与JPA

我们可以将Spring-data-jpa理解为Spring Boot对于JPA的再次封装,使得我们通过Spring-data-jpa即实现常用的数据库操作:

  • JpaRepository实现基本功能: 编写接口继承JpaRepository既有crud及分页等基本功能
  • 定义符合规范的方法命名: 在接口中只需要声明符合规范的方法,即拥有对应的功能
  • 支持自定义查询: @Query自定义查询,定制查询SQL
  • Specifications查询(Spring Data JPA支持JPA2.0的Criteria查询)

使用Spring Data JPA的基本流程

创建实体类(entity)

package cn.newtol.springboot07.entity;
import javax.persistence.*;

/**
 * @Author: 公众号:Newtol
 * @Description:  JPA使用示例:使用JPA注解配置映射关系
 * @Date: Created in 18:45 2018/9/24
 */

@Entity   //表示一个实体类,和数据表进行映射
@Table(name = "t_user")   //所映射的表的名字,可省略,默认为实体类名
public class User {


    @Id //设置为主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) //定义为自增主键
    private Integer id;

    @Column(name = "t_name",nullable = false)  //设置该字段在数据表中的列名,并设置该字段设置为不可为空
    private String name;

    @Column     //默认则字段名就为属性名
    private Integer phone;

    @Transient   //增加该注解,则在数据表中不会进行映射
    private String address;
    
    // //省略getter settet方法、构造方法,创建时自行补上
}

创建Dao层(Repository)

package cn.newtol.springboot07.repository;

import cn.newtol.springboot07.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @Author: 公众号:Newtol
 * @Description:
 * @Date: Created in 19:35 2018/9/24
 */
//定义为接口
public interface UserRepository extends JpaRepository<User,Integer>{
	//直接继承即可,不用编写任何方法
}

编写配置文件

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot?useSSL=false
    username: root
    password:

  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true

创建Controller

package cn.newtol.springboot07.controller;

import cn.newtol.springboot07.entity.User;
import cn.newtol.springboot07.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;

/**
 * @Author: 公众号:Newtol
 * @Description:
 * @Date: Created in 19:37 2018/9/24
 */


@RestController
public class UserController {

    @Autowired
    UserRepository userRepository;

    //根据Id查询用户
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") Integer id){
        User user = userRepository.getOne(id);
        return user;
    }

    //插入用户
    @GetMapping("/user")
    public User addUser(User user){
        userRepository.save(user);
        return user;   //返回插入的对象及其自增ID
    }
}

直接启动项目,看到Spring Boot自动帮我们在数据库中自动创建了t_user表:

在这里插入图片描述

并且因为我们之前对address属相使用了@Transient注解,不进行映射,所以我们看到表中没有address字段。现在我们将@Transient注释后,再次启动项目:

在这里插入图片描述

address就也被成功的创建。

接着,我们尝试着往数据库中插入一条数据,我们在浏览器中输入:localhost:8080/user?name=zhangsan&phone=123&address=beijing

在这里插入图片描述

再到数据库中进行查看:

在这里插入图片描述
插入成功。

现在我们来查询刚刚插入的数据,浏览器输入:http://localhost:8080/user/1

在这里插入图片描述

成功查询到刚才插入的数据,并且我们可以在控制台看到Spring Boot为我们打印的刚才执行的查询语句:

在这里插入图片描述

JPA常用注解说明

虽然我们在上面的示例中已经使用了常用了的注解,但是为了方便和理解,我们在这个地方归纳一下关于JPA的一些常用的注解及其对应的参数。

  • @Entity:表示该类是一个的实体类。@Entity标注是必需的 ,name属性为可选;需要注意的是:@Entity标注的实体类至少需要有一个无参的构造方法。

  • @Table:表示该实体类所映射的数据表信息,需要标注在类名前,不能标注在方法或属性前。参数如下:

    参数说明
    name实体所对应表的名称,默认表名为实体名
    catalog实体指定的目录名
    schema表示实体指定的数据库名
    uniqueConstraints该实体所关联的唯一约束条件,一个实体可以有多个唯一的约束,默认没有约束条件。创建方式:uniqueConstraints = {@uniqueConstraint(columnNames = {“name”,“phone”})}
    indexes该实体所关联的索引。创建方式:indexes = { @Index(name = “index_name”, columnList = “t_name”)}
  • @Column:表示该属性所映射的字段,此标记可以标注在Getter方法或属性前。它有如下参数:

    参数说明
    unique字段是否为唯一标识,默认为false
    nullable字段是否可以为null值,默认为true
    insertable使用“INSERT” SQL语脚本插入数据时,是否需要插入该字段的值。
    updatable使用“UPDATE”脚本插入数据时,是否需要更新该字段的值
    table当映射多个表时,指定表中的字段。默认值为主表的表名。
    length当字段的类型为varchar时的字段的长度,默认为255个字符。
    precision用于表示精度,数值的总长度
    scale用于表示精度,小数点后的位数。
    columnDefinition用于创建表时添加该字段所需要另外执行的SQL语句
  • @Id:表示该属性为主键,每一个实体类至少要有一个主键(Primary key)。

    参数说明
    strategy表示生成主键的策略 ,有4种类型:GenerationType.TABLE 、 GenerationType.SEQUENCE 、 GenerationType.IDENTITY 、 GenerationType.AUTO 默认为:AUTO,表示自动生成。
    generator生成规则名,不同的策略有不同的配置
  • @Basic: 实体属性设置加载方式为惰性加载

    参数说明
    fetch表示获取值的方式,它的值定义的枚举型,可选值为LAZY(惰性加载)、EAGER(即时加载),默认为即时加载。
    optional属性是否可以为null,不能用于java基本数据型( byte、int、short、long、boolean、char、float、double )

自定义查询

我们刚刚在上面使用的是JPA已经封装好的一些默认的查询方式,但是我们在实际的项目中,可能需要自定义一些符合实际需求的查询,那么我们就需要用到自定义查询

关键字查询

JPA已经帮我们做好了关键字,我们只需要将这些关键字组成方法名,就可以实现相对应的查询。

关键字示例同功能JPQL
AndfindByLastnameAndFirstnamewhere x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstnamewhere x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEqualswhere x.firstname = 1?
BetweenfindByStartDateBetweenwhere x.startDate between 1? and ?2
LessThanfindByAgeLessThanwhere x.age < ?1
LessThanEqualfindByAgeLessThanEqualwhere x.age <= ?1
GreaterThanfindByAgeGreaterThanwhere x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqualwhere x.age >= ?1
AfterfindByStartDateAfterwhere x.startDate > ?1
BeforefindByStartDateBeforewhere x.startDate < ?1
IsNullfindByAgeIsNullwhere x.age is null
IsNotNull,NotNullfindByAge(Is)NotNullwhere x.age not null
LikefindByFirstnameLikewhere x.firstname like ?1
NotLikefindByFirstnameNotLikewhere x.firstname not like ?1
StartingWithfindByFirstnameStartingWithwhere x.firstname like ?1 (参数前面加 %)
EndingWithfindByFirstnameEndingWithwhere x.firstname like ?1 (参数后面加 %)
ContainingfindByFirstnameContainingwhere x.firstname like ?1 (参数两边加 %)
OrderByfindByAgeOrderByLastnameDescwhere x.age = ?1 order by x.lastname desc
NotfindByLastnameNotwhere x.lastname <> ?1
InfindByAgeIn(Collection<Age> ages)where x.age in ?1
NotInfindByAgeNotIn(Collection<Age> age)where x.age not in ?1
TruefindByActiveTrue()where x.active = true
FalsefindByActiveFalse()where x.active = false
IgnoreCasefindByFirstnameIgnoreCasewhere UPPER(x.firstame) = UPPER(?1)

我们除了使用find关键字以外,还可以使用countdeleteget等。

例如,我们在Controller中加入如下方法得:

/**
 * @Author: 公众号:Newtol
 * @Description: 自定义关键字查询
 * @Date: Created in 19:37 2018/9/24
 */
@GetMapping("/test/{address}/{phone}")
public List<User> getUserByAddressAndPhone (@PathVariable("address") String address, @PathVariable("phone") Integer phone){
	return userRepository.findByAddressEqualsAndPhoneNot(address,phone);
}
    

在userRepository中鸡加入如下方法:


/**
 * @Author: 公众号:Newtol
 * @Description:
 * @Date: Created in 19:35 2018/9/24
 */
public interface UserRepository extends JpaRepository<User,Integer>{
    public List<User> findByAddressEqualsAndPhoneNot (String address, Integer phone);
}

在数据库中插入下面两条数据:

INSERT INTO `t_user` VALUES ('2', 'beijing', 'zhangsi', '456');
INSERT INTO `t_user` VALUES ('3', 'beijing', 'wangwu', '123');

在浏览器输入:http://localhost:8080/test/beijing/456

我们就可以看到返回了如下数据,查询成功。

在这里插入图片描述

自定义SQL语句

通常如果使用JPA提供给我们的关键字组成的查询方法仍然无法满足需求,那么我们就可以使用@Query来实现自定的SQL语句。

原生SQL

JPA中支持使用原生的SQL语句,例如:

在userRepository中加入下面的方法:

//自定义SQL查询
    @Query(value = "select * from t_user WHERE phone = ? " ,nativeQuery = true)
    List<User> getAllUser (Integer phone);

在controller中加入以下方法:

 //自定义SQL查询
    @GetMapping("/test/{phone}")
    public List<User> getAllUser(@PathVariable("phone") Integer phone){
        return userRepository.getAllUser(phone);
    }

在浏览器中输入:http://localhost:8080/test/123

返回数据,查询成功。在这里插入图片描述

但是,如果我们需要执行UPDATEDELETE操作时,除了使用@Query外,还需要使用@Modifying
@Transactional两个注解。

例如:

在userRepository中加入下面的方法:

 //自定义SQL删除
    @Query(value = "delete from t_user WHERE phone = ? ",nativeQuery = true)
    @Modifying
    @Transactional
    void deleteUser(Integer phone);

在controller中加入以下方法:

//自定义SQL删除
    @GetMapping("/del/{phone}")
    public void deleteUser(@PathVariable("phone") Integer phone){
       userRepository.deleteUser(phone);
    }

在浏览器中输入:http://localhost:8080/del/456,我们就会发现数据库中phone为456的数据就已经被删除了。

JPQL查询

在userRepository中加入下面的方法:

//JPQL查询
    @Query("select u from User u where u.name = ?1")
    User getUserByName(String name);

在controller中加入以下方法:

//JPQL查询
    @GetMapping("/get/{name}")
    public User getUserByName(@PathVariable("name") String name){
        return userRepository.getUserByName(name);
    }

浏览器输入:http://localhost:8080/get/zhangsan,数据返回,查询成功:

在这里插入图片描述

复杂查询

分页查询

我们经常会遇到进行分页查询的情况,而JPA就很好的替我们解决了这一个问题。只需要几行代码就可以解决:

例如:在controller中加入以下方法:

//分页查询
@GetMapping("/userList/{page}/{size}")
public Page<User> getUserList(@PathVariable("page") Integer page, @PathVariable("size") Integer size) {
	Sort sort = new Sort(Sort.Direction.DESC,"id");
    PageRequest pageRequest = PageRequest.of(page,size,sort);
    return  userRepository.findAll(pageRequest);
 }

浏览器输入:http://localhost:8080/userList/0/3

返回的数据格式为:

{
    "content": [
        {
            "id": 3, 
            "name": "wangwu", 
            "phone": 123, 
            "address": "beijing"
        }, 
        {
            "id": 2, 
            "name": "lisi", 
            "phone": 789, 
            "address": "shanghai"
        }, 
        {
            "id": 1, 
            "name": "zhangsan", 
            "phone": 123, 
            "address": "beijing"
        }
    ], 
    "pageable": {
        "sort": {
            "sorted": true, 
            "unsorted": false
        }, 
        "offset": 0, 
        "pageSize": 3, 
        "pageNumber": 0, 
        "paged": true, 
        "unpaged": false
    }, 
    "totalPages": 1, 
    "last": true, 
    "totalElements": 3, 
    "number": 0, 
    "size": 3, 
    "sort": {
        "sorted": true, 
        "unsorted": false
    }, 
    "numberOfElements": 3, 
    "first": true
}

限制查询

除了限制查询以外,对于排行榜类的数据,或许我们只需要取出前面几名即可,Jpa也对这种情况做了很好的支持:

例如:在userRepository中加入下面的方法:

//限制查询 
    List<User> findFirst2ByAddressOrderByIdDesc (String address);

在controller中加入以下方法:

@GetMapping("/top/{address}")
public List<User> getTop2User(@PathVariable("address") String address){
	return userRepository.findFirst2ByAddressOrderByIdDesc(address);
 }

在浏览器输入:http://localhost:8080/top/beijing

返回的数据为:

[{"id":3,"name":"wangwu","phone":123,"address":"beijing"},{"id":1,"name":"zhangsan","phone":123,"address":"beijing"}]

常见问题及其解决方案:

  1. 如果提示:No identifier specified for entity: xxx

    解决方案:这是因为在创建实体类时,导入的jar包错误导致的。我们将import org.springframework.data.annotation.*;更改为:import javax.persistence.*;即可

  2. 如果提示: No default constructor for entity: :

    解决方案:这是因为在实体类中创建了含参数的构造方法造成的,这是因为在使用类反射机制 Class.newInstance()方法创建实例时,需要有一个默认的无参数构造方法,否则会执出实例化异常(InstantiationException),所以将构造方法更改为无参数即可。

  3. 如果提示:Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query

    解决方案:这是因为在JPA中执行DELETE/UPDATE操作时,需要使用@Modifying
    @Transactional两个注解,补上即可。

  4. 如果提示:Can not issue data manipulation statements with executeQuery()。

    解决方案:如果提示这个错误,就需要查询检查自己的SQL语句是否书写正确,如果正确,检查是否加上@Modifying@Transactional两个注解

  5. 如果提示:Ambiguous handler methods mapped for HTTP path

    解决方案:这是由于URL映射重复引起的,将你所请求的URL重新进行定义即可。

  6. 如果在使用PageRequest方法显示过期

    解决方案:将构造方法更改为:PageRequest.of即可。在2.0版本以后,就使用of(…) 方法代替 PageRequest(…)构造器。

总结

我们首先介绍了spring Data,然后演示了一遍使用JPA的基本流程,最后详细的介绍了我们主要使用的几个注解、注意事项等;以及除了使用默认的查询方式外,如何去使用自定义查询和复杂查询。

源码地址

源码地址

联系作者

有关转载、错误指正、问题咨询等事宜请扫码关注个人公众号进行联系,更有大量视频学习资源分享 70

转载于:https://www.cnblogs.com/newtol/p/10159081.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值