일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- BOF
- LOB
- 정보처리기사 실기
- wargame
- stack overflow
- System
- Spring MVC
- webhacking.kr
- 정보보안기사
- 네트워크
- webhacking
- 해킹
- Shell code
- SQL
- 웹해킹
- Payload
- 워게임
- Spring Framework
- pwnable
- Lord of BOF
- system hacking
- hacking
- Buffer Overflow
- 운영체제
- Operating System
- Spring
- 정보보안기사 실기
- PWN
- Pwnable.kr
- OS
- Today
- Total
DongDD's IT
[Spring] Spring JPA 1 - JPA, JPA CRUD 본문
[Spring] Spring JPA 1 - JPA, JPA CRUD
JPA(Java Persistence API)
ORM/JPA
- 자바 표준 ORM(Object Relational Mapping)
1. ORM(Object Relational Mapping)
- RDB에 데이터를 읽고 쓰는 것과 같이 객체에 데이터를 읽고 쓰는 방식의 기술
- 객체지향언어와 객체 지향이 아닌 RDB의 격차를 메워줌
- 객체에 접근하기 위한 코드로 DB table에도 접근할 수 있게 해줌
2. JPA(Java Persistence API)
- 자바 표준 ORM
- Java EE의 구성 요소와 결합도가 낮음
1 2 3 4 5 6 7 8 9 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; public TestModel getId(String id) { return entityManager.find(TestModel.class, id); } } | cs |
- @PersistenceContext를 사용하여 EntityManager 의존성 주입
- EntityManager의 find를 사용하여 TestModel 객체를 찾음
- JdbcTemplate과 달리 SQL문을 기술할 필요가 없고, 객체와 table을 mapping해줌
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 | @Configuration @EnableTransactionManagement public class JPAConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.H2) .setScriptEncoding("UTF-8").build(); } @Bean public JpaVendorAdapter jpaVendorAdapter() { HibernateJpaVendorAdapter bean = new HibernateJpaVendorAdapter(); bean.setDatabase(Database.H2); bean.setGenerateDdl(true); return bean; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) { LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean(); bean.setDataSource(dataSource); bean.setJpaVendorAdapter(jpaVendorAdapter); bean.setPackagesToScan("com.model"); return bean; } @Bean public JpaTransactionManager transactionManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); } } | cs |
- EntityManager를 사용하기 위해 설정을 해줘야함
- JpaVendorAdapter : 사용할 Database의 설정 정보를 지정
- LocalContainerEntityManagerFactoryBean : EntityManagerFactory를 생성
- EntityManagerFactory : EntityManager를 생성
- JpaTransactionManager : JPA의 트랜잭션 관리, @Transactional을 지정하여 사용
Entity
- 데이터베이스에 저장된 데이터를 자바 객체로 mapping한 것
- 메모리 상 자바 객체의 인스턴스 형태로 존재
ex) TestModel Entity
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 | @Entity @Table(name = "user") public class User { @Id @Column(name = "id") private String id; @Column(name = "name") private String name; public User() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } | cs |
- @Entity : 대상 클래스를 Entity 클래스로 지정
- @Table : Mapping할 table 이름 지정(생략 시, 대문자 클래스명의 table mapping -> TESTMODEL table)
- @Id : primary key 설정
- @Column : table에서 사용하는 column명과 변수를 mapping(생략 시, 대문자 변수명으로 mapping -> id(ID), name(NAME))
EntityManager
- Entity를 데이터베이스와 동기화하는 역할
- Persistence Context인 entity를 관리하기 위한 영역이 있음
- 어플리케이션이 DB에 접근하는 경우, EntityManager에서 entity를 얻거나 새로 생성한 entity를 persistence context에 등록해야함
- 구조
1. EntityManager API
- find : primary key를 기반으로 Entity 검색(SELECT)
- persist : 어플리케이션에서 생성한 인스턴스를 Entity로 persistence context에 삽입(INSERT)
- merge : 분리 상태가 된 entity를 persistence context에 merge(UPDATE)
- remove : entity를 persistence context에서 삭제, db에서 삭제(DELETE)
- flush : persistence context의 변경 내용을 DB에 동기화
- refresh : persistence context의 내용을 DB의 내용으로 변경
- detach : entity를 persistence context에서 분리
- clear : persistence context에 있는 모든 entity를 분리
2. Persistence Context-Database
- persistence context는 DB의 캐시와 같은 역할
- entity에 대한 작업이 수행되어도 DB에 즉시 반영되지는 않음
- 트랜잭션이 commit되거나 application에서 flush 호출 시 DB에 반영
Entity State
1. 상태
1) new
- 새로운 entity 인스턴스가 생성되고 persistence context에 등록되지 않은 상태
2) managed
- persistence context에 entity가 등록된 상태
- DB 동기화 활성화
3) detached
- managed 상태였던 entity가 persistence context에서 분리된 상태
- DB와 동기화되지 않음
4) removed
- DB에서 삭제될 것이 예정된 상태
2. Lifecycle
연관관계
- RDB에서 Table간 관계를 정의하는 것처럼 JPA에서는 Entity 간 참조 관계로 매핑하여 사용
1. 종류
1) OneToMany
- 하나의 entity가 여러개의 다른 entity를 가질 수 있음
2) ManyToOne
- 여러개의 entity가 하나의 다른 entity를 가질 수 있음
3) ManyToMany
- 여러개의 entity가 여러개의 다른 entity를 가질 수 있음
4) OneToOne
- 하나의 entity가 하나의 다른 entity를 가질 수 있음
2. 예제
1) User Model
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 | @Entity @Table(name = "user") public class User { @Id @Column(name = "id") private String id; @Column(name = "name") private String name; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List<Book> books; public User() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Book> getBook() { return books; } public void setBook(List<Book> book) { this.books = book; } } | cs |
- 하나의 유저가 책을 빌린다고 했을 때, 여러 개의 책을 빌릴 수 있음
- 유저(One)-책(Many)이기 때문에 @OneToMany로 지정
- mappedBy : 다른 entity에 mapping(다른 entity의 변수명과 mappedBy의 값이 일치해야함)
- cascade : 적용할 cascade type을 지정
2) Book Model
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 | @Entity @Table(name = "book") public class Book { @Id @Column(name = "book_id") private String bookId; @Column(name = "book_name") private String name; @ManyToOne @JoinColumn(name = "id") private User user; public Book() { } public String getBookId() { return bookId; } public void setBookId(String bookId) { this.bookId = bookId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } } | cs |
- 여러 개의 책은 한명의 유저에게 빌려질 수 있음
- 책(Many)-유저(One)이므로 @ManyToOne 지정
3) Service 구현
1 2 3 4 5 6 7 8 9 10 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; public List<Book> getBook(String id) { User user = entityManager.find(User.class, id); return user.getBook(); } } | cs |
- EntityManager를 통해 user를 불러오고 user가 가진 책을 조회하기 위해 getBook() 호출
4) Fetch
- Eager fetch : Entity 자체에서 API가 사용될 때 SQL 실행(User user = entityManager.find(User.class, id);)
- Lazy fetch : Entity와 매핑된 프로퍼티에 처음 접근할 때 SQL 실행(user.getBook();)
JPQL(Java Persistence Query Language)
- 기본키 지정없이 데이터베이스의 데이터를 처리할 때 사용
1. 종류
1) JPQL(Java Persistence Query Language)
- JPA 쿼리 언어를 사용하여 Entity를 가져오거나 값을 변경할 때 사용
2) Criteria Query
- JPQL보다 더 객체지향적
- Builder 패턴의 CriteriaQuery 객체를 이용하기 때문에 컴파일 시점에 타입 검사를 할 수 있음
3) Native Query
- SQL을 직접 사용하여 entity를 얻거나 갱신할 때 사용
2. Query
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class JpaService { @PersistenceContext private EntityManager entityManager; public List<Book> getBooksByName(String bookName) { String jpql = "SELECT b FROM Book b WHERE b.name = :name"; TypedQuery<Book> query = entityManager.createQuery(jpql, Book.class); query.setParameter("name", bookName); return query.getResultList(); } public List<Book> getBooks() { String jpql = "SELECT b FROM Book b"; TypedQuery<Book> query = entityManager.createQuery(jpql, Book.class); return query.getResultList(); } } | cs |
- String으로 query문 생성
- *과 같은 와일드카드가 불가능하고 alias를 설정하여 전체 column을 불러올 수 있음
- 생성한 query문을 createQuery의 인자로 넣어주고 반환받을 entity class를 등록
- parameter는 :param으로 설정하고 setParameter로 등록
JPA 사용
JPA CRUD
1. Entity
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 | @Entity @Table(name = "user") public class User { @Id @Column(name = "id") private String id; @Column(name = "name") private String name; public User() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } | cs |
- id와 name을 갖는 entity 생성
2. Create(POST)
1) Controller
1 2 3 4 5 6 7 8 9 10 11 | @RestController @RequestMapping(value = "/jpa") public class JpaController { @Autowired private JpaService jpaService; @RequestMapping(value = "/users", method = RequestMethod.POST) public void createUser(@RequestBody User user){ jpaService.createUser(user); } } | cs |
- POST 메소드로 요청을 받고 JpaService의 createUser 호출
2) Service
1 2 3 4 5 6 7 8 9 10 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; @Transactional public void createUser(User user) { entityManager.persist(user); } } | cs |
- persist 수행 시, @Transactional을 지정해주어야함
- EntityManager의 persist method를 사용하여 insert
3) 결과
- 성공
- 실패(중복)
3. Read(GET)
1) Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @RestController @RequestMapping(value = "/jpa") public class JpaController { @Autowired private JpaService jpaService; @RequestMapping(value = "/users/{id}", method = RequestMethod.GET) public User getUserOne(@PathVariable String id){ return jpaService.getUserOne(id); } @RequestMapping(value = "/users", method = RequestMethod.GET) public List<User> getUserAll(){ return jpaService.getUserAll(); } } | cs |
- 한개의 user를 가져오는 핸들러와 모든 user를 가져오는 handler
2) Service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; public User getUserOne(String id) { User user = entityManager.find(User.class,id); return user; } public List<User> getUserAll() { String jpql = "SELECT u FROM User u"; TypedQuery<User> query = entityManager.createQuery(jpql, User.class); return query.getResultList(); } } | cs |
- find의 경우는 @Transactional이 필요없음
- Primary key값으로 EntityManager를 사용하여 1개의 user를 가져옴
- jpql을 작성하여 모든 User를 가져옴
3) 결과
- GetOne
- GetAll
4. Update(PUT)
1) Controller
1 2 3 4 5 6 7 8 9 10 11 | @RestController @RequestMapping(value = "/jpa") public class JpaController { @RequestMapping(value = "/users", method = RequestMethod.PUT) public void updateUser(@RequestBody User user){ if(jpaService.getUserOne(user.getId()) == null) { throw new TestException(HttpStatus.NOT_FOUND, "존재하지 않습니다."); } jpaService.updateUser(user.getId(),user.getName()); } } | cs |
- id가 없을 시, ExceptionHandler에서 처리
2) Service
1 2 3 4 5 6 7 8 9 10 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; @Transactional public void updateUser(String id, String name) { getUserOne(id).setName(name); } } | cs |
- get을 하여 managed 상태로 만들고 managaed 상태인 entity의 name을 변경
- update는 entity를 변경하기 때문에 @Transactional을 지정해야함
3) 결과
- update
- update 내용 확인(get)
5. Delete(DELETE)
1) Controller
1 2 3 4 5 6 7 8 9 10 11 | @RestController @RequestMapping(value = "/jpa") public class JpaController { @RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE) public void removeUser(@PathVariable String id){ if(jpaService.getUserOne(id) == null) { throw new TestException(HttpStatus.NOT_FOUND, "존재하지 않습니다."); } jpaService.removeUser(id); } } | cs |
- id가 없을 시, ExceptionHandler에서 처리
2) Service
1 2 3 4 5 6 7 8 9 10 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; @Transactional public void removeUser(String id) { entityManager.remove(getUserOne(id)); } } | cs |
- remove는 entity를 삭제하기때문에 @Transactional을 지정해야함
- id를 사용하여 get으로 entity 불러옴
3) 결과
- remove
- remove 결과 확인(get)
6. Controller(전체)
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 | @RestController @RequestMapping(value = "/jpa") public class JpaController { @Autowired private JpaService jpaService; @RequestMapping(value = "/users/{id}", method = RequestMethod.GET) public User getUserOne(@PathVariable String id){ return jpaService.getUserOne(id); } @RequestMapping(value = "/users", method = RequestMethod.GET) public List<User> getUserAll(){ return jpaService.getUserAll(); } @RequestMapping(value = "/users", method = RequestMethod.POST) public void createUser(@RequestBody User user){ jpaService.createUser(user); } @RequestMapping(value = "/users", method = RequestMethod.PUT) public void updateUser(@RequestBody User user){ if(jpaService.getUserOne(user.getId()) == null) { throw new TestException(HttpStatus.NOT_FOUND, "존재하지 않습니다."); } jpaService.updateUser(user.getId(),user.getName()); } @RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE) public void removeUser(@PathVariable String id){ if(jpaService.getUserOne(id) == null) { throw new TestException(HttpStatus.NOT_FOUND, "존재하지 않습니다."); } jpaService.removeUser(id); } } | cs |
7. Service(전체)
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 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; @Transactional public void createUser(User user) { entityManager.persist(user); } public User getUserOne(String id) { User user = entityManager.find(User.class,id); return user; } public List<User> getUserAll() { String jpql = "SELECT u FROM User u"; TypedQuery<User> query = entityManager.createQuery(jpql, User.class); return query.getResultList(); } @Transactional public void updateUser(String id, String name) { getUserOne(id).setName(name); } @Transactional public void removeUser(String id) { entityManager.remove(getUserOne(id)); } } | cs |
연관 관계 Entity
- OneToMany의 연관 관계를 가진 entity의 경우, JOIN을 사용하여야 함
- 사용하지 않으면 error 발생
1 2 3 4 5 6 | public List<User> getUser(String id) { String jpql = "SELECT u FROM User u LEFT JOIN FETCH u.books WHERE u.id = :id"; TypedQuery<User> query = entityManager.createQuery(jpql, User.class); query.setParameter("id",id); return query.getResultList(); } | cs |
- jpql 문에 LEFT JOIN FETCH를 사용하여 여러 개의 Book List를 가져올 수 있게함
- 결과(Join)
- 결과(Join X)
배타 제어
- 동시에 여러 트랜잭션이 실행될 때, 갱신 처리에서 배타 제어 필요
- JPA의 배타 제어 기능 사용
1. Optimistic Lock
- Entity에 버전 번호를 지정
- Transaction이 끝날 때, DB와의 버전 번호가 다르면 다른 트랜잭션이 동작했다고 판단하여 에러
1) Version
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 43 44 45 | @Entity @Table(name = "user") public class User { @Id @Column(name = "id") private String id; @Column(name = "name") private String name; @OneToMany(mappedBy = "bookId", cascade = CascadeType.ALL) private List<Book> books; @Version @Column(name ="version") private Integer version; public User() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Book> getBook() { return books; } public void setBook(List<Book> book) { this.books = book; } } | cs |
- Entity에 @Version과 함께 version 변수 추가
- 갱신 작업 성공 시, version 값 증가
- DB에 반영하려 할 때, 버전 값이 다르면 exception 발생 후 rollback
2) lock
1 2 3 4 5 6 7 8 9 10 11 12 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; @Transactional public void updateUser(String id, String name) { User user = getUserOne(id); entityManager.lock(user, LockModeType.OPTIMISTIC); user.setName(name); } } | cs |
- 갱신 작업을 시작하기전 EntityManager의 lock 수행
- LockModeType
-> OPTIMISTIC : 해당 Entity를 갱신하는 동안 해당 Entity에 대한 다른 트랜잭션의 갱신이 불가능(변경 내용이 없으면 Version 그대로)
-> OPTIMISTIC_FORCE_INCREMENT : OPTIMISTIC과 같지만 변경 내용이 없어도 Version 증가
2. Pessimistic Lock
- 공유 잠금, 배타 잠금
- Version이 불필요하지만 version을 사용할 수 있는 Type도 존재
1) lock
1 2 3 4 5 6 7 8 9 10 11 12 | @Service public class JpaService { @PersistenceContext private EntityManager entityManager; @Transactional public void updateUser(String id, String name) { User user = getUserOne(id); entityManager.lock(user, LockModeType.PESSIMISTIC_READ); user.setName(name); } } | cs |
- LockModeType
-> PESSIMISTIC_READ : 공유 잠금 -> 다른 트랜잭션에서 읽기는 가능하지만 update는 불가능
-> PESSIMISTIC_WRITE : 배타 잠금 -> 다른 트랜잭션에서 해당 Entity에 접근 불가능
-> PESSIMISTIC_FORCE_INCREMENT : PERSSIMISTIC_WRITE + OPTIMISTIC_FORCE_INCREMENT
'프로그래밍 > Spring' 카테고리의 다른 글
[Spring] Spring MyBatis (0) | 2019.05.04 |
---|---|
[Spring] Spring JPA 2 - Spring Data JPA, Spring Data JPA CRUD (0) | 2019.04.28 |
[Spring] Spring Security - 인증, 인가, CSRF, Test (0) | 2019.04.20 |
[Spring] Spring Test (0) | 2019.04.20 |
[Spring] Spring MVC - Session, Async, 공통 처리, Locale (0) | 2019.04.14 |