Open Chris940915 opened 3 years ago
일반적인 웹 어플리케이션 계층 구조
EntityManager는 Entity를 관리하는 역할을 수행하는 클래스. 엔티티 매니저 내부에 영속성 컨텍스트(Persistence Context)를 두어서 엔티티들을 관리. 영속성 컨텍스트는 엔티티를 영구히 저장하는 환경.
엔티티(Entity)를 영구 저장하는 환경. 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나만 생성해서 같은 트랜잭션 범위에 있는 엔티티 매니저는 동일 영속성 컨텍스트에 접근할 수 있다. 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트를 관리할 수 있다.
영속성 컨텍스트는 어플리케이션과 DB의 중간에 위치한다.
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManager.persist(member);
위 코드의 persist()를 통해 member 엔티티가 영속성 컨텍스트에 저장되어 영속 상태가 된다. 이 엔티티는 영속성 컨텍스트의 1차 캐시에 저장된다.
영속성 컨텍스트 내부의 캐시로 영속 상태의 엔티티는 모두 이 곳에 저장. 영속성 컨텍스트 내부에 Map이 하나 있는데 (1차 캐시), 키는 @Id로 매핑한 식별자고 값은 엔티티 인스턴스.
// 앞서 저장한 엔티티를 조회
Member findMember = EntityMangaer.find(Member.class, "member1");
엔티티를 조회하는 find() 가 실행되면,
트랜젝션 내부에서 persist()
가 일어날 때, 쓰기 지연 SQL 저장소에 INSERT 쿼리들을 생성해서 저장해놓는다.
쿼리를 실행해서 데이터베이스에 바로 넣지않고 기다린다.
commit()
하는 시점에 데이터베이스에 동시에 쿼리들을 보내며 이렇게 보내는 동작이 flush()
이다.
flush()
는 1차 캐시를 지우지않고, 데이터베이스의 싱크를 맞추는 역할.
실제로 쿼리를 보내고 나서, commit()
을 한다.
트랜젝션을 커밋하면, flush()
와 commit()
두 가지 일을 하게 되는 것이다.
EntitiyManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경시 트랜잭션을 시작.
transaction.begin(); // 트랜잭션 시작.
em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT 를 데이터베이스 보내지 않는다.
// 커밋하는 순간 데이터베이스에 SQL를 보낸다.
transaction.commit();
https://stackoverflow.com/questions/14581865/hibernate-commit-and-flush 너무 비슷해 보여서 차이점. flush는 영속성 컨텍스트에 있는 엔티티 정보를 데이터베이스 동기화 하는 작업(싱크를 맞춘다.)이며, 아직 트랜잭션이 commit이 안되어서 에러가 발생할 경우 롤백 할 수 있다. 반면, 트랜잭션이 commit이 된 후에는 데이터베이스에 동기화된 정보는 영구히 반영되는, 즉 롤백 할 수 없는 상태가 된다. flush() 가 발생하고 나서 commit() 이 일어난다.
엔티티 수정이 일어나면 update()
나 persist()
로 영속성 컨텍스트에 알릴 필요 없음.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // 트렌잭션 시작.
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 수정
memberA.setUsername("nj");
memberA.setAge(27);
transaction.commit();
엔티티 데이터만 수정하면 수정이 끝.
1차 캐시에 데이터를 저장할 때 스냅샷도 저장하며, commit()
또는 flush()
가 일어날 때, 엔티티와 스냅샷을 비교해서 변경이 있으면 UPDATE SQL을 알아서 DB에 저장.
트랜잭션은 처리의 원자성을 보장하기 위한 개념으로 데이터베이스의 트랜잭션과 통하는 의미다. 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산들을 의미한다. 스프링에서 트랜잭션을 설정하는 방식은 2가지 방식이 있음. 1) 코드 기반 방식, 2) 설정파일 또는 @ Transactional 어노테이션을 이용한 선언전 트랜잭션 방식.
public class BooksImpl implements Books {
@Transactional
public void addBook(String bookName) {
Book book = new Book(bookName);
bookRepository.save(book);
book.setFlag(true);
}
}
스프링은 해당 클래스의 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋. 만약 런타임 예외가 발생하면 롤백을 수행 JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행
외래 키가 있는 곳을 연관관계의 주인으로 정해라. 연관관계의 주인은 단순히 외래 키를 누가 관리하냐의 문제이지 비즈니스상 우위에 있다고 주인으로 정하면 안된다.. 예를 들어서 자동차와 바퀴가 있으면, 일대다 관계에서 항상 다쪽에 외래 키가 있으므로 외래 키가 있는 바퀴를 연관관계의 주인으로 정하면 된다. 물론 자동차를 연관관계의 주인으로 정하는 것이 불가능 한 것은 아니지만, 자동차를 연관관계의 주인으로 정하면 자동차가 관리하지 않는 바퀴 테이블의 외래 키 값이 업데이트 되므로 관리와 유지보수가 어렵고, 추가적으로 별도의 업데이트 쿼리가 발생하는 성능 문제도 있 다. 자세한 내용은 JPA 기본편을 참고하자.
@Repository @PersistenceContext : 엔티티 매니저(EntityManager) 주입
JPA의 가장 중요한 점은 엔티티와 테이블을 정확하게 매핑.
크게 4가지.
DB의 테이블과 매칭이 되는 개념.
CREATE TABLE 'Member' (
'id' BIGINT(20) unsigned NOT NULL AUTO_INCREMENT,
'name' varchar(225) NOT NULL,
'age' int(11) NOT NULL,
PRIMARY KEY ('id')
)
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenrationType.AUTO)
@Column(nullable = false)
private long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private int age;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
JPA에서 제공하는 데이터베이스 기본 키 생성 전략.
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
데이터베이스 접근을 하기 위한 로직과 비지니스 로직을 분리하기 위해 사용.
DAO의 경우, DB와 연결한 Connection까지 설정되어 있는 경우가 많음. Mybatis를 사용할 경우, DAO를 별도로 만드는 경우는 드물다.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestDao {
public void add(TestDto dto) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "root");
PreparedStatement preparedStatement = connection.prepareStatement("insert into users(id,name,password) value(?,?,?)");
preparedStatement.setString(1, dto.getName());
preparedStatement.setInt(2, dto.getValue());
preparedStatement.setString(3, dto.getData());
preparedStatement.executeUpdate();
preparedStatement.close();
connection.close();
}
}
DAO 사용 예제
계층 간의 의미는 Controller, View, Business Layer, Persistent Layer 등을 말하며 각 계층간 데이터 교환을 위한 객체. DTO는 로직을 가지지 않는 순수한 데이터 객체로 getter, setter 메소드만 가진 클래스를 의미.
public class PersonDTO {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age){
this.age = age;
}
}
리플렉션이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법. BeanFactory라는 Spring Container 개념. BeanFactory는 어플리케이션이 실행한 후 객체가 호출 될 당시 객체의 인스턴스를 생성하는데 그 때 필요한 기술이 reflection. 다른말로, 객체를 통해 클래스의 정보를 분석해내는 프로그램 기법.
Class c = Data.class;
Method[] m = c.getMethods();
Field [] f = c.getFields();
Constructor[] cs = c.getConstructors();
Class [] inter = c.getInterfaces();
Class superClass = c.getSuperclass();
그래서 layer간(특히 서버 -> View로 이동 등)에 데이터를 넘길때에는 DTO를 쓰면 편하다는 것이 이런이유 때문입니다. View에 있는 form에서 name 필드 값을 프로퍼티에 맞춰 넘겼을 때, 받아야 하는 곳에서는 일일히 처리하는 것이 아니라 name속성의 이름이랑 매칭되는 프로퍼티에 자동적으로 DTO가 인스턴스화 되어 PersonDTO를 자료형으로 값을 받을 수 있습니다. 그래서 key-value 로 존재하는 데이터는 자동화 처리된 DTO로 변환되어 쉽게 데이터가 셋팅된 오브젝트를 받을 수 있습니다.
media 파일 다루기.
프로젝트 Dev DB에 있는 데이터를 긁어오지만 /media/sampe/ 에 해당 데이터가 없어서 rendering 오류가 난다.
upload_to 파라미터로 경로를 넣어주면 된다.