jpa @manytoone示例在春季启动中
#网络开发人员 #教程 #java #todayilearned

在本教程中,我将向您展示如何在Spring Boot中实现Spring Data JPA多一对一的示例,以使用@ManyToOne注释进行一对多映射。您会知道:

  • 如何配置弹簧数据,JPA,Hibernate与数据库一起工作
  • 如何使用@ManyToOne定义JPA一对多关系的数据模型和存储库接口
  • 使用Spring JPA与多对一关联的数据库进行交互的方式
  • 创建Spring Rest Controller来处理HTTP请求
  • 的方法

相关文章:

JPA @manytoone是适合春季映射的一项映射

在关系数据库中,表A和表B之间的一对多关系表明表A中的一个行链接到表B中的许多行,但表B中的一行仅链接到表A中的一行。

例如,您需要为一个教程博客设计数据模型,其中一个教程具有许多评论。所以这是一个一对多的协会。

您可以将儿童实体映射为父对象(Tutorial)中的集合(Comments列表),而jpa/hibernate为这种情况提供了@OneToMany注释:只有父端定义了关系。我们称其为单向@OneToMany协会。

有关教程,请访问:JPA One To Many Unidirectional example

同样,当只有儿童侧管理关系时,我们与@ManyToOne注释有多一的单向关联,其中儿童(Comment)通过映射外国钥匙具有实体对象对其母体实体(Tutorial)的参考。列(tutorial_id)。

实施JPA/Hibernate对许多映射的最合适方法是与@ManyToOne的单向相关性,因为:

  • 使用@OneToMany,我们需要在父级(教程)中声明一个集合(注释),我们不能限制该集合的大小,例如,在分页时。
  • 使用@ManyToOne,您可以修改存储库:
    • 与分页合作
    • 或通过多个字段进行排序/订单

jpa多人示例

我们将从头开始创建一个春季项目,然后我们用tutorialscomments表实现JPA/Hibernate,如下:

jpa-many-to-one-diagram

我们还编写REST API,以对评论实体进行CRUD操作。

这些是我们需要提供的API:

方法 urls 动作
post /api/tutorials/:id/评论 为教程创建新评论
get /api/tutorials/:id/评论 检索教程的所有评论
get /api/comment/:id 检索:id的评论
put /api/comment/:id 更新:id的评论
delete /api/comment/:id 删除:id的评论
delete /api/tutorials/:ID 删除:id的教程(及其评论)
delete /api/tutorials/:id/评论 删除教程的所有评论

假设我们有教程表:

jpa-manytoone-parent-table

这是示例请求:

  • 创建新评论:发布/api/tutorials/[:id]/comments

jpa-manytoone-example-create-child-entity

注释之后:

jpa-manytoone-child-table

  • 检索特定教程的所有评论:获取/api/tutorials/[:id]/comments

jpa-manytoone-example-spring-crud-retrieve

  • 删除特定教程的所有评论:删除/api/tutorials/[:id]/comments

jpa-manytoone-example-spring-crud-delete

检查注释表,删除了带有ID = 2的教程的所有评论:

jpa-manytoone-example-spring-crud-table-delete

  • 删除教程:删除/api/tutorials/[:id]

jpa-manytoone-example-spring-crud-delete-cascade

jpa-manytoone-example-spring-crud-delete-parent-table

ID = 3的教程的所有评论都是 CASCADE 自动删除。

jpa-manytoone-example-spring-crud-delete-cascade-table

让我们构建我们的春季靴子@manytoone crud示例。

春季靴子多个示例

技术

  • Java 17/11/8
  • Spring Boot 3/2(带有Spring Web MVC,Spring Data JPA)< / li>
  • h2/postgresql/mysql
  • Maven

项目结构

jpa-manytoone-example-hibernate-spring-boot-project-structure

让我简要解释。

TutorialComment数据模型类对应于实体和表教程注释
TutorialRepositoryCommentRepository是延伸JpaRepository的接口,用于CRUD方法和自定义查找器方法。它将在TutorialControllerCommentController中自动进行。
TutorialControllerCommentControllerRestControllers,它请求对静止的CRUD API请求请求映射方法。
< application.properties pom.xml 包含Spring Boot和MySQL/PostgreSQL/H2数据库的依赖性。

关于例外包装,要使此帖子保持直接,我不会解释。有关更多详细信息,您可以阅读以下教程:
@RestControllerAdvice example in Spring Boot

创建&设置Spring Boot Project

使用Spring web tool或您的开发工具(Spring Tool Suite,Eclipse,Intellij)创建一个春季启动项目。

然后打开 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-web</artifactId>
</dependency>

我们还需要再添加一个依赖项。

  • 如果您想使用 mysql
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
  • postgresql
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>
  • H2 (嵌入数据库):
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

配置Spring DataSource,JPA,Hibernate

在src/main/resources文件夹下,打开应用程序。

  • 对于mysql:
spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=update
  • 用于PostgreSQL:
spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.username=postgres
spring.datasource.password=123

spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=update
  • spring.datasource.usernamespring.datasource.password属性与您的数据库安装相同。
  • Spring Boot使用Hibernate用于JPA实现,我们为MySQL或PostgreSQLDialect配置MySQLDialect for Postgresql
  • spring.jpa.hibernate.ddl-auto用于数据库初始化。我们将值设置为update值,以便在数据库中创建一个表,该表将自动与已定义的数据模型相对应。对模型的任何更改也将触发表格的更新。对于生产,该属性应为validate
  • 对于H2数据库:
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update

spring.h2.console.enabled=true
# default path: h2-console
spring.h2.console.path=/h2-ui
  • spring.datasource.url:用于内存数据库的jdbc:h2:mem:[database-name]和基于磁盘的数据库的jdbc:h2:file:[path/database-name]
  • 我们为H2数据库配置H2Dialect
  • spring.h2.console.enabled=true告诉Spring启动H2数据库管理工具,您可以在浏览器上访问此工具:http://localhost:8080/h2-console
  • spring.h2.console.path=/h2-ui用于H2控制台的URL,因此默认URL http://localhost:8080/h2-console将更改为http://localhost:8080/h2-ui

定义了JPA的数据模型,

模型软件包中,我们定义TutorialComment类。

教程有四个字段:ID,标题,描述,已发布。

模型/ tutorial.java

package com.bezkoder.spring.hibernate.onetomany.model;

import jakarta.persistence.*;

@Entity
@Table(name = "tutorials")
public class Tutorial {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tutorial_generator")
  private long id;

  @Column(name = "title")
  private String title;

  @Column(name = "description")
  private String description;

  @Column(name = "published")
  private boolean published;

  public Tutorial() {

  }

  public Tutorial(String title, String description, boolean published) {
    this.title = title;
    this.description = description;
    this.published = published;
  }

  // getters and setters
}
  • @Entity注释表明该类是持久的Java类。
  • @Table注释提供了映射此实体的表。

  • @Id注释用于主键。

  • @GeneratedValue注释用于定义主要键的生成策略。 GenerationType.SEQUENCE是指使用数据库序列生成唯一值。
    我们还指示了主键generator的名称。如果您不给它名称,则ID值将使用 hibernate_squerence 表(默认情况下由Persistence提供者提供)。

  • @Column注释用于定义映射注释字段的数据库中的列。

Comment类具有与Tutorial实体的多对一关系的@ManyToOne注释。 optional元素设置为false,以进行非无效关系。

模型/ comment.java

package com.bezkoder.spring.hibernate.onetomany.model;

import jakarta.persistence.*;

import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = "comments")
public class Comment {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "comment_generator")
  private Long id;

  @Lob
  private String content;

  @ManyToOne(fetch = FetchType.LAZY, optional = false)
  @JoinColumn(name = "tutorial_id", nullable = false)
  @OnDelete(action = OnDeleteAction.CASCADE)
  @JsonIgnore
  private Tutorial tutorial;

  // getters and setters
}

我们还使用@JoinColumn注释来指定外键列(tutorial_id)。如果您不提供JoinColumn名称,则将自动设置该名称。

@JsonIgnore用于忽略在序列化和挑选化中使用的逻辑属性。

我们还使用@OnDelete(action = OnDeleteAction.CASCADE)实施了外国钥匙的级联删除功能。

我们将@ManyToOne设置为FetchType.LAZY用于fetch类型:

jpa-manytoone-lazy-fetch

默认情况下,@ManyToOne关联使用FetchType.EAGER用于fetch类型,但对性能不利:

public class Comment {
  ...

  @ManyToOne(fetch = FetchType.EAGER, optional = false)
  @JoinColumn(name = "tutorial_id", nullable = false)
  @OnDelete(action = OnDeleteAction.CASCADE)
  private Tutorial tutorial;

  ...
}

jpa-manytoone-eager-fetch

为许多人映射创建存储库接口

让我们创建一个与数据库交互的存储库。
存储库中软件包,创建TutorialRepositoryCommentRepository接口扩展了JpaRepository

存储库/ tutorialrepository.java

package com.bezkoder.spring.hibernate.onetomany.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.bezkoder.spring.hibernate.onetomany.model.Tutorial;

public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
  List<Tutorial> findByPublished(boolean published);

  List<Tutorial> findByTitleContaining(String title);
}

存储库/ commentrepository.java

package com.bezkoder.spring.hibernate.onetomany.repository;

import java.util.List;

import jakarta.transaction.Transactional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.bezkoder.spring.hibernate.onetomany.model.Comment;

public interface CommentRepository extends JpaRepository<Comment, Long> {
  List<Comment> findByTutorialId(Long postId);

  @Transactional
  void deleteByTutorialId(long tutorialId);
}

现在我们可以使用JPAREPOSITOR的方法:save()findOne()findById()findAll()count()count()delete()deleteById() ...

我们还定义自定义查找器方法:

  • findByPublished():返回所有教程,带有published的价值为输入published
  • findByTitleContaining():返回所有标题包含输入title的教程。
  • findByTutorialId():返回tutorialId指定的教程的所有评论。
  • deleteByTutorialId():删除tutorialId指定的教程的所有评论。

该实现由Spring Data JPA自动插入。

现在我们可以看到@ManyToOne注释的专利。

更多的派生查询:
JPA Repository query example in Spring Boot

@Query注释自定义查询:
Spring JPA @Query example: Custom query in Spring Boot

您还找到了为此JPA存储库编写单元测试的方法:
Spring Boot Unit Test for JPA Repository with @DataJpaTest

最后,我们创建为CRUD操作提供API的控制器:创建,检索,更新,删除和查找教程和评论。

控制器/tutorialcontroller.java

package com.bezkoder.spring.hibernate.onetomany.controller;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.bezkoder.spring.hibernate.onetomany.exception.ResourceNotFoundException;
import com.bezkoder.spring.hibernate.onetomany.model.Tutorial;
import com.bezkoder.spring.hibernate.onetomany.repository.TutorialRepository;

@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class TutorialController {

  @Autowired
  TutorialRepository tutorialRepository;

  @GetMapping("/tutorials")
  public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
    List<Tutorial> tutorials = new ArrayList<Tutorial>();

    if (title == null)
      tutorialRepository.findAll().forEach(tutorials::add);
    else
      tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);

    if (tutorials.isEmpty()) {
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    return new ResponseEntity<>(tutorials, HttpStatus.OK);
  }

  @GetMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
    Tutorial tutorial = tutorialRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id));

    return new ResponseEntity<>(tutorial, HttpStatus.OK);
  }

  @PostMapping("/tutorials")
  public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
    Tutorial _tutorial = tutorialRepository.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), true));
    return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
  }

  @PutMapping("/tutorials/{id}")
  public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
    Tutorial _tutorial = tutorialRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + id));

    _tutorial.setTitle(tutorial.getTitle());
    _tutorial.setDescription(tutorial.getDescription());
    _tutorial.setPublished(tutorial.isPublished());

    return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
  }

  @DeleteMapping("/tutorials/{id}")
  public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
    tutorialRepository.deleteById(id);

    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
  }

  @DeleteMapping("/tutorials")
  public ResponseEntity<HttpStatus> deleteAllTutorials() {
    tutorialRepository.deleteAll();

    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
  }

  @GetMapping("/tutorials/published")
  public ResponseEntity<List<Tutorial>> findByPublished() {
    List<Tutorial> tutorials = tutorialRepository.findByPublished(true);

    if (tutorials.isEmpty()) {
      return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }

    return new ResponseEntity<>(tutorials, HttpStatus.OK);
  }
}

控制器/commentcontroller.java

package com.bezkoder.spring.hibernate.onetomany.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.bezkoder.spring.hibernate.onetomany.exception.ResourceNotFoundException;
import com.bezkoder.spring.hibernate.onetomany.model.Comment;
import com.bezkoder.spring.hibernate.onetomany.repository.CommentRepository;
import com.bezkoder.spring.hibernate.onetomany.repository.TutorialRepository;

@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class CommentController {

  @Autowired
  private TutorialRepository tutorialRepository;

  @Autowired
  private CommentRepository commentRepository;

  @GetMapping("/tutorials/{tutorialId}/comments")
  public ResponseEntity<List<Comment>> getAllCommentsByTutorialId(@PathVariable(value = "tutorialId") Long tutorialId) {
    if (!tutorialRepository.existsById(tutorialId)) {
      throw new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId);
    }

    List<Comment> comments = commentRepository.findByTutorialId(tutorialId);
    return new ResponseEntity<>(comments, HttpStatus.OK);
  }

  @GetMapping("/comments/{id}")
  public ResponseEntity<Comment> getCommentsByTutorialId(@PathVariable(value = "id") Long id) {
    Comment comment = commentRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("Not found Comment with id = " + id));

    return new ResponseEntity<>(comment, HttpStatus.OK);
  }

  @PostMapping("/tutorials/{tutorialId}/comments")
  public ResponseEntity<Comment> createComment(@PathVariable(value = "tutorialId") Long tutorialId,
      @RequestBody Comment commentRequest) {
    Comment comment = tutorialRepository.findById(tutorialId).map(tutorial -> {
      commentRequest.setTutorial(tutorial);
      return commentRepository.save(commentRequest);
    }).orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId));

    return new ResponseEntity<>(comment, HttpStatus.CREATED);
  }

  @PutMapping("/comments/{id}")
  public ResponseEntity<Comment> updateComment(@PathVariable("id") long id, @RequestBody Comment commentRequest) {
    Comment comment = commentRepository.findById(id)
        .orElseThrow(() -> new ResourceNotFoundException("CommentId " + id + "not found"));

    comment.setContent(commentRequest.getContent());

    return new ResponseEntity<>(commentRepository.save(comment), HttpStatus.OK);
  }

  @DeleteMapping("/comments/{id}")
  public ResponseEntity<HttpStatus> deleteComment(@PathVariable("id") long id) {
    commentRepository.deleteById(id);

    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
  }

  @DeleteMapping("/tutorials/{tutorialId}/comments")
  public ResponseEntity<List<Comment>> deleteAllCommentsOfTutorial(@PathVariable(value = "tutorialId") Long tutorialId) {
    if (!tutorialRepository.existsById(tutorialId)) {
      throw new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId);
    }

    commentRepository.deleteByTutorialId(tutorialId);
    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
  }
}

结论

今天,我们已经使用弹簧数据JPA建立了一个春季靴子CRUD示例,与MySQL/PostgreSQL/Embedded Database(H2)冬眠了许多关系。

我们还看到@ManyToOne注释是实施JPA一对下映射的最合适的方法,而JpaRepository支持一种制作CRUD操作的好方法,无需样板板代码。

@Query注释自定义查询:
Spring JPA @Query example: Custom query in Spring Boot

如果要在这个春季项目中添加分页,可以在:
上找到指令 Spring Boot Pagination & Filter example | Spring JPA, Pageable

通过多个字段进行排序/顺序:
Spring Data JPA Sort/Order by multiple Columns | Spring Boot

处理此REST API的例外是必要的:

或为JPA存储库编写单元测试的方法:
Spring Boot Unit Test for JPA Repository with @DataJpaTest

您也可以知道:

快乐学习!再次见。

进一步阅读

Fullstack Crud应用:

源代码

您可以在Github上找到本教程的完整源代码。

而不是使用@OneToManyJPA One To Many Unidirectional example
多对多:JPA Many to Many example with Hibernate in Spring Boot
一对一:JPA One To One example with Hibernate in Spring Boot

您可以在以下教程中应用此实现:

更多的派生查询:
JPA Repository query example in Spring Boot

文档:Spring Boot + Swagger 3 example (with OpenAPI 3)
缓存:Spring Boot Redis Cache example