You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 29 Next »

JPA Repositori를 통한 일반적인 검색 기능은 여러가지 샘플을 통해 살펴보았습니다.

이번장에서는 영속성(Persistence)관점에서 Repository기능을 살펴보겠습니다.

(lightbulb) persistence 두번째의미이며, 상태를 저장하고 유지한다는점에서 IT전반적으로 사용되어지는 단어입니다.


몇가지 연관 되어 사용되는 용어를 정리하였습니다.

  • persistent route : 웹호출시 노드가 변경안되도록 라우터가 IP기반으로 유지해줍니다. 서버캐시기능을 가진 웹서비스에서 중요한 요소입니다.
  • persitent FSM : AKKA에서 사용되는 단어이며, 분산환경에서 액터의 상태머신을 유지해줍니다.
  • redis persitence : 메모리DB인 redis의 상태가 휘발되지 않게 IO장치에도 저장하는 기능입니다.
  • rdb persitence : 가장 대표적인 데이터베이스입니다. 지속성보다 영구성의 의미가 더강하며 가장 강력합니다.

JPA Repository

JPA를 사용하면 데이터베이스 사용을위한 어플리케이션 레이아웃에 영속성을 위한 저장소(Repository)를 중간에 두게됩니다.

JPA Repository가 항상 이렇게 작동되는것은 아니나, DB객체를 직접 저장하지 않고 중간객체를 통해

DB를 효율적으로 이용한다는것입니다. 


JPA 저장소는 SQL직접호출 방식과 비교해 아래와 같은 특징이 있습니다.

  • 기존 SQL/SP를 직접 호출하여 값을 얻어 처리하는 방식은, 로컬에 반환된 값을 사용하고 버리는 상태가 필요없는 방식이며 매번 호출이 필요합니다.
  • 기존에는 상태가 없기때문에 데이터중심적 설계방식이였다고하면, JPA에서는 중간상태가 있기때문에  메시지 중심 설계 방식도 고려해야합니다.
  • JPA에서는 저장소를 통해, 같은 값을 다시 읽는것을 시도할때 SQL중복호출을 막고 효율적인 읽기전략을 수행할수 있습니다.(Proxy전략,캐싱전략)
  • JPA에서 쓰기전략은 어플리케이션 예외까지 트랜잭션영역에 포함할수 있으며, Repository에 임시저장후 원하는 타이밍에 실제 Update(Push)를 할수가 있습니다.(쓰기지연전략)


장점을 간략하게 요약하면 다음과같습니다.

  • 1차 캐시기능
  • 동일성보장
  • 트랜잭션을 지원하는 쓰기지연
  • 변경감지
  • 지연로딩


위와같은 장점이 있다고, SQL 직접호출방에서 배운 데이터베이스의 개념을 몰라도 될까요?

그것은 큰 착각입니다. JPA는 반복적인 SQL작성시간을 줄여주지만, 기존에 존재하지않던

중간 영속성 객체까지 잘 다뤄야하기 때문에 JPA의 특성과 더불어 관계형 DB의 특성을 같이 잘알고 있어야 합니다. 


단순한 검색및 집계처리에 대한 사용법을 다시한번 정리하였습니다. 이것은 시작에 불과합니다.

검색및 집계 처리

검색 함수쿼리

장점: 바람직한 네이밍룰에의해 검색 기능을 작동시킬수있다.

단점: 아규멘트가 길어질수록,네이밍도 같이 길어진다.

public interface AddressRepo extends CrudRepository<Address, Long>{
	
	List<Address> findBySex(String sex);
	
	List<Address> findBySex(String sex, Sort sort);
	
	List<Address> findBySexOrderByAgeDesc(String sex);
	
	List<Address> findByAgeGreaterThan(int age);
	
	List<Address> findByAgeGreaterThanEqual(int age);
		
	List<Address> findByAgeLessThan(int age);
		
	List<Address> findByAgeBetween(int low,int high);
	
	List<Address> findByAgeGreaterThanAndSex(int age,String sex);
	
	List<Address> findByAgeGreaterThanOrSex(int age,String sex);
		
	List<Address> findByAgeIn(int age[]);
}

JPQL을 사용한 집계처리

장점:SQL친화적으로 검색 인터페이스를 작성할수 있다.

단점:지원되지 않는 SQL문은 작동안한다.

	@Query("select new com.example.demo.data.AddressStatistics(t.address,AVG(t.age)) from Address t where t.age > :minage GROUP BY t.address HAVING AVG(t.age) > :filterage ")
	List<AddressStatistics> findRegionAvgage(
			@Param("minage") int minage,
			@Param("filterage") double filterage
			);


Native 쿼리

장점:네이티브 SQL문을 맘것 활용할수 있다.

단점:맵핑및 아규멘트전달에 어려움이 있으며, 다른 DB로의 호환성을 포기해야할수도 있다.

	@Query(value="SELECT address,age,name,phoneNbr,sex FROM address " + 
			"UNION " + 
			"SELECT address,age,name,phoneNbr,sex FROM address2 ",
			nativeQuery = true)
	List<?> makeUniOn();


SQL 윈도우함수를 대체하는 랭킹처리

장점:특수한 기능구현이 가능하고 어플리케이션에게 연산부담을 시킬수있다.

단점:커스텀한 로직이 늘어나 관리가 안될수 있으니 , 효율적인 SQL문 작성법부터 알아보자

	public void RankTest() {		
	     int[] score = {Integer.MIN_VALUE};
	     int[] no = {0};
	     int[] rank = {0};	
	     List<AddressAgeRank> ageRankList =  
				addressRepo.findByAgeBetween(10, 90).stream()
				.sorted((a,b) -> b.getAge() - a.getAge() )
				.map(p -> {
		             ++no[0];
		             if (score[0] != p.getAge()) rank[0] = no[0];
		             return new AddressAgeRank(p.getName(),score[0] = p.getAge(),  rank[0] );					
				})
				.collect(Collectors.toList());
	     
	     ageRankList.forEach(item ->{
	    	 System.out.println(item.toString());	    	 
	     });	    
	}


QueryDSL 지원이되는 저장소

장점:여러가지 검색옵션을 조합을 할수 있으며, 대표적으로 null 인자값에대해 유연하게 대처할수 있다.

단점: 초기셋팅이 번거럽고, DSL사용을위하  QEntityObject가 메타빌드로 자동추가 생성이 된다. (어플리케이션이 엔티티증가에따라 무거워진다는것)

//인터페이스 선언부
public interface AddressRepoDSL extends JpaRepository<Address, Long>,
QueryDslPredicateExecutor<Address>{
	
}
//사용부
	@Autowired
	private AddressRepoDSL addressRepoDSL;
	public void jpa_queryDslTest() {
		
		QAddress	userAddress = QAddress.address1;
		BooleanBuilder builder = new BooleanBuilder();
		
		builder.and(userAddress.name.eq("민수1") )
			   .and(userAddress.address.like("%" + "서" +"%"));		
				
		Iterable<Address> addressList =  addressRepoDSL.findAll(builder);
		addressList.forEach( item -> {
			String itemString = String.format("%d%s %s %s %s %s",item.getId(),item.getName(),
					item.getPhoneNbr(), item.getSex(),item.getAddress(),item.getAge());
			System.out.println(itemString);
		});
		
	}


참고 원본문서:


데이터 변경(Insert,Update,Delete)

스토어 프로시져 이용

장점: 전통적인 SP개발 방식을 이용할수 있다.

단점: 형상관리 분리로 인해, 서비스로직을 분리 관리해야하며, 상태가 없기때문에 JPA 중간저장소 기능을 100%활용못할수 있음  

예를 들면, 높은 TPS요구에대해 EntityManager의 커넥션풀 기능이 비활성화되어 커넥션 Max에러가 발생할수 있음(직접관리해야함)

CREATE OR REPLACE FUNCTION addaddress(
	address character varying,
	age integer,	
	name character varying,
	sex character varying,
	phonenbr character varying )
 
RETURNS integer AS $BODY$	
	BEGIN 
		INSERT INTO address ( address, age, name,sex,phonenbr) VALUES( address, age, name,sex,phonenbr);
		return 0;
	END; 
$BODY$
LANGUAGE plpgsql VOLATILE
public interface AddressRepo extends CrudRepository<Address, Long>{
	@Procedure
	public Integer addaddress(String address,Integer age, String name, String sex, String phonenbr);	
}


//SP 호출방법 : 커넥션풀 관리가 안되어, Loop로 트랜잭션을 유발하는것은 위험합니다.
addressRepo.addaddress("독도", 45, "포켓몬", "남", "010-1233-3321");


//호출로그 : 실제 호출로그에는 아규멘트가 5개가아닌 6개입니다. 이것은 리턴처리가,OUT 파라메터로 자동연결되었기때문입니다. 
Hibernate: 
    {call addaddress(?,?,?,?,?,?)}


more info: http://roufid.com/3-ways-to-call-a-stored-procedure-with-hibernate-jpa-2-1/


Persitence Context

PersitenceContext 는 JPA를 이해하는데 가장 중요한 단어이며 , 엔티티를 저장하고 유지하는 논리적인 장치쯤으로 생각해두자 

이것은, JPA에서 Persitence를 이용할때 데이터베이스에 직접 쿼리를 날리지않고 중간에 어떠한 개체가 개입을 함을 의미합니다.

DBMS자체도 사용자의 질의에 반응하여, 즉시 물리적인 디스크에 IO를 발생시키지  않는다는점에서 유사한점이 있습니다.

DBMS에서 이루어지는 몇가지 중요기능인  캐시처리/지연쓰기 등을 JPA 어플리케이션에서도 활용이 가능해보입니다.

이것이 필요없으면  DBMS의 성능을 믿고 , 모두 즉시 처리전략을 선택할수 있습니다.

어쨋든  이러한 영속성기능은 동일성을 유지하는 조건내에서, DBMS가 디스크 IO접근을 최소화하려는 노력과 비슷하게

JPA Persitence는  데이터 베이스 접근을 최소화하여 성능을 높일수있는 논리적인 장치를 제공해줍니다. 

오라클과 비교해보는 JPA 영속성 장치

JPA의 영속성기능은 오라클 DBMS의 핵심기능중 축소된 Buffer Cache 처리기능과 유사해 보입니다.

필수적이지는 않으나 두가지 개념을 함께 익히면 JPA의 영속성개념을 익히는데 도움이 될듯합니다.

순서상 JPA와 별개로 관계형DB를 잘 이용하기위해서 DBMS 아키텍쳐를 익혀야하는것을 추천하며

오라클에서 관련자료를 많이 찾을수 있습니다.



CRUD Repository

영속성은 어딘가에 존재하는 논리적인 개념으로 설명하기도 이해하기도 어렵습니다. ( (sad)  정리하느라 여러가지 참고자료를 봐도 어렵네요 )

실제 존재하는 객체인 CrudRepository를 활용하여 , 영속성 관련 기능을 하나씩 사용해보겠습니다. 익숙해지는 그날까지~~

데이터 추가/삭제/변경/조회등  추가구현없이, 이미 구현된 CrudRepository를 이용하면 대부분의 기능을 사용할수가 있습니다. 


인터페이스 준비

/선언
public interface GroupRepository extends CrudRepository<GroupInfo, Long> {
}
public interface UserRepository extends CrudRepository<User, Long> {
}


Insert

// 그룹생성
GroupInfo newGroup = new GroupInfo();
newGroup.setName("학생");
groupRepository.save(newGroup);

// 사용자 생성
User addUser = new User();
addUser.setName("minsu");
addUser.setEmail("test@x.com");


//SQL문에서는 외래키인 그룹ID를 지정하지만,객체모델에서는 이미 알고있는 참조지정을 합니다.
//이것이 OOP의 특성을 잃지않으면서,관계형DB의 특성까지 확장이된 한가지 케이스입니다. 
addUser.setGroupInfo(newGroup);
userRepository.save(addUser); 


Update


Delete


Custom Repository

기본적인 데이터 입출력에서 CurdRepository 를 이용하면, 세부적인 트랜잭션 처리를 크게 신경쓸필요가 없습니다.

하지만, EntityManager(이하 em)가 관리하는 PersitenceContext를 이해하려면 

em을 통해 저수준의 JPA API를 통해 영속성과 관련된 몇가지 기능을 직접 호출하여 데이터 베이스를 제어해보겠습니다. 


인터페이스 준비

public interface MyAddressRepository  {
	void someTest(Address2 address2);
}


public class AddressRepositoryImpl implements MyAddressRepository { 
	@PersistenceContext
	private EntityManager em;
	  
	@Override
	@Transactional
	public void someTest(Address2 address2) {
		em.refresh(address2);		
		em.detach(address2);
		em.persist(address2);
		em.lock(address2, null);
		em.flush();
	}	  
}
public interface AddressRepository extends JpaRepository<Address2, Long>,MyAddressRepository {

}

네이밍 룰

네이밍설명
MyAddressRepository

인터페이스이며, 커스텀함 함수규격을 미리 설명을 해놓아야합니다.

네이밍 특성 : 특성+테이블명+저장소

My + Address + Repository

AddressRepositoryImpl

커스텀한 구현체를 정의합니다.

네이밍 특성: 테이블명+저장소+구현

MyAddressRepositoryImpx ( X )

AddressRepository

최종 사용가능해진 저장소이름입니다. JPA의 기본 저장소 특성과함께

사용자 정의 기능을 함께 사용가능합니다.

네이밍특성: 테이블명+저장소




Insert


Update


Delete



  • No labels