묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
준영속상태 ? 영속상태 질문있어요
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.준영속상태랑 영속상태랑 차이점은 알겠는데요 .. Book book = new Book(); book.setId(form.getId()); book.setName ... ... 이게 기존 디비를 갔다왔다 ?라는데 BookForm 객체중에 저장된 id를 가져와서 그런건가요 ??? 그러면 객체에서 findById로 가져와서 객체새로 만든거에 아이디를 셋하는 형태는 전부 준영속 상태인건지 ... jpa가 관리하는지 안하는지 코드로 어떻게 알수있는지 정확히 모르겠어요 애가 영속성엔티티 인지 준영속성 상태인지 알수있는방법좀 알려주세요 코드상으로 봤을떄..다른글에 공통으로 걸어주신 링크봤는데도 이해가 안되요 ..ㅠㅠ Book book = new Book(); book.setId(form.getId()); book.setName ... ... 위 코드를 그러면 영속성이라고 볼려면 어디가 바껴야되요 ? 그냥 코드상 보면 BookForm이 엔티티고 이게 아이디를 가지고 있으니 디비에 저장되어 있는거고 그 아이디를 가지고 왔으니 준영속성이라고 보는건가요? 그럼 그냥 제가 임의로 Book book= new Book(); book.setId(12);이러면 어떻게 되는건가요???
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
No Persistence provider for EntityManager named hello
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 안녕하세요 No Persistence provider for EntityManager named hello라는 에러가 뜨는데요, persistence.xml 경로도 확인해봤고, 캐시 지우고 다시 빌드해도 여전히 persistence-unit name 을 못찾는것 같습니다. xml 파일은 슬라이드 그대로 복사하여 사용 했고, 디펜던시는 조금 다릅니다. 사진과 pom.xml을 첨부합니다. 디펜던시에 추가해야할 것이 있는건지, 아니면 경로를 왜 못찾는 걸까요,,? <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>jpa-basic</groupId> <artifactId>ex1-hello-jpa</artifactId> <version>1.0.0</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-core</artifactId> <version>6.2.6.Final</version> </dependency> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <version>1.0.0.Final</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.199</version> </dependency> </dependencies> </project> <?xml version="1.0" encoding="UTF-8"?> <persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"> <persistence-unit name="hello"> <properties> <!-- 필수 속성 --> <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> <property name="javax.persistence.jdbc.user" value="sa"/> <property name="javax.persistence.jdbc.password" value=""/> <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <!-- 옵션 --> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.use_sql_comments" value="true"/> <!--<property name="hibernate.hbm2ddl.auto" value="create" />--> </properties> </persistence-unit> </persistence>package hellojpa; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public class JpaMain { public static void main(String [] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); } }Exception in thread "main" javax.persistence.PersistenceException: No Persistence provider for EntityManager named hello at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:61) at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:39) at hellojpa.JpaMain.main(JpaMain.java:8)
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
테스트 에러 질문드립니다.
package com.service.com.service; import static org.junit.Assert.assertEquals; import javax.persistence.EntityManager; import javax.transaction.Transactional; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import com.service.com.domain.Member; import com.service.com.repository.MemberRepository; @RunWith(SpringRunner.class) @SpringBootTest @Transactional public class MemberServiceTest { @Autowired MemberService memberService; @Autowired MemberRepository memberRepository; @Autowired EntityManager em; @Test public void 회원가입() throws Exception{ Member member = new Member(); member.setName("Kim"); // when Long saveId = memberService.join(member); // then assertEquals(member, memberRepository.findOne(saveId)); } @Test(expected = IllegalStateException.class) public void 중복_회원_예외() throws Exception { Member member1 = new Member(); member1.setName("kim"); Member member2 = new Member(); member2.setName("kim"); fail("예외가 발생해야 한다"); } } 2023-11-16 20:00:08.269 INFO 22664 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext@1c3b9394 testClass = MemberServiceTest, testInstance = com.service.com.service.MemberServiceTest@5cc152f9, testMethod = 중복_회원_예외@MemberServiceTest, testException = java.lang.AssertionError: Expected exception: java.lang.IllegalStateException, mergedContextConfiguration = [WebMergedContextConfiguration@6f2cfcc2 testClass = MemberServiceTest, locations = '{}', classes = '{class com.service.com.JpashopApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@4310d43, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3527942a, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@4f80542f, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@576d5deb, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@2794eab6, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@3972a855], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]java.lang.NoSuchMethodError: org.junit.platform.engine.TestDescriptor.getAncestors()Ljava/util/Set;at org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener.getTestClassNames(StackTracePruningEngineExecutionListener.java:50)at org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener.executionFinished(StackTracePruningEngineExecutionListener.java:39)at org.junit.platform.launcher.core.DelegatingEngineExecutionListener.executionFinished(DelegatingEngineExecutionListener.java:46)at org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener.reportEngineFailure(OutcomeDelayingEngineExecutionListener.java:83)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:203)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:169)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:93)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:58)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:141)at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:57)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:103)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:94)at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:52)at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:70)at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:98)at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:40)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:529)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:756)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)plugins {id 'java'id 'org.springframework.boot' version '2.7.17'id 'io.spring.dependency-management' version '1.0.15.RELEASE'} group = 'com.example'version = '0.0.1-SNAPSHOT' java {sourceCompatibility = '11'} repositories {mavenCentral()} dependencies {implementation 'org.springframework.boot:spring-boot-starter-data-jpa'implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'implementation 'org.springframework.boot:spring-boot-starter-validation'implementation 'org.springframework.boot:spring-boot-starter-web'implementation 'org.projectlombok:lombok'runtimeOnly 'com.h2database:h2'developmentOnly 'org.springframework.boot:spring-boot-devtools'testImplementation 'org.springframework.boot:spring-boot-starter-test'//JUnit4 추가testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' tasks.named('bootBuildImage') {builder = 'paketobuildpacks/builder-jammy-base:latest'} tasks.named('test') {useJUnitPlatform()}이거 어디가 잘못된걸까요 예제 파일 다운받아서 4로 해도 마찬가지고 5로 해도 마찬가지로 나오네요 ..
-
미해결
JPA 문제
public ResponseEntity<?> updateBoard(Long boardId, CreateBoardDTO boardDTO, String memberEmail) { try { // 게시글 조회 BoardEntity findBoard = boardRepository.findByBoardId(boardId) .orElseThrow(EntityNotFoundException::new); log.info("게시글 닉네임 : " + findBoard.getMember().getNickName()); // 유저 조회 MemberEntity findUser = memberRepository.findByEmail(memberEmail); log.info("유저 : " + findUser); // 받아온 유저를 조회하고 그 유저 정보와 게시글에 담긴 유저가 일치하는지 boolean equalsEmail = findUser.getEmail().equals(findBoard.getMember().getEmail()); if(equalsEmail) { // 수정할 내용, 유저정보, 게시글을 작성할 때 받은 상품의 정보를 넘겨준다. findBoard = BoardEntity.builder() .boardId(findBoard.getBoardId()) .title(boardDTO.getTitle()) .content(boardDTO.getContent()) .item(findBoard.getItem()) .member(findBoard.getMember()) .boardSecret(BoardSecret.UN_LOCK) .build(); BoardEntity updateBoard = boardRepository.save(findBoard); BoardDTO returnBoard = BoardDTO.toBoardDTO(updateBoard, findUser.getNickName()); log.info("게시글 : " + returnBoard); return ResponseEntity.ok().body(returnBoard); } else { return ResponseEntity.badRequest().body("일치하지 않습니다."); } } catch (Exception e) { e.printStackTrace(); return ResponseEntity.badRequest().body("수정하는데 실패했습니다. : " + e.getMessage()); } }package com.example.shopping.entity.board; import com.example.shopping.domain.board.BoardDTO; import com.example.shopping.domain.board.BoardSecret; import com.example.shopping.domain.board.CreateBoardDTO; import com.example.shopping.domain.comment.CommentDTO; import com.example.shopping.entity.Base.BaseEntity; import com.example.shopping.entity.comment.CommentEntity; import com.example.shopping.entity.item.ItemEntity; import com.example.shopping.entity.member.MemberEntity; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import javax.persistence.*; import java.util.ArrayList; import java.util.List; @Entity(name = "board") @Table @Getter @NoArgsConstructor public class BoardEntity extends BaseEntity { @Id @GeneratedValue @Column(name = "board_id") private Long boardId; @Column(length = 300, nullable = false) private String title; private String content; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private MemberEntity member; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "item_id") private ItemEntity item; @Enumerated(EnumType.STRING) private BoardSecret boardSecret; // 댓글 // 여기에 적용해야 합니다. 보통 게시물을 삭제해야 이미지가 삭제되므로 // 게시물이 주축이기 때문에 여기에 cascade = CascadeType.ALL을 추가 // orphanRemoval = true도 게시글을 삭제하면 // 댓글도 삭제되므로 여기서 작업을 해야합니다. @OneToMany(mappedBy = "board", cascade = CascadeType.ALL, orphanRemoval = true) @OrderBy("commentId asc ") private List<CommentEntity> commentEntityList = new ArrayList<>(); @Builder public BoardEntity(Long boardId, String title, String content, MemberEntity member, ItemEntity item, BoardSecret boardSecret, List<CommentEntity> commentEntityList) { this.boardId = boardId; this.title = title; this.content = content; this.member = member; this.item = item; this.boardSecret = boardSecret; this.commentEntityList = commentEntityList; } // 게시글 DTO를 엔티티로 변환 public static BoardEntity toBoardEntity(BoardDTO board, MemberEntity member, ItemEntity item) { BoardEntity boardEntity = com.example.shopping.entity.board.BoardEntity.builder() .boardId(board.getBoardId()) .title(board.getTitle()) .content(board.getContent()) .member(member) .item(item) .build(); // 댓글 처리 List<CommentDTO> commentDTOList = board.getCommentDTOList(); for (CommentDTO commentDTO : commentDTOList) { CommentEntity commentEntity = CommentEntity.toCommentEntity(commentDTO, member, boardEntity); boardEntity.commentEntityList.add(commentEntity); } return boardEntity; } /* 비즈니스 로직 */ // 게시글 작성 public static BoardEntity createBoard(CreateBoardDTO boardDTO, MemberEntity member, ItemEntity item) { return com.example.shopping.entity.board.BoardEntity.builder() .title(boardDTO.getTitle()) .content(boardDTO.getContent()) // 본인이 작성한 글은 읽을 수 있어야하기 때문에 UN_ROCK .boardSecret(BoardSecret.UN_LOCK) .member(member) .item(item) .build(); } // 문의글 상태 변화 public void changeSecret(BoardSecret secret) { this.boardSecret = secret; } } package com.example.shopping.domain.board; import com.example.shopping.domain.comment.CommentDTO; import com.example.shopping.entity.board.BoardEntity; import com.example.shopping.entity.comment.CommentEntity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import javax.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; @Getter @ToString @NoArgsConstructor public class BoardDTO { @Schema(description = "문의글 번호", example = "1", required = true) private Long boardId; @Schema(description = "문의글 제목", required = true) @NotNull(message = "문의글 제목은 필 수 입력입니다.") private String title; @Schema(description = "문의글 본문") private String content; @Schema(description = "유저 닉네임") private String nickName; @Schema(description = "문의글 작성 시간") private LocalDateTime regTime; @Schema(description = "관리자 답변") private List<CommentDTO> commentDTOList = new ArrayList<>(); @Schema(description = "문의글이 본인글인지 확인") private BoardSecret boardSecret; @Builder public BoardDTO(Long boardId, String title, String content, String nickName, LocalDateTime regTime, List<CommentDTO> commentDTOList, BoardSecret boardSecret) { this.boardId = boardId; this.title = title; this.content = content; this.nickName = nickName; this.regTime = regTime; this.commentDTOList = commentDTOList; this.boardSecret = boardSecret; } // 엔티티를 DTO로 변환하는 작업 public static BoardDTO toBoardDTO(BoardEntity board, String nickName) { // 게시글 댓글 처리 List<CommentEntity> commentEntityList = board.getCommentEntityList(); List<CommentDTO> commentDTOS = new ArrayList<>(); // 엔티티 댓글을 DTO 리스트에 담아주는 작업을 진행하고 있다. if (commentEntityList != null) { for (CommentEntity commentEntity : commentEntityList) { CommentDTO commentDTO = CommentDTO.toCommentDTO(commentEntity); commentDTOS.add(commentDTO); } } else { // commentEntityList가 null일 경우 빈 리스트로 초기화 commentDTOS = Collections.emptyList(); } return BoardDTO.builder() .boardId(board.getBoardId()) .title(board.getTitle()) .content(board.getContent()) .nickName(nickName) .commentDTOList(commentDTOS) .regTime(LocalDateTime.now()) .boardSecret(board.getBoardSecret()) .build(); } } 수정 문구인데 로그를 확인하면 게시글 로그까지 제대로 나오는 것을 볼 수있는데 근데 리턴이 안되고 400번 오류가 발생하는데 body에 적은 내용은 안나오는 상황입니다. 로그까지 찍혔는데 왜 리턴이 안되는 걸까요?? 아무 에러 표시도 안나옵니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
Spring Security 적용 시 controller @WebMvcTest
안녕하세요 강사님 좋은 강의를 듣고 공부하면서 실무에 적용시키고 있는 중에 두 가지 문제를 만났는데 구글링으로 해결점이 보이지 않아서 강의 내용과 관련 없지만 힌트라도 얻고자 글을 남기게 되었습니다Spring boot 3.x 이상 버전이고, Spring security가 적용된 프로젝트입니다첫 번째 문제는 controller 레이어에서 @WebMvcTest를 적용 시에 검증하고자 하는 controller와 주입받은 빈 객체 이외의 뜬금없는 객체 주입 error가 발생하고 있습니다@V1RestController @RequiredArgsConstructor public class CommonController { private final CommonService commonService; private static final Logger log = LoggerFactory.getLogger(CommonController.class); @GetMapping("/term") public ResponseEntity<TermResDto> getTerm(@RequestParam("account_id") Long id) { return ResponseEntity.ok(commonService.getTerm(id)); } @GetMapping("/message") public ResponseEntity<List<UserMessageResDto>> getUserMessageList(@RequestParam("user_id") Long userId) { return ResponseEntity.ok(commonService.getUserMessageList(userId)); } @GetMapping("/notice") public ResponseEntity<List<UserNoticeResDto>> getUserNoticeList(@RequestParam("user_id") Long userId) { return ResponseEntity.ok(commonService.getNoticeList(userId)); } }class CommonControllerTest extends ControllerTestSupport { @DisplayName("현재 학기 정보를 가져온다") @Test void getCurrentTermInfo() throws Exception { // given String termName = "2023년도 2학기"; LocalDateTime startAt = LocalDateTime.of(2023, 9, 1, 00, 00); LocalDateTime endAt = LocalDateTime.of(2023, 12, 15, 23, 59); TermResDto res = TermResDto.builder() .id(1L) .name(termName) .startAt(startAt) .endAt(endAt) .build(); given(commonService.getTerm(1L)).willReturn(res); // when & then mockMvc.perform( get("/api/v1/term") ) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.status").value("OK")) .andExpect(jsonPath("$.message").value("OK")) .andExpect(jsonPath("$.data").isArray()); } }@WebMvcTest(CommonController.class) @ActiveProfiles("test") public abstract class ControllerTestSupport { @Autowired protected MockMvc mockMvc; @Autowired protected ObjectMapper objectMapper; @MockBean protected CommonService commonService; }순서대로 controller, controllertest, controllertestsupport 클래스 입니다 java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@2ca54da9 testClass = kr.lineedu.cha.domain.common.controller.CommonControllerTest, locations = [], classes = [kr.lineedu.cha.BaseApplication], contextInitializerClasses = [], activeProfiles = ["test"], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@3012646b, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@416bfba7, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@2aa749... at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55) Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authController' defined in file [/Users/luca/Desktop/prod/cha-backend/out/production/classes/kr/lineedu/cha/auth/AuthController.class]: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'kr.lineedu.cha.auth.AuthService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'kr.lineedu.cha.auth.AuthService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1824) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1383) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337) at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:888) at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ... 97 more에러 내용에 보면 전혀 관계가 없는 AuthService에 대한 bean error가 뜨고 있어서 질문드립니다두 번째는 외부 api와 통신해서 구현되는 로직을 openfeign으로 사용하고 있습니다. BDDMockito로 feign client를 stub해서 테스트를 짜던 중 검증이 부족할 것 같다는 생각에 wiremock을 이용해서 테스트를 진행하려 하는데 best practice를 찾지 못하고 있습니다. 혹시 이에 대한 좋은 자료나 실무에서 사용하는 테스트 케이스가 있을지 궁금해서 질문드립니다
-
미해결실전! 스프링 데이터 JPA
QueryHint와 update 관련 질문
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]안녕하세요. 강의와 자료를 병행하며 공부하고있는 학생입니다. 음.. 개념에 대해 헷갈리는 부분이 있어서 질문드리는데,queryHint를 통해서 읽기전용이라고 하이버네이트에 인식 시키고 em.flush()에 update쿼리가 먹히지 않는다.라고 이해했는데 비슷한 질문글을 보니 db에는 "member2"로 들어가있다는 질문을 보고 의문이 들었습니다. 이름이 바뀌지 않아야 정상아닌가요?
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
[질문X] orderV6 for문으로 발라내기
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]복붙 하려다 못참아서 그만.. ㅎㅎ @GetMapping("/api/v6/orders") public List<OrderQueryDto> ordersV6(){ List<OrderFlatDto> orderFlats = orderQueryRepository.findAllByDto_flat(); Map<Long, List<OrderItemQueryDto>> orderItemMap = new HashMap<>(); Map<Long, OrderQueryDto> orderMap = new HashMap<>(); orderFlats.forEach(orderFlat -> { Long orderId = orderFlat.getOrderId(); if(orderMap.get(orderId) == null){ orderMap.put(orderId,new OrderQueryDto(orderId, orderFlat.getName(), orderFlat.getOrderDate(), orderFlat.getOrderStatus(), orderFlat.getAddress())); } if(orderItemMap.get(orderId) == null){ orderItemMap.put(orderId,new ArrayList<OrderItemQueryDto>()); } orderItemMap.get(orderId).add(new OrderItemQueryDto(orderId, orderFlat.getItemName(), orderFlat.getOrderPrice(), orderFlat.getCount())); }); orderItemMap.forEach((orderId, orderItem)->{ orderMap.get(orderId).setOrderItems(orderItem); }); return new ArrayList<OrderQueryDto>(orderMap.values()); }stream 저처럼 잘 모르시는 분들은 이게 더 와 닿으실 거임.. 아 보람차다 ㅎㅎㅎ
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
batch는 정확히 등록된 엔티티를 조회해 올때만 작동되는 건가요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]from 해서 엔티티에서 가져오는 거니 Dto로 가져와도 왠지 먹힐 줄 알았는데 아니었네요..정확히 fetch 등이나 엔티티 자체를 가져올 때만 되는 거 맞나요?
-
해결됨Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
인증서버를 겸하는 api gateway로 구성하려면
안녕하세요. 강의 초반부 수강 중인데 궁금한 게 생겨서 질문드립니다.Spring Security, JWT, OAuth 등을 사용한 인증서버를 api gateway가 겸하게 하고자 하는데요, UserMicroService의 내용을 Spring Cloud Gateway 프로젝트에서 진행하면 되는 걸까요?해당 강의를 참고해서 api gateway를 사용한 프로젝트를 Aws에 올릴 예정이다보니 비용에 대해 고려하게 되는데요, 해당 강의에서 api gateway와 관련해서 사용하게 될 서버 개수가 궁금합니다. 섹션7 까지 강의대로 하면 마이크로서비스나 DB는 제외하고, Spring Cloud Gateway와 관련한 서버만 총 3개 사용하게 되는 건가요? (Eureka, Spring Cloud Gateway, Spring Config Cloud Server)
-
해결됨자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
springframework를 import하는 곳에서 빨간색이 나옵니다.
aws 하기 전에 다시 복습해보고 싶어서springboot를 하나 다시 만들었습니다. 이때 버전을 3.0.12로 생성을 했구요 그렇게 생성해서 새로운 프로젝트를 열기만 했는데 어떤 에러가 나서 찾아보니 사용자 폴더에 있는 .gradle을 삭제하고 다시 프로젝트를 열어보라고 하더라고요. 그래서 삭제하고 새로 만든 프로젝트를 열어보니까 오류 없이 아주 잘 실행이 됩니다. 그렇게 혼자 배운 것들을 연습하다가 다시 강의 들으면서 작성했던 프로젝트를 열어보니 다 빨간색으로 나오네요.. 스프링에 관련된 거는 모두 빨간색으로 나오는거 같아요 사용자 폴더에 있던 .dradle 폴더를 지워서 이렇게 오류가 나오는 걸까요? 버전에 다른 프로젝트를 만들어서 충돌이 생긴 건지 어떤 이유인지 모르겠습니다. 기존 프로젝트 코드는 건들인게 하나도 없는데 왜 그런거죠? 아 그리고 강의 듣기 전에도 다른 책을 보면서 만든 프로젝트도 있는데 그 프로젝트를 열어보니 똑같이 빨간색으로 나오더라구요. 그 프로젝트도 버전이 3인가 그랬는데... 살려주세요 선생님!!
-
미해결실전! Querydsl
encrypt된 데이터의 like 검색
안녕하세요.querydsl 의 where 절을 사용하여 aes256으로 encrypt되어 저장된 db 데이터를 검색하려고 합니다.eq()일때는 가능한데 like() 검색을 어떻게 해야할지 고민이 되어 질문 드립니다.그래서 현재 생각한 방법은 where절을 사용하지 않고, Converter를 사용하여 DB에서 데이터를 전부 읽어온 다음, 복호화된 데이터를 stream으로 contains를 사용하여 like 검색과 limit, skip으로 페이징을 처리하는 방법인데요.이럴경우 데이터 양이 많아지게되면 where 조건이 없기 때문에 속도나 성능 측면에서 문제가 있을까 고민이 됩니다.현재 코드를 사용해도 문제가 없을지, 새로운 방법이 있을지 질문 드립니다.
-
미해결실전! Querydsl
querydsl 설정
스프링부트 버전 3.1.5자바 17ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ위 버전들을 사용중인데, 강의자료에 있는 querydsl로 잘 되지않아 구글링 및 질문게시판을 보다가 plugins { id 'java' id 'org.springframework.boot' version '3.1.5' id 'io.spring.dependency-management' version '1.1.3' } group = 'study' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' // ⭐ Spring boot 3.x이상에서 QueryDsl 패키지를 정의하는 방법 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" //쿼리 파라미터 로그 남기기 implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' } tasks.named('test') { useJUnitPlatform() } // === ⭐ QueryDsl 빌드 옵션 (선택) === def querydslDir = "$buildDir/generated/querydsl" sourceSets { main.java.srcDirs += [ querydslDir ] } tasks.withType(JavaCompile) { options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir) } clean.doLast { file(querydslDir).deleteDir() } 이렇게 설정 후 돌려보니 이렇게 설정 했을 때만 Q파일이 생성되고 잘 작동하였습니다.Build and run using을 IntelliJ IDEA로 하면 Q파일이 경로가 잘못된 것인지 생성은 되는데 import가 되지 않았습니다. 그냥 저렇게 사용하다가 '조회 API 컨트롤러 개발' 강의를 보다 QuerydslApplication을 돌려보니 오류가 나와 구글링 해보니 Build and run using을 IntelliJ IDEA로 설정해야 한다고 하더라구요 근데 그렇게 하면 또 Q파일이 에러가 나고 계속 에러가 돌고 도는 것 같습니다. Build and run using을 IntelliJ IDEA로 설정하려면 build.gradle을 어떻게 작성해야 하나요??
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
test오류
테스트 오류가 계속 노란색 불로 생기는데 어떻게 해결해야 하나요??java.lang.IllegalStateException: Failed to load ApplicationContextCaused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]Caused by: org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not setError creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]Caused by: org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
CRUD에 따른 ResponseDto를 보내는 유형 및 ResponseDto 구성 방식에 대한 질문
안녕하십니까 영한님.영한님 덕분에 JAVA Spring 기반 백엔드 개발의시작을 할 수 있었고,이제는 5개월 차 스타트업 백엔드 개발자로써 커리어를 시작하게 되었습니다. 현업에 와서는 오히려 취준생 때 보다 더 많은 고민을 하게 되느데요, 질문 1. 그중 최근에 CRUD의 각 상황별 API 응답을 어떻게 보내는게 적절할지, 질문 2. 그리고 ResponseDto를 어떻게 구성하는것이 적절할지에 대한 고민을 팀장님과 함께 하고있어서, 영한님의 생각을 여쭙고자 질문글을 올리게 되었습니다.답변 해주시면 정말 감사할 거 같습니다! Q1. 생성, 조회 , 수정, 삭제 API의 응답을 각각 어떻게 보내시는지 여쭤보고 싶습니다. 가장 먼저 조회의 경우는 말 그대로 path의 Entity 및 관련된 Entity 정보를 조합하여 응답 DTO로 변환하여 보내고 있습니다. 그런데 나머지 Write Operation에 대한 응답을 어디까지 보내야 하냐가 이슈 입니다.예를들면 엔티티의 생성의 경우 엔티티의 응답 DTO를 보내면 - 프론트에서 별도의 조회 API 호출 없이 바로 프론트가 화면에 뿌려줄 수 있으니깐 저는 생성의 경우에도 조회와 마찬가지로 Entity의 정보를 조합하여 응답 DTO로 변환하여 보내고 있었습니다. 그런데 이러한 부분이 Command Query Sperate 원칙에 어긋나는것 같아, 영한님께서는 혹시 생성한 Entity의 Key만 보내시는지, 아니면 Entity의 정보를 DTO로 변환하여 보내시는지 궁금합니다. 만약 Entity의 Id만 보내신다면 이후에 별도로 조회API를 호출해야 하고 그 또한 비용일텐데 이러한 부분은 어떻게 하시는지 여쭤보고 싶습니다. 이제 수정 API인데요,제가 다룬 비즈니스 로직의 경우 수정 비즈니스 로직이 다양하고 , 각 비즈니스 로직의 경우 다뤄지는 Entity의 종류가 다른 경우였습니다. (중심 Entity는 동일하지만, 연관된 Entity를 누구까지 건드리냐의 차이) 그래서 하나의 통일된 응답으로 보내기 모호한 점이 첫 번째 이유이고,애초에 수정 후에 프론트 화면에서 그 엔티티의 정보를 보여줄 필요가 없어서 라는 두번째 이유에 의해서 에초에 엔티티의 Id값도 보내지 않고 있었는데요,이 수정 API의 응답을 영한님은 어떻게 진행하지는지 그 이유가 궁금합니다. 마지막으로 삭제의 경우는 정말, 프론트에게 보낼 응답이 없어도 되는 경우 라고 생각했는데요,팀장님의 의견은 만약에 나중에 삭제한 Entity를 복구하는 요구사항이 추가되는것을 고려하여Id 정도는 넘기자는 의견을 내어주셨습니다.마찬가지로 삭제의 경우도 어떤식으로 수행하시는지 그 이유가 궁금합니다. Q3. 마지막으로 Entity의 ResponseDto의 필드를 어떤식으로 구성하시는지 궁금합니다 예를들면 저의 경우는 API는 프론트와 서버 간의 스펙이라고 생각하고, Entity의 단건조회의 경우는 단건 조회용 ResponseDto를, 전체조회의 경우는 전체 조회용 SummaryResponseDto를 별도로 만들어서 사용하고 있었습니다. 저희 팀장님 께서는 프론트쪽도 일을 해오시다가 , 백엔드쪽 분야로 전향하신 케이스 인데요,그렇다 보니 어떻게 해야 프론트의 생산성이 올라가는지를 고려하시는 분이셨고,팀장님의 생각은 서버에서 넘겨주는 응답에 일관성이 있어야 그 응답을 사용하는 프론트 측도 학습이 되고 놓치는 부분 없이 생산성이 올라간다는 의견이셨습니다. 그래서 Entity별로 당장 사용하지 않더라도 가능한 모든 필드를 담은 ResponseDto를 하나만 만들고,해당 ResponseEntity의 조합으로 각 API별 응답 Dto를 만들어서 사용하면 ,프론트 측 에서는 일관성 있는 응답값을 사용할 수 있다는 의견이셨습니다.물론 이 방법이 네트워크 패킷의 양을 쓸데없이 증가시킨다는 것을 알고 계시면서도,생산성에 큰 영향을 미치는 부분이라고 생각하셨습니다. 예를들어 다음과 같이 각 Entity의 응답 Dto의 조합별로 API의 ResponseDto를 만들 수 있습니다.ResponseDto{ UserDto{id : 1,name : “aaa”… // User엔티티의 거의 모든 필드} ItemDto{ id : 2,name : “bbb”,… // Item엔티티의 거의 모든 필드}} 저는 이러한 부분에 대해 생각해 본 적이 없이,그냥 제가 “해당 API를 호출하는 화면에서 필요한 정보들만을 담아 (혹은 여러 화면에서 쓰인다면 여러개를 고려) ResponseDto를 각각 만들어서” 넘겼는데요 영한님께서는 이러한 ResponseDto를 구성하는 부분에 있어서상황별로 필드를 재구성 하여 ResponseDto를 정의하여 사용하시는 편 인지 (SummaryResponseDto 등의 별도 Dto에 사용될 Entity의 필드들을 풀어서 정의하시는지)아니면 생산성을 고려하여 각 Entity별 Dto를 만들고, 이들을 조합하여 ReponseDto를 정의하시는 편 이신지 ,혹은 다른 규칙이 있으신지 궁금합니다. 물론 그렇다고 , 팀장님의 의견에서 전체조회시 사용하는 DTO와 단건조회시 사용하는 DTO가 동일하더라도,전체조회 후 단건조회를 할때 단건조회 API를 호출하지 말고 기존 Front가 가지고 있는 값을 쓰지는 말자는 의견 이십니다 (단건조회API는 별도로 호출해야 한다)그저 핵심은 프론트가 다루는 ResponseDto의 일관성을 위해서 입니다 (결론은 생산성을 위해) 긴글 읽어주셔서 감사합니다.항상 건강하셨으면 좋겠고,다음 강의들도 손꼽아 기다리고 있습니다!
-
미해결스프링과 JPA 기반 웹 애플리케이션 개발
Compile 안됩니다...
강의 내용대로 컴파일을 시도했는데 안됩니다...저 폴더를 인식을 못하는 걸까요..[INFO] BUILD FAILURE[INFO] ------------------------------------------------------------------------[INFO] Total time: 4.095 s[INFO] Finished at: 2023-11-15T01:23:56+09:00[INFO] ------------------------------------------------------------------------[ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources) on project studyolle: /Users/swnam/Documents/IntelliJ/java/whiteship/target/classes/static/node_modules/.bin/jdenticon -> [Help 1][ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.[ERROR] Re-run Maven using the -X switch to enable full debug logging.[ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: 오류 내용은 이렇습니다.시도한 내용.pom.xml에서 노드 얀 버전도 맞춰봤습니다.깃허브에서 클론 가져왔습니다.조금 밑에있는 커밋들은 제대로 동작을 하는데 최신 커밋내용은 안되는것같습니다..
-
미해결실전! 스프링 데이터 JPA
엔티티, DTO 유효성 검사 관련 질문드립니다!
https://www.inflearn.com/questions/548289/%EC%97%94%ED%8B%B0%ED%8B%B0-dto-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC%EC%97%90-%EB%8C%80%ED%95%B4-%EC%A7%88%EB%AC%B8-%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4안녕하세요 강사님!위 질문글을 보고 이유에 대해 궁금증이 생겨서 질문 드립니다!"엔티티, DTO를 둘 다 유효성 검사를 하나요?" 라는 질문에 대해서"저는 주로 파라미터로 넘어오는 DTO에 유효성 검사를 선호하는 편입니다."라고 답변 주셨는데 혹시 그 이유를 알 수 있을까요? 그러면 RequestDto에서만 유효성 검사를 해주고Entity에는 validation관련 어노테이션 같은 걸 따로 안해주시는 건가요? 이유가 궁금합니다!
-
미해결호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
서비스 계층에서 삭제시 @Transactional 사용에 관해
당연히 구글링 해보셨져? 원하는 결과를 못찾으셨나요? 어떤 검색어를 입력했는지 알려주세요.- 검색해보았으나, 원하는 결과를 찾지 못했습니다. 검색어: 서비스 계층에서의 @Transactional 사용 기준문제가 발생한 코드(프로젝트)를 Github에 올리시고 링크를 알려주세요. ``` java@Transactionalpublic void write(Long postId, CommentCreate request) {Post post = postRepository.findById(postId).orElseThrow(PostNotFound::new);Comment comment = Comment.builder().post(post).author(request.getAuthor()).password(request.getPassword()).content(request.getContent()).build();post.addComment(comment);}public void delete(Long commentId, CommentDelete request) {Comment comment = commentRepository.findById(commentId).orElseThrow(CommentNotFound::new);commentRepository.delete(comment);}```서비스 계층에서 댓글 작성의 경우 @Transational 적용했지만, 삭제의 경우 적용하지 않았는데 강의에서 이 두 경우의 차이에 대해 이해해야 하고, 찾아보라고 이야기해주셨습니다. 지금까지 제가 공부한 내용에 비추어보면 서비스 게층의 생성 및 삭제의 경우모두 @Transcational 어노테이션을 항상 사용해 왔습니다. 왜냐하면, 수정 삭제의 경우 하나의 트랜잭션에서 시작 및 종료되어야 하기 때문이라고 생각했습니다.구글링도 해보았는데, 납득할만한 이유를 찾지 못하여 호돌맨님 및 수강생분들에게 질문드립니다.
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
복사 단축키
안녕하세요 ! 도메인과 영속성 객체 구분하기 수업에서 1분 34초에서 UserEntity 에 있는 행을 복사하는데 이 때 나오는 단축키가 궁금합니다.
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
강의 소제목에는 비대칭키를 활용한 암호화라고 되어있는데
apiEncryptionKey.jks (개인키)와 이에 파생된 public.key (공개키)를 생성한 것 까지는 이해가 되었습니다.강의중 encrypt/decrypt 를 하는 과정에서 모두 apiEncryptionKey.jks (개인키)를 사용한 대칭키 방식을 사용한 것 같은데 비대칭키 전략을 사용하는 부분이 있나요? 아니면 비대칭키를 추후에 활용할 수 있도록 파생키를 생성하는 방법을 알려주신건가요?java key store를 통해 구성정보의 암/복호화에 대칭키 방식을 사용한건지 비대칭키 방식을 사용한건지 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
생성과 수정 API 응답, 그리고 그 응답 Dto를 어떻게 구성할 것인가에 대한고민
안녕하십니까 강사님.저는 이제 5개월차가 된 신입? 백엔드 개발자 입니다.다름이 아니라 , 생성과 수정 API에 대한 응답으로 어떤 정보까지 넘기는것이 적합할지에 대해 고민을 하던 도중 강사님의 생각을 여쭤보고 싶어 질문을 드리게 되었습니다. Q1. 강사님 께서는 생성, 조회 , 수정, 삭제 API의 응답을 각각 어떻게 보내시는지 여쭤보고 싶습니다. 가장 먼저 조회의 경우는 말 그대로 path의 Entity 및 관련된 Entity 정보를 조합하여 응답 DTO로 변환하여 보내고 있습니다. 그런데 나머지 Write Operation에 대한 응답을 어디까지 보내야 하냐가 이슈 입니다.예를들면 엔티티의 생성의 경우 엔티티의 응답 DTO를 보내면 - 프론트에서 별도의 조회 API 호출 없이 바로 프론트가 화면에 뿌려줄 수 있으니깐 저는 생성의 경우에도 조회와 마찬가지로 Entity의 정보를 조합하여 응답 DTO로 변환하여 보내고 있었습니다. 그런데 이러한 부분이 Command Query Sperate 원칙에 어긋나는것 같아, 강사님께서는 혹시 생성한 Entity의 Key만 보내시는지, 아니면 Entity의 정보를 DTO로 변환하여 보내시는지 궁금합니다. 만약 정보를 다 보내신다면 이후에 별도로 조회API를 호출해야 하고 그 또한 비용일텐데 이러한 부분은 어떻게 하시는지 여쭤보고 싶습니다. 이제 수정 API인데요,제가 다룬 비즈니스 로직의 경우 수정 비즈니스 로직이 다양하고 , 각 비즈니스 로직의 경우 다뤄지는 Entity의 종류가 다른 경우였습니다. (중심 Entity는 동일하지만, 연관된 Entity를 누구까지 건드리냐의 차이) 그래서 응답으로 보내기 모호한 점이 첫 번째 이유이고,애초에 수정 후에 프론트 화면에서 그 엔티티의 정보를 보여줄 필요가 없어서 라는 두번째 이유에 의해서 에초에 엔티티의 Id값도 보내지 않고 있었는데요,이 수정 API의 응답을 성렬님은 어떻게 진행하지는지 그 이유가 궁금합니다. 마지막으로 삭제의 경우는 정말, 프론트에게 보낼 응답이 없어도 되는 경우 라고 생각했는데요,팀장님의 의견은 만약에 나중에 삭제한 Entity를 복구하는 요구사항이 추가되는것을 고려하여Id 정도는 넘기자는 의견을 내어주셨습니다.마찬가지로 삭제의 경우도 어떤식으로 수행하시는지 그 이유가 궁금합니다. Q2. 마지막으로 Entity의 ResponseDto의 필드를 어떤식으로 구성하시는지 궁금합니다 예를들면 저의 경우는 API는 프론트와 서버 간의 스펙이라고 생각하고, Entity의 단건조회의 경우는 단건 조회용 ResponseDto를, 전체조회의 경우는 전체 조회용 SummaryResponseDto를 별도로 만들어서 사용하고 있었습니다.(이런식으로 각 상황별 ResponseDto를 별도로 정의하고, 그 안에 관련된 Entity들의 필드를 직접 풀어넣는 방법) 저희 팀장님 께서는 프론트쪽도 일을 해오시다가 , 백엔드쪽 분야로 전향하신 케이스 인데요,그렇다 보니 어떻게 해야 프론트의 생산성이 올라가는지를 고려하시는 분 이셨고,팀장님의 생각은 서버에서 넘겨주는 응답에 일관성이 있어야 그 응답을 사용하는 프론트 측도 학습이 되고 놓치는 부분 없이 생산성이 올라간다는 의견이셨습니다. 그래서 Entity별로 당장 사용하지 않더라도 가능한 모든 필드를 담은 ResponseDto를 하나만 만들고,해당 ResponseEntity의 조합으로 각 API별 응답 Dto를 만들어서 사용하면 ,프론트 측 에서는 일관성 있는 응답값을 사용할 수 있다는 의견이셨습니다.물론 이 방법이 네트워크 패킷의 양을 쓸데없이 증가시킨다는 것을 알고 계시면서도,생산성에 큰 영향을 미치는 부분이라고 생각하셨습니다. 예를들어 다음과 같이 각 Entity의 응답 Dto의 조합별로 API의 ResponseDto를 만들 수 있습니다.ResponseDto{ UserDto{ id : 1, name : “aaa” … // User엔티티의 거의 모든 필드 } ItemDto{ id : 2, name : “bbb”, … // Item엔티티의 거의 모든 필드 }} 저는 이러한 부분에 대해 생각해 본 적이 없이,그냥 제가 “해당 API를 호출하는 화면에서 필요한 정보들만을 담아 |(혹은 여러 화면에서 쓰인다면 여러개를 고려) ResponseDto를 각각 만들어서” 넘겼는데요 강사님께서는 이러한 ResponseDto를 구성하는 부분에 있어서상황별로 필드를 재구성 하여 ResponseDto를 정의하여 사용하시는 편 인지아니면 생산성을 고려하여 각 Entity별 Dto를 만들고, 이들을 조합하여 ReponseDto를 정의하시는 편 이신지 혹은 다른 규칙이 있으신지 궁금합니다. 물론 그렇다고 , 팀장님의 의견에서 전체조회시 사용하는 DTO와 단건조회시 사용하는 DTO가 동일하더라도,전체조회 후 단건조회를 기존 Front가 가지고 있는 값을 그대로 쓰지 말고.,단건 조회용 API를 다시 호출하자 입니다!그저 핵심은 프론트가 다루는 ResponseDto의 일관성을 위해서 입니다 (결론은 생산성을 위해) 항상 좋은 강의,그리고 무엇보다도 강상님의 의견과 고민을 강의에 녹여주셔서정말 감사할 따름입니다. 덕분에 함께 고민하고 많이 배우는거 같습니다. 긴 글 읽어주셔서 감사합니다!