이야기를 나눠요
140만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
스프링 핵심 원리 - 고급편
동시성 문제에 대해 질문합니다.
안녕하세요!.. 강의와 무관한 내용이지만 마땅히 스프링 관련된 질문을 아는 곳이 없어 평소 스프링 강의를 들은 선생님께 질문을 하게 되었습니다.질문에 대한 전체 코드는 아래 링크에 작성했습니다.https://www.notion.so/7iwook/userVolunteerService-1d911afe2d6545bfa1e8247cc6006748?pvs=4 @Lock(LockModeType.PESSIMISTIC_READ) @Override public Optional<UserVolunteerWork> findByVolunteerWorkIdAndUserId(Long volunteerWorkId, Long userId) { return Optional.ofNullable(queryFactory .selectFrom(userVolunteerWork) .join(userVolunteerWork.volunteerWork, volunteerWork) .join(userVolunteerWork.user, user).fetchJoin() .where( userVolunteerWork.volunteerWork.id.eq(volunteerWorkId) .and(userVolunteerWork.user.id.eq(userId)) ).setLockMode(LockModeType.PESSIMISTIC_READ) .fetchOne()); }위 코드를 순차적 시행하면 문제없이 원하는 결과값을 반환하지만 (테스트 코드가 길어 똑같은 부분을 제외하고 차이점만 올립니다.)for (int i = 0; i < tryCnt; i++) { User user = users.get(i); try { volunteerService.approve(adminEmail, volunteerWorkId, user.getId()); successCount++; // 성공한 신청 수 증가 } catch (Exception e) { failedCount++; } }동시성 테스트를 위해 아래와 같이 구성을 하면 쿼리 결과 값이 null로 나와 EntityNotFoundException을 던지게 됩니다..int numThreads = 50; // 50명의 유저가 동시에 신청 CountDownLatch doneSignal = new CountDownLatch(numThreads); ExecutorService executorService = Executors.newFixedThreadPool(numThreads); AtomicInteger successCount = new AtomicInteger(); AtomicInteger failCount = new AtomicInteger(); for (int i = 0; i < numThreads; i++) { User user = users.get(i % users.size()); // 유저 목록에서 순차적으로 유저를 가져옴 executorService.execute(() -> { try { log.info("approved userId = {}", user.getId()); volunteerService.approve(adminEmail, volunteerWorkId, user.getId()); successCount.getAndIncrement(); // 성공한 신청 수 증가 } catch (Exception e) { e.printStackTrace(); failCount.getAndIncrement(); // 예외 처리 } finally { doneSignal.countDown(); // 쓰레드 작업 완료를 알림 } }); }인터넷에 나와있는 비관적, 낙관적 락을 모두 시도해보았으나 null로만 반환되는 현상이 나오는 데 이 현상이 동시성에 있는건지도 아직 모르겠습니다... 원인에 대해 찾아주신다면 정말 감사합니다!!좋은 강의 항상 잘 듣고 있습니다!!
-
스프링 DB 2편 - 데이터 접근 활용 기술
스프링 완전정복 로드맵을 수강중 JPA책공부
안녕하세요. 김영한 강사님의 Spring 완전 정복 로드맵을 수강중인 학생입니다. 지금 DB 2편까지 수강을 마쳤는데 여기서 다음으로 바로넘어갈지 아니면 JPA책을 읽어볼지 고민중인데요. 이미책은 구입해놔서 실행에만 옮기면되서 조언구하고싶습니다. 시간은 굉장히여유로워서 순서가중요할것 같습니다!
-
스프링 DB 2편 - 데이터 접근 활용 기술
DB섹션 강의를 들으면서 궁금한 점이 생겨 질문을 남깁니다.외래키를 설정하는 방법과 조인검색결과를 json으로 어떻게 반환하나요?
어느 강의영상에 질문을 남겨야할지 감이 안잡혀 여기에 질문 남깁니다….웹 개발을 할 때 DDL을 통해 생성되는 테이블들은 모두 서비스에서 사용되는 객체들을 보고 만드는것 같은데 (예를 들면 Member 클래스, Item클레스들을 생각했습니다)만약 어떤 회원 A가 아이템A를 등록하였으면 데이터 베이스의 회원과 아이템 사이에는 등록이라는 관계가 생성이 되고 회원 1명은 아이템을 여러개 등록이 가능하다면 Member 테이블의 PK를 Item 테이블의 FK로 등록되며 Item 테이블의 속성들은 (id, item_name, price, quantity, member_id)로 설정이 될것 같은데 이런 경우 Item 클레스의 멤버변수로Member member_id; 를 생성해주어야 할것 같은데 이런 외래키 값은 도메인을 설계할때 어떻게 처리해야 하나요?그리고 api로 통신할 때 클라이언트 에게 데이터를 넘겨줄 때 스프링 입문 강의에서 hello 객체자체를 return 하면 스프링의 잭슨라이브러리가 json포멧으로 변환해서 넘겨준다고 해주셨는데 여러개의 테이블이 조인된 결과를 json으로 반환 해주려면(예를 들어서 멤버 A가 등록한 아이템의 이름과 가격, 멤버의 이름을 요청한다면 반환되는 튜플이 item_name, price, member_name) 이것들은 하나의 객체가 아닌 Member클래스와 Item클래스의 일부 변수들을 사용한 새로운 값들인데 이럴때는 어떤 방법으로 return해주어야 하나요? 클라이언트측과 조율을 하여 검색되는 조건을 설정하여 조인검색의 제약을 설정하나요? 제약을 설정한다면 반환할 때 (item_name, price, member_name)이 3개의 속성들을 멤버변수로 사용하는 새로운 클래스를 생성하여 반환해 주어야 하나요?항상 질문글에 상세한 답글 남겨주셔서 열심히 공부할 수 있습니다. 감사합니다.
-
스프링 DB 2편 - 데이터 접근 활용 기술
DB1편을 듣지않고 2편을 듣기 어려운가요?
삭제된 글입니다
-
스프링 DB 2편 - 데이터 접근 활용 기술
사이드프로젝트 전, 어디까지 강의를 들어야 할까요?
하나 여쭤보고 싶은게 있는데, 기존에 NestJS를 사용하다가 Spring으로 전향하게 되어, NestJS로 작성된 토이 프로젝트를 Spring으로 변환하면서 강의 내용을 체득해보려고 합니다! 토이 프로젝트의 규모도 꽤 크고, 기존에 GraphQL로 작성된 쿼리들을 REST API로 변경할 계획인지라 사실상 새로 제작한다에 가까울 것 같습니다. 이걸 어떤 강의까지 들은 뒤 작업할 지 고민이 되는데, 혹시 조언을 주실 수 있을까요?1. DB 2편 2. 스프링 완전 정복 로드맵3. DB 2편까지 듣고, JPA 실무 완전 정복 로드맵까지 4. 스프링 완전 정복 로드맵과 JPA 실무 완전 정복 로드맵까지 현재 DB 2편 듣고 있는 중이며, 이 강의를 듣기 전엔 Spring에 대한 기반 지식이 아예 없었던 상태입니다.조언 감사합니다!
-
스프링 DB 2편 - 데이터 접근 활용 기술
Mapper bean not found 해결 & MyBatis 오류: Invalid bound statement (not found) 해결
커뮤니티에서 Mybatis 관련 비슷한 오류가 많아 보이길레, 여러분들의 시간을 아껴드리고자 제가 해결한 방법을 공유해드립니다.Mapper bean not found저의 경우 Spring Boot 버전과 MyBatis 버전 불일치 문제여서 Mapper 빈 생성이 정상 작동하지 않았었습니다. (Mapper Spring 연동 모듈이 정상 작동하지 않은 문제??) // build.gradle plugins { id 'org.springframework.boot' version '2.6.5' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' }스프링 부트 3.0 이상 버전만 썼기 때문에 습관상 당연히 3.0 버전과 호환이 되는 MyBatis 버전 3.0.1 을 설정했지만위와 같이 영한님의 수업 자료로 진행하셨다면 스프링 2.6.5 로 설정되어 있기 때문에// build.gradle dependencies { //MyBatis 추가 implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0' }이렇게 바꾸시면 되실 겁니다! Invalid boud statement (not found)XML 파일에 오타가 있거나 XML 파일 경로 설정이 이상할 경우 발생하는 오류라 합니다ItemMapper.xml 파일 경로를 잘 따라한 것 같엤는데 경로가 복잡하기도 했고 그래서 pdf 수업 자료 2번째 방법인 resources/mapper/ItemMapper.xml 로 xml 파일을 옮겼고 (기존 껀 부모 디렉토리까지 삭제)// application.properties #MyBatis mybatis.type-aliases-package=hello.itemservice.domain mybatis.configuration.map-underscore-to-camel-case=true logging.level.hello.itemservice.repository.mybatis=trace mybatis.mapper-locations=classpath:mapper/**/*.xml설정 파일에 마지막 줄 (mapper 경로 설정) 을 추가해서 해결했습니다.저와 동일한 문제가 아니신 분들도 계시겠지만 같은 수업 자료에서 출발했기 때문에 비슷하게 해결되지 않을까 싶어서 글 적었습니다! (질문글이 아니지만)
-
실전! Querydsl
QueryDsl SpringBoot 2.7의 gradle 설정을 공유합니다.
plugins { id 'org.springframework.boot' version '2.7.4' id 'io.spring.dependency-management' version '1.0.14.RELEASE' id 'java' } group = 'study' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' 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' // queryDSL 설정 implementation "com.querydsl:querydsl-jpa" implementation "com.querydsl:querydsl-core" implementation "com.querydsl:querydsl-collections" annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa" // querydsl JPAAnnotationProcessor 사용 지정 annotationProcessor "jakarta.annotation:jakarta.annotation-api" // java.lang.NoClassDefFoundError (javax.annotation.Generated) 대응 코드 annotationProcessor "jakarta.persistence:jakarta.persistence-api" // java.lang.NoClassDefFoundError (javax.annotation.Entity) 대응 코드 } tasks.named('test') { useJUnitPlatform() } // Querydsl 설정부 def generated = 'src/main/generated' // querydsl QClass 파일 생성 위치를 지정 tasks.withType(JavaCompile) { options.getGeneratedSourceOutputDirectory().set(file(generated)) } // java source set 에 querydsl QClass 위치 추가 sourceSets { main.java.srcDirs += [ generated ] } // gradle clean 시에 QClass 디렉토리 삭제 clean { delete file(generated) } 해당 소스는 타사 강의 보다가 본 설정에서 가져왔습니다.기존 영한님 강의와 다른 점이 3가지 있으니 주의하시기 바랍니다.Querydsl Q파일 생성 위치가 다릅니다. 기존 영한님 강의대로 $build 로 시작하는 설정을 사용하면 테스트 실행 시 Q파일의 위치를 찾지 못해서 테스트가 실패합니다.Gradle -> Tasks -> build -> cleanGradle -> Tasks -> build -> build 혹은 classes기존 영한님 교안에는 빌드 시 Gradle -> Tasks -> other -> compileQuerydsl 로 Q파일을 생성하지만, 이 방법의 경우 other에 해당 메뉴가 없습니다. 그래서 빌드 시에는 그냥 build 메뉴의 build 혹은 classes 로 빌드하시면 Q파일이 생깁니다.영한님 강의에서는 gradle build 폴더가 대부분 git 버전관리에 포함되지 않으므로 따로 설정할 필요가 없지만, 이 경우 Q파일이 소스폴더에 들어가므로 .gitignore 에 아래와 같이 별도로 경로를 설정해 주어야 합니다.### Querydsl /src/main/generated 혹시 저같이 청개구리마냥 강의에 나온 버전 사용 안 하고 최신 버전 사용하시는 분들께 도움이 되었으면 좋겠습니다. 저도 타사 강의에서 가져온 것이고 기초 테스트만 통과한 것이라서 혹시 강의에 맞지 않는 경우 영한님이나 다른 분들이 추가 정보를 주시면 좋을 것 같습니다.여담인데 QueryDSL은 쿼리 짜긴 확실히 편한데 설정이 버전마다 중구난방이라 불편합니다. 똑똑한 개발자분들이 이런 설정도 그냥 @Configuration 으로 빼버리는 거 만들어주지 않을까 하는 기대가 있긴 합니다.