Data Persistence
- 객체들을 영구적으로 보관하는 것
- Web Application 관점에서는 객체지만 Database에서는 이러한 객체들이 테이블로 관리된다.
DB에 데이터를 유지하는 방법
- JDBC
- ORM
- 객체와 객체 사이는 의존성이 존재(의존성 주입)
객체지향 언어를 사용하면 그래프 형태로 객체를 관리할 수 있고
Relational database systems에서는 테이블 형태로 관리할 수 있다.- 객체지향 언어와 DB를 같이 사용하면 객체 모델과 관계 모델에 불일치가 발생할 수 있다
- 그 부분을 ORM framework의 Hibernate가 해결해준다.(Framework을 사용하여 불일치를 해결)
Mismatch가 발생되는 경우!
- 객체지향언어에서 클래스가 2개이지만 RDBMS에서는 1개인 경우가 있다.
- 객체지향 언어에서는 상속 개념이 있지만 RDBMS에는 상속 개념이 없다.
Association
- Java의 객체 참조
- reference는 방향성이 존재 -> 양 방향을 설정하기 위해서는 reference를 두 번 사용하여 association을 표현한다. - RDBMS의 외래 키
- RDBMS는 foreign key를 사용하여 association을 표현한다.
=> foreign키 자체로는 방향성이 있지만 foreign키를 바탕으로 join을 하면 방향성이 없어진다.
※ 쿼리값으로 요청한 query를 던져주면 hibernate가 알아서 sql문을 생성하여 실행한다.
- One to One Relationship
=> 1:n의 특별한 경우가 1:1인 것
- 클래스 관계 설정
- 자료형으로 Address를 사용한 멤버 변수를 추가하여 참조 관계 형성
public class Student {
private long studentId;
private String studentName;
private Address studentAddress;
}
public class Address {
private long addressId;
private String street;
private String city;
private String state;
private String zipcode;
}
결과
- One to Many Relationship
- 클래스 관계 설정(2가지 방법)
- Phone 클래스에 Student 클래스를 만들어 참조 관계 형성
- Student 클래스에 Set 자료형을 사용하여 Phone 참조
-객체 안에 set이나 array와 같은 변수를 설정해주면 테이블에서는 Join Table로서 별로의 테이블을 만들어 관리한다.
//1번 방법
public class Student {
private long studentId;
private String studentName;
}
public class Phone {
private long phoneId;
private String phoneType;
private String phoneNumber;
private Student student;
}
//2번 방법
public class Student {
private long studentId;
private String studentName;
private Set<Phone> studentPhoneNumbers ;
…
}
public class Phone {
private long phoneId;
private String phoneType;
private String phoneNumber;
…
}
결과
Hibernate
- Java를 위한 ORM 솔루션
- 객체를 테이블에 매핑해준다.
- GPL의 rule를 완화시킨 LGPL이다.
(LGPL: open source를 사용하여도 프로그램의 소스를 공개 안 해도 된다.)
- application은 단일 Configuration을 생성
- SessionFactory의 단일 인스턴스를 빌드 한 다음 client request를 서비스하는 스레드에서 세션을 인스턴스 화한다.
- Application Logic ---data request---> DAO ---Hiberante API---> Hibernate---JDBC API---JDBC API---> JDBC
- 이제는 Hibernate가 ResultSet를 domain object(도메인 객체)로 매핑을 해주어 DAO로 넘겨준다.
- Application Logic -> User Interface로 객체를 넘겨줄 때 전부 넘겨주는 것이 아니라 필요한 것만 넘겨준다
: data Xfer object(data transfer object, 필요한 것만 넘겨준다면 테스트할 때 용의 하다). - Hibernate는
- SessionFactory,
- hibernate.cfg.xml(= jdbc.property와 같은 역할을 해다.),
- *. hbm.xml class mappings(객체에 annotation을 달거나 xml 설정, default값으로 할 수도 있고 내가 설정할 수도 있다.)
이 세 가지를 설정해 주어야 한다.
※domain object 중에서 저장하려고 하는 object를 Persistent Object라고 부른다.
1. Configuration Object
- Configuration이라는 클래스가 Hibernate Mapping(*. hbm.xml.hbm.xml class mappings)와 Hibernate Config(*hibernate.cof.xml)를읽어 들이고 Session Factory를 만든다.
- Session Factory는 Session을 만들어 JDBC에 접근한다.(Session에는 Transaction을 활용한다.)
-> (같은 개념) Web app과 DB Sever 간에 DB Session을 만들어 DB와 연동이 가능하다. - database connection과 mapping을 이용하여 SessionFactory를 생성
- JDBC는 Hibernate에 속한 것은 아니다.
hibernate.cfg.xml 예시
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/myDB</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">1234</property>
<!-- show mysql queries output in console -->
<property name="hibernate.show_sql">true</property>
<!-- manage automatic database creation -->
<property name="hibernate.hbm2ddl.auto">create</property>
<!-- mappings for annotated classes -->
<mapping class="testHibernate.Category"/>
<mapping class="testHibernate.Product"/>
</session-factory>
</hibernate-configuration>
- hibernate.dialect : 어떤 DB를 사용할 것인지 설정하는 부분
- hibernate.show_sql : hibernate가 생성하는 sql를 console에서 볼 것인지 아닌지 설정하는 부분
- hibernate.hbm2ddl.auto : 애플리케이션을 실행시킬 때마다 기존에 있는 것을 지우고 db와 table를 새롭게 만든다.(create라고 설정하면, 개발하는 경우에 주로 사용), update라고 주면 지워지지 않음
mapping class 예시
@Entity
public class Product {
@Id
@GeneratedValue
@Column(name=“product_id”)
private int id;
private String name;
private int price;
private String description;
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="category_id")
private Category category;
}
- ★Class를 Table로 mapping을 하고 싶다면 반드시 @Entity라는 annotation을 붙여야 한다.
- 내가 primary키 값인 id를 부여하는 것이 아니라 Hibernate에게 시킨다면 @GeneratedValue를 사용하여 자동생성을 한다.
- @Column으로 table에 들어갈 속성 이름을 바꿀 수 있다.
- 외래키를 설정하려고 한다면 포인터를 사용(객체를 넣어줌)하여 참조시킨다.
->@ManyToOne, JoinColumn을 사용해주어야 한다.
2. SessionFactory Object
- session 생성
- SessionFactory Object는 Application에서 하나만 만들어진다.(싱글톤)
-> SessionFactory는 Spring IOC Container에서 만들어준다. - request가 들어올 때마다 Thread를 만들고 여러 개가 들어오면 멀티 쓰레딩이 된다.
- 스레드들은 SessionFactory로 들어가 공유자원을 사용하는데 상호 배제가 필수다.
-> SessionFactory는 상호배제가 잘 되어있어서 멀 티쓰 레딩 환경에서도 잘 돌아간다. (Thread safe object) - Session Factory는 무거운 객체기 때문에 application이 시작할 때 만들어지고 계속 사용.
SessionFactory Object
- SessionFactory.openSession()
-항상 새로운 세션을 연다.
-Spring famework이 session의 close와 flush를 해주기 때문에 따로 안해줘도 된다. - SessionFactory.getCurrentSession()
- Context에 바인딩된 세션을 반환
- 처음 호출 시에 새로운 세션이 열린다.
예시 코드
public class PersonService {
@Authowired
private SessionFactory sessionFactory;
public Person edit(Integer id, String firstName,
String lastName, Double money) {
Session s= sessionFactory.openSession();
Transaction tx= s.beginTransaction();
Session session = sessionFactory.getCurrentSession(); Person person = (Person) session.get(Person.class, id);
person.setFirstName(firstName);
person.setLastName(lastName);
person.setMoney(money);
session.update(person);
tx.commit();
return person;
}
@Transactional
public class PersonService {
@Authowired
private SessionFactory sessionFactory;
public Person edit(Integer id, String firstName,
String lastName, Double money) {
Session session = sessionFactory.getCurrentSession();
Person person = (Person) session.get(Person.class, id);
person.setFirstName(firstName);
person.setLastName(lastName);
person.setMoney(money);
session.update(person);
return person;
}
- Container에 의해서 SessionFactory에 주입이 일어나고.....
- session.get(Person.class, id) 디비에 접근에서 id값을 primary로 가진 data를 가져와 매핑하여 person에 넣어준다.
- session.update(person)를 하면 중간에 수정된 값들이 db에 적용된다.
- @Transactional annotation을 사용하면 sessionOpen과 transaction시작 그리고 transaction commit 코드를 생략해도 된다. --> Before advice와 after advice가 @Transactional annotation(AOP annotation)을 사용하면 자동으로 삽입.
3. Session Object
- application과 database 간의 "대화"를 나타낸다.
- database와 physical connection 하는 데 사용
- 일반적으로 스레드 안전하지 않기 때문에 오랫동안 열어 두면 안된다.
- 맵핑된 엔티티 클래스의 인스턴스에 대한 작성, 읽기, 저장 및 삭제 오퍼레이션 제공
- 객체의 1 단계 캐시 유지
- 데이터베이스에 커밋하기 전에 개체를 자체 권한으로 유지
- CRUD를 하면 바로 DB에 반영되는 것이 아니라 Cache에 저장해 두었다가 반영한다.
-> flush를 하게 되면 Cache에 있는 것이 DB에 반영이 된다. - Cache는 performance optimization을 위해 사용된다.
- CRUD를 하면 바로 DB에 반영되는 것이 아니라 Cache에 저장해 두었다가 반영한다.
- 데이터베이스에 커밋하기 전에 개체를 자체 권한으로 유지
Object States
- 처음에 객체를 만들게 되면 객체는 Transient 상태가 되고 save나 saveOrUpdate()로 디비에 저장이 되면 이 객체는 Persistent상태가 된다. 또 세션을 close()하게 되면 Detached 상태가 된다.
예시 코드
public class HibernateTest {
public static void main() {
Person person = new Person(); // Transient Object
person.setUserName("Test User");
SessonFactory sessionFactory =
new Configuration().configure().buildSessionFactory();
Sesson session = SessionFactory.openSession();
session.beginTransaction();
session.save(person); // Persistent Object
// Any Changes made to the persistent object get reflected in the DB
person.setUserName("Updated User");
person.setUserName("Updated User Again");
session.getTransaction().commit();
session.close();
// Detached Object; hibernate is not going to track the changes
person.setUserName("Updated User After session close");
}
}
- session.save(person)을 한 상태에서 -> DB 반영됨
- person.setUserName으로 값을 변경하게 되면 DB 반영된다.
- session.close()를 하게 되면 이제 DB에 완전히 반영되어 setUserName을 해도 반영되지 않는다.
Session Methods( Hibernate를 사용하면 sql 대신 session Methods 사용 )
- get()
: 값을 읽어옴 , 없으면 NULL 값이 넘어온다. - save
: 객체를 디비에 저장한다, 참조한 테이블들도 cascade로 저장해준다. - saveOrUpdate()
: identifier가 존재하면 update가 호출되고 없다면 save가 호출된다. - delete()
: 객체에 해당되는 DB의 Record가 사라진다. - flush()
: session memory(cache)에 저장되어있는 객체를 database에 동기화시켜주는 작업을 해준다.
flush mode
: always, commit, manual(수동),
Auto(default, 쿼리가 실행되기 전에 트랜잭션이 완료됐을 때... 계속 실행됨
-> 너무 자주 flush를 시켜주어 performance가 좋지않다, 초보용)
flush 과정
Step 1: Begin transaction
Step 2: Create employee A
Step 3: Create employee B
Step 4: Associate A with its manager C
Step 5: Look up all employees reporting to C
Step 6: Associate B with its manager D
Step 7: Look up all employees reporting to D
Step 8: Commit transaction
=> Step 5에서 디비를 확인해보면 반영이 안되어있다.(cache에서 아직 db로 반영이 안되어있다.)
따라서 Step 4 다음에 flush를 해주어야 한다. - close()
: JDBC 연결을 해제하고 정리하여 세션을 종료 but 스프링이 알아서 해줌
예시 코드
@Repository
@Transactional
public class ProductDao {
@Autowired
private SessionFactory sessionFactory;
public Product getProductById (int id) {
Session session = sessionFactory.getCurrentSession();
Product product = (Product) session.get(Product.class, id);
return product;
}
public void addProduct (Product product) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(product);
session.flush();
}
}
- createQuery()
: 내가 쿼리를 만들 수 있다. -> HQL
HQL(Hibernate Query Language)- SQL similarity
- 테이블, 칼럼 이름이 아니라 클래스와 프로퍼티 이름을 사용한다.
- 테이블 이름이 아니라 클래스 이름이기 때문에 첫 문자를 대문자로 표현한다.
- Java 클래스와 그 속성의 대소 문자 구분 이름을 고려한다.
- SQL과 마찬가지로 HQL의 키워드는 대소 문자를 구분하지 않는다.
예시 코드
//List Query
Session session = sessionFactory.getCurrentSession();
Query query = session.createQuery("from Product");
List<Product> productList = query.list();
//Search Query
String hql = "from Product where category.name = 'Computer‘ ";
Query query = session.createQuery(hql);
List<Product> listProducts = query.list();
...
//Update Query
String hql = "update Product set price = :price where id = :id";
Query query = session.createQuery(hql);
query.setParameter("price", 500.0);
query.setParameter("id", 45);
int rowsAffected = query.executeUpdate();
...
//Delete Query
String hql = "delete from OldCategory where id = :catId";
Query query = session.createQuery(hql);
query.setParameter("catId", new Long(1));
int rowsAffected = query.executeUpdate();
...
※ hql로 참조하여 검색하는 경우 내부적으로 join이 일어나 검색을 해준다.
References
https://roadtoprogramming.com/hibernate-architecture/
https://www.slideshare.net/joseluismms/hibernate-an-introduction-43883660
'Spring > 이론' 카테고리의 다른 글
Hibernate with Spring (0) | 2020.04.19 |
---|---|
Entity Relationships (0) | 2020.04.17 |
Apache Tiles (0) | 2020.04.08 |
Logging (SLF4J and Logback) (0) | 2020.04.01 |
Spring Security (0) | 2020.03.30 |
댓글