JPA relation annotations and search method generation - TheOpenCloudEngine/uEngine-cloud GitHub Wiki

JPA relation annotations and search method generation

์ด๋ฒˆ์—๋Š” Clazz(๊ฐ•์˜) ํด๋ ˆ์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ Course(์ฝ”์Šค) ์™€์˜ ์—ฐ๊ฒฐ๊ณ ๋ฆฌ๋ฅผ JPA์˜ˆ์ œ๋ฅผ ํ†ตํ•ด์„œ ์•Œ์•„๋ณด๋„๋ก ํ•œ๋‹ค.

Clazz.java

@Entity
@Table(name="CLASS")
public class Clazz {
    @Id
    @GeneratedValue
    Long id;

    @ManyToOne @JoinColumn(name="COURSE_ID")
    Course course;

    @Column(name = "TITLE")
    String title;
}

๊ฐ•์˜๋Š” ์–ด๋–ค ๊ณผ์ •์˜ ๊ฐ•์˜์ธ์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ Course๋ฅผ ๋ณ€์ˆ˜๋กœ ์„ค์ •ํ•œ๋‹ค.
Clazz ์ž…์žฅ์—์„œ ๋ณด๋ฉด Course ๋Š” ManyToOne ์ด๋‹ค. Clazz๋Š” ์—ฌ๋Ÿฌ๊ฐœ ์ธ๋ฐ Course๋Š” 1๊ฐœ ๋ผ๋Š” ๋ง์ด๋‹ค.
๋ฐ˜๋Œ€๋กœ Course ์ž…์žฅ์—์„œ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ • ํ•  ์ˆ˜ ์žˆ๋‹ค.

Course.java

    @OneToMany(mappedBy = "course")
    List<Clazz> clazzList;

Course course๋ฅผ @ManyToOne๋กœ ์„ค์ •์„ ํ•˜์˜€์ง€๋งŒ, ์‹ค์ œ DBํ…Œ์ด๋ธ”๋กœ ์ƒ๊ฐ์„ ํ•˜์˜€์„๋•Œ, ํ•ด๋‹น ํ…Œ์ด๋ธ”๊ณผ ๋งค์นญ์„ ํ•  ์ˆ˜ ์žˆ๋Š” FK(Foreign Key)๋ฅผ ๋ช…์‹œ ํ•ด์ค˜์•ผ ํ•œ๋‹ค. ์ž๋™์œผ๋กœ ์ƒ์„ฑ์„ ํ•ด ์ค„์ˆ˜๋„ ์žˆ์ง€๋งŒ, Course rootCourse; ๊ฐ™์ด ์ถ”๊ฐ€๋กœ ๊ด€๊ณ„๊ฐ€ ์ƒ์„ฑ๋ ์ˆ˜ ์žˆ์œผ๋‹ˆ
@JoinColumn(name="COURSE_ID") ๊ณผ ๊ฐ™์ด ๋ช…์‹œ์ ์œผ๋กœ Column์ด๋ฆ„์„ ๋„ฃ์–ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค.
๋˜ํ•œ java์—์„œ๋Š” reserved keyword ๊ทœ์น™๋•Œ๋ฌธ์— Clazz ๋ผ๋Š” ์šฉ์–ด๋ฅผ ์ผ์ง€๋งŒ,
DB์—์„œ๋Š” class๊ฐ€ ์˜ˆ์•ฝ์–ด๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์—
@Table(name="CLASS") ๋กœ ๋ช…๋ช…ํ•˜์—ฌ class๋ผ๋Š” ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑ ํ•  ์ˆ˜ ์žˆ๋‹ค.

DB์šฉ์–ด(ํ…Œ์ด๋ธ”๋ช…, ์ปฌ๋Ÿผ๋ช…๋“ฑ)๋Š” ๋Œ€๋ฌธ์ž๋กœ ์“ฐ๋Š”๊ฒƒ์ด ์ฝ”๋”ฉ ๊ทœ์น™์ด๋‹ค.

Course.java ์—์„œ (mappedBy = "course") ์˜ ์˜๋ฏธ๋Š”
Clazz์˜ course ๋ผ๊ณ  ํ•˜๋Š” java field๊ฐ€ ๋‚˜๋ฅผ ID๋กœ ๋ฌผ๊ณ  ์žˆ๋‹ค ๋ผ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

JoinColumn์€ ์ปฌ๋Ÿผ์˜ ๋ช…์นญ์„ ์ฃผ๋Š” ๊ฑฐ๊ณ , mappedBy๋Š” java class์˜ ํ•„๋“œ๋ช… mappedBy๋Š” ์ƒ๋žต ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ framework์— ๋”ฐ๋ผ์„œ ์ธ์‹์„ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์ƒ๊ธฐ๋‹ˆ
๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธ์„ ํ•ด์ฃผ๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค. spring-boot๊ฐ€ Hibernate๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ์—, ์—ฌ๊ธฐ์„œ๋Š” Hibernate๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์„ค๋ช…ํ•œ๋‹ค.

@ManyToMany

ManyToOne ๊ณผ OneToMany๋ฅผ ์‚ดํŽด ๋ณด์•˜๋‹ค.
๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„์—์„œ๋Š” ์ค‘๊ฐ„์— Table์ด ํ•˜๋‚˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
ManyToMay์˜ˆ์ œ ์—ฌ๊ธฐ์„œ ์ž์„ธํ•œ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด ๋ณด์‹œ๊ธธ ๋ฐ”๋ž€๋‹ค.
์ž ์‹œ ์„ค๋ช…์„ ๋“œ๋ฆฌ๋ฉด Post์™€ Tag๋ฅผ ๋ฌถ๊ธฐ ์œ„ํ•˜์—ฌ Post_tagํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜์˜€๊ณ ,
๋‘ ํ…Œ์ด๋ธ”์˜ ๊ด€๊ณ„๋ฅผ JoinTable ๊ณผ mappedBy๋กœ ์„ค์ •์„ ํ•˜์˜€๋‹ค.

Post.java

    @ManyToMany(cascade = { 
        CascadeType.PERSIST, 
        CascadeType.MERGE
    })
    @JoinTable(name = "post_tag",
        joinColumns = @JoinColumn(name = "post_id"),
        inverseJoinColumns = @JoinColumn(name = "tag_id")
    )
    private List<Tag> tags = new ArrayList<>();

Tag.java

    @ManyToMany(mappedBy = "tags")
    private List<Post> posts = new ArrayList<>();

๊ฒ€์ƒ‰ ์กฐํšŒ์กฐ๊ฑด (Filltering)

๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ• ์ ์— ํŠน์ • ์กฐ๊ฑด์œผ๋กœ ๊ฒ€์ƒ‰์„ ํ•˜๋Š”๊ฒƒ์€ ๋‹น์—ฐํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ด๋‹ค.
์•„๋ž˜ ์„ค๋ช…ํ•  ๋ฐฉ๋ฒ•์€ JPA ๋ฐฉ์‹์€ ์•„๋‹ˆ๊ณ  Spring-data์—์„œ ์“ฐ๋Š” ๋ฐฉ์‹์ด๋‹ค.
Spring-data-jpa๋Š” ์ด ๊ฒ€์ƒ‰ ์กฐ๊ฑด์„ naming ๊ทœ์น™์— ์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ queryํ˜•์‹์œผ๋กœ ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ jpql๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด๊ณผ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•œ๋‹ค.
์šฐ์„  ์•„๋ž˜ ์˜ˆ์ œ์—์„œ๋Š” ๋„ค์ด๋ฐ ๊ทœ์น™์— ์˜ํ•œ ์ผ๋ฐ˜์ ์ธ ํŒจํ„ด์„ ์กฐํšŒํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•œ๋‹ค.

CourseRepository.java

public interface CourseRepository extends PagingAndSortingRepository<Course, Long> {
    // 1๋ฒˆ ๋ฐฉ๋ฒ•
    List<Course> findByTitle(@Param("title") String title);
    // 2๋ฒˆ ๋ฐฉ๋ฒ•
    List<Course> findByTitleContaining(@Param("title") String title);
}

์ด๋ฆ„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด 1๋ฒˆ ๋ฐฉ๋ฒ•์€ Title์„ ๊ฒ€์ƒ‰ํ•˜์ง€๋งŒ, Title์ด ์™„์ „ํžˆ ๊ฐ™์„๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๊ณ ,
2๋ฒˆ ๋ฐฉ๋ฒ•์€ Title์ค‘ ์ผ๋ถ€ ๋‹จ์–ด๊ฐ€ ํฌํ•จ ๋˜์–ด์žˆ๋Š” ๊ฒƒ์„ ์กฐํšŒํ• ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
2๋ฒˆ ๋ฐฉ๋ฒ•์ด 1๋ฒˆ ๋ฐฉ๋ฒ•์„ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋‹ˆ 2๋ฒˆ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.

์ด์ œ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์‚ดํŽด ๋ณด๊ฒ ๋‹ค.
์šฐ์„  sample ๋ฐ์ดํ„ฐ๋ฅผ insertํ•œ๋‹ค.

$ http localhost:8080/courses title="MSA์‹ค์Šต" duration=5 maxEnrollment=5 minEnrollment=10
$ http localhost:8080/courses title="MSA์ด๋ก " duration=5 maxEnrollment=5 minEnrollment=10
$ http localhost:8080/courses title="MSA๊ฐ•์˜" duration=5 maxEnrollment=5 minEnrollment=10

๊ทธ ํ›„ http localhost:8080/courses ๋ผ๋Š” root๋กœ ์กฐํšŒ๋ฅผ ํ•˜๋ฉด search๋ผ๋Š” ๊ฒƒ์ด ์ƒˆ๋กญ๊ฒŒ ์ƒ๊ฒผ๋‹ค.

$ http localhost:8080/courses

 "_links": {
        "profile": {
            "href": "http://localhost:8080/profile/courses"
        },
        "search": {
            "href": "http://localhost:8080/courses/search"
        },
        "self": {
            "href": "http://localhost:8080/courses{?page,size,sort}",
            "templated": true
        }
    },

HATEOAS ์—์„œ๋Š” ์‚ฌ์šฉ๋ฐฉ๋ฒ•์„ ์ž๊ธฐ๊ฐ€ ์•Œ๋ ค์ค€๋‹ค.

$ http http://localhost:8080/courses/search
{
    "_links": {
        "findByTitle": {
            "href": "http://localhost:8080/courses/search/findByTitle{?title}",
            "templated": true
        },
        "findByTitleContaining": {
            "href": "http://localhost:8080/courses/search/findByTitleContaining{?title}",
            "templated": true
        },
        "self": {
            "href": "http://localhost:8080/courses/search"
        }
    }
}

## ๊ฒ€์ƒ‰ ํ™•์ธ
$ http http://localhost:8080/courses/search/findByTitleContaining\?title="์‹ค์Šต"
โš ๏ธ **GitHub.com Fallback** โš ๏ธ