jpa 시작해보기
> 아주 기본만 익히고, 일단 실습. 필요한 것은 그때그때 찾아본다.
==> JPA 1회독 하고, 다시 이 글을 보니... 참... 맨땅에 삽질했구나싶다. 결국 기본부터 다시 보게 된다...
개발환경
java 1.8
springboot 2.1.9
gradle 4.10.2
1. 패키지명을 java.orm.jpa로 해서 Application.java를 실행하면, 다음 에러 발생
누가 패키지명을 java로 시작하나-_-'
--------------------------- 
Java Virtual Machine Launcher 
--------------------------- 
Error: A JNI error has occurred, please check your installation and try again 
--------------------------- 
확인    
---------------------------

2. 클래스명을 Order로 하면 다음 에러 발생
2021-04-05 21:13:49.754 WARN 14880 --- [ restartedMain] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL "create table order (order_id bigint not null, name varchar(255), member_id bigint, primary key (order_id)) engine=InnoDB" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table order (order_id bigint not null, name varchar(255), member_id bigint, primary key (order_id)) engine=InnoDB" via JDBC Statement
	at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:67) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]해결: Orders로 변경
또는 Order class에 @Table(name = "ORDERS") 추가
3. FK가 있는 경우 에러
2021-04-05 21:17:38.390 WARN 16476 --- [ restartedMain] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL "alter table orders drop foreign key FKpktxwhj3x9m4gth5ff6bkqgeb" via JDBC Statement
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "alter table orders drop foreign key FKpktxwhj3x9m4gth5ff6bkqgeb" via JDBC Statement
	at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:67) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final]
    
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "ORDERS" not found; SQL statement:
alter table orders drop foreign key FKpktxwhj3x9m4gth5ff6bkqgeb [42102-199]
해결: application.properties 추가
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update==> 이제 보니 테이블을 못찾은 것 같은데??
===> 서버 시작할 때, drop을 하려고 하는데, 없으니 당연히 에러... ddl-auto=update가 맞다.
4. Test Case에서 Member 도메인에 걸어놓은 Lombok의 @ToString 어노테이션 에러
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.example.domain.member.Member.orders, could not initialize proxy - no Session
	at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)
	at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)
	at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:581)
	at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:148)
	at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:600)
	at java.lang.String.valueOf(Unknown Source)
	at java.lang.StringBuilder.append(Unknown Source)
	at org.example.domain.member.Member.toString(Member.java:22)
	at java.lang.String.valueOf(Unknown Source)
	at java.lang.StringBuilder.append(Unknown Source)
	at java.util.AbstractCollection.toString(Unknown Source)
	at java.lang.String.valueOf(Unknown Source)
	at java.lang.StringBuilder.append(Unknown Source)
	at org.example.domain.member.MemberRepositoryTest.멤버저장_불러오기(MemberRepositoryTest.java:42)==> lombok @ToString대신 직접 만들기.
==> 이제 보니, fetch가 문제인가? could not initialize proxy - no Session 문제
===> 영속성은 끊어졌는데, 프록시객체를 조회하려고 해서 그런가?
5. Test Case에서 다음에러 발생
org.springframework.orm.jpa.JpaSystemException: No default constructor for entity:  : org.example.domain.Address; nested exception is org.hibernate.InstantiationException: No default constructor for entity:  : org.example.domain.Address
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:352)
    
Caused by: org.hibernate.InstantiationException: No default constructor for entity:  : org.example.domain.Address
	at org.hibernate.tuple.PojoInstantiator.instantiate(PojoInstantiator.java:85)Address class에 @AllArgsConstructor 처리를 했는데,
해결: @NoArgsConstructor 도 필요하다.
==> 엔티티는 기본생성자 필수
6. 객체 비교?
Member member2 = memberService.findById(saveId);
assertEquals(member.getId(), member2.getId());
assertEquals(member.getName(), member2.getName());
// 대신, 객체 비교를 할려면, hashCode, equals 재정의?
assertEquals(member, member2);
7. 왜 JpaRepository에는 find 함수가 없을까?
EntityManager에는 find함수가 있다.ㅠㅠ
em.find(Item.class, id);
8. 서버 실행하면 되는데, 왜 TestCase에서는 안될까?
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.example.domain.order.Order.orderItems, could not initialize proxy - no Session
	at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:602)
	at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:217)
	at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:581)
	at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:148)
	at org.hibernate.collection.internal.PersistentBag.get(PersistentBag.java:540)
	at org.example.OrderServiceTest.상품주문(OrderServiceTest.java:50)Test class에 @Transactional 추가 필요
==> 음,, 이제 보니 이것도 could not initialize proxy - no Session 오류..
9. JPA의 동시성 문제는 어떻게 처리하나?
A상품, B상품을 동시에 조회해서 재고를 차감하면, 둘다 반영해야 한다.
==> "트랜잭션 락" 참고~
10. Entity 순환참조 이슈...
단순히 DTO만 쓴다고 해결되는 문제는 아니다.
Jackson 1.6+ 버전에서는
부모클래스에 @JsonManagedReference와 참조클래스에@JsonBackReference
Jackson 2.0+ 에서는
@JsonIdentityInfo@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property="id")
클래스에 추가하면,
무한루프에 걸리는 부분(products)이 id만 남고, 나머지는 나타나지 않아서, 순환참조를 피해갈 수 있다.
{
  "category": [
    {
      "id": 1,
      "name": "category12",
      "products": [
        {
          "id": 2,
          "name": "name0.21342207004622693",
          "category": {
            "id": 1,
            "name": "category12",
            "products": [
              2
            ]
          }
        }
      ]
    },
    {
      "id": 3,
      "name": "category12",
      "products": [
        
      ]
    }
  ]
}
{
  "product": [
    {
      "id": 2,
      "name": "name0.21342207004622693",
      "category": {
        "id": 1,
        "name": "category12",
        "products": [
          2
        ]
      }
    },
    {
      "id": 4,
      "name": "name0.11252326618453645",
      "category": null
    }
  ]
}@JsonManagedReference와 @JsonBackReference를 사용하면, 한쪽만 제대로 볼 수 있고, 나머지 한쪽은 포기해야 한다.
@JsonIdentityInfo를 사용하면, 양쪽모두 볼 수 있다.
==> 이상한 현상 발견. category1개를 이용해서 product 여러개를 동시에 생성할 경우, 아래와 같이 json 구조가 나온다. 음....
{
  "product": [
    {
      "id": 3,
      "name": "name8.075188198917967",
      "category": {
        "id": 2,
        "name": "name1",
        "products": [
          3,
          {
            "id": 4,
            "name": "name4.431918383718946",
            "category": {
              "id": 2,
              "name": "name1",
              "products": [
                3,
                4,
                {
                  "id": 5,
                  "name": "name7.751463573838379",
                  "category": {
                    "id": 2,
                    "name": "name1",
                    "products": [
                      3,
                      4,
                      5,==> 개선
@JsonManagedReference와 @JsonBackReference를 사용하되 Dto를 이용해서 json 출력하기.
Dto에서 아래와 같이 하더라도,
category 및 category 에 속한 product까지는 출력되지만, 그 product에 속한 category는 출력되지 않는다. @JsonBackReference 때문에...
@Data
public class ProductDto {
	private final Long id;
	private final String name;
	private Category category;
11. AOP 에러 처리
//@Aspect
//@Component
@RestControllerAdvice
public class ControllerAspect {
	
	// 400이면서 에러 메시지를 json으로 넘기기
	@ExceptionHandler(ValidationException.class)
	public ResponseEntity handleCustomException(ValidationException ex) {
		return new ResponseEntity<>(ResponseDto.of(ex.getMessage()), null, HttpStatus.BAD_REQUEST);
	}
//	200이면서 에러 메시지를 json으로 넘기기
//	@ExceptionHandler(ValidationException.class)
//	public ResponseDto handleCustomException(ValidationException ex) {
//		return ResponseDto.of(ex.getMessage());
//	}
    @ExceptionHandler(CustomException.class)
    public ResponseDto handleCustomException(CustomException ex) {
    	return ResponseDto.of(ex.getMessage());
    }
    
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseDto handleCustomException(IllegalArgumentException ex) {
    	return ResponseDto.of(ex.getMessage());
    }
}
12. custom validator
==> enum도 검증해보자.
velog.io/@hellozin/Annotation%EC%9C%BC%EB%A1%9C-Enum-%EA%B2%80%EC%A6%9D%ED%95%98%EA%B8%B0
==> Exception발생시 aop에서 받아야 한다.
그런데, 너무 다양한 에러가 발생한다.
단순히 e.getMessage()로 했을때, 패키지 정보까지 다 줘버리니...-_-;
BindException
	@GetMapping("/test11")
	public ResponseEntity test1(@Valid GetRequest request/*, BindingResult bindingResult*/) {MethodArgumentNotValidException
	@PostMapping("/test3")
	public String test2(@Valid @RequestBody InputRequest request) {ConstraintViolationException
리스트각 요소를 형변환 하다가...
	@PostMapping("/test5")
	public String test5(@Valid @RequestBody List<PostRequest> request) {ValidationException
Exception (NumberFormatException)
	@GetMapping("/test2/{id}")
	public String test2(@PathVariable("id") @Min(5) int id) {==> 컨트롤러에서 파라메터 변환을 할 수 있다.
@Component
public class StringToLocalDateTimeConverter implements Converter<String, MyLocalDateTime> {13.querydsl gradle 세팅 실패
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileQuerydsl'.
> Annotation processor 'com.querydsl.apt.jpa.JPAAnnotationProcessor' not found다음을 추가했다.
compileQuerydsl{
	options.annotationProcessorPath = configurations.querydsl
}
configurations {
	querydsl.extendsFrom compileClasspath
}
14.
jpa
could not initialize proxy - no Session
==> LAZY로 안되는 문제
	@OneToMany(mappedBy = "order", fetch = FetchType.EAGER) 
	private List<OrderItem> orderItems = new ArrayList<OrderItem>();
15
@Query 의 select 절에 new DTO()
DTO 생성자 함수에서 sort하려고ㅠㅠ stream을 사용하면 다음 에러 발생..
그냥, Collections 써야겠다..
@Query("select new com.kakaocommerce.order.api.dto.OrderResultDto(o) from Order o "
this.items = order.getOrderItems().stream().sorted().collect(Collectors.toList()); // Tuple 변환 에러발생
{
  "data": null,
  "error": "org.hibernate.QueryException: could not instantiate class [com.kakaocommerce.order.api.dto.OrderResultDto] from tuple; nested exception is java.lang.IllegalArgumentException: org.hibernate.QueryException: could not instantiate class [com.kakaocommerce.order.api.dto.OrderResultDto] from tuple"
}
16. h2에서 rollback이 안된다.
@Transactional로 하고, RuntimeException을 날렸는데,,,, 롤백이 안된다ㅠㅠ jpa에서...
==> 아.. insert rollback은 되는데, 이미 증가된 sequence가 롤백이 안되는 거였다.
17. 딜레마
parent
children: fk는 not null
=======> cascade 영속성전이 이용
children의 fk는 not null이므로, parent를 먼저 save해서 id를 받아오면, children을 save
그런데, parent를 save하고, children save에서 에러발생하면(length 초과), insert 롤백은 할 수 있지만 parent의 auto_increment는 롤백이 안된다.ㅠ
최대한 insert시에 에러가 발생하는 것이 아니라
그 전에 class 생성후 setter에서 데이터 체크가 되면 좋을텐데...
==> @Column, @Size, @Length의 차이를 이해하고, setter하고 나서 validation 을 돌리면 된다.
	@Size(max = 50)
	private String name;
// 검증
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.usingContext().getValidator();
Set<ConstraintViolation<OrderItem>> constrains = validator.validate(orderItem);
for (ConstraintViolation<OrderItem> constrain : constrains) {
    throw new IllegalArgumentException("[" + constrain.getPropertyPath() + "][" + constrain.getMessage() + "]");
}참고: phoenixnap.com/kb/spring-boot-validation-for-rest-services
=> 이걸 좀더 편하게 하면 좋을 텐데....
=> 어차피 모든 Model에 적용을 하면 좋으니,,, 뭔가 공통적으로 해주는 무언가 있지 않을까?
'Java > Spring' 카테고리의 다른 글
| [Spring data JPA] 조회: Entity + 연관 Entity(LAZY) + Pageable (0) | 2021.04.24 | 
|---|---|
| [Spring data JPA] native query + XML + DTO (0) | 2021.04.24 | 
| JPA 씹어먹기 (0) | 2021.04.19 | 
| [spring을 spring 답게] net.sf.log4jdbc.sql.jdbcapi.DriverSpy (0) | 2020.03.28 | 
| Memory usage is low, parachute is non existent, your system may start failing (0) | 2017.10.25 | 
