Web

Spring Boot - Eclipse Maven으로 Spring Web Layer 실습

kakaroo 2022. 2. 14. 21:53
반응형

article logo

 

아래 기등록한 포스트는 IntelliJ + Gradle 로 구현하였기에

Eclipse + Maven 환경으로 다시 해 보겠습니다. (공부삼아.. )

 

 

https://kakaroo.tistory.com/39

 

2 - Spring Boot - JPA 구현 by Spring Web Layer

Spring Web Layer 구조로 H2 database를 사용하여 JPA를 구현해 보겠습니다. https://kakaroo.tistory.com/41 Spring Web Layer (공사중...) 보호되어 있는 글입니다. 내용을 보시려면 비밀번호를 입력하세요. kak..

kakaroo.tistory.com

 

1. Project 생성

Eclipse New Spring Starter Project
Eclipse New Spring Starter Project

 

dependency 설정
dependency 설정

여기까지만 하면 아래 Project 생성한 게 간단하게 끝이 납니다.

https://kakaroo.tistory.com/38

 

1 - Spring Boot - Start Application<IntelliJ-Gradle>

1. Gradle project 를 Spring Boot preject로 변경하기 아래와 같이 Spring Boot 환경에 맞게 gradle을 수정해줍니다. buildscript { // ext는 build.gradle에서 사용하는 전역변수를 설정하겠다는 의미이다. ext..

kakaroo.tistory.com

 

Maven으로 생성했기에 자동생성된 dependency 관련 설정 내용은 다음과 같습니다.

<?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.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.kakaroo</groupId>
	<artifactId>SpringTestJPA</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringTestJPA</name>
	<description>Eclipse JPA project for Spring Boot</description>
	<properties>
		<java.version>11</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>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</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>
				<configuration>
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

 

2. Domain 생성 (Entity, EntityRepository)

package com.kakaroo.springtestjpa.domain.posts;

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

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@Entity
public class Posts {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(length=500, nullable = false)
	private String title;
	
	@Column(columnDefinition = "TEXT", nullable = false)
	private String content;
	
	private String author;
	
	@Builder
	public Posts(String title, String content, String author) {
		this.title = title;
		this.content = content;
		this.author = author;
	}

}
package com.kakaroo.springtestjpa.domain.posts;

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

public interface PostsRepository extends JpaRepository<Posts, Long> {
	
}

3. DTO 

DTO는 Controller에서 argument 또는 return type으로 사용되어 REST API 등을 사용할 때 JSON or xml 타입으로 값이 리턴됩니다. Service 전에 DTO를 먼저 생성합니다.

 

CRUD에서 Delete를 뺀 나머지를 차례대로 구현합니다.

 

3.1 Create는 Parameter로 DTO를 받아들이고, 이를 Entity로 변환해서 저장해야 합니다.

package com.kakaroo.springtestjpa.web;

import com.kakaroo.springtestjpa.domain.posts.Posts;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class PostsCreateDto {
	private String title; // Id 값은 들어오지 않고, 자동 생성된다.
	private String content;
	private String author;

	@Builder
	public PostsCreateDto(String title, String content, String author) {
		this.title = title;
		this.content = content;
		this.author = author;
	}

	// Post :: Request arg 값이 JSON 형태로 들어온다.
	// Service 에서 repository에 담을 값은 entity가 되어야 한다.
	public Posts toEntity() {
		return Posts.builder().title(title).content(content).author(author).build();
	}

}

 

3.2 Read는 Parameter로 id를 받아들이고, Service에서 repository의 findById를 호출할 것이고, 리턴된 entity를 Web에 리턴할 JSON형태인 Dto 클래스로 재변환해야 합니다.

package com.kakaroo.springtestjpa.web;

import com.kakaroo.springtestjpa.domain.posts.Posts;

import lombok.Getter;

@Getter
public class PostsReadDto {
	private Long id;
	private String title;
	private String content;
	private String author;
	
	//Service에서 repository를 findById 로 query(read) 한다.
	//이 때, return 되는 값은 Entity이고, 이 값을 파라미터로 갖는 생성자 함수를 정의한다.
	public PostsReadDto(Posts entity) {
		this.id = entity.getId();
		this.title = entity.getTitle();
		this.content = entity.getContent();
		this.author = entity.getAuthor();
	}

}

 

3.2 Update는 Id와 Dto를 param으로 가지는데, findById로 entity를 검색합니다. 

package com.kakaroo.springtestjpa.web;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class PostsUpdateDto {
	private String title;
	private String content;
	private String author;

	// Service에서 findById로 검색한 뒤에, update를 한다.

	@Builder
	public PostsUpdateDto(String title, String content, String author) {
		this.title = title;
		this.content = content;
		this.author = author;
	}
}

 

Domain의 entity는 이전에 구현했던 부분에서 update 메소드만 추가로 구현해 줍니다.

update 메소드가 필요한 이유는 update시 repository에 query를 따로 보내지 않습니다.

JPA에는 엔티티를 영구저장하는 환경인 영속성 컨텍스트 기능이 있는데, 데이터의 값을 변경하면 트랜젝션이 끝나는 시점에 해당 테이블에 변경분을 반영합니다. 이것을 더티체킹이라 합니다.

package com.kakaroo.springtestjpa.domain.posts;

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

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@Entity
public class Posts {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(length=500, nullable = false)
	private String title;
	
	@Column(columnDefinition = "TEXT", nullable = false)
	private String content;
	
	private String author;
	
	@Builder
	public Posts(String title, String content, String author) {
		this.title = title;
		this.content = content;
		this.author = author;
	}	
	
	public void update(String title, String content, String author) {
		this.title = title;
		this.content = content;
		this.author = author;
	}
}

 

4. Service

readAllByDesc 는 query하는 부분에서 문제가 생겨 error 발생으로 임시로 주석처리하고 아래에서 다시 다루겠습니다.

package com.kakaroo.springtestjpa.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.kakaroo.springtestjpa.domain.posts.Posts;
import com.kakaroo.springtestjpa.domain.posts.PostsRepository;
import com.kakaroo.springtestjpa.web.PostsCreateDto;
import com.kakaroo.springtestjpa.web.PostsReadDto;
import com.kakaroo.springtestjpa.web.PostsUpdateDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class PostsService {
	private final PostsRepository postsRepository;
	
	@Transactional
	public Long create(PostsCreateDto createDto) {
		return postsRepository.save(createDto.toEntity()).getId();
	}
	
	@Transactional(readOnly = true)
	public PostsReadDto read(Long id) {
		Posts posts = postsRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("read error!!! id: "+id));
		return new PostsReadDto(posts);
	}
	
	@Transactional(readOnly = true)
	public List<PostsReadDto> readAll() {
		List<PostsReadDto> ret = postsRepository.findAll().stream().map(post -> new PostsReadDto(post)).collect(Collectors.toList());
		return ret;
	}
	
	/*
	 * @Transactional(readOnly = true) public List<PostsReadDto> readAllByDesc() {
	 * List<PostsReadDto> ret = postsRepository.findAllByDesc().stream().map(post ->
	 * new PostsReadDto(post)).collect(Collectors.toList()); return ret; }
	 */
	
	@Transactional
	public Long update(Long id, PostsUpdateDto updateDto) {
		Posts posts = postsRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("update error!!! id: "+id));
		posts.update(updateDto.getTitle(), updateDto.getContent(), updateDto.getAuthor());		
		return id;
	}
	
	@Transactional
	public void delete(Long id) {
		Posts posts = postsRepository.findById(id).orElseThrow(()-> new IllegalArgumentException("delete error!!! id: "+id));
		postsRepository.delete(posts);
	}

}

 

5. Controller

package com.kakaroo.springtestjpa.web;

import java.util.List;

import org.springframework.stereotype.Service;
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.RestController;

import com.kakaroo.springtestjpa.domain.posts.Posts;
import com.kakaroo.springtestjpa.domain.posts.PostsRepository;

import lombok.RequiredArgsConstructor;
import com.kakaroo.springtestjpa.service.PostsService;

@RequiredArgsConstructor
@RestController
public class PostApiController {
		
	private final PostsRepository postsRepository;
	private final PostsService postsService;
	
	@GetMapping("/")
	public String test() {
		for(int i=0 ; i<5; i++) {
			postsRepository.save(Posts.builder().title("Title"+i).content("Content"+i).author("Author"+i).build());
		}
		return "Test added!";
	}
	
	//Create of CRUD
	@PostMapping("/post")
	public Long create(@RequestBody PostsCreateDto createDto) {
		return postsService.create(createDto);
	}
	//Return Value는 JSON 타입을 갖는 DTO 클래스로
	//Read of CRUD	
	@GetMapping("/get/{id}")
	public PostsReadDto read(@PathVariable("id") Long id) {
		return postsService.read(id);
	}
	
	@GetMapping("get/list")
	public List<PostsReadDto> readAll() {
		return postsService.readAll();
	}
	
	/*
	 * @GetMapping("get/listdesc") public List<PostsReadDto> readAllByDesc() {
	 * return postsService.readAllByDesc(); }
	 */
	
	//Update of CRUD
	@PutMapping("/put/{id}")
	public Long update(@PathVariable("id") Long id, @RequestBody PostsUpdateDto updateDto) {
		return postsService.update(id, updateDto);
	}
	
	//Delete of CRUD
	@DeleteMapping("/delete/{id}")
	public void delete(@PathVariable Long id) {
		postsService.delete(id);
	}
}

Failed 발생!!!

 

***************************
APPLICATION FAILED TO START
***************************

Description:
Parameter 1 of constructor in com.kakaroo.springtestjpa.web.PostApiController required a bean of type 'service.PostsService' that could not be found.


Action:
Consider defining a bean of type 'service.PostsService' in your configuration.

 

 

해당 에러를 구글링하면 여러가지 원인이 나오는데, 제 경우에는 원인을 구글링 결과에서는 못 찾았습니다.

코드 리뷰 결과, 아래의 service package가 java 아래에 생성되어 bean을 찾지 못해 생긴 문제였습니다. ^^;;

package 트리 구성
수정한&nbsp;package 트리 구성

 


결과

1. GetMapping for Read

findAll
findAll
findById
findById

2. PostMapping for Create

postmapping by postman
postmapping by postman

3. PutMapping for Update

putmapping by postman
putmapping by postman

 

4. DeleteMapping for Delete

deletemapping by postman
deletemapping by postman

 


위에서 에러가 발생해 임시로 주석처리한 부분을 다시 해결해 보겠습니다.

Caused by: org.hibernate.QueryException: Unable to resolve path [p.Id], unexpected token [p] [SELECT p FROM com.kakaroo.springtestjpa.domain.posts.Posts ORDER BY p.Id DESC] at org.hibernate.QueryException.generateQueryException(QueryException.java:120) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:220) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:636) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final] at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:748) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]

 

아래 query 부분에 뭔가 잘못되었다고 나옵니다.

package com.kakaroo.springtestjpa.domain.posts;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

public interface PostsRepository extends JpaRepository<Posts, Long> {
	@Query("SELECT p FROM Posts ORDER BY p.Id DESC")
	List<Posts> findAllByDesc();
}

 

....

반응형