Sprint 5에서 리액티브 프로그래밍을 지원하는 웹 애플리케이션을 만들 수 있다.

리액티브 프로그램이란?
이전시간에 정리했었지만 스프링 리액티브 프로그래밍을 들어가기 전에 간단하게 정리해 보자. 일반적으로 리액티브 프로그래밍은 비동기, evnet-driven 방식으로 non-blocking 하는 프로그래밍으로써 일반적인 시스템 보다 작은 작은 쓰레드 수가 필요하다.

그럼 왜?
비동기-논블록킹 리액티브 개발이 가능. 기존 멀티쓰레드 블로킹 방식과는 다르게 서버의 응답에 의지하지 않는 효율적 개발이 가능. 서버간에 호출이 잦은 마이크로 서비스에서 사용됨

Spring Web Reactive Module (Servlet 3.1 이상부터 지원)
Spring Framework 5는 spring-web-reactive module을 포함한다. 이 module은 REST, HTML browser 그리고 웹 소켓에서 상호작용을 할 수 있도록 reactive HTTP와 Web Socket을 지원한다.


Annotation-based 프로그래밍 모델
기존 Spring MVC와 동일하게 @Controller를 사용이 가능하다. 주요한 차이점은 HandlerMapping과 HandlerAdapter등의 내용이 비동기인지 차이이고 리액티브에서는 HttpServletRequest, HttpServletResponse 보다 ServerHttpRequest, ServerHttpResponse에서 동작한다.

exeample)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
public class PersonController {
 
    private final PersonRepository repository;
 
    public PersonController(PersonRepository repository) {
        this.repository = repository;
    }
 
    @PostMapping("/person")
    Mono<Void> create(@RequestBody Publisher<Person> personStream) {
        return this.repository.save(personStream).then();
    }
 
    @GetMapping("/person")
    Flux<Person> list() {
        return this.repository.findAll();
    }
 
    @GetMapping("/person/{id}")
    Mono<Person> findById(@PathVariable String id) {
        return this.repository.findOne(id);
    }
}
cs


함수형 프로그래밍 모델
함수형 프로그래밍 모델을 사용하여 routing과 request를 handling 할 수있다. 가장 주요로 사용하는 함수형 인터페이스는 RouterFunction과 HandlerFunction이다. 이 둘을 사용하면 웹 애플리케이션에서 손쉽게 block을 생성할 수 있다.

아래 예는 person에 대한 데이터를 router로 request handling 하는 것을 보여준다.
- endpoint, accept type, response 등등

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 
/**
 * reactive
 *
 * @author wedul
 * @since 2019-01-12
 **/
@Configuration
@EnableWebFlux
public class PersonRouterFunction implements WebFluxConfigurer {
 
  @Bean
  public RouterFunction<ServerResponse> routes(FunctionHandler handler) {
    return RouterFunctions.route(GET("/person"), handler::functionHandler);
  }
 
}
 
@Component
class FunctionHandler {
  public Mono<ServerResponse> functionHandler(ServerRequest req) {
    Mono<Person> person = Mono.just(new Person("wedul"));
    return ServerResponse.ok().body(person, Person.class);
  }
}
cs


여기서 route에서 받는 파라미터는 RouterFuction 인터페이스로써 Mono<HandlerFunction<T>> route(ServerRequest request) 함수를 정의해서 전달해야한다. 그래서 route에 미리 정의해놓은 fuctionHandler를 전달한다. 이렇게 정의해서 사용하는 Handler를 route에 무조건 적용한다면 복잡해지고 다른곳에서 공통으로 사용할 수 없으니 위에 처럼 따로 구분해서 사용할 것.

MVC annotation으로만 사용하던 나에게는 불편해보이지만 함수형 프로그램을 할 수 있다는 장점이 있는거 같다.


웹 클라이언트
선언된 컨트롤러에서 Flux 또는 Mono를 읽고 구독을 진행해서 데이터를 읽는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * reactive
 *
 * @author wedul
 * @since 2019-01-12
 **/
public class PersonClient {
 
  WebClient client = WebClient.create("https://wedul.pos:8080");
 
  public void Test() {
    Flux<Person> personMono = client.get().uri("/person").retrieve().bodyToFlux(Person.class);
 
    personMono.subscribe(System.out::println);
  }
 
}
cs


예제 github

https://github.com/weduls/reactive

지금까지 Spring5에서 추가되었던 리액트 프로그램을 사용하여 간단한 프로그램을 만들어 봤지만 정확하게 Mono와 Flux에 차이와 정의를 정리하지 못한 것 같다. 이번기회에 두 개의 정확한 차이와 사용방법등을 정리해보자.


리액티브 프로그래밍
비동기 블록킹 프로세스로 동작하는 애플리케이션을 논블록킹 프로세스로 동작하기 위해서 지원하는 프로그래밍. (현재 node.js의 동작방식과 유사)


기존 Spring 블록킹 방식
웹에서 서버에 요청이 왔을때 서버는 요청에 대한 적절한 응답을 보내야 하는데 만약 작업이 오래 걸릴 경우에는 요청에 대한 응답이 모두 종료될 때까지 블록킹된다. Spring에서는 그래서 동시 요청 처리를 위해서 멀티 thread를 지원한다. 그러면 하나의 작업이 thread에서 진행되고 다른 thread가 다른 요청을 할당받아서 처리한다. 하지만 이렇게 결국 thread가 늘어나게 되는 경우에는 thread 할당에 필요한 리소스가 늘어나게 되어 비 효율적이 될 수도 있다.


Spring5의 Non blocking
Spring 5가 도입 되면서 클라이언트에 요청에 별도의 thread를 생성하지 않고 buffer를 사용해서 요청을 받고 뒤에서 처리하는 처리하는 thread는 여러개를 두어서 처리한다. 결국 node.js의 싱글스레드 논블로킹을 따라가는 것 같다.

그럼 왜 블로킹 방식을 지원하던 스프링에서 왜 논블로킹 방식을 생각하게 된걸까? 만약에 수천개의 스트림 데이터가 초당 계속 업데이트 되는 시스템이고 적절하게 응답을 해줘야할 때 기존의 블로킹 방식에 경우 상당한 부담을 받게 된다. 그래서 이런 부담을 효율적으로 처리하기 위해서 도입되었다.


Mono와 Flux
Mono는 0-1개의 결과만을 처리하기 위한 Reactor 객체
Flux는 0-N개의 결과물을 처리하기 위한 Reactor 객체

보통 여러 스트림을 하나의 결과를 모아줄 때 Mono를 쓰고 각각의 Mono를 합쳐서 하나의 여러 개의 값을 여러개의 값을 처리할 떄 Flux를 사용한다.

근데 이 부분에서 의문이 있다. 왜 그럼 Flux를 사용하면 되는거지 한개까지만 데이터를 처리할 수 있는 Mono라는 타입이 있는걸까? Mono와 Flux는 같은 Publisher 인터페이스를 구현해서 만들어졌다. 하지만 어떤 시스템에서는 Multi Result가 아닌 하나의 결과셋만 있는 경우가 있다. 그럴경우에는 Mono를 사용한다. 예를 들어 우리가 보통 자바에서 하나의 결과 또는 결과가 없는경우에 List를 사용해서 결과를 받지 않는다. 그와 동일한 개념이라고 생각하면 좋다.

그럼 Mono와 Flux를 사용해서 리액티브 프로그래밍을 하는 방식을정리해보자.

리액티브 스트림

  • 비동기 스트림 처리를 위한 표준으로써 next는 다음신호를 담고 complete는 신호가 끝난것 그리고 error은 신호보내는 도중 에러가 발생한 것을 의미한다.
  • Publisher가 전송하면 데이터는 sequence 대로 전송한다. 그러면 Subscriber가 데이터를 수신한다.
  • next, complete, error 신호를 발생시킨다.


기본적인 설명

1
2
3
4
5
6
// Integer 값을 발생하는 Flux 생성
Flux<Integer> seq = Flux.just(4, 5, 6); 
 
// 구독
seq.subscribe(System.out::println); 
 
cs


Flux.just(1, 2, 3);
--1-2-3-|→ 이처럼 1, 2, 3 세개의 next신호를 발생하고 마지막에 complete 신호를 발생시켜 시퀀스를 끝낸다.

Flux.just();
아무런 sequence가 없는 경우에는 complete 신호만 발생시킨다.

Mono.just(1);
--1-|→ Mono와 Flux의 차이는 Mono는 최대 발생할 수 있는 값이 1개이다.


구독과 신호 발생
sequence는 바로 신호를 발생하지 않는다. 구독을 하는 시점에 신호를 발생하기 시작한다.

1
2
3
4
Flux.just(1, 2, 3)
 .doOnNext(i -> System.out.println("호출: " + i))
 .subscribe(i -> System.out.println("출력 결과: " + i));
 
cs

-> doOnNext메소드는 consumer로부터 구독이 일어났을때 실행된다. 그래서 위에 메시지에 next 신호가 발생했을때 다음과 같은 결과가 발생한다.

호출: 1
출력 결과: 1
호출: 2
출력 결과: 2
호출: 3
출력 결과: 3


Subscriber 인터페이스 메서드 사용방법 정의
subscriber에서 제공하는 메소드는 다음과 같고 구독이 발생하면 onSubscribe가 호출되고 다음 값을 요청하면 onNext 오류가 발생하면 onError 모든 데이터 요청이 끝나면 onComplete가 호출된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Flux<Integer> seq = Flux.just(1, 2, 3);
 
seq.subscribe(new Subscriber<>() {
    private Subscription subscription;
    @Override
    public void onSubscribe(Subscription s) {
        // 구독 시작
        this.subscription = s;
        this.subscription.request(1);
    }
 
    @Override
    public void onNext(Integer i) {
        System.out.println("Costomer가 Publisher에게 데이터 요청: " + i);
        this.subscription.request(1);
    }
 
    @Override
    public void onError(Throwable t) {
        System.out.println("Subscriber.onError: " + t.getMessage());
    }
 
    @Override
    public void onComplete() {
        System.out.println("Subscriber.onComplete");
    }
});
cs

-> seq.subscribe 메서드에서 전달한 임의 Subscriber 객체를 onSubscribe 메서드에서 인자로 받아서 이를 필드로 저장하여 사용한다. request(1)은 한개의 데이터를 요청한다는 뜻이다. 만약 모든 데이터를 한번에 받고 싶다면 다음과 같이 지정하면 된다

1
2
3
4
5
6
@Override
public void onSubscribe(Subscription s) {
    System.out.println("Subscriber.onSubscribe");
    this.subscription = s;
    this.subscription.request(Long.MAX_VALUE);
}
cs


콜드 시퀀스와 핫 시퀀스
시퀀스는 구독 시점부터 데이터를 새로 생성하는 Cold sequence와 구독하는 customer와 상관 없이 데이터를 생성하는 hot sequence가 존재한다.

앞 예제 Flux.just()로 생성한 시퀀스가 콜드 시퀀스이다.
콜드 시퀀스는 위에 보면 알겠지만 subscribe가 발생하지 않는다.

Flux<Integer> seq = Flux.just(1, 2, 3);
seq.subscribe(v -> System.out.println(“첫번 째 요청: " + v)); // 구독
seq.subscribe(v -> System.out.println("두번 째 요청: " + v)); // 구독

-> 이 코드를 보면 알겠지만 seq 시퀀스는 구독을 두번한다.이 결과를 seq 시퀀스는 각 구독마다 데이터를 새롭게 생성한다. 마치 API에서 호출하는 것 처럼 매 호출마다 새로운 응답을 만들어 낸다.
첫번 째 요청: 1
첫번 째 요청: 2
첫번 째 요청: 3
두번 째 요청: 1
두번 째 요청: 2
두번 째 요청: 3

핫 시퀀스는 구독여부에 상관없이 데이터가 생성된다. 구독을 하면 구독한 시점이후에 발생하는 데이터부터 신호를 받는다.
-> 예전부터 있었던 데이터를 똑같은 응답을 받는게 아니라 구독을 시작한 부분부터 받는다.


다음 시간에는 스프링으로 직접 만들어보자.



Spring reactor
https://ahea.wordpress.com/2017/02/15/spring-reactive/

단계별 설명
http://wiki.sys4u.co.kr/pages/viewpage.action?pageId=8552586


spring에서 사용하는 의존성 주입에 경우 @Autowired만을 사용하면 나중에 테스트 케이스를 만들때 어려움이 생긴다. 


그래서 생성자 주입을 사용하는데 잘 정돈된 사이트가 있어 공유한다.


http://multifrontgarden.tistory.com/214

스프링 프레임워크에서 제공하는 JPA는 별도의 구현 클래스 없이 인터페이스만을 사용할 수 있도록 제공한다. 제공되는 인터페이스 JpaRepository는 실행시점에 자동으로 인터페이스 내용을 연결하는 엔티티에 맞게 자동으로 구현해준다. 만약 스프링 JPA 인터페이스에서 제공하지 않는 기능을 사용하고 싶을 때는 메서드명을 특정한 규칙대로 만들어서 사용하면 인터페이스가 알아서 그 이름에 맞는 JPQL을 만들어서 실행해준다.


스프링 JPA 인터페이스는 Mysql같은 RDBMS 뿐만 아니라 Mongodb, Redis와 같은 NoSQL에도 동일한 인터페이스를 사용해서 기능을 사용할 수 있도록 제공해준다. 공통으로 사용할 수 있기에 아주 편리하다.


우선 스프링 부트에 JPA를 사용하기 위해서 Gradle에 라이브러리를 넣자.

1
compile ('org.springframework.boot:spring-boot-starter-data-jpa')
cs

 

기본적인 구조

JpaRepository를 상속받아 구현하고자 하는 인터페이스를 만들고 제네릭에 구현하려는 엔티티와 엔티티의 식별자 타입을 지정하여 인터페이스를 선언한다.

1
2
3
public interface StudentRepository extends JpaRepository<Student, Long> {
}
 
cs


주요 메서드 몇개만 정리해보자.

save() : 저장하거나 업데이트한다.

delete(entity) : em.remove() 호출하여 엔티티를 제거한다.

findOne(ID) : em.find() 호출하여 엔티티를 찾는다.

findAll() : 엔티티 모두를 조회한다. 


JpaRepository 인터페이스로 부족한 기능을 구현한 PagingAndSortingRepository CrudRepository 사용해서 보완할 있다.


JPA에서 메서드 사용하는 방법

JPA에서 엔티티에 맞는 메서드를 사용하는 방법은 크게 3가지이다.

- 메서드 이름으로 쿼리 생성

- 메서드 이름으로 JPA NamedQuery 호출

- @Query 어노테이션을 사용해서 레포지토리 인터페이스에 쿼리 직접 정의


1. 메서드 이름으로 쿼리 생성

메서드 이름으로 쿼리를 생성할 있는데 정식 Document 이용하면 자세히 나와있다

https://docs.spring.io/spring-data/jpa/docs/2.1.2.RELEASE/reference/html/#jpa.query-methods


2. 메서드 이름으로 JPA NamedQuery 호출

JPA NamedQuery 쿼리에 이름을 부여해서 사용하는 방법은 다음과 같이 엔티티에 선언해주면 된다.

1
2
3
4
5
@NamedQuery(
  name="student.findByName",
  query="select s from student s where s.name = :name")
public class Student {

}
cs


위와 같이 선언하고 실제 사용할 때는 entityManager에 아래와 같이 createQuery를 사용해서 쿼리를 호출하면 된다.

1
2
3
4
5
6
7
@PersistenceContext
private EntityManager entityManager;
 
public List<Student> findByUser(String name) {
  List<Student> students = entityManager.createQuery("student.findByName", Student.class).setParameter("name""wedul").getResultList();
}
 
cs


위와 같이 EntityManager를 사용할 수 있지만 스프링 JPA를 사용하여 간단하게 메소드 이름만으로 호출이 가능하다. 이렇게 호출하면 레포지토리에서 Student.쿼리메소드 형태로 쿼리를 찾는다. 만약 실행할 쿼리가 없으면 메서드 이름으로 쿼리를 자동으로 바꿔 동작한다.

1
2
3
4
5
public interface StudentRepository extends JpaRepository<Student, Long> {
  
  List<Student> findByUserName(@Param("name") String name);
  
}
cs


3. @Query 어노테이션을 사용해서 레포지토리 인터페이스에 쿼리 직접 정의

2번에서는 @Entity 클래스에서 정의한 쿼리를 레포지토리에서 메소드 형태로 접근하여 사용하였다.  이번에는 레포지토리에서 직접적으로 쿼리를 만들어서 조회하는 방식을 확인해보자.


@Query("select s from Student s where s.name = ?1")

Student findByName(String name);


인터페이스에 정의하는 메소드에 @Query 어노테이션을 붙혀서 정의하고 사용하면 된다. 바인딩 값은 1부터 시작한다. 스프링 데이터 JPA에서는 ?1 ?2 같은 위치기반 파라미터와 :name 같은 이름 기반 방식을 모두 사용가능하다.


페이징과 정렬

스프링 데이터 JPA에서 쿼리 메서드에 페이징과 정렬 기능을 사용할 있다. 파라미터로 Pageable 인터페이스를 사용할 경우에는 Page 또는 List 반환 받을 있다.

1
2
3
4
public interface StudentRepository extends JpaRepository<Student, Long> {
 
  Page<Student> findByNameStartingWith(String name, Pageable pageable);
}
cs


실제 사용할 때는 Pageable 인터페이스이기 때문에 구현체인 PageRequest 객체를 사용해서 사용한다.

// 파라미터 순서대로 페이지 번호, 사이즈, 정렬 기준 등으로 사용한다.

1
PageRequest pageRequest = new PageRequest(0, 10, new Sort(Sort.Direction.DESC, "name"));
cs


반환되는 값인 Page에서 제공하는 다양한 메소드를 사용해서 편하게 페이징과 소트 기능을 사용할 있다. 

컨트롤러에서 사용자 요청에게 전달되는 페이징 정보를 받기 위해서는 다음과 같이 Pageable 인터페이스를 받으면 되고 받을 속성값은 page, size, sort 사용해서 받는다. (/student?page=0&size=20&sort=name,desc&sort=address.city)


1
2
3
4
5
6
7
@GetMapping("/student")
public String list(Pageable pageable, Model model) {
  Page<Student> page = studentRepository.findByNameStartingWith("dbsafer", pageable);
 
  return "dfdfdf";
}
 
cs


참고 싸이트

https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Page.html


출처. : 자바 ORM 표준 JPA 프로그래밍


연관관계 매핑을 해야하는 경우가 많다. 예를 들어 학생을 가지고 학생에 소속 반을 찾거나 반을 사용해서 학생들을 찾거나 때가 있다.

 

양방향과 단방향 관계가 존재하는데 아래의 객체 형태를 보면 이해가 쉽다.

 

단방향

class Student {

 Class class;

}

 

class Class {}

 

양방향

class Student {

 Class class;

}

 

class Class {

 Student student;

}


이중에서 먼저 단방향 연관 관계에 대해 먼저 공부해보자.

 

단방향 연관관계

  • 학생과 반이있다.
  • 학생은 하나의 반에 소속된다.
  • 학생과 반은 다대일 관계이다. (학생이 , 반이 )
  • 학생 테이블을 담는 객체는 Student, 반 테이블을 담는 객체는 Classes를 사용한다.
  • 학생 테이블은 반 테이블의 키 CLASSES_ID를 외래키로 테이블과 연관 관계를 가진다.

 

학생 테이블이 다, 반 테이블이 일로 다대일 관계이기 때문에 Student 클래스에 포함된 Classes 객체에 다음과 같이 선언한다.


1
2
3
@ManyToOne
@JoinColumn(name="CLASSES_ID")
private Classes classes;
cs


@ManyToOne

- 다대일 관계라는 표현

 

 

@JoinColumn(name = "CLASSES_ID")

- 조인 컬럼은 외래키를 매핑할 때 사용한다. 이 어노테이션은 생략해도 된다.

- 만약 생략 할 경우 외래키는 다음과 규칙으로 찾는다. 필드명 + _ + 참조하는 테이블의 컬럼명 그래서 위에서는 생략할 경우 classes_CLASSES_ID의 형태의 외래키를 찾는다.


다대다 테이블 관계 매핑설정이 끝난 객체


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.wedul.springboottest.student.dto;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
 
/**
 * springboottest
 *
 * @author wedul
 * @since 23/10/2018
 **/
@Table(name = "classes")
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Classes {
 
  @Id
  @Column(name = "CLASSES_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
}
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.wedul.springboottest.student.dto;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
import javax.persistence.*;
 
/**
 * springboottest
 *
 * @author wedul
 * @since 23/10/2018
 **/
@Entity
@Table(name = "student")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
 
  @Id
  @Column(name = "STUDENT_ID")
  private String id;
 
  @Column(name = "name")
  private String name;
 
  @ManyToOne
  @JoinColumn(name = "CLASSES_ID")
  private Classes classes;
 
}
cs


삽입

그럼 이 다대다 관계를 이용해서 데이터를 넣어보자. entityManager를 데이터를 삽입하는 코드를 만들자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.wedul.springboottest.student.serviceImpl;
 
import com.wedul.springboottest.student.dto.Classes;
import com.wedul.springboottest.student.dto.Student;
import com.wedul.springboottest.student.service.StudentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
 
/**
 * springboottest
 *
 * @author wedul
 * @since 23/10/2018
 **/
@Service
@Slf4j
public class StudentServiceImpl implements StudentService {
 
  @PersistenceContext
  private EntityManager entityManager;
 
  public StudentServiceImpl(EntityManager entityManager) {
    this.entityManager = entityManager;
  }
 
  @Override
  @Transactional
  public void insertStudent() {
    Classes classes1_1 = new Classes("1-1""1학년 1반");
    entityManager.persist(classes1_1);
 
    Student wedul = new Student("1-1-01""wedul", classes1_1);
    entityManager.persist(wedul);
 
    Student chul = new Student("1-1-02""chul", classes1_1);
    entityManager.persist(chul);
  }
}
cs


테스트 코드를 통해 테스트해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.wedul.springboottest.member;
 
import com.wedul.springboottest.member.service.MemberService;
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.SpringJUnit4ClassRunner;
 
/**
 * springboottest
 *
 * @author wedul
 * @since 03/10/2018
 **/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest // rolleback 설정
public class MemberTest {
 
  @Autowired
  MemberService memberService;
 
  @Test
  public void jpa_test() {
    memberService.jpaService();
  }
 
}
 
cs


조회

1

2

3

4

5

6

@Override

@Transactional

public void selectStudent() {

  Student wedul = entityManager.find(Student.class"1-1-01");

  print(wedul);

}

Colored by Color Scripter

cs

 

출력 결과

아이디 : 1-1-02, 이름 : chul, 소속반: 1?? 1?

 

JPQL을 사용한 조회

1

2

3

4

5

6

7

8

9

 

  @Override

  @Transactional

  public void selectStudentWithJPQL() {

    String jpql = "select s from Student s join s.classes c where s.name=:studentName";

 

    Student student = entityManager.createQuery(jpql, Student.class).setParameter("studentName""chul").getSingleResult();

    print(student);

  }

Colored by Color Scripter

cs

 

 


연관관계제거


1

2

3

4

5

6

@Override

@Transactional

public void removeClasses() {

  Student wedul = entityManager.find(Student.class"1-1-01");

  wedul.setClasses(null);

}

Colored by Color Scripter


wedul 학생의 반 정보가 지워진것을 알 수 있다.


생각보다 어렵지 않다. 

+ Recent posts