Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Tip

JPA의 컨셉은 Java Persistence API 의 약자로 데이터 베이스에 존재하는 모델을

자바객체로 맵핑하는데 목적이 있습니다. DB는 종속적이지 않게 여러가지 DB 선택이가능하며

여기서는 MYSQL을 통해 실습이 진행됩니다.


설명을 위해, 축약된 코드로 설명을 하였으며, 풀소스는 아래에서 확인가능합니다.

CodeLink : http://git.webnori.com/projects/WEBF/repos/spring_jpa/browse

JPA 개념 추가설명 : http://blog.woniper.net/255 - woniper 님의 블로그


Table of Contents

접속 DB 설정

No Format
# application.properties
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/spring
spring.datasource.username=test
spring.datasource.password=test1234

spring.jpa.hibernate.ddl-auto 옵션

  • none: 테이블 구조 변경에 관여하지 않지않기때문에, DB 스키마와 JPA 모델을 맞추어놓아야합니다.
  • update: JPA에서 정의한 데이터모델과,  데이터베이스의 스키마 변경이 있을때 반영됩니다. 

  • create: 매번 테이블을 생성하지만, 어플리케이션이 닫힐때 드롭하지 않습니다.

  • create-drop: 매번 테이블을 생성하고, 세션이 닫힐때 자동으로 테이블을 드롭합니다.


네이밍룰

JPA에서 class를 통해 테이블을 정의할때 몇가지 규칙이 있습니다.

...

  • spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.EJB3NamingStrategy
  • spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl


Data Model(Entity) 생성

Code Block
languagejava
themeEmacs
titleUser Entity
package com.psmon.springdb;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity // This tells Hibernate to make a table out of this class
public class User {
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private Integer id;
	
	private String name;
	
	private String email;

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

}

...

실제 DataBase에서는 위와같은 테이블이, 어플리케이션 시작시 자동 생성됩니다.



CRUD 저장소생성

 전통적인 DataBase를 통한 개발방법은,  SQL을 직접이용하거나, 주로 SP를 이용하여 Table의 정보를 읽거나 변경을 하였습니다.

...

Code Block
languagejava
themeEmacs
titleUserRepository
package com.psmon.springdb;

import org.springframework.data.repository.CrudRepository;

//This will be AUTO IMPLEMENTED by Spring into a Bean called userRepository
//CRUD refers Create, Read, Update, Delete

public interface UserRepository extends CrudRepository<User, Long> {

}


CRUD를 이용하여 데이터 제어하기

CURD를 이용한 데이터제어를 유닛테스트기를 이용하여 테스트해보겠습니다.

...

Code Block
languagesql
themeEmacs
collapsetrue
-- 사용자생성
INSERT INTO `spring`.`user`
(`id`,
`email`,
`name`)
VALUES
(<{id:}>,
<{email: >,
<{name:}>);

-- 사용자 조회
SELECT * FROM user



JPA Relation

일반적으로 DB의 테이블은 하나의 테이블에 모든 정보를 포함하지 않고, 데이터의 효율적인 관리를 위해서 몇개의 테이블구조로

...

그 목적을 달성하기위해 데이터베이스와 객체지향의 몇가지 차이점을 알아야합니다.


Class VS Table

Code Block
languagejava
themeEmacs
class User{
	int id;   // 특별한 구현없이, 사용자의 객체만으로 자신을 포함하는 특정정보(클릭로그)만 추출할수가 없습니다.
	string name;
	string email;
}
class ClickLog{
	int clickid;
	User user; // User객체 참조를 포함합니다.
	string clickurl;	
}

TABLE User{
   int id   //자신의 키를 참조하는 다른 테이블에 접근가능합니다. on u.id = c.clickid
   varchar name
   varchar email   
}
TABLE ClickLog{
	int clickid
	int userid  //외래키를 통해서 접근가능합니다. on c.clickid = u.id
	varchar clickurl;
}

...

  • ManyToOne : 다대일 관계 매칭정보
  • JoinColumn : 외래키를 매핑때 사용함
  • mappedBy : 연관관계설정시 주인이 아님을 설정
  • OneToMany : 일대다 관계 매핑정보
  • OneToOne : 일대일 관계 매칭정보, 어느곳이나 외래키를 가짐
  • ManyToMany : 다대다 관계매칭정보, 맵핑테이블을 만들어서 사용하기를 권장
  • 연관관계주인 : 외래키가 있는곳이 주인이며 주인만이 수정가능 아닌경우 조회만가능


식별관계 VS 비식별관계

  • 식별 관계는 부모 테이블의 기본 키를 자식 테이블로 전파하면서 자식 테이블의 기본 키 컬럼이 점점 늘어난다. 그러면 조인할 때 SQL이 복잡해지고 기본 키 인덱스가 불필요하게 커질 수 있다.
  • 식별 관계는 2개 이상의 컬럼을 합해서 복합 기본 키를 만들어야 하는 경우가 많다.
  • 식별 관계를 사용할 때 기본 키로 비지니스 의미가 있는 자연 키 컬럼을 조합하는 경우가 많다. 반면에 비식별관계의 기본 키는 비지니스와 전혀 관계없는 대리 키를 주로 사용한다. 언제든지 요구사항은 변한다. 식별 관계의 자연 키 컬럼들이 자식에 손자까지 전파되면 변경하기 힘들다.
  • 식별 관계는 부모 테이블의 기본 키를 자식 테이블의 기본 키로 사용하므로 비식별 관계보다 테이블 구조가 유연하지 못하다.

...

Info

이후 샘플은 비식별관계만 이용하여, JPA특성을 살펴 보겠습니다.

JPA 관계에대해 더 자세한 정보는 아래 블로그에 잘 정리가 되었으며

여기 자료도 일부 참고하여 정리하였습니다.

URL : http://wonwoo.ml/index.php/post/832 -머루의 개발 블로그


ManyToOne

위 샘플에서 사용자(User) 데이터 모델링에서,  사용자가 속한 그룹을 표한하는 테이블을 추가해보겠습니다.

...

-부모의 키와 자식의 키가 다르며(전파되지 않았으며) , 자식의 테이블에서만 부모를 찾는 참조(외래)키만 있기때문에 비식별관계입니다.


SQL MODE

우리가 원하는 테이블 모델링은 위와같을 모습이며, user테이블에 group_id가 외래키로 설정이 되어있습니다.

...

Code Block
languagesql
themeEmacs
titleSQL문으로 표현했을시
collapsetrue
CREATE TABLE `group_info` (
  `group_id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`group_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;


CREATE TABLE `user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `group_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`user_id`),
  KEY `FKa36i4ekojwk70bxen390i6tek` (`group_id`),
  CONSTRAINT `FKa36i4ekojwk70bxen390i6tek` FOREIGN KEY (`group_id`) REFERENCES `group_info` (`group_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;


JPA MODE

Code Block
languagejava
themeEmacs
titleGroupInfo Entity
collapsetrue
@Entity
public class GroupInfo {
	@Id
    @GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name = "GROUP_ID")
    private Integer id; 
    
    private String name;    
}

...

전통적인 처리 방법은 Insert쿼리를 실행하고 다시조회쿼리를 실행하고,그 데이터셋의 결과를 어플리케이션에 가지고와서
사용하기 과정까지 각각 다른 처리코드와 변환과정이 필요했을것입니다.( 테이블을 쿼리로 설계 <-> SQL <-> DataSet <-> Object <-> Json )
Json의 뷰단의 데이터가 하나만 바뀌어도 최대 5가지 수정 포인트에서 코드 수정이 이루어 졌을것입니다.


JPA를 통한 데이터모델링 정의및 데이터제어가 다소 익숙하지않고 SQL문의 자유롭고 복잡한
표현을 모두 표현하기에 어려울수도 있습니다. 현재로서는 일괄적인 단일지점( 객체지향 정의)에서
모두 가능하다란것정도 이해를 해두고 넘어갑시다.


OneToMany

테이블구조는 변함이 없으며 접근 방식을 ManytoOne에서 OneToMany로 변경을 하여 객체지향접근방식으로

접근해보겠습니다.  Group을 핸들링하고 User는 리스트처럼 사용하는것을 시도해보겠습니다.

SQL MODE

DB는 구조상, 하나의 테이블에서 서브 List형태의 데이터를 가질수가 없습니다.

...

Code Block
languagesql
themeEmacs
collapsetrue
-- 그룹을 추가한다.
INSERT INTO test.group_info(
   group_id
  ,name
) VALUES (
   NULL -- group_id - IN int(11)
  ,''  -- name - IN varchar(255)
)


-- 사용자를 추가할시 그룹을 지정하거나, 나중에 지정한다.
INSERT INTO test.user(
   user_id
  ,email
  ,name
  ,group_id
) VALUES (
   NULL -- user_id - IN int(11)
  ,''  -- email - IN varchar(255)
  ,''  -- name - IN varchar(255)
  ,0   -- group_id - IN int(11)
)


-- 여러명을 등록할시 위 과정이 반복됩니다.




--학생인 사용자만 조회시..Join을 통해 해결
select * from user join group_info gi on gi.name='학생'



JPA MODE 

JPA에서의 목적은, 기존 SQL에서 처리하는 방식을 객체지향적으로 변경하는것입니다.

...

데이터를 넣을 일은 없을것이나, Insert처리 조회처리를 일괄적이고 효과적인 방법으로 할수 있다란 

예일뿐입니다.


Paging 처리

JPA MODE

Code Block
languagejava
themeEmacs
public interface UserPageRepo extends Repository<User, Long>{

	Optional<User> findOne(Long id);
	
	// 전체 페이징처리
	Page<User> findAll(Pageable pageRequest);
	
	// 검색 확장-그룹명으로 페이지 필터
	Page<User> findByGroupInfoName(String groupName,Pageable pageRequest);
		
	// Update
	void delete(User deleted);
	
	User save(User persisted);
	
	void flush();
}

...

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-lookup-strategies


UseCase

Code Block
languagejava
themeEmacs
collapsetrue
		//Test를 위해 100개의 데이터 인입
		GroupInfo newGroupA = new GroupInfo("학생");
		Set usersA = new HashSet<User>() {{
			for(int i=0; i<100 ; i++) {
				String userName = String.format("minsu%d", i);
				String email = String.format("min%d@x.com", i);
				add(new User(userName,email,newGroupA));
			}					
		}};
		newGroupA.setUsers(usersA);
        groupRepository.save(new HashSet<GroupInfo>() {{
        	add(newGroupA);
        }});


		//원하는 페이지를 조회합니다.( 페이지번호 , 페이지당 처리수)
        PageRequest  pageRequest =  new PageRequest(1,10);                       
        Page<User> sPage = userPageRepo.findAll(pageRequest);
        System.out.println( String.format(" %d:Contents %d:Page", sPage.getNumberOfElements(),sPage.getNumber() ) );
        
        Page<User> sPage2 = userPageRepo.findByGroupInfoName("학생", pageRequest);
        System.out.println( String.format(" %d:Contents %d:Page", sPage2.getNumberOfElements(),sPage2.getNumber() ) );


JQL MODE

Code Block
languagejava
themeEmacs
public interface UserPageRepo extends Repository<User, Long>{

	// QueryMode
	@Query(value="select t from User t "			
			+ "where  t.name =:name  "
			+ "order by t.id " , nativeQuery=false )
	List<User> findBySomeName( @Param("name") String name, Pageable pageable);
	
}

JPA의 함수처리방식이 익숙하지 않다면, SQL방식으로 인터페이스 작성도 가능합니다.


JQL VS SQL(Native)

JQL은 JPA와 연동되어 일반적으로 대부분의 DB에 호환이되는 다소 제약적인 쿼리사용이 가능하지만

...