쌓고 쌓다

[스프링 부트] MySQL 연결 및 JPA - 2 본문

프로그래밍/spring

[스프링 부트] MySQL 연결 및 JPA - 2

승민아 2023. 6. 24. 20:29

build.gradle

dependencies {
	...
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa' //추가 작성
    implementation 'mysql:mysql-connector-java:8.0.29' // 추가 작성
    ...
}

java뒤에 MySQL 버전을 적어주자 -> java:<MySQL버전>

안그러면 제대로 가져오지 못해 com.mysql.cj.jdbc.Driver에 빨간줄이 뜬다.

 

application.properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/board
spring.datasource.username=root
spring.datasource.password=1234


spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

board는 MySQL를 통해 내가 생성한 DB 이름임.

 

DB이름: board

 

spring.jpa.show-sql=true? 아래와 같이 실행한 쿼리를 보여줌.

spring.jpa.show-sql=true

 

MySQL 테이블 생성

CREATE TABLE poster (
                        id bigint not null auto_increment,
                        title varchar(100),
                        writer varchar(100),
                        content varchar(300),
                        PRIMARY KEY (id)
);

자바 long은 bigint와 매칭? 맞는 자료형이라고 언뜻 들은듯하다...!

 

Poster

@Entity
public class Poster {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String writer;
    private String content;
    
    ...
    Getter, Setter
    ...
}

ORM은 객체-관계(Relational)-매핑의 약자이다.

객체와 Relational DB를 매핑한다는 뜻!

매핑은 어노테이션으로한다.

 

@Entity: JPA가 관리하는 엔티티가 된다.

@Id: PK라고 명시.

@GeneratedValue: poster의 PK는 DB에서 자동 생성하고 있었다.  이런 방식을 IDENTITY 전략이라고 함.

(시퀀스나 직접 넣는 전략도 따로 있다.)

 

1:N처럼 외래키를 가지는 객체는 어떻게 작성할지 궁금한데.. 나중에 공부해보자.!

 

@Column?

만약, 클래스 변수는 writer인데 DB의 컬럼명이 username이라고 되어있다면 아래와 같이 어노테이션을 사용한다.

@Entity
public class Poster {
    ...
    @Column(name="username")
    private String writer;
}

 

JpaPosterRepository

@Repository
public class JpaPosterRepository implements PosterRepository{

    private final EntityManager em;

    @Autowired
    public JpaPosterRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Poster save(Poster poster) {
        em.persist(poster);
        return poster;
    }

    @Override
    public Optional<Poster> findById(Long id) {
        return Optional.empty();
    }

    @Override
    public Optional<Poster> findByTitle(String title) {
        return Optional.empty();
    }

    @Override
    public List<Poster> findAll() {
        return em.createQuery("SELECT p FROM Poster as p", Poster.class)
                .getResultList();
    }
}

JPA는 EntityManger라는 것과 함께 모든 동작이 이뤄진다.

em이 DB 커넥션 정보 등 가지고 처리함.!

 

em.persist: INSERT 쿼리를 생성해 알아서 집어넣어준다. 반환 값이 없다.

 

findById

@Override
public Optional<Poster> findById(Long id) {
    Poster poster = em.find(Poster.class, id);
    return Optional.ofNullable(poster);
}

return 값이 Optional이고 Null 값이 나올 수 있기에 Optional.ofNullable()로 감싸서 반환.

 

 

 

findAll

@Override
public List<Poster> findAll() {
    return em.createQuery("SELECT p FROM Poster as p", Poster.class)
            .getResultList();
}

EntityManager의 createQuery 메소드는 JPQL 문법을 사용.

원래 테이블을 대상으로 쿼리를 날리지만 엔티티(객체)를 대상으로 쿼리를 날림. 이게 SQL로 번역됨.

보통 SELECT p.id로 가져오지만 SELECT p로 객체 자체를 SELECT 함.

 

getResultList: 쿼리로부터 반환된 결과가 하나 이상인 경우 리스트를 반환. 비어있는경우 비어있는 리스트임.

 

 

findByTitle (파라미터 바인딩)

    @Override
    public Optional<Poster> findByTitle(String title) {
        List<Poster> posters = em.createQuery("SELECT p FROM Poster p WHERE p.title = :title", Poster.class)
                .setParameter("title", title)
                .getResultList();

        return posters.stream().findAny();
    }

동작 원리

(1)을 보자.

매개변수 title이 setParameter 함수에 의해

"title"이라는 키로 매개변수 title 값을 갖게 된다.

 

(2)를 보자

키 title이 :title로 들어간다.

 

이름을 바꿔 한번 살펴보면.

키(Key)로 title2를 하고.

값으로 매개변수 title의 값을 set한다.

그 키와 값을 쿼리에 사용함.

 

반환값이 Optional<Poster>이기에 하나만 반환하게 findAny 메소드 사용함.

 

 

여긴 @Autowired안해도 왜 되는가? 생성자 하나는 자동? 아니면 em은 스프링이 알아서?

@Autowired
public JpaPosterRepository(EntityManager em) {
    this.em = em;
}

단일 생성자는 어노테이션을 붙이지 않아도 된다.

 

 

에러 - @Transactional

No EntityManager with actual transaction available for current thread 에러

 

서비스 클래스에 @Transactional 어노테이션을 추가함으로써 해결한다.

해결 방법

JPA 사용시 항상 트랜잭션이 있어야한다.

데이터를 저장하거나 변경할때.

서비스에 @Transactional을 붙여주자.

write 함수에 @Transactional을 붙여도 무방하다.

write 함수에 @Transactional 작성

 

 

최종 결과

 

 

+ 생성자 주입 vs 필드 주입

https://jackjeong.tistory.com/entry/Spring-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85-vs-%ED%95%84%EB%93%9C-%EC%A3%BC%EC%9E%85-Autowired

 

다음 목표: 게시글 상세 조회, 작성일 추가

Comments