서론
이 피드에서 다루는 내용
엔티티 매핑 및 엔티티 선정 시 주의사항
엔티티?
주로 비즈니스 도메인에서 "특정 행위" 를 하는 주체나 "기록이 추적되는", "독자적으로 CRUD 연산이 되야하는" 도메인 모델을 엔티티로 삼는다.
Java Class 선언 바로 위에 @Entity 어노테이션을 붙인 도메인 모델을 엔티티라고 부른다.
이 엔티티는 JPA에 의해 DB의 테이블에 매핑되며, 고유 식별자(DB 입장에서 PK)를 통해 유니크하게 참조된다.
이때, 고유식별자는 멤버필드에 @Id를 작성하여 지정한다.
@Entity
public Class User{
@Id
@GeneratedValue
Long id;
}
또한 엔티티는 아래와 같은 특징을 가진다.
- 식별성 : 고유한 식별자를 가져야 하며, 프로퍼티 값 일부가 변경되어도 고유 식별자를 통해 Java Level에서 "동등성" 을 보장받는다.
- 독립된 생명주기 : 독립적인 CRUD 객체로 관리되어야 한다. (이건 값 객체 설명에서 보강하겠다.)
- 가변성 : 엔티티는 값이 변할 수 있다.
- 참조대상 : 다른 엔티티나 VO가 참조할 수 있는 중심 개체가 된다.
- 불변하는 규칙을 가진다 : 상태값은 변할 수 있지만, 비즈니스 상에서 정한 특정 규칙을 따라야 한다.
예시 -> (사용자는 가입 당일 탈퇴할 수 없다) 라는 규칙이 발생하면, 엔티티 내부에서 이런 규칙을 따르도록 개발해야 한다.
위와 다른 개념으로 "값 객체, Value Object" 라는 개념이 있다.
값 객체 (Value Object) ?
엔티티의 속성으로 존재하는 도메인 모델을 칭한다.
JAVA의 영역에서 @Entity가 붙은 도메인 모델(이하 엔티티) 의 멤버필드로 존재하며, @Embedded 어노테이션이 붙는다.
또한, 값 객체로 존재하기 위해서는 값 객체 선언 클래스에 @Embeddable을 붙여야 한다.
** Jpa 명세상에서는 값 객체 라는 표현 대신 Embeddable Class 라는 표현을 쓴다.
@Entity
public class User{
@Id
@GeneratedValue
Long id;
@Embedded
Address homeAddress;
}
@Embeddable
public class Address{
String zipCode;
String city;
String Distrcit;
String dong;
}
이때, 값 객체는 아래와 같은 특징을 가진다.
- 엔티티에 종속적 : 엔티티의 속성으로만 존재하고, 그 엔티티 없이는 의미가 없다.
- 식별자가 없음 : 엔티티의 속성으로만 존재하기 때문에, 별도의 PK를 가지지 않으며 엔티티의 식별자에 의존한다.
- 별도 테이블로 관리되지 않음 :
엔티티의 멤버필드로만 존재하기 때문에, Java 영역에서는 Class지만 DB에서는 flat화 되어 값 객체의 필드가 각각 한개의 column으로 관리된다.
!주의! 예외 상황이 있다. 만약 @Embedded 가 컬랙션(List, Set)으로 표현되어야 한다면, 별도의 테이블이 생성된다.
이때, Default로는 별도 PK를 가지지 않으며 @Entity의 PK를 FK로 가지고 기타 값들을 column으로 가진다.
- 불변성 : 보통 값 객체는 불변객체로 설계된다. 만약 다른 값이 주입되어야 한다면 값을 변경하는게 아니라 새로 만들어서 재할당한다.
* 이런 불변성 때문에 캐싱, 동등성 비교에서 장점을 지닌다.
Entity와 Value Object의 선정 및 관리
어떤것을 엔티티로 삼고(별도 테이블을 만들어 관리하고), 어떤것을 값 객체로 삼을지(별도 테이블 없이 지배 엔티티의 테이블의 컬럼으로 둘지)
결정하는 과정을 거치게 되는데, 이것이 곧 "비즈니스 도메인 내 도메인 모델 식별 및 설계" 절차가 되겠다.
1. 비즈니스 도메인 내, 존재하는 객체를 식별하고 우선 모두 값 객체(Value Object)로 간주한다.
2. 1에서 식별한 값 객체가 아래의 조건을 충족한다면, 엔티티로 격상한다
- 특정 행위의 주체가 될 수 있다 : "사용자는 주문/취소/배송지변경 을 할수 있다." 라면 사용자는 엔티티로 격상
- 변경이력이 관리되어야 한다 : "사용자는 주문기록을 조회할 수 있다." 라면 "주문" 자체가 엔티티로 격상되어 관리되어야 함
- 런타임에 다른 엔티티와 공유되어야 한다 : "A 사용자도 1 물품을 볼 수 있고, B 사용자도 1 물품을 조회할 수 있다." 라면 "물품"은 엔티티로 격상
- 특정 엔티티에 종속되지 않고 CRUD 될 수 있어야 한다 (= 독자적인 생명주기를 가진다.)
: "사용자는 그 어떠한 엔티티에 의존하지 않고 가입(C), 조회(R), 변경(U), 탈퇴(D) 될 수 있어야 한다."
* 위 조건들은 베타적이지 않으며 조건을 충족하더라도 값 객체로 남기거나, 충족하지 않더라도 개발자의 필요에 따라 엔티티로 격상하는 경우가 있다.
3. 위를 충족하지 못한, 아래의 경우에는 값 객체로 남긴다
- 특정 엔티티 없이는 의미가 없다 :
a) 원래대로라면, "주문 기록" 은 "상품" 과 "사용자" 없이는 생기지 못하는 값이다. (사용자 A가 물품 1을 주문해야 "주문" 이 생김)
** 여기까지만 보면 "주문" 은 값 객체로 남겨도 된다.
b) 그러나 "사용자가 주문한 기록을 본다" 라는 행위에 의해 "주문" 이 기록관리가 될 필요가 있다면, 주문은 엔티티로 격상될 수 있다.
c) 반대로, "사용자는 특정 주소에 거주한다" 라는 명제에 의하면 사용자 없이는 거주지 라는 값이 식별될 필요가 없다. 이러면 값 객체로 남겨도 좋다.
-> 위에서 "엔티티에 종속적" 이라 표현했는데, 이는 곧 "생명주기 의존성" 이다. 지배 엔티티가 삭제되면 종속 값 객체도 삭제되어야 한다.
-> 또한 엔티티 없이 식별될 필요가 없으므로, 별도의 식별자를 가지지 않으며 지배 엔티티의 식별자에 의존한다.
식별자가 있는 엔티티 매핑
위에서 엔티티는 "고유한 식별자" 를 가진다 라는 이야기를 자주 했다.
이만큼, "고유한 식별자" 로 식별되는것은 중요한데, 이는 Java의 영역에 오면 좀 상황이 달라진다.
Java에서는 "같은 객체다" 를 판단할 때 동일성과 동등성이 있다.
객체 동일성 : JVM 메모리상 인스턴스변수가 가리키는 주소값이 동일하다
객체 동등성 : Object.equals()에 의해 판단되는 동일 여부로 서로 다른 두 인스턴스가 같은 값을 가지면 동일하다.
* 이때, 동등성은 반사성, 대칭성, 추이성을 띈다.
이로인해 복잡해지는것이, 객체/관계형 영속성에서 영속 인스턴스는 "DB 상 특정 Row를 메모리상에서 인스턴스로 표현한것" 이다.
이때문에 JPA를 사용할 때에는 참조를 구별할 때 한가지 더 구별법을 생각해야 한다.
데이터베이스 동일성 : 동일한 테이블과 동일한 기본키 값(식별자 값) 을 가진 인스턴스는 동일하다.
* 다만 JPA의 경우 복잡한 객체 동일/동등성 문제를 피하기 위해 영속성 컨텍스트(1차캐시)에 엔티티를 저장하고
데이터베이스 동일성을 충족하는 Where 조건 JPQL을 받으면, 캐싱해둔 인스턴스를 리턴해준다.
엔티티 클래스 생성과 PK 매핑
자바 POJO를 Entity로 선언하려면, 클래스에 @Entity 를 선언하고, 식별자가 될 멤버변수에 @Id를 선언해야 한다.
@Entity
public Class User{
@Id
@GeneratedValue
Long id;
}
그래야 JPA가 그 POJO를 엔티티로 인식하고 영속화 대상으로 간주하고 데이터베이스 동일성을 애플리케이션에 노출하기 시작한다.
이때, Spring-Data JPA와 Hibernate는 DB Row로 부터 엔티티 인스턴스를 만들 때 Getter/Setter로 접근하는게 아니라, 필드 자체를 사용해 프로퍼티에 접근한다. Java 리플렉션을 사용해 객체를 인스턴스화 하기 때문에 가능한 것인데, new 연산자로 직접 호출하는게 아니라 리플렉션을 통한 생성자 호출을 수행하므로 접근제어자를 무시하고 해당 멤버변수에 접근할 수 있기 때문이다.
이에 따라, Getter와 Setter를 하이버네이트를 신경쓰지 않고 자유롭게 설계할 수 있다는 장점이 따라온다.
이것으로부터 "식별자는 Setter를 public으로 노출하지 않는다" 가 가능해진다.
PK는 DB에서 고유하며, 변경되지 않는 불변 값이어야 하기 때문.
따라서 JPA/Hibernate에서는 기본키 값을 업데이트 하지 않으며 public 으로 식별자(@Id 프로퍼티) Setter를 노출해서는 안된다.
PK 자동 생성기 구성
@GeneratedValue를 사용하면 PK 값을 JPA/Hibernate가 알아서 생성한다.
만약 @GeneratedValue를 선언하지 않는다면, JPA 공급자는 인스턴스 저장 전에 개발자가 코드상으로 직접 PK를 생성한다고 간주하며,
이런 방식을 "애플리케이션 할당 식별자" 라고 한다. 자연키를 PK로 삼거나, 레거시 DB를 사용하고 있다면 엔티티 식별자를 수동으로 할당해야 한다.
이때 Hibernate는 JPA 표준에 따라 4가지 생성 전략을 제공하며, JPA 표준을 따르지 않는 자체 확장 전략을 별도로 지원한다.
또한, 상기 전략 외에도 직접 "명명 식별자 생성기" 를 지정해서 설정할 수 있다.
JPA 표준을 따르는 4가지 전략
이 경우, @GeneratedValue의 인자값으로 아래의 값을 넣을 수 있다.
아래에서 영속성 공급자는 Hibernate 또는 Hibernate를 사용하는 Spring-Data JPA를 말한다.
GenerationType.AUTO
- 영속성 공급자에게 SQL 방언에 따른 최선을 물어 자동으로 선택하게 한다.
GenerationType.SEQUENCE
- 영속성 공급자가 DB에 HIBERNATE_SEQUENCE라는 시퀀스가 있을것으로 기대하고, 없으면 생성한다.
- 이 시퀀스는 INSERT가 실행되기 전에 개별적으로 호출되어 순차적으로 값을 생서한다.
GenerationType.IDENTITY
- 영속성 공급자가 Insert를 실행할 때 숫자값을 자동으로 생성하는 특별한 자동증가 PK 칼럼이 DB에 있을것으로 기대하고, 테이블 DDL에 생성한다.
GenerationType.TABLE :
- 영속성 공급자가 DB 스키마 내의 다음 숫자 PK 값이 담긴 별도 테이블을 사용한다. 이 테이블은 Insert 전에 읽혀지고 업데이트한다.
- Default 테이블 명은 HIBERNATE_SEUQNCES 이다.
명명 식별자 생성기
데이터베이스 시퀀스 명이나 기타 설정을 바꾸고 싶을 때 사용한다.
@GeneratedValue의 인자값으로 generator = "{{지정하려는 이름}}") 을 선언하고, 설정을 수행해주면 된다.
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_seq_gen")
@SequenceGenerator(
name = "order_seq_gen", // 생성기 이름
sequenceName = "order_sequence", // DB 시퀀스 이름
initialValue = 1,
allocationSize = 50 // 시퀀스 미리 할당(성능 최적화)
)
private Long id;
private String name;
}
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "product_table_gen")
@TableGenerator(
name = "product_table_gen",
table = "id_gen_table", // 키 생성용 테이블명
pkColumnName = "gen_name", // 구분용 이름 컬럼
valueColumnName = "gen_value", // 현재 시퀀스 값 저장
pkColumnValue = "product_id", // 구분값
allocationSize = 10
)
private Long id;
private String name;
}
Native Hibernate (Hibernate 확장) 식별자 생성전략
위에서 작성한 방식은 모두 특정 @Entity Class 내에서만 사용이 가능하다.
네이티브 하이버네이트가 제공하는 생성전략을 사용하면, packge-info.java (패키지 단위 메타데이터 정의 파일) 라는 파일에다가도 선언할 수 있다.
어노테이션은 @org.hibernate.annotations.GenericGenerator 를 사용한다.
@org.hibernate.annotations.GenericGenerator(
name = "사용하려는 제너레이터 고유명칭"
strategy = "사용하려는 제너레이터 전략"
parameters = {
@org.hibernate.annotations.Parameter(
name = "sequence_name" // DB가 sequence를 지원 안하면 "시퀀스 테이블" 로 만듬
value = "사용하려는 시퀀스 명"
)
}
)
이때, 여러 엔티티에서 한개의 식별자 생성기를 참조할 수 있다.
아래에서 "사용하려는 제너레이터 전략"에 가능한 값들을 알아보자.
Native Hibernate GenericGenerator Strategy & JPA 표준 식별자 생성전략과 매핑
Native Hibernate에서는 표준 전략을 "이전 매핑" 이라 칭하고, 네이티브 전략을 "새 매핑" 이라 칭한다.
하이버네이트가 지속적으로 발전한 반면 JPA는 표준으로써 제 위치를 지키고 있기 때문에 더 올드하다고 판단한듯..
native : SQL 방언에 따라 sequence나 Identity를 자동 선택. 이전 매핑에서 JPA GenerationType.AUTO 와 동일하다.
sequence : HIBERNATE_SEQUENCE 라는 네이티브 데이터베이스 시퀀스를 사용한다. 시퀀스명을 바꾸거나 추가 DDL 설정이 가능하다.
enhanced-sequence : 네이티브 데이터베이스 시퀀스가 지원되면 시퀀스를 쓰고, 아니면 시퀀스를 흉내내는 테이블(HIBERNATE_SEUQENCE)를
만든다. 이 전략을 사용하면 INSERT가 실행되기 전에 항상 DB 시퀀스를 호출하므로, RDB가 시퀀스를 지원하든 안하든 항상 동일 동작을 보장한다.
이와 더불어 별도 최적화 전략 사용이 가능하며, 새 매핑이 활성화된 JPA의 .SEQUENCE 타입과 .AUTO와 동일하다. 아마 기본 전략중 제일 좋은전략일거라고.
enhanced-table : HIBERNATE_SEQUENCE 라는 별도 테이블을 사용하며, 새 매핑이 활성화된 JPA .TABLE 타입과 같다.
identity : DB2, MySQL, MSSQL, Sybase에서 Identity와 자동증가 컬럼을 지원한다. 식별자 값은 INSERT를 실행할 때 생성된다.
다만, Native Hibernate의 @GenericGenerator에서 직접 전략을 구성할 수 없다. DDL 생성에는 기본키 칼럼에 대한 식별자나 자동증가 옵션이 포함되지 않을것이며, 사용할 수 있는 방법은 JPA GenerationType.IDENTITY를 사용하는것.
increment : 하이버네이트가 시작될 때, 각 엔티티 테이블의 PK MAX값을 읽고 새 로우가 삽입될 때마다 +1 하는 방식이지만
단일 어플리케이션이 특정 DB에 베타적으로 붙을 수 있는 상황이 아니면 안쓰는게좋다.
select : Hibernate가 키 값을 생성하거나 INSERT문에 기본키 칼럼을 포함하지 않는다. DBMS가 트리거나 default 값으로 넣을것으로 기대한다.
uuid2 : 어플리케이션 계층에서 고유 128bit UUID를 생성한다.
guid : 데이터베이스에서 SQL 내장 함수로 생성한 전역적 고유 식별자를 사용하며 INSERT 전에 DB 함수를 호출한다.
엔티티 클래스 내 메타데이터 제어
@Entity 클래스에서 어노테이션에 인자를 넘겨, 특정 제어가 가능하다.
테이블명 별도 지정
@Table(name = "") : 엔티티가 매핑될 테이블의 명칭을 지정할 수 있다. 이때, 카멜 표기법(BidItem) 이 스네이크 표기법(bid_item)으로 변환된다.
만약 대소문자나 식별자를 테이블명으로 쓰고싶다면 이스케이프 처리한 따옴표로 묶거나 (name="\"USER\"") 영속성 단위 구성에서 hibernate.auto_quote_keyword를 true로 돌린다.
테이블 Prefix 지정 (테이블명 일괄 추가/정정)
예를들어, 모든 테이블 명에 CE_ 라는 prefix가 붙거나, _tb 라는 postFix가 붙어야 한다고 가정하자.
이런 경우 PhysicalNamingStrategy 인터페이스를 구현하고 이 구성을 영속성 단위 구성에서 함께 넣어서 활성화 해주 된다.
Hibernate JPA는 persistence.xml에서, Spring data jpa의 경우 LocalContainerEntityManagerFactoryBean에서 넣을 수 있다.
* PhysicalNamingStrategy 인터페이스 구현 예시
public class CENamingStrategy extends PhysicalNamingStrategyStandardImpl {
@Override
public Identifier toPhysicalTableName(Identifier name,
JdbcEnvironment context) {
return new Identifier("CE_" + name.getText(), name.isQuoted());
}
}
활성화 (Hibernate JPA)
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="ch05.mapping">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<property name="jakarta.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/CH05_MAPPING?serverTimezone=UTC"/>
<property name="jakarta.persistence.jdbc.user" value="root"/>
<property name="jakarta.persistence.jdbc.password" value=""/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="hibernate.physical_naming_strategy"
value="com.manning.javapersistence.ch05.CENamingStrategy"/>
</properties>
</persistence-unit>
</persistence>
활성화(Spring-Data JPA)
만약 이걸 사용하려면, Spring-Boot 의 application.properties에서 자동 설정되는 방식을 따르는게 아니라,
직접 JPA를 configure를 구성해서 넣어줘야 한다. 물론 @Value를 통해서 properties 값을 끌고와야겠지만.
아래 LocalContainerEntityManagerFactoryBean 부분에서 활성화한다.
@EnableJpaRepositories("com.manning.javapersistence.ch05.repositories")
public class SpringDataConfiguration {
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/CH05_MAPPING?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("");
return dataSource;
}
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabase(Database.MYSQL);
jpaVendorAdapter.setShowSql(true);
return jpaVendorAdapter;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean =
new LocalContainerEntityManagerFactoryBean();
localContainerEntityManagerFactoryBean.setDataSource(dataSource());
Properties properties = new Properties();
properties.put("hibernate.hbm2ddl.auto", "create");
properties.put("hibernate.physical_naming_strategy", "com.manning.javapersistence.ch05.CENamingStrategy");
localContainerEntityManagerFactoryBean.setJpaProperties(properties);
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter());
localContainerEntityManagerFactoryBean.setPackagesToScan("com.manning.javapersistence.ch05");
return localContainerEntityManagerFactoryBean;
}
}
엔티티명 직접지정
엔티티 명칭은 직접 지정하지 않으면 Class명과 동일하게 지정된다.
다만, 다른 패키지에서 동일 클래스명을 사용하는 경우 아래 어노테이션을 사용해 충돌을 피해야 한다.
기본적으로 모든 엔티티명은 쿼리 엔진의 네임스페이스로 자동으로 가져오는데,
만약 클래스명이 동일한데 엔티티명을 직접 지정하지 않으면 직접 패키지 풀 경로를 적어서 엔티티를 명시해줘야 한다.
@javax.persistence.Entity(name = "엔티티명")
동적 SQL 생성
SpringDataJPA(Hibernate base) 는 기동시 자동으로 엔티티에 대한 SQL문을 생성하고 올라간다.
이때, Update문의 경우 변경된 값은 변경값으로, 변경되지 않은 값은 기존값으로 set을 돌리는데,
컬럼수가 많으면 이 작업 자체가 비효율적일 수 있다.
이에따라 아래 어노테이션을 사용하면 SQL 캐싱을 비활성화 할 수 있다.
다만 Native Hibernate 어노테이션을 사용한다.
@org.hibernate.annotations.DynamicInsert
@org.hibernate.annotations.DynamicUpdate
엔티티를 불변으로 만들기
엔티티를 불변객체로 지정하면 (값이 변할 일이 없는 엔티티에 대해) Update문을 실행하지 않게되므로 변경 감지를 방지하는 등 최적화가 가능하다.
@org.hibernate.annotations.Immutable
이런 기능을 통해, DB Schema에서 View를 만들 수 없는경우 불변 엔티티 클래스를 만들고 SQL select 쿼리에 매핑할 수 있다.
엔티티를 서브쿼리에 매핑
하이버네이트 어노테이션(Subselect와 Immutable)을 조합하면 SQL Select에 매핑된 읽기 전용 엔티티를 만들 수 있다.
@Entity
@org.hibernate.annotations.Immutable
@org.hibernate.annotations.Subselect(
value = """
SELECT i.ID as ITEMID, i.NAME as NAME, COUNT(b.ID) as NUMBEROFBIDS
FROM ITEM i
LEFT OUTER JOIN BID b
ON i.ID = b.ITEM_ID
GROUP BY i.ID, i.NAME
"""
)
@org.hibernate.annotations.Synchronize({"ITEM", "BID"})
public class ItemBidSummary {
@Id
private Long itemId;
private String name;
private long numberOfBids;
public Long getItemId() {
return itemId;
}
public String getName() {
return name;
}
public long getNumberOfBids() {
return numberOfBids;
}
}
이 경우, SELECT에서 참조하는 모든 테이블명을 @Synchronize 어노테이션에 나열해야한다.
그래야 ItemBidSummary 관련 쿼리를 실행하기 전에 Item과 Bid 인스턴스의 변경사항을 Flush해야 한다는것을 알기 때문.
* Spring-Data JPA(Hibernate Based)와 Hibernate는 DB에 동기화가 안됐지만 인메모리 변경사항이 있는경우(변경 감지가 발생한경우) 쿼리 실행 전에 변경사항을 flush한다. 그렇지 않으면 Stale state(오래된 상태, 변경이 반영되지 않은 Old한 값이 남아있는 케이스) 가 리턴될 수 있다.
위의 @Entity에는 @Table 어노테이션이 없기 때문에, 자동 flush 시점을 알지 못한다. 따라서 @Synchronize 어노테이션을 통해 쿼리 실행 전 Item 테이블과 Bid 테이블을 flush 해야한다고 알리는 격이다.
물론 Spring-Data jpa에서 이 엔티티를 사용하려면, 이 엔티티를 타입 매개변수로 넘기는 Repository를 따로 만들어야 한다.
@Repository
public interface ItemBidSummaryRepository extends JpaRepository<ItemBidSummary, Long>{
}
'공부일기 : JPA' 카테고리의 다른 글
| JPA 1권 독파하기(7) : 값 타입 매핑 (0) | 2025.10.06 |
|---|---|
| JPA 1권 독파하기(5) : 1부 / 엔티티 기초 영역 갈무리 (0) | 2025.10.04 |
| JPA 1권 독파하기(4) : Spring Data JPA 다루기 / 엔티티 (0) | 2025.10.04 |
| JPA 1권 독파하기(3) : 도메인 모델과 메타데이터 (0) | 2025.09.21 |
| JPA 1권 독파하기(2) : 하이버네이트, 스프링 데이터 프로젝트 (1) | 2025.09.10 |