Initial commit

This commit is contained in:
Epikhin Aleksey 2022-08-16 12:04:20 +03:00
commit 41af59a122
17 changed files with 725 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

52
pom.xml Normal file
View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yapract</groupId>
<artifactId>spring-boot-one-to-many</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-one-to-many</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.4.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.yprac.spring.hibernate.onetomany;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootOneToManyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootOneToManyApplication.class, args);
}
}

View File

@ -0,0 +1,89 @@
package com.yprac.spring.hibernate.onetomany.controller;
import java.util.List;
import com.yprac.spring.hibernate.onetomany.exception.ResourceNotFoundException;
import com.yprac.spring.hibernate.onetomany.repository.CommentRepository;
import com.yprac.spring.hibernate.onetomany.repository.TutorialRepository;
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.yprac.spring.hibernate.onetomany.model.Comment;
@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);
}
}

View File

@ -0,0 +1,104 @@
package com.yprac.spring.hibernate.onetomany.controller;
import com.yprac.spring.hibernate.onetomany.exception.ResourceNotFoundException;
import com.yprac.spring.hibernate.onetomany.model.Tag;
import com.yprac.spring.hibernate.onetomany.model.Tutorial;
import com.yprac.spring.hibernate.onetomany.repository.TagRepository;
import com.yprac.spring.hibernate.onetomany.repository.TutorialRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class TagController {
@Autowired
private TutorialRepository tutorialRepository;
@Autowired
private TagRepository tagRepository;
@GetMapping("/tags")
public ResponseEntity<List<Tag>> getAllTags() {
List<Tag> tags = new ArrayList<Tag>();
tagRepository.findAll().forEach(tags::add);
if (tags.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tags, HttpStatus.OK);
}
@GetMapping("/tutorials/{tutorialId}/tags")
public ResponseEntity<List<Tag>> getAllTagsByTutorialId(@PathVariable(value = "tutorialId") Long tutorialId) {
if (!tutorialRepository.existsById(tutorialId)) {
throw new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId);
}
List<Tag> tags = tagRepository.findTagsByTutorialsId(tutorialId);
return new ResponseEntity<>(tags, HttpStatus.OK);
}
@GetMapping("/tags/{id}")
public ResponseEntity<Tag> getTagsById(@PathVariable(value = "id") Long id) {
Tag tag = tagRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Not found Tag with id = " + id));
return new ResponseEntity<>(tag, HttpStatus.OK);
}
@GetMapping("/tags/{tagId}/tutorials")
public ResponseEntity<List<Tutorial>> getAllTutorialsByTagId(@PathVariable(value = "tagId") Long tagId) {
if (!tagRepository.existsById(tagId)) {
throw new ResourceNotFoundException("Not found Tag with id = " + tagId);
}
List<Tutorial> tutorials = tutorialRepository.findTutorialsByTagsId(tagId);
return new ResponseEntity<>(tutorials, HttpStatus.OK);
}
@PostMapping("/tutorials/{tutorialId}/tags")
public ResponseEntity<Tag> addTag(@PathVariable(value = "tutorialId") Long tutorialId, @RequestBody Tag tagRequest) {
Tag tag = tutorialRepository.findById(tutorialId).map(tutorial -> {
long tagId = tagRequest.getId();
// tag is existed
if (tagId != 0L) {
Tag _tag = tagRepository.findById(tagId)
.orElseThrow(() -> new ResourceNotFoundException("Not found Tag with id = " + tagId));
tutorial.addTag(_tag);
tutorialRepository.save(tutorial);
return _tag;
}
// add and create new Tag
tutorial.addTag(tagRequest);
return tagRepository.save(tagRequest);
}).orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId));
return new ResponseEntity<>(tag, HttpStatus.CREATED);
}
@PutMapping("/tags/{id}")
public ResponseEntity<Tag> updateTag(@PathVariable("id") long id, @RequestBody Tag tagRequest) {
Tag tag = tagRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("TagId " + id + "not found"));
tag.setName(tagRequest.getName());
return new ResponseEntity<>(tagRepository.save(tag), HttpStatus.OK);
}
@DeleteMapping("/tutorials/{tutorialId}/tags/{tagId}")
public ResponseEntity<HttpStatus> deleteTagFromTutorial(@PathVariable(value = "tutorialId") Long tutorialId, @PathVariable(value = "tagId") Long tagId) {
Tutorial tutorial = tutorialRepository.findById(tutorialId)
.orElseThrow(() -> new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId));
tutorial.removeTag(tagId);
tutorialRepository.save(tutorial);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@DeleteMapping("/tags/{id}")
public ResponseEntity<HttpStatus> deleteTag(@PathVariable("id") long id) {
tagRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}

View File

@ -0,0 +1,98 @@
package com.yprac.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.yprac.spring.hibernate.onetomany.exception.ResourceNotFoundException;
import com.yprac.spring.hibernate.onetomany.model.Tutorial;
import com.yprac.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);
}
}

View File

@ -0,0 +1,37 @@
package com.yprac.spring.hibernate.onetomany.exception;
import java.util.Date;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
@RestControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public ErrorMessage resourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.NOT_FOUND.value(),
new Date(),
ex.getMessage(),
request.getDescription(false));
return message;
}
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorMessage globalExceptionHandler(Exception ex, WebRequest request) {
ErrorMessage message = new ErrorMessage(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
new Date(),
ex.getMessage(),
request.getDescription(false));
return message;
}
}

View File

@ -0,0 +1,33 @@
package com.yprac.spring.hibernate.onetomany.exception;
import java.util.Date;
public class ErrorMessage {
private int statusCode;
private Date timestamp;
private String message;
private String description;
public ErrorMessage(int statusCode, Date timestamp, String message, String description) {
this.statusCode = statusCode;
this.timestamp = timestamp;
this.message = message;
this.description = description;
}
public int getStatusCode() {
return statusCode;
}
public Date getTimestamp() {
return timestamp;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,10 @@
package com.yprac.spring.hibernate.onetomany.exception;
public class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,46 @@
package com.yprac.spring.hibernate.onetomany.model;
import javax.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;
@Column(name = "content")
private String content;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "tutorial_id", nullable = false)
// @OnDelete(action = OnDeleteAction.CASCADE)
@JsonIgnore
private Tutorial tutorial;
public Long getId() {
return id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Tutorial getTutorial() {
return tutorial;
}
public void setTutorial(Tutorial tutorial) {
this.tutorial = tutorial;
}
}

View File

@ -0,0 +1,36 @@
package com.yprac.spring.hibernate.onetomany.model;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
@Entity
@Table(name = "tags")
public class Tag {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "name")
private String name;
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "tags")
@JsonIgnore
private Set<Tutorial> tutorials = new HashSet<>();
public Tag() {
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Tutorial> getTutorials() {
return tutorials;
}
public void setTutorials(Set<Tutorial> tutorials) {
this.tutorials = tutorials;
}
}

View File

@ -0,0 +1,108 @@
package com.yprac.spring.hibernate.onetomany.model;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@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;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
@JoinColumn(name = "tutorial_id")
private Set<Comment> comments = new HashSet<>();
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "tutorial_tags",
joinColumns = { @JoinColumn(name = "tutorial_id") },
inverseJoinColumns = { @JoinColumn(name = "tag_id") })
private Set<Tag> tags = new HashSet<>();
public Tutorial() {
}
public Tutorial(String title, String description, boolean published) {
this.title = title;
this.description = description;
this.published = published;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isPublished() {
return published;
}
public void setPublished(boolean isPublished) {
this.published = isPublished;
}
public Set<Tag> getTags() {
return tags;
}
public void setTags(Set<Tag> tags) {
this.tags = tags;
}
public Set<Comment> getComments() {
return comments;
}
public void setComments(Set<Comment> comments) {
this.comments = comments;
}
@Override
public String toString() {
return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
}
public void addTag(Tag tag) {
this.tags.add(tag);
tag.getTutorials().add(this);
}
public void removeTag(Long tagId) {
Tag tag = this.tags.stream().filter(t -> t.getId() == tagId).findFirst().orElse(null);
if (tag != null) {
this.tags.remove(tag);
tag.getTutorials().remove(this);
}
}
}

View File

@ -0,0 +1,16 @@
package com.yprac.spring.hibernate.onetomany.repository;
import java.util.List;
import javax.transaction.Transactional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.yprac.spring.hibernate.onetomany.model.Comment;
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByTutorialId(Long postId);
@Transactional
void deleteByTutorialId(long tutorialId);
}

View File

@ -0,0 +1,11 @@
package com.yprac.spring.hibernate.onetomany.repository;
import com.yprac.spring.hibernate.onetomany.model.Tag;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface TagRepository extends JpaRepository<Tag, Long> {
List<Tag> findTagsByTutorialsId(Long tutorialId);
}

View File

@ -0,0 +1,15 @@
package com.yprac.spring.hibernate.onetomany.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.yprac.spring.hibernate.onetomany.model.Tutorial;
public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
List<Tutorial> findByPublished(boolean published);
List<Tutorial> findByTitleContaining(String title);
List<Tutorial> findTutorialsByTagsId(Long tagId);
}

View File

@ -0,0 +1,11 @@
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.PostgreSQL9Dialect
#spring.jpa.generate.ddl=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

View File

@ -0,0 +1,13 @@
package com.yprac.spring.hibernate.onetomany;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringBootOneToManyApplicationTests {
@Test
void contextLoads() {
}
}