반응형

JPA (Java Persistence API)

 

정의: JPA는 자바 애플리케이션에서 객체 관계 매핑(ORM)을 위한 표준 명세입니다.

주요 특징:

ORM 제공: 객체를 관계형 데이터베이스 테이블과 매핑합니다.

표준화: JPA는 여러 구현체 (예: Hibernate, EclipseLink) 간의 표준을 제공합니다.

애너테이션: 엔티티 클래스에 애너테이션을 사용하여 매핑을 정의합니다.

EntityManager: JPA의 핵심 인터페이스로, 데이터베이스 작업을 관리합니다.

장점:

코드와 데이터베이스 간의 간단한 매핑을 제공합니다.

여러 데이터베이스 벤더에 대한 추상화를 제공합니다.

단점:

복잡한 쿼리 작성이 어렵고, 특히 동적 쿼리에서는 제한적입니다.

 

JPQL (Java Persistence Query Language)

 

정의: JPQL은 JPA에서 제공하는 객체 지향 쿼리 언어입니다.

주요 특징:

객체 지향 쿼리: SQL과 비슷하지만, 테이블이 아닌 엔티티 객체를 대상으로 합니다.

엔티티 기반: 데이터베이스 테이블이 아닌 엔티티 클래스와 속성에 대해 쿼리합니다.

표준 쿼리 언어: JPA 표준의 일부로, 모든 JPA 구현체에서 지원됩니다.

장점:

JPA와 통합되어 있어 일관된 API를 제공합니다.

SQL과 유사하지만 객체 지향적인 쿼리를 작성할 수 있습니다.

단점:

복잡한 동적 쿼리 작성에 한계가 있습니다.

정적 쿼리에 적합하며, 동적 쿼리의 경우 문자열 조작이 필요합니다.

@Query("SELECT " +
            " blog  " +
            "FROM TbBlog blog " +
            "WHERE blog.isUse = UseYn.Y " +
            "AND blog.point > 0 " +
            "AND blog.writeType != 'INFO' " +
            "ORDER BY " +
            " blog.issuedType ASC" )
List<TbBlog> findBlogList();

QueryDSL

 

정의: QueryDSL은 Java에서 타입 안전한 SQL-like 쿼리를 작성하기 위한 프레임워크입니다.

주요 특징:

타입 안전성: 컴파일 시점에 쿼리의 유효성을 검사합니다.

동적 쿼리: 복잡한 동적 쿼리를 쉽게 작성할 수 있습니다.

플루언트 API: 메서드 체이닝을 통해 직관적이고 가독성 높은 쿼리를 작성할 수 있습니다.

지원되는 언어: JPA, SQL, MongoDB, Lucene 등 다양한 백엔드를 지원합니다.

장점:

컴파일 시점에 쿼리의 유효성을 검사하여 오류를 줄입니다.

복잡한 동적 쿼리를 간단하게 작성할 수 있습니다.

IntelliJ와 같은 IDE에서 자동 완성을 지원하여 생산성을 높입니다.

단점:

초기 설정이 복잡할 수 있습니다.

JPA와의 통합을 위해 추가적인 설정과 코드가 필요합니다.


예시

public List<TbBlog> findBlogList() {
        JPAQueryFactory queryFactory = new JPAQueryFactory(em);
        return queryFactory.selectFrom(tbBlog)
                .where(
                        tbBlog.isUse.eq(UseYn.Y),
                        tbBlog.point.gt(0),
                        tbBlog.writeType.ne("INFO")
                )
                .orderBy(
                        tbBlog.regDt.asc() 
                )
                .fetch();
    }



요약

 

JPA는 Java 애플리케이션에서 ORM을 위한 표준 명세로, 데이터베이스와 객체를 매핑합니다.

JPQL은 JPA에서 제공하는 객체 지향 쿼리 언어로, SQL과 유사하지만 엔티티 객체를 대상으로 합니다.

QueryDSL은 타입 안전한 SQL-like 쿼리를 작성하기 위한 프레임워크로, 동적 쿼리를 작성할 때 특히 유용합니다.

반응형
반응형

 

mysql 간단한 blog 테이블 만들기

-- mydb.tb_blog definition

CREATE TABLE mydb.tb_blog (
	blog_id BIGINT auto_increment NOT NULL,
	blog_title varchar(100) NOT NULL,
	blog_content varchar(100) NOT NULL,
	reg_dt DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
	upd_dt DATETIME DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP NOT NULL,
	PRIMARY KEY (`blog_id`)
)
ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_0900_ai_ci;

 

 

spring jpa 라이브러리 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.mysql:mysql-connector-j'
    ...
}

 

entity 생성

@Getter
@Setter
@Entity
@Table(name="tb_blog")
public class TbBlog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long blogId;

    @Column(nullable = false, length = 100)
    private String blogTitle;

    @Column(name = "blog_content", nullable = false, length = 100)
    private String blogContent;

    @CreationTimestamp
    @Column(updatable = false)
    private LocalDateTime regDt;

    @UpdateTimestamp
    @Column
    private LocalDateTime updDt;
}

 

@Respository 생성

@Repository
public interface TbBlogRepository extends JpaRepository<TbBlog, Long> {


}


dto 클래스 생성

@Setter
@Getter
public class BlogReq {

    private Long blogId;
    private String blogTitle;
    private String blogContent;
}

create 메소드 

public TbBlog createBlog(BlogReq req) {
    TbBlog tbBlog = new TbBlog();
    tbBlog.setBlogTitle(req.getBlogTitle());
    tbBlog.setBlogContent(req.getBlogContent());

    return tbBlogRepository.save(tbBlog); // 생성된 로또 번호 Set 반환
}

 

update 메소드

public TbBlog updateBlog(BlogReq req) {
    TbBlog tbBlog = new TbBlog();
    tbBlog.setBlogId(req.getBlogId());
    tbBlog.setBlogTitle(req.getBlogTitle());
    tbBlog.setBlogContent(req.getBlogContent());

    return tbBlogRepository.save(tbBlog); // 생성된 로또 번호 Set 반환
}

 

test 용 전체 데이터 조회 메소드

public List<TbBlog> getListAll() {
    return tbBlogRepository.findAll();
}

 

특정 id 를 조회하는 메소드 

public TbBlog getBlogId(Long blogId) {
    Optional<TbBlog> blog = tbBlogRepository.findById(blogId);
    return blog.get(); // 생성된 로또 번호 Set 반환
}

/**
 * Optional.isPresent() 메서드를 사용하여 값이 존재하는지 확인한 후에 값을 처리하는 방법입니다.
 */
public TbBlog getBlogByIdV1(Long blogId) {
    Optional<TbBlog> blog = tbBlogRepository.findById(blogId);
    if (blog.isPresent()) {
        return blog.get();
    } else {
        // 값이 없을 경우 예외를 던지거나 다른 처리를 합니다.
        throw new BlogNotFoundException("Blog not found with id: " + blogId);
    }
}

/**
 * orElse() 메서드 사용
 * Optional에 값이 없을 경우 기본 값을 반환하는 방법입니다.
 */
public TbBlog getBlogByIdV2(Long blogId) {
    Optional<TbBlog> blog = tbBlogRepository.findById(blogId);
    return blog.orElse(null); // 또는 적절한 기본값을 설정할 수 있습니다.
}

/**
 * orElseThrow() 메서드 사용
 * Optional에 값이 없을 경우 예외를 던지는 방법입니다.
 */
public TbBlog getBlogByIdV3(Long blogId) {
    return tbBlogRepository.findById(blogId)
            .orElseThrow(() -> new BlogNotFoundException("Blog not found with id: " + blogId));
}

Exception 클래스 추가 

public class BlogNotFoundException extends RuntimeException {
    public BlogNotFoundException(String message) {
        super(message);
    }
}

 


Optional 이라는 메소드

Optional은 Java 8에서 도입된 유틸리티 클래스입니다. 이 클래스는 주어진 값이 존재할 수도 있고, 존재하지 않을 수도 있는 상황을 처리하기 위한 도구입니다. Optional은 널(null) 참조로 인한 NullPointerException을 피하고, 더 명확하고 안전한 코드를 작성하는 데 도움이 됩니다.

controller 참고하세요

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/blog")
public class BlogController {

    private final BlogService blogService;


    @GetMapping(value = "/list-all")
    public ResponseEntity<Object> getBlogList() {
        return ResponseEntity.ok(blogService.getListAll());
    }

    @GetMapping(value = "/detail/{blogId}")
    public ResponseEntity<Object> getBlogDetail(
            @PathVariable Long blogId
    ) {
        return ResponseEntity.ok(blogService.getListAll());
    }


    @PostMapping(value = "/create")
    public ResponseEntity<Object> postBlog(BlogReq req) {
        return ResponseEntity.ok(blogService.createBlog(req));
    }

    @PutMapping(value = "/update/{blogId}")
    public ResponseEntity<Object> putBlog(
                BlogReq req) {
        return ResponseEntity.ok(blogService.updateBlog(req));
    }
}

 

반응형
반응형

JPA 란

JPA(Java Persistence API)는 자바 애플리케이션에서 관계형 데이터베이스와 객체 간의 매핑을 관리하는 표준 API입니다.

 

객체-관계 매핑(Object-Relational Mapping, ORM): 객체 지향 프로그래밍의 객체를 관계형 데이터베이스의 테이블에 매핑.

표준 인터페이스: JPA는 인터페이스를 제공하고, Hibernate, EclipseLink 등의 구현체를 통해 사용할 수 있음.

데이터베이스 독립성: 코드 변경 없이 다양한 데이터베이스에서 동작.

 

하이버네이트란

Hibernate는 자바 환경에서 객체 관계 매핑(Object-Relational Mapping, ORM) 프레임워크입니다. 이를 통해 자바 객체와 관계형 데이터베이스 간의 변환을 자동화할 수 있습니다. Hibernate는 JPA(Java Persistence API)의 주요 구현체 중 하나로, 객체 지향적인 방식으로 데이터베이스 작업을 수행할 수 있도록 도와줍니다.

 

1. 객체-관계 매핑 (ORM):

Hibernate는 자바 객체를 데이터베이스 테이블에 매핑하여, 객체 지향 프로그래밍과 관계형 데이터베이스의 패러다임을 연결합니다.

2. 자동화된 CRUD 작업:

Hibernate는 기본적인 CRUD(Create, Read, Update, Delete) 작업을 자동으로 처리합니다. 이를 통해 개발자는 SQL 쿼리를 직접 작성하지 않아도 됩니다.

3. 데이터베이스 독립성:

Hibernate는 다양한 데이터베이스를 지원하며, 데이터베이스에 종속되지 않는 코드를 작성할 수 있게 도와줍니다.

4. HQL (Hibernate Query Language):

SQL과 유사한 쿼리 언어를 제공하여 객체를 대상으로 쿼리를 작성할 수 있습니다.

5. 캐싱:

Hibernate는 1차 캐시(세션 수준 캐시)와 2차 캐시(전역 캐시)를 지원하여 데이터베이스 액세스를 최적화합니다.

6. 트랜잭션 관리:

Hibernate는 트랜잭션 관리를 지원하여 데이터 일관성과 무결성을 유지할 수 있도록 합니다.

 

 

 

jpa 사용방법

라이브러리 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}

 

Entity 정의

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    // Getters and Setters
}

 

Repository 정의

import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}

 

Service 클래스에서 사용

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    public List<Product> getAllProducts() {
        return productRepository.findAll();
    }

    public Product saveProduct(Product product) {
        return productRepository.save(product);
    }
}

 

JPA와 JDBC의 관계

JPA를 사용하면 일반적으로 직접적인 JDBC 라이브러리를 사용할 필요가 없습니다. JPA는 JDBC를 추상화하여 객체 지향적인 방식으로 데이터베이스와 상호작용할 수 있게 해주기 때문입니다. 그러나 JPA는 내부적으로 JDBC를 사용하여 데이터베이스와 통신합니다.

 

JPA와 JDBC의 관계

 

JPA: Java Persistence API는 자바 객체를 관계형 데이터베이스의 테이블에 매핑하는 프레임워크입니다. 개발자는 객체 지향적인 방식으로 데이터베이스 작업을 수행할 수 있습니다.

JDBC: Java Database Connectivity는 자바에서 데이터베이스에 접속하고 SQL 쿼리를 실행하기 위한 API입니다. JDBC는 데이터베이스와의 저수준 통신을 처리합니다.

 

JPA는 내부적으로 JDBC를 사용하지만, 개발자가 직접 JDBC 코드를 작성할 필요는 없습니다. 대신 JPA의 리포지토리, 엔터티 매니저 등을 사용하여 데이터베이스 작업을 처리합니다.

 

JDBC 예제

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class Product {
    private Long id;
    private String name;
    private double price;

    // Getters and Setters
}

public class ProductDAO {
    private String jdbcURL = "jdbc:h2:mem:testdb";
    private String jdbcUsername = "sa";
    private String jdbcPassword = "password";

    private static final String INSERT_PRODUCTS_SQL = "INSERT INTO products (name, price) VALUES (?, ?)";
    private static final String SELECT_ALL_PRODUCTS = "SELECT * FROM products";

    public ProductDAO() {}

    protected Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(jdbcURL, jdbcUsername, jdbcPassword);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }

    public void insertProduct(Product product) {
        try (Connection connection = getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement(INSERT_PRODUCTS_SQL)) {
            preparedStatement.setString(1, product.getName());
            preparedStatement.setDouble(2, product.getPrice());
            preparedStatement.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public List<Product> selectAllProducts() {
        List<Product> products = new ArrayList<>();
        try (Connection connection = getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement(SELECT_ALL_PRODUCTS)) {
            ResultSet rs = preparedStatement.executeQuery();
            while (rs.next()) {
                Long id = rs.getLong("id");
                String name = rs.getString("name");
                double price = rs.getDouble("price");
                products.add(new Product(id, name, price));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return products;
    }
}

public class ProductService {
    private ProductDAO productDAO = new ProductDAO();

    public List<Product> getAllProducts() {
        return productDAO.selectAllProducts();
    }

    public void saveProduct(Product product) {
        productDAO.insertProduct(product);
    }
}

public class ProductController {
    private ProductService productService = new ProductService();

    public List<Product> getAllProducts() {
        return productService.getAllProducts();
    }

    public void saveProduct(Product product) {
        productService.saveProduct(product);
    }
}

 

 

요약

 

JPA 사용: JPA는 내부적으로 JDBC를 사용하지만, 개발자가 직접 JDBC 코드를 작성할 필요가 없습니다. JPA의 엔터티, 리포지토리, 서비스 계층을 통해 데이터베이스 작업을 쉽게 수행할 수 있습니다.

JDBC 사용: JDBC는 저수준의 데이터베이스 접근 방법으로, 개발자가 직접 SQL 쿼리와 데이터베이스 연결을 관리해야 합니다.

 

따라서 JPA를 사용하면 직접적인 JDBC 라이브러리 사용을 피할 수 있으며, 코드의 유지보수성과 생산성을 높일 수 있습니다.

반응형
반응형

 

Pageable

Pagination 기능을 편리하게 사용할 수 있도록 JPA는 Pageable이라는 객체를 제공한다.

 

 

controller 에서 부터 pageable 관련 파라미터를 받을 수 있다. 

정렬 기능

@GetMapping("/")
public ResponseEntity getData(
              @PageableDefault(size = 10, sort = "reqDt", direction= Sort.Direction.DESC)
              Pageable pageable) 
// reqDt 에 대한 내림차순 정렬


@SortDefault 

위의 방식은 정렬을 한가지만 할 수 있어, 여러 정렬 조건을 추가하려면 @SortDefault 를 추가하면 된다. 

@GetMapping("/")
public ResponseEntity getData(
      		@SortDefault.SortDefaults({
              	@SortDefault(sort = "id", direction = Sort.Direction.ASC),
                @SortDefault(sort = "reqDt", direction = Sort.Direction.DESC)
              })
              @PageableDefault(size = 10)
              Pageable pageable) 
// id 오름차순 및 reqDt 에 대한 내림차순 정렬

 

controller 요청방법 

?page=0&size=10&sort=regDt,desc


PageRequest

다른 방법으로는 controller 에서 외에 paging 관련 데이터를 PageRequset 객체에 넣어 
Pagable 을 생성 후 find 쿼리 메소드에 객체를 전달하면 된다.

 

Pagable pageObj = PageRequest.of(0, 5, Sort.by("reqDt").descending().and(Sort.by("id")));

 

controller 에서 부터 PageRequest 를 세팅 하고 싶다면 Pageable을 대체하는 PageRequest 관련 객체를 따로 생성하면 된다.

public final class PageRequest {

    private int page;
    private int size;
    private Sort.Direction direction;

    public void setPage(int page) {
        this.page = page <= 0 ? 1 : page;
    }

    public void setSize(int size) {
        int DEFAULT_SIZE = 10;
        int MAX_SIZE = 50;
        this.size = size > MAX_SIZE ? DEFAULT_SIZE : size;
    }

    public void setDirection(Sort.Direction direction) {
        this.direction = direction;
    }
    // getter

    public org.springframework.data.domain.PageRequest of() {
        return org.springframework.data.domain.PageRequest.of(page -1, size, direction, "createdAt");
    }
 }
  • setPage(int page) 메서드를 통해서 0보다 작은 페이지를 요청했을 경우 1 페이지로 설정
  • setSize(int size) 메서드를 통해서 요청 사이즈 50 보다 크면 기본 사이즈인 10으로 바인딩
  • of() 메서드를 통해서 PageRequest 객체를 응답해줍니다. 페이지는 0부터 시작하니 page -1 합니다. 본 예제에서는 sort는 createdAt 기준으로 진행.

 


Repository

@Repository
public interface BoardRepository extends JpaRepository<Board, Long> {
    Page<Board> findAll(Pageable pageable);
}

find 객체의 리턴에 Page<객체명> 을 해주면 자동으로 count 쿼리를 jpa 가 요청하여, paging 에 필요한 데이터를 조회 후 객체에 추가해 준다. 

Pageable 에 해당 되는 size 및 page, sort 에 해당 되는 쿼리를 jpa 가 생성하여 보내준다.

 

결과가 아래와 같이 생성된다.

{
    "content": [
        {
            "id": 3,
            "name": "이름",
            "description": "설명",
            "createdAt": "2024-03-10T13:22:09",
            "updatedAt": "2024-03-10T13:22:09"
        },
        {
            "id": 2,
            "name": "이름2",
            "description": "자세한설명",
            "createdAt": "2024-03-10T13:22:09",
            "updatedAt": "2024-03-10T13:22:09"
        }
    ],
    "pageable": {
        "sort": {
            "empty": false,
            "sorted": true,
            "unsorted": false
        },
        "offset": 0,
        "pageSize": 2,
        "pageNumber": 0,
        "paged": true,
        "unpaged": false
    },
    "last": false,
    "totalPages": 3,
    "totalElements": 5,
    "size": 2,
    "number": 0,
    "sort": {
        "empty": false,
        "sorted": true,
        "unsorted": false
    },
    "first": true,
    "numberOfElements": 2,
    "empty": false
}
 

 

 

Page 객체는 totalPages 및 totalElements 값을 count 쿼리를 통해 가지고 있다.

totalCount를 얻기 위해서는 다음과 같이 getTotalElements() 메서드를 사용한다:

public interface Page<T> extends Slice<T> {
    static <T> Page<T> empty() {
        return empty(Pageable.unpaged());
    }

    static <T> Page<T> empty(Pageable pageable) {
        return new PageImpl(Collections.emptyList(), pageable, 0L);
    }

    int getTotalPages();

    long getTotalElements();

    <U> Page<U> map(Function<? super T, ? extends U> converter);
}

 

 

https://www.baeldung.com/spring-data-jpa-pagination-sorting [PageRequest 예시]

 
반응형
반응형

jpa 관련 어노테이션

  • @Column : 컬럼을 매핑한다.
  • @Enumerated : enum 타입을 매핑한다.
  • @Temporal : 날짜 타입 매핑한다.
  • @Lob : BLOB, CLOB 타입을 매핑한다.
  • @Transient : 해당 필드를 데이터 베이스에 매핑 시키지 않는다.
  • @Access : JPA가 엔티티 접근하는 방식을 지정한다.

@Column

name : 필드와 매핑할 테이블의 컬럼 이름
insertable : 엔티티 저장시 이 필드도 같이 저장한다. false로 설정하면 데이터베이스에 저장하지 않는다. 읽기 전용일때 사용한다.
updatable : 위와 동일한 하지만 수정일때 해당 된다.

@Column(insertable=false, updatable=false)
private String defaultField;

위와같이 하면 update 나 insert 가 안된다. update 만 안할 경우 updatable 만 false 로 하면 된다.

 

table : 지정한 필드를 다른 테이블에 맵핑할 수 있도록 합니다.

nullable(DDL) : null 값 허용 여부를 설정한다. false일 경우 DDL생성시 not null 제약조건이 된다.
unique(DDL) : 한 컬럼에 간단히 유니크 제약 조건을 걸 때 사용한다. 만약 두개 이상 걸고 싶다면 클래스 레벨에서 @Table.uniqueConstraints를 사용해야 한다.
columnDefinition(DDL) : 데이터베이스 컬럼 정보를 직접 줄 수 있다.
length : 문자 길이 제약 조건이다. String만 해당된다.
precision, scale(DDL) : BigDecimal 타입에서 사용한다.(BigInteger 가능) precision은 소수점을 포함한 전체 자리수이고, scale은 소수점 자릿수이다. double랑 float타입에는 적용 되지 않는다.

 

@Enumerated

value : EnumType.ORDINAL값은 enum 순서를 데이터 베이스에 저장한다. EnumType.STRING값은 enum 이름을 저장한다.

enum RoleType {
  ADMIN, USER
}

@Enumerated(EnumType.STRING)
private RoleType roleType;

m.setRoleType(RoleType.ADMIN) //ADMIN으로 저장된다.

보통은 STRING을 쓰는것을 권장한다.

 

@Temporal

날짜 타입(java.util.Date, java.util.Calendar)을 맵핑할 때 사용한다.

value :

TemporalType.DATE  날짜, 데이터베이스 date 타입과 매핑 (2016-04-19),

TemporalType.TIME 시간, 데이터베이스 time 타입과 매핑 (21:22:11)

TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스 timestamp 타입과 매핑(2016-04-19 21:22:11)

 

@Lob

속성이 없다. 대신 매핑하는 필드 타입이 문자라면 CLOB으로 매핑하고 나머지는 다 BLOB으로 매핑하면 된다.
CLOB : String, char[], java.sql.CLOB
BLOB : byte[], java.sql.BLOB

 

@Transient

위와 같이 선언된 필드는 데이터베이스에 저장, 조회도 하지않는다. 임시로 값을 보관할 때 사용한다.

 

@Access

필드 접근 : AccessType.FIELD로 지정한다.필드에 직접 접근한다. private 권한이여도 접근할 수 있다.
프로퍼티 접근 : AccessType.PROPERTY로 지정한다. 접근자 getter로 접근한다.

@Entity
public class Member {

  @Id
  private String id;

  @Transient
  private String firstName;

  @Transient
  private String lastName;

  @Access(AccessType.PROPERTY)
  public String getFullName(){
    return this.firstName + this.lastName;
  }

  ...
}

@Id가 필드에 있으면 기본적으로 필드접근 방식을 사용한다.
만약 프로퍼티에 있으면 기본적으로 프로퍼티접근 방식을 사용한다.

위의 코드는 @Id가 필드에 있으므로 필드 접근 방식을 사용하고 getFullName만 프로퍼티를 사용한다. 그러면 회원이라는 엔티티의 FULLNAME 컬럼이 생성되고 firstName + lastName의 결과가 저장된다.

 

반응형
반응형

@Id는 해당 프로퍼티가 테이블의 주키(primary key) 역할을 한다는 것을 나타낸다.


@GeneratedValue는 주키의 값을 위한 자동 생성 전략을 명시하는데 사용한다.


Primary 키 생성 전략으로 JPA가 지원하는 것은 아래의 네 가지이다.


1. AUTO : (persistence provider가) 특정 DB에 맞게 자동 선택

  데이터베이스 벤더에 의존하지 않고, 데이터베이스는 기본키를 할당하는 벙법
- 데이터베이스에 따라서 IDENTITY, SEQUENCE, TABLE 방법 중 하나를 자동으로 선택해주는 방법이다.
- 예를들어, Oracle일 경우 SEQUENCE를 자동으로 선택해서 사용합니다. 따라서, 데이터베이스를 변경해도 코드를 수정할 필요가 없다.


2. IDENTITY : DB의 identity 컬럼을 이용

  기본 키 생성을 데이터베이스에 위임하는 방법 (데이터베이스에 의존적)
  - 주로 MySQL, PostgresSQL, SQL Server, DB2에서 사용합니다.


3. SEQUENCE : DB의 시퀀스 컬럼을 이용

  데이터베이스 시퀀스를 사용해서 기본 키를 할당하는 방법 (데이터베이스에 의존적)
  - 주로 시퀀스를 지원하는 Oracle, PostgresSQL, DB2, H2에서 사용한다. 
  - @SequenceGenerator를 사용하여 시퀀스 생성기를 등록하고, 실제 데이터베이스의 생성될 시퀀스이름을 지정해줘야 한다.


4. TABLE : 유일성이 보장된 데이터베이스 테이블을 이용

  키 생성 테이블을 사용하는 방법
- 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만드는 방법이다.
- 테이블을 사용하므로, 데이터베이스 벤더에 상관없이 모든 데이터베이스에 적용이 가능하다.

postgres를 이용하여 테스트해보면 AUTO와 SEQUENCE는 실제 INSERT 쿼리가 일어나기 전에 다음 쿼리를 통해서 주키를 가져오는 것을 확인할 수 있다.

select nextval ('hibernate_sequence')


기본키 자동생성 사용방법

  • @Id 어노테이션을 사용한다.
  • @GeneratedValue를 사용한다.
  • @GeneratedValue에 원하는 키 생성 전략을 선택한다. (IDENTITY, SEQUENCE, TABLE, AUTO)

Sample 코드

@Entity
public class Team {
  @Id
  @Column(name = "team_id")
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
}


@Entity
@SequeceGenerator(name = "TEAM_SEQ_GENERATOR", sequenceName = "TEAM_SEQ", initialValue = 1, allocationSize = 1)
// name=식별자 생성기 이름, sequenceName=DB에 등록될 시퀀스이름, initialValue=최초시작하는 수, allocationSize=증가하는수)
public class Team {

  @Id
  @Column(name = "team_id")
  @GeneratedValue(strategy = GenerationType. SEQUENCE, generator = "TEAM_SEQ_GENERATOR")
  private Long id;

}

@Entity
@TableGenerator(name="TEAM_SEQ_GENERATOR", table="TEAM_SEQUENCES", pkColumnValue="TEAM_SEQ", allocationSize=1)
// name=식별자 생성기 이름, table=키생성 테이블 이름, pkColumnValue=DB에 등록될 시퀀스이름)
public class Team {
  @Id
  @Column(name = "team_id")
  @GeneratedValue(strategy = GenerationType. TABLE, generator = "TEAM_SEQ_GENERATOR")
  private Long id;

}

@Entity
public class Team {
  @Id
  @Column(name = "team_id")
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

}


참고문헌

ithub.tistory.com/24

 

반응형
반응형

jpa 를 사용하려면 @Entity 클래스를 먼저 생성해야 한다.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="coin.market_code")
public class MarketCode {

    @Id
    @Column(name = "market_code")
    private String marketCode;
    @Column(name = "korean_name")
    private String koreanName;
    @Column(name = "english_name")
    private String englishName;

    @Builder
    public MarketCode(String marketCode, String koreanName, String englishName) {
        this.marketCode = marketCode;
        this.koreanName = koreanName;
        this.englishName = englishName;
    }
}

 

먼저 데이터베이스에 저장하기 위해 유저가 정의한 클래스가 필요한데 그런 클래스를

Entity라고 한다

@Id

primary key를 가지는 변수를 선언하는 것을 뜻한다. @GeneratedValue 어노테이션은 해당 Id 값을

어떻게 자동으로 생성할지 전략을 선택할 수 있다. 

보통 일반적으로

@GeneratedValue(strategy = GenerationType.AUTO)

를 사용한다. DB에 맞게 자동으로 생성해주는 역할이다.

참고 -> juntcom.tistory.com/156 - GeneratedValue생성방법

@Table

별도의 이름을 가진 데이터베이스 테이블과 매핑한다. 기본적으로 @Entity로 선언된 클래스의 이름은 실제

데이터베이스의 테이블 명과 일치하는 것을 매핑한다. 따라서 @Entity의 클래스명과 데이터베이스의 테이블명이

다를 경우에 @Table(name=" ")과 같은 형식을 사용해서 매핑이 가능하다.

@Column

@Column 선언이 꼭 필요한 것은 아니다. 하지만 @Column에서 지정한 변수명과 데이터베이스의 컬럼명을

서로 다르게 주고 싶다면 @Column(name=" ") 같은 형식으로 작성하면 된다.

그렇지 않은 경우에는 기본적으로 멤버 변수명과 일치하는 데이터베이스 컬럼을 매핑한다.

컬럼에 언더바가 있을 경우에는 꼭 column 어노테이션을 사용해야 한다.

 

 

JpaRepository

public interface MarketCodeRepository extends JpaRepository<MarketCode, String> {

}

Spring Data JPA에서 제공하는 JpaRepository 인터페이스를 상속하기만 해도 되며,

인터페이스에 따로 @Repository등의 어노테이션을 추가할 필요가 없다



Spring Data JPA에서 제공하는 JpaRepository 인터페이스를 상속하기만 해도 되며,

인터페이스에 따로 @Repository등의 어노테이션을 추가할 필요가 없다

 

 

그렇게 JpaRepository를  단순하게 상속하는 것만으로 위의 인터페이스는 Entity 하나에 대해서

아래와 같은 기능을 제공하게 된다.

 method

 기능

 save()

 레코드 저장 (insert, update)

 findOne()

 primary key로 레코드 한건 찾기
 findAll()

 전체 레코드 불러오기. 정렬(sort), 페이징(pageable) 가능

 count()  레코드 갯수
 delete()  레코드 삭제

 

public interface MarketCodeRepository extends JpaRepository<MarketCode, String> {
	List<MarketCode> findBykoreanName(String koreanName);
}

주의사항 변수명을 korean_name 처럼 언더바로 사용할시에 findBy가 적용이 안된다.

변수명을 카멜케이스로 맞추자.

 

Query 메소드에 포함할 수 있는 키워드는 다음과 같다.

메서드 이름 키워드

 샘플

 설명

 And

 findByEmailAndUserId(String email, String userId)

 여러필드를 and 로 검색

 Or

 findByEmailOrUserId(String email, String userId)

 여러필드를 or 로 검색

 Between

 findByCreatedAtBetween(Date fromDate, Date toDate)

 필드의 두 값 사이에 있는 항목 검색

 LessThan

 findByAgeGraterThanEqual(int age)

 작은 항목 검색

 GreaterThanEqual

 findByAgeGraterThanEqual(int age)

 크거나 같은 항목 검색

 Like

 findByNameLike(String name)

 like 검색

 IsNull

 findByJobIsNull()

 null 인 항목 검색

 In

 findByJob(String … jobs)

 여러 값중에 하나인 항목 검색

 OrderBy

 findByEmailOrderByNameAsc(String email)

 검색 결과를 정렬하여 전달

 


참고문헌

docs.spring.io/spring-data/jpa/docs/1.10.1.RELEASE/reference/html/#jpa.sample-app.finders.strategies  - jpa문서
반응형
반응형

JPA 를 사용하면서 DB 에 어떤 특정값이 들어오면 변환해줘야 할 떄가 있다.

예를 들어 empty string 을 null 로 넣어줘야 할떄다. (mysql)

 

빈스트링이 "" 가 들어올때 null 로 바꿔서 db 에 넣고 싶을때 @Convert 가 필요하다, 

이 외에도 어떤 고정된 값들은 특정 값으로 변환해서 db 에 넣어줄떄 이 Convert 가 필요하다.

 

 

구현 방법은 변경하고자 하는 컬럼명 위에 @Convert 적고, 커스텀한 Class 명을 기입한다.

@Table(name = "db1.test")
public class Test {   

    ...

    @Convert(converter = EmptyStringToNullConverter.class)
    @Column(name = "test_data")
    private String test_data;
    
    ...
}

커스텀할 Classs 에는 AttributeConverter 를 구현한다.

이름 그대로 디비 넣기 전에 변환해주는 메소드명은  convertToDatabaseColumn 에서 구현한다.

반대로 db 에서 꺼내서 vo,entity 에서 사용할떄는 convertToEntityAttribute 를 사용해서 다시 변환해 준다.

@Converter(autoApply = true)
public class EmptyStringToNullConverter implements AttributeConverter<String, String> {

    @Override
    public String convertToDatabaseColumn(String string) {
        // Use defaultIfEmpty to preserve Strings consisting only of whitespaces
        return "".equals(string) ? null : string;
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        //If you want to keep it null otherwise transform to empty String
        return dbData;
    }
}

 

 

참고문헌

https://www.baeldung.com/jpa-attribute-converters [jpa 컨버터]

 

반응형

+ Recent posts