[Spring] Spring Data 를 이용한 데이터베이스 접근

Martin the dog·2023년 12월 17일

스프링 복습하기

목록 보기
10/15

Spring에서 데이터 다루기

테이블과 객체 맵핑하기

@Entity & @Table

Spring은 데이터를 JAVA POJO 객체로 관리한다. 테이블과 객체를 맵핑시켜주기 위해서는 다양한 설정이 필요하다.

@Entity(name="value")

해당 어노테이션이 붙은 클래스가 JPA에 의해 관리되는 엔터티 클래스임을 나타낸다. name 설정시 JPA에서 해당 클래스가 사용될 때 표기될 이름을 설정해준다. 만약 name이 따로 설정되어 있지 않다면 클래스 명과 동일하다.

@Table(name="value")

해당 어노테이션이 붙은 클래스가 "value" 테이블과 맵핑됨을 알린다. 만약 name을 설정하지 않으면 클래스 명과 같아진다.

컬럼과 필드멤버 맵핑하기

@Id

해당 필드멤버가 Primary key에 속함을 알림, 여러개 붙을 수 있음

Column(name = "columnNm")

해당 필드멤버가 Table에서 컬럼명이 columnNm인 컬럼과 맵핑됨을 알림

GeneratedValue

해당 필드 멤버의 값이 자동으로 생성됨을 알림

이 어노테이션들을 이용하면 다음과 같이 사용할 수 있다.

@Entity
@Table(name="People")
public class person{
	@Id
    @Column(name="juminNo")
    private Long juminNo;
    
    @Column(name = "gender")
    private String gender;
    
    @Column(name="address")
    private String addr;
    
    ~Getter & Setter & Constructoro 정의~
}

데이터 조작하기

이제 CrudRepository를 이용하여 DB에 저장된 데이터들을 수정할 것이다.

CrudRepository Interface

CrudRepository에는 기본적으로 Save(Object obj),SaveAll(Object obj), findById(Id id), findAll(), count(), deleteById(Id id) 등이 있다.
해당 기능들을 사용하고 싶으면 다음과 같이 implements 해줘야 한다.

@Repository
public interface PersonRepository implements CrudRepository<Person,Long>{
}

여기서는 CrudRepository<엔터티 타입, ID 타입> 을 넣어 타입을 지정해줬다.

위에서 언급한 내용만으로도 충분히CRUD가 되지만 우리는 특정 조건을 지닌 아이템만 업데이트, 삭제등을 하고 싶어질때가 있다.

QueryMethod

이를 위해 CrudRepository에서는 특정 규칙을 지닌 메써드 명을 CrudRepository를 구현하는 클래스에 정의하면 해당되는 Object를 가져온다

Query

find...By, read...By, get...By, query...By,stream...By,search...By 형식의 메써드를 지정하면 알아서 Entity들을 가져온다.

만약 person의 address가 서울인 엔터티들을 가져오고 싶다면.

Iterable<Person> findPersonByAddr(String addr)

를 해주면 된다.

Count

count...By를 해주면 된다.

Exists

엔터티의 존재여부를 반환하며 exists...By로 정의하면된다.

Delete

delete...By, remove...By를 사용하면된다.

결과가 2개이상인 경우 Iterable< Entity type > 를 반환값으로 사용하면 된다.

@NamedQuery

위의 QueryMethod만으로는 부족한 필요한 기능을 구현하기에는 어렵기도 하거니와 함수명이 장황해진다는 단점이 있다. 그냥 쿼리를 바로 사용하는 방법은 없을까?

있다! @NamedQuery를 이용하면 된다.
@NamedQuery는
1. 세밀하게 최적화된 쿼리를 그대로 사용하고자 하는 경우
2. 두 개 이상의 테이블을 조인해서 데이터를 가져와야 하는 경우
사용하는 것이 좋다.

사용법은 다음과 같다

@Entity(name="Person")
@Table(name="People")
@NamedQuery(name="person.findAllPersonWhoseGenderIs", query="select p from Person p where p.gender = ?1"
public class person{
	@Id
    @Column(name="juminNo")
    private Long juminNo;
    
    @Column(name = "gender")
    private String gender;
    
    @Column(name="address")
    private String addr;
    
    ~Getter & Setter & Constructoro 정의~
}

여기서 앞서 언급한 @Entity의 사용법을 알 수 있다. 객체는 person이지만 @Entity에 정의된 name이 Person이기 때문에 Person이 쿼리문에 들어간 것을 알 수 있다. @NamedQuery에서 name은 "엔터티이름.메써드명"으로 정의하며 query는 jpql쿼리 기준이며 ?1은 은 함수 파라미터 첫번째 와 맵핑시킨다는 의미이다.
이를 사용하려면 CrudRepository를 구현하는 클래스에 name에 해당하는 메써드를 정의해야한다.

@Repository
public interface personRepository extends CrudRepository<person,Long>{
	Iterable<person> findAllPersonWhoseGenderIs(String gender);
}

이렇게 하면 Query를 쉽게 사용할 수 있다.

@Query

하지만 @NamedQuery를 이용하면 Query가 DTO에 정의된다는 문제가 있다. 이는 DTO에 불필요한 정보를 추가하는 것으로 그렇게 좋지는 않다. 그렇기 때문에 @Query를 사용하는게 좋다.

@Repository
public interface personRepository extends CrudRepository<person,Long>{
	@Query("select p from Person p where p.gender=?1") //jpql을 사용하는 모습 여기서는 ?1,?2는 파라미터에 순사에 따라 맵핑된다.
	Iterable<person> findAllPersonWhoseGenderIs(String gender);
    
    @Query("select p from Person p where p.gender= :gender and p.juminNo < :no")//똑같은 jpql이지만 순서가 아니라 파라미터명에 따라 맵핑이 된다.
    Iterable<person> findPeopleWhoseGenderAndJuminNo(@Param("no") juminNo, @Param("gender") sex);
    
    @Query("Select * from Person where gender = :gender and juminNo <100",nativeQuery=true)//native query를 true로 설정하면 사용하는 DBMS의 언어를 사용할 수 있다.
    Iterable<person> findPeopleWhoseGenderAndJuminNo(@Param("gender") sex);
    
    @Modifying //해당 쿼리가 수정 작업을 한다는 것을 스프링에게 알려주어 쿼리가 작동할 수 있게 한다. @Modifying이 안 붙은 DML쿼리는 작동하지 않는다.
    @Transactional//데이터 처리단위인 트랜색션은 선언하여 해당 작업 단위로 롤백 될 수 있게 설정해준다.
    @Query("update Person p set p.gender = :gender where addr = :location")
    int updateGenderByLocation(@Param("gender) sex , @Param("location") loc);
}

QueryDsl로 간단하게 Query짜기

그런데 우리는 CrudRepository에서 명명규칙에 따라 정의하는 것도 어렵고 JPQL쿼리를 사용하는것도 어려울 때가 있다. 이럴때는 QueryDsl를 이용하면 된다.

먼저 dependency는 다른곳에서도 많이 정의되어 있으니 생략하고 바로 어떻게 사용하는지 넘어가겠다.
QueryDSL dependency가 잘 설정되었다면 @Entity로 표시한 Class에 대응되는 QueryDsl에서 사용할 수 있는 QClass가 자동 생성된다.

public class main{
	@Autowired
    private EntityManager entityManager;
	public static void main(String args[])
    {
    	//QueryDsl로 인해 생성된 Person의 Qclass 새로 생성하는 것이 아니라 클래스의 스태틱 변수를 사용
    	QPerson person = QPerson.person;
        //query를 작성할 JPAQuery
        JPAQuery query = new JPAQuery(entityManager);
        //JPAQuery를 작성하여 결과를 가져온다.
        query.from(person).where(person.gender.eq("MAN")).fetch();
    }
}

Association 관리하기

DB는 테이블이 서로 독립적으로만 존재하지만 않는다. 그렇기 때문에 JPA에서도 이를 관리하기 위한 여러 어노테이션이 존재한다.

@ManyToMany

다대다 테이블을 연결할 때 쓰이는 어노테이션이다.
아래 예시를 보자

테이블 Course와 Author은 many to many 관계를 가지고 있다고 하자
Course와 Author은 각각 id 컬럼이 존재하며 두 아이디를 저장하여 관계를 맺어주는 Courses_Authors 테이블이 있다고 하자
Courses_Authors에는 author_id와 course_id가 있다
여기서 Author가 주인이라고 하자

@Entity
@Table
public class Courses_Authors {
	@Id
    @Column(name="AUTHORID")
	private int author_id;
    @Id
    @Column(name="COURSEID")
    private int course_id;
    
}
//Course
@Entity(name="course")
@Table(name="COURSE")
public class Course{
	@Id
    @Column(name="id")
	private int id;
    //기타 필드들//
    
    @ManyToMany 
    //Author와 Course들이 many to many 관계임을 나타낸다.
    @JoinTable(
    //jointable은 소유자 엔터티에서 설정되며 어떤 조인 테이블을 통해 연결할 지 설정해 준다.
    name="Courses_Authors",
    //name은 조인 테이블을 의미한다.
    joinColumns = {@JoinColumn(name="author_id",referencedColumnName="id",nullable=false,updatable=false)},
    
    //joinColumns는 조인테이블과 소유자 엔터티 테티블을 맵핑할 때 어떤 것들 끼리 맵핑 시킬 것인지를 지정해주는 것이다.
    //여기서는 Courses_Authors의 author_id와 소유자 엔터티인 Course id와 맵핑된다.
    
    inverseJoinColumns = @joinColumn(name="course_id",referencedColumnName="id",nullable=false,updatble=false)}}
    //inverseColumns는 피소유자 엔터티와 조인 테이블을 의미하며 위 joincolumn과 같다.
    )
    private Set<Course>courses=new HashSet<>();
    //나머지 코드들
}


@Entity(name="Author")
@Table(name="AUTHOR")
public class Author{
	@Id
    @Column(name="id")
    privaet int id;
    //기타 필드값//
    @ManyToMany(mappedBy="course")
    //피소유테이블은 mappedBy를 해주면 알아서 맵핑이 된다.
    private Set<Course> courses = new HashSet<>()
}
    

대부분의 경우 ManyToMany로 해결된다.

JoinColumn 제대로 사용하기

joinColumn은 name 에 ="맵핑할table명_key"를 입력해주면 알아서 연결 시켜준다.

각 관계 어노테이션 별

@OneToOne

@JoinColumn을 사용하는 Entity가 연관관계의 주인, 즉 FK를 가진다.

@ManyToOne

@JoinColumn을 사용하는 Entity가 연관관계의 주인, 즉 FK를 가진다.

@OneToMany

@JoinColumn을 사용하는 Entity의 반대 Entity가 연관관계의 주인, 즉 FK를 가진다.

@ManyToMany

@JoinColumn을 사용하는 Join Table(Entity)가 연관관계의 주인, 즉 FK를 가진다.

읽어볼 글 -joinColumn 상세정리

profile
Happy Developer

0개의 댓글