Spring Boot整合Spring Data JPA

Spring Data作为Spring全家桶中重要的一员,在Spring项目全球使用市场份额排名中多次居前位,而在Spring Data子项目的使用份额排名中,Spring Data JPA也一直名列前茅。Spring Boot为Spring Data JPA提供了启动器,使Spring Data JPA在Spring Boot项目中的使用更加便利。

Spring Data JPA概述

对象关系映射(Object Relational Mapping,ORM)框架在运行时可以参照映射文件的信息,把对象持久化到数据库中,可以解决面向对象与关系数据库存在的互不匹配的现象,常见的ORM框架有Hibernate、OpenJPA等。ORM框架的出现,使开发者从数据库编程中解脱出来,把更多的精力放在业务模型与业务逻辑上,但各ORM框架之间的API差别很大,使用了某种ORM框架的系统会严重受限于该ORM的标准,基于此,SUN公司提出JPA(Java Persistence API,Java持久化API)。

JPA是Sun官方提出的Java持久化规范,用于描述对象和关系表的映射关系,并将运行期的实体对象持久化到数据库中。JPA规范本质上是一套规范,它提供了一些编程的API接口,但具体实现则由服务厂商来提供基于JPA的数据访问。

Spring Data JPA是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,它提供了增删改查等常用功能,使开发者可以用较少的代码实现数据操作,同时还易于扩展。

基于JPA的数据访问如下:

Spring Data JPA整体处理逻辑如下: 

 整合Spring Data JPA

1.在全局配置文件中添加配置

mysql数据库配置

spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata?serverTimezone=UTC&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=zptc1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

jpa配置 

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.jpa.hibernate.ddl-auto 是 Spring Boot 应用程序中与 JPA(Java Persistence API)和 Hibernate 相关的配置属性。Hibernate 是一个流行的 JPA 实现,用于将 Java 对象映射到关系数据库中的表。

ddl-auto 属性用于控制 Hibernate 在启动时如何自动处理数据库架构(DDL,即数据定义语言)。具体来说,它定义了 Hibernate 是否应该基于实体类自动创建、更新或验证数据库表结构。

以下是 ddl-auto 的几个常用值:

  1. create:Hibernate 会在启动时创建数据库表。如果表已经存在,它会被删除并重新创建。这是一个非常危险的设置,因为它会丢失所有现有数据。通常,这仅用于开发环境或测试数据库。
  2. create-drop:在 create 的基础上,当 Hibernate 的 SessionFactory 关闭时,它会删除所有创建的表。这同样适用于开发或测试环境。
  3. update:Hibernate 会根据实体类更新数据库表结构。它只会添加、修改或删除必要的列,以保持与实体类的同步。这是一个相对安全的设置,但仍然建议在生产环境中谨慎使用,因为自动模式可能会引入难以预料的问题。
  4. validate:Hibernate 验证数据库表结构是否与实体类匹配。如果不匹配,它会抛出异常。这不会创建或修改任何表,只用于验证。

注意:尽管 ddl-auto 在开发过程中可能非常方便,但在生产环境中使用它通常是不推荐的。在生产环境中,建议使用迁移工具(如 Flyway 或 Liquibase)来管理数据库架构的版本控制。这样可以确保架构更改的清晰、可预测和可审计。

2.在pom文件中添加依赖启动器

<!--Spring Data JPA-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<!--mysql 驱动-->
<dependency>
     <groupId>com.mysql</groupId>
     <artifactId>mysql-connector-j</artifactId>
     <scope>runtime</scope>
</dependency>

 准备测试环境

 新建ORM实体类

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity(name = "t_comment")
public class Discuss {

    @Id
    //指定主键的生成策略为数据库自动生成
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String content;

    private String author;

    @Column(name = "a_id")
    private Integer aId;
    
    //补上get、set、toString方法

}

新建DiscussRepository

public interface DiscussRepository extends JpaRepository<Discuss,Integer> {}

新建服务类

​@Service
public class DiscussService {
    @Autowired
    private DiscussRepository discussRepository;
    
}

新建控制器类

@Controller
@RequestMapping("discuss")
public  class DiscussController {

    @Autowired
    private DiscussService discussService;


}

新建单元测试类

import java.util.List;
import java.util.Optional;


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

@SpringBootTest
class JpaTests {
 
    @Autowired
    private DiscussRepository repository;
}

Spring Data JPA快速入门 

Spring Data JPA提供了很多模板代码,易于扩展,可以大幅提高开发效率,使开发者用极简的代码即可实现对数据的访问。使用Spring Data JPA可以通过Repository接口中的方法对数据库中的数据进行增删改查,也可以根据方法命名规则定义的方法、JPQL,以及原生SQL的方式进行操作。

使用Spring Data JPA进行数据操作的多种实现方式

1、如果自定义接口继承了JpaRepository接口,则默认包含了一些常用的CRUD方法。

2、自定义Repository接口中,可以使用@Query注解配合SQL语句进行数据的查、改、删操作。

3、自定义Repository接口中,可以直接使用关键字构成的方法名进行查询操作。

4、在自定义的Repository接口中,使用@Query注解方式执行数据变更操作(修改、删除)时,必须添加@Modifying注解表示数据变更和@Transactional注解表示事务管理

  • 在自定义的Repository接口中,针对数据的变更操作(修改、删除),无论是否使用了@Query注解,都必须在方法上方添加@Transactional注解进行事务管理,否则程序执行就会出现InvalidDataAccessApiUsageException异常。
  • 如果在调用Repository接口方法的业务层Service类上已经添加了@Transactional注解进行事务管理,那么Repository接口文件中就可以省略@Transactional注解。

5、使用Example实例进行复杂条件查询

Repository接口

自定义Repository接口,必须继承XXRepository<T, ID>接口,T:实体类,ID:实体类中主键对应的属性的数据类型。

Repository继承关系如下:

CrudRepository

在Spring Data JPA中,CrudRepository 是一个接口,它提供了基本的 CRUD(创建、读取、更新、删除)操作。虽然 CrudRepository 并没有直接提供一个专门用于更新的方法,但你可以通过 save() 方法来实现更新的功能。

当你调用 save() 方法并传递一个实体对象时,Spring Data JPA 会检查该实体是否已经存在于数据库中(通常是通过主键来判断,所以实体对象设置了主键值时会出现一条查询语句)。

  • 如果实体不存在,save() 方法会创建一个新的记录。
  • 如果实体已经存在,save() 方法会更新该实体的状态。 

案例

创建服务类,在里面直接调用数据操作对象的save方法 

    public Discuss add(Discuss discuss){
        return discussRepository.save(discuss);
    }

创建控制器类,在里面直接调用服务对象的add方法 

    //@PostMapping("/add")
    //http://localhost:8088/discuss/add?content=oookkk&author=zhubajie&aId=2
    @GetMapping("/add")
    @ResponseBody
    public String add(Discuss discuss){

        Discuss add = discussService.add(discuss);
        if (add != null) {
            return "success add";
        }else {
            return "fail add";
        }

    }

在浏览器里访问

http://localhost:8088/discuss/add?content=oookkk&author=zhubajie&aId=2

http://localhost:8088/discuss/add?id=1&content=oookkk&author=zhubajie&aId=2

http://localhost:8088/discuss/add?id=100&content=oookkk&author=zhubajie&aId=2

mysql也可在测试类里测试 

    //保存评论
    @Test
    public void saveDiscuss() {
 
        Discuss discuss=new Discuss();
        discuss.setContent("张某的评论xxxx");
        discuss.setAuthor("张某");
        Discuss newDiscuss = repository.save(discuss);
        System.out.println(newDiscuss);
 
    }

案例

在测试类里添加如下方法测试

    @Test
    public void selectComment() {
 
        Optional<Discuss> optional = repository.findById(1); 
        if (optional.isPresent()) { 
            System.out.println(optional.get()); 
        }
 
    }

动手试一试

实现删除 

JpaRepository接口提供的方法

案例 

在测试类添加如下方法测试 

    //使用Example封装参数,精确匹配查询条件
    @Test
    public void selectCommentByExample() {
 
        Discuss discuss=new Discuss();
        discuss.setAuthor("张三");
        Example<Discuss> example = Example.of(discuss);
        List<Discuss> list = repository.findAll(example);
        list.forEach(t -> System.out.println(t));
 
    }
 
    //使用ExampleMatcher模糊匹配查询条件
    @Test
    public void selectCommentByExampleMatcher() {
 
        Discuss discuss=new Discuss();
        discuss.setAuthor("张");
        ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("author",
        		ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.CONTAINING));
        Example<Discuss> example = Example.of(discuss, matcher);
        List<Discuss> list = repository.findAll(example);
        System.out.println(list);
 
    }

根据方法命名规则定义方法

Spring Data中按照框架的规范自定义了Repository接口,除了可以使用接口提供的默认方法外,还可以按特定规则来定义查询方法,只要这些查询方法的方法名遵守特定的规则,不需要提供方法实现体,Spring Data就会自动为这些方法生成查询语句。Spring Data对这种特定的查询方法的定义规范如下:

        以find、read、get、query、count开头。

        涉及查询条件时,条件的属性使用条件关键字连接,并且条件属性的首字母大写。

        支持属性的级联查询:

                若当前类有符合条件的属性,则优先使用,而不使用级联属性。

                若需要使用级联属性,则属性之间使用_连接。

条件关键字如下:

关键字

方法名示例

对应的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

关键字

方法名示例

对应的JPQL片段

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

findByAgeIsNotNull()

… where x.ageisnot null

NotNull

findByAgeNotNull()

… where x.age not null

关键字

方法名示例

对应的JPQL片段

Like

findByFirstnameLike()

… where x.firstname like ?1

NotLike

findByFirstnameNotLike()

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith()

… where x.firstname like ?1 (绑定参数 %)

EndingWith

findByFirstnameEndingWith()

… where x.firstname like ?1 (绑定参数 %)

Containing

findByFirstnameContaining()

… where x.firstname like ?1 (绑定参数 %)

OrderBy

findByAgeOrderByLastnameDesc()

… where x.age = ?1 order by x.lastname desc

关键字

方法名示例

对应的JPQL片段

Not

findByLastnameNot()

… where x.lastname <> ?1

In

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> ages)

… 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)

案例 

在数据操作接口添加如下方法 

    //查询作者不为空的评论
    public List<Discuss> findByAuthorNotNull();

在测试类添加如下方法测试  

    //使用方法名关键字进行数据操作
    @Test
    public void selectCommentByKeys() {
 
        List<Discuss>list = repository.findByAuthorNotNull();
        for (Discuss discuss : list) {
 
            System.out.println(discuss);
 
        }
 
    }

JPQL

使用Spring Data JPA提供的查询方法已经可以满足大部分应用场景的需求,但是有些业务需要更灵活的查询条件,这时就可以使用@Query注解,结合JPQL的方式来完成查询。 JPQL是JPA中定义的一种查询语言,此种语言旨在让开发者忽略数据库表和表中的字段,而关注实体类及实体类中的属性。 JPQL语句的写法和SQL语句的写法十分类似,但是要把查询的表名换成实体类名称,把表中的字段名换成实体类的属性名称。

JPQL支持命名参数和位置参数两种查询参数。

命名参数:在方法的参数列表中,使用@Param注解标注参数的名称,在@Query注解的查询语句中,使用“:参数名称” 匹配参数名称。

位置参数:在@Query注解的查询语句中,使用“?位置编号的数值” 匹配参数,查询语句中参数标注的编号需要和方法的参数列表中参数的顺序依次对应。

示例:

 在数据操作接口添加如下方法 

    //通过文章id分页查询出Discuss评论信息。JPQL
    @Query("SELECT c FROM t_comment c WHERE c.aId = ?1")
    public List<Discuss> getDiscussPaged(Integer aid, Pageable pageable);

    //使用命名参数:bb
    @Query("SELECT c FROM t_comment c WHERE c.aId = :bb")
    public List<Discuss> getDiscussPaged3(@Param(value = "bb") Integer aid, Pageable pageable);

在测试类添加如下方法测试  

    //使用@Query注解
    @Test
    public void selectCommentPaged() {
 
        Pageable page = PageRequest.of(0, 3);
        List<Discuss>list = repository.getDiscussPaged(1, page);
        list.forEach(t -> System.out.println(t)); 
    }

在数据操作接口添加如下方法 

//命名参数绑定
@Query("from Book b where b.author=:author and b.name=:name") 
List<Book> findByCondition1(@Param("author") String author,
 @Param("name") String name); 
//位置参数绑定
@Query("from Book b where b.author=?1 and b.name=?2") 
List<Book> findByCondition2(String author, String name);

JPQL中使用like模糊查询、排序查询、分页查询子句时,其用法与SQL中的用法相同,区别在于JPQL处理的类的实例不同。

示例:

//like模糊查询 
@Query("from Book b where b.name like %:name%")
 List<Book> findByCondition3(@Param("name") String name); 
//排序查询
 @Query("from Book b where b.name like %:name% order by id desc")
List<Book> findByCondition4(@Param("name") String name); 
//分页查询 
@Query("from Book b where b.name like %:name%") 
Page<Book> findByCondition5(Pageable pageable, @Param("name") String name);

JPQL中除了可以使用字符串和基本数据类型的数据作为参数外,还可以使用集合和Bean作为参数,传入Bean进行查询时可以在JPQL中使用SpEL表达式接收变量。

示例:

//传入集合参数查询 
@Query("from Book b where b.id in :ids") 
List<Book> findByCondition6(@Param("ids") Collection<String> ids); 
//传入Bean进行查询(使用SPEL表达式) 
@Query("from Book b where b.author=:#{#Book.author} and " +
      " b.name=:#{#Book.name}") 
Book findByCondition7(@Param("Book") Book Book); 

原生SQL 

如果出现非常复杂的业务情况,导致JPQL和其他查询都无法实现对应的查询,需要自定义SQL进行查询时,可以在@Query注解中定义该SQL。@Query注解中定义的是原生SQL时,需要在注解使用nativeQuery=true指定执行的查询语句为原生SQL,否则会将其当作JPQL执行。

示例:

 在数据操作接口添加如下方法

    //通过文章id分页查询出Discuss评论信息。原生sql
    @Query(value = "SELECT * FROM t_comment  WHERE  a_Id = ?1",nativeQuery = true)
    public List<Discuss> getDiscussPaged2(Integer aid,Pageable pageable);
@Query(value="SELECT * FROM book WHERE id = :id",nativeQuery=true)
Book findByCondition8(@Param("id") Integer id);

数据变更操作(修改、删除)

在数据操作接口添加如下方法 

    //对数据进行更新和删除操作
    @Transactional
    @Modifying
    @Query("UPDATE t_comment c SET c.author = ?1 WHERE c.id = ?2")
    public int updateDiscuss(String author,Integer id);  

    @Transactional
    @Modifying
    @Query("DELETE t_comment c WHERE c.id = ?1")
    public int deleteDiscuss(Integer id);

 如果是mysql在测试类添加如下方法测试


    //更新作者
    @Test
    public void updateDiscuss() {
 
        int i = repository.updateDiscuss("更新者", 1);
        System.out.println("discuss:update:"+i);
 
    }
 
    //删除评论
    @Test
    public void deleteDiscuss() {
 
        int i = repository.deleteDiscuss(7);
        System.out.println("discuss:delete:"+i);
 
    }

如果是h2,从浏览器端测

服务类添加方法如下 

    public int update(Discuss discuss){

        return discussRepository.updateDiscuss(discuss.getAuthor(),discuss.getId());
    }

    public int delete(Integer id){

        return discussRepository.deleteDiscuss(id);
    }

控制器类添加方法如下

    @ResponseBody
    @GetMapping("/update")
    public String update(Discuss discuss){
        int i = discussService.update(discuss);
        if (i==1) {
            return "gengxin chenggong!";
        }else {
            return "gengxin shibai";
        }
    }
    @ResponseBody
    @GetMapping("/delete")
    public String delete(Integer id){
        int i = discussService.delete(id);
        if (i==1) {
            return "shangchu chenggong!";
        }else {
            return "shanchu shibai";
        }
    }

小提示

使用@Query注解可以执行JPQL和原生SQL查询,但是@Query注解无法进行DML数据操纵语言,主要语句有INSERT、DELETE和UPDATE操作,如果需要更新数据库中的数据,需要在对应的方法上标注@Modifying注解,以通知Spring Data当前需要进行的是DML操作。需要注意的是JPQL只支持DELETE和UPDATE操作,不支持INSERT操作。

动手试一试

使用jpa实现文章的增删改查 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值