@MapsId Annotation Example
1. Introduction
The @MapsId annotation provided by JPA 2.0 is used to map a foreign key in a one-to-one or many-to-one relationship to the primary key of another entity. In this example, I will create a spring-boot project that utilizes @MapsId annotation along with both @ManyToOne and @OneToOne annotations for the following four tables:
T_ORDER: the primary key is theIDcolumn.T_PRODUCT: the primary key is theIDcolumn.T_ORDER_ITEM: the primary key is the composite key ofORDER_IDandPRODUCT_ID. Both are foreign keys to theT_ORDERandT_PRODUCTtables with the many-to-one relationship.T_PRODUCT_PROFILE: the primary key is theIDcolumn, which is also the foreign key to theT_PRODUCTtable with the one-to-one relationship.
2. Setup
In this step, I will create a gradle project along with JDK17, Spring Web, Spring Data JPA, Lombok, H2, and Junit5 libraries via Spring Initializer.
2.1 Gradle Build File
No modification is needed for the generated build.gradle file.
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.0'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'org.zheng.demo'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
2.2 Spring Boot Application
No modification is needed for the generated DemoApplication.java file.
DemoApplication
package org.zheng.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2.3 Spring Properties
Update the generated application.properties file to enable the formatted Hibernate logging for SQL statements and binding parameter values.
application.properties
spring.application.name=demo spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.format_sql=true logging.level.org.hibernate.orm.jdbc.bind=trace spring.h2.console.enabled=true
3. One-to-One with @MapsId
3.1 Product Entity
In this step, I will create a Product.java entity class that has a name and id fields.
Product.java
package org.zheng.demo.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "T_PRODUCT")
@Data
@NoArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
3.2 ProductProfile Entity with @MapsId Annotation
In this step, I will create a ProductProfile.java entity class that has a productDetail and id fields. The @MapsId("id") annotation maps the id field as the foreign key to the Product entity’s primary key with the @OneToOne annotation.
ProductProfile.java
package org.zheng.demo.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.MapsId;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "T_PRODUCT_PROFILE")
@Data
@NoArgsConstructor
public class ProductProfile {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@MapsId("id") // maps to primary "id"
@OneToOne
@JoinColumn(name = "id") // foreign key column in T_PRODUCT Table
private Product product;
private String productDetail;
}
- Line 23: the
"id"attribute in the@MapsId("id")annotation indicates that the primary key “id” is the foreign key of theProductentity. - Line 24: the
@OneToOneannotation indicates thatProductandProductProfilehave the One-to-One relationship.
3.3 Product Repository
In this step, I will create a ProductRepo.java interface that extends from JpaRepository.
ProductRepo.java
package org.zheng.demo.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.zheng.demo.entity.Product;
@Repository
public interface ProductRepo extends JpaRepository<Product, Integer> {
List<Product> findByName(String prodName);
}
3.4 ProductProfile Repository
In this step, I will create a ProductProfileRepo.java interface that extends from JpaRepository.
ProductProfileRepo.java
package org.zheng.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.zheng.demo.entity.ProductProfile;
@Repository
public interface ProductProfileRepo extends JpaRepository<ProductProfile, Integer> {
}
3.5 Test Product Repository
In this step, I will create a ProductRepoTest.java class that tests the save and find methods.
ProductRepoTest.java
package org.zheng.demo.repository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.transaction.annotation.Transactional;
import org.zheng.demo.entity.Product;
import org.zheng.demo.entity.ProductProfile;
@DataJpaTest
@Transactional
class ProductRepoTest {
@Autowired
private ProductRepo prodRepo;
@Autowired
private ProductProfileRepo prodProfileRepo;
@Test
void save_product_and_profile() {
Product prod = new Product();
prod.setName("product");
prodRepo.save(prod);
ProductProfile prodProfile = new ProductProfile();
prodProfile.setProduct(prod);
prodProfile.setProductDetail("some details");
prodProfileRepo.save(prodProfile);
Optional<Product> foundProd = prodRepo.findById(prod.getId());
Optional<ProductProfile> foundProdProfile = prodProfileRepo.findById(prodProfile.getId());
assertTrue(foundProd.isPresent());
assertTrue(foundProdProfile.isPresent());
assertEquals(foundProd.get().getId(), foundProdProfile.get().getId());
}
}
4. Many-to-One with @MapsId
4.1 Order Entity
In this step, I will create an Order.java entity class that has a name and id fields.
Order.java
package org.zheng.demo.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "T_ORDER")
@Data
@NoArgsConstructor
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
4.2 OrderItemId Embeddable
In this step, I will create an OrderItemId.java class annotated with @Embeddable to define a composite key with both orderId and productId fields.
OrderItemId.java
package org.zheng.demo.entity;
import java.io.Serializable;
import java.util.Objects;
import jakarta.persistence.Embeddable;
import lombok.Getter;
import lombok.Setter;
@Embeddable
@Getter
@Setter
public class OrderItemId implements Serializable {
private static final long serialVersionUID = 2946564272714473110L;
private Integer orderId;
private Integer productId;
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OrderItemId other = (OrderItemId) obj;
return Objects.equals(orderId, other.orderId) && Objects.equals(productId, other.productId);
}
@Override
public int hashCode() {
return Objects.hash(orderId, productId);
}
}
4.3 OrderLineItem Entity With @MapsId Annotation
In this step, I will create an OrderLineItem.java entity class that uses @MapsId annotation to map the orderId field as the foreign key to the Order entity’s primary key with the @ManyToOne annotation. It also maps the productId fields as the foreign key to the Product entity’s primary key with @MapsId.
OrderLineItem.java
package org.zheng.demo.entity;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapsId;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "T_ORDER_ITEM")
@Data
@NoArgsConstructor
public class OrderLineItem {
@EmbeddedId
private OrderItemId id;// primary key of the order line item
@MapsId("orderId") // maps to the "orderId" field in "OrderItemId" class
@ManyToOne
@JoinColumn(name = "order_id") // foreign key column in T_Order_Item Table
private Order order;
@MapsId("productId") // maps to "productId" in "OrderItemId"
@ManyToOne
@JoinColumn(name = "product_id") // foreign key column in T_Order_Item Table
private Product product;
private Integer quantity;
}
- Line 18:
@EmbeddedIdannotation for the@Embeddablecomposite key defined in step 4.2. - Line 21:
@MapsIdannotation maps theorderIdfield as the foreign key to theOrderentity’s primary key with the@ManyToOneannotation. - Line 26:
@MapsIdannotation maps theproductIdfield as the foreign key to theProductentity’s primary key with the@ManyToOneannotation.
4.4 Order Repository
In this step, I will create an OrderRepo.java interface that extends from JpaRepository.
OrderRepo.java
package org.zheng.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.zheng.demo.entity.Order;
@Repository
public interface OrderRepo extends JpaRepository<Order, Integer> {
}
4.5 OrderLineItem Repository
In this step, I will create an OrderLineItemRepo.java interface that extends from JpaRepository.
OrderLineItemRepo.java
package org.zheng.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.zheng.demo.entity.OrderLineItem;
@Repository
public interface OrderLineItemRepo extends JpaRepository<OrderLineItem, Integer> {
}
4.6 Test Order Repository
In this step, I will create an OrderRepoTest.java to test both save and find methods.
OrderRepoTest.java
package org.zheng.demo.repository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.annotation.Transactional;
import org.zheng.demo.entity.Order;
import org.zheng.demo.entity.OrderItemId;
import org.zheng.demo.entity.OrderLineItem;
import org.zheng.demo.entity.Product;
import org.zheng.demo.entity.ProductProfile;
@DataJpaTest
@Transactional
class OrderRepoTest {
@Autowired
private OrderLineItemRepo lineItemRepo;
@Autowired
private OrderRepo orderRep;
@Autowired
private ProductProfileRepo prodProfRepo;
@Autowired
private ProductRepo prodRepo;
private Product prod1;
private Product prod2;
@BeforeEach
void setup() {
prod1 = saveProduct("product 1", "some details");
prod2 = saveProduct("product 2", "some details");
}
private Order buildOrder(final String orderName) {
Order order = new Order();
order.setName(orderName);
return order;
}
private OrderLineItem buildOrderItem(final Order order, final Product prod, final Integer itemQuantity) {
OrderLineItem item = new OrderLineItem();
OrderItemId itemId1 = new OrderItemId();
itemId1.setOrderId(order.getId());
itemId1.setProductId(prod.getId());
item.setId(itemId1);
item.setOrder(order);
item.setProduct(prod);
return item;
}
@Test
void test_save_order() {
// create an order
Order order = buildOrder("first order");
assertNull(order.getId());
orderRep.save(order);
assertNotNull(order.getId());
// create an order line item
OrderLineItem item1 = buildOrderItem(order, prod1, 1);
lineItemRepo.save(item1);
OrderLineItem item2 = buildOrderItem(order, prod2, 2);
lineItemRepo.save(item2);
List<OrderLineItem> items = lineItemRepo.findAll();
assertEquals(2, items.size());
}
@Test
void test_save_order_update() {
// create an order
Order order = buildOrder("first order");
orderRep.save(order);
// create an order line item
OrderLineItem item1 = buildOrderItem(order, prod1, 1);
lineItemRepo.save(item1);
item1.setQuantity(2);
lineItemRepo.save(item1);
List<OrderLineItem> items = lineItemRepo.findAll();
assertEquals(1, items.size());
}
@Test
void test_save_item_wo_prod_throw_error() {
// create an order
Order order = buildOrder("first order");
orderRep.save(order);
// create an order line item
OrderLineItem item1 = buildOrderItem(order, prod1, 1);
item1.setProduct(null);
JpaSystemException jpaException = assertThrows(JpaSystemException.class, () -> lineItemRepo.save(item1));
assertTrue(jpaException.getMessage().contains(
"attempted to assign id from null one-to-one property [org.zheng.demo.entity.OrderLineItem.product"));
}
private Product saveProduct(final String productName, final String prodDetail) {
Product prod = new Product();
prod.setName(productName);
prod = prodRepo.save(prod);
ProductProfile prodProfile = new ProductProfile();
prodProfile.setProduct(prod);
prodProfile.setProductDetail(prodDetail);
prodProfRepo.save(prodProfile);
return prod;
}
}
5. Demonstrate Controller
5.1 Demo Service
In this step, I will create a DemoService.java service that wires the entities and repositories to save a product along with its profile data. It also saves an order and its line items.
DemoService.java
package org.zheng.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.zheng.demo.entity.Order;
import org.zheng.demo.entity.OrderItemId;
import org.zheng.demo.entity.OrderLineItem;
import org.zheng.demo.entity.Product;
import org.zheng.demo.entity.ProductProfile;
import org.zheng.demo.repository.OrderLineItemRepo;
import org.zheng.demo.repository.OrderRepo;
import org.zheng.demo.repository.ProductProfileRepo;
import org.zheng.demo.repository.ProductRepo;
@Service
@Transactional
public class DemoService {
@Autowired
private OrderLineItemRepo lineItemRepo;
@Autowired
private OrderRepo orderRep;
@Autowired
private ProductProfileRepo prodProfRepo;
@Autowired
private ProductRepo prodRepo;
private Order buildOrder(final String orderName) {
Order order = new Order();
order.setName(orderName);
return order;
}
private OrderLineItem buildOrderItem(final Order order, final String product, final Integer itemQuantity) {
OrderItemId itemId1 = new OrderItemId();
itemId1.setOrderId(order.getId());
Product prod = findProduct(product);
itemId1.setProductId(findProduct(product).getId());
OrderLineItem item = new OrderLineItem();
item.setId(itemId1);
item.setOrder(order);
item.setProduct(prod);
item.setQuantity(itemQuantity);
return item;
}
public List<OrderLineItem> getOrderItems() {
List<OrderLineItem> found = lineItemRepo.findAll();
if (found.isEmpty()) {
dummyOrderData();
} else {
return found;
}
return lineItemRepo.findAll();
}
private Order saveOrder(final String orderDesc) {
return orderRep.save(buildOrder(orderDesc));
}
private void saveOrderLineItem(final Order order, final String product, final Integer quantity) {
lineItemRepo.save(buildOrderItem(order, product, quantity));
}
private void dummyOrderData() {
Order order = saveOrder("first order");
saveOrderLineItem(order, "product 1", 1);
saveOrderLineItem(order, "product 2", 2);
}
private Product saveProduct(final String productName, final String prodDetail) {
Product prod = new Product();
prod.setName(productName);
prod = prodRepo.save(prod);
ProductProfile prodProfile = new ProductProfile();
prodProfile.setProduct(prod);
prodProfile.setProductDetail(prodDetail);
prodProfRepo.save(prodProfile);
return prod;
}
private Product findProduct(final String prodName) {
List<Product> found = prodRepo.findByName(prodName);
if (found.isEmpty()) {
return saveProduct(prodName, "some Detail");
}
return found.get(0);
}
public List<ProductProfile> getProductProfiles() {
List<ProductProfile> found = prodProfRepo.findAll();
if (found.isEmpty()) {
saveProduct("prod 1", "prod detail");
} else {
return found;
}
return prodProfRepo.findAll();
}
}
5.2 Test Demo Service
In this step, I will create a DemoServiceTest.java to test the getProductProfiles and getOrderItems methods.
DemoServiceTest.java
package org.zheng.demo.controller;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.zheng.demo.entity.OrderLineItem;
import org.zheng.demo.entity.ProductProfile;
@SpringBootTest
class DemoServiceTest {
@Autowired
private DemoService testService;
@Test
void test_getProductProfiles() {
List<ProductProfile> foundProdProfile = testService.getProductProfiles();
assertTrue(foundProdProfile.size() > 0);
}
@Test
void test_getOrderItems() {
List<OrderLineItem> foundOrderLineItems = testService.getOrderItems();
assertTrue(foundOrderLineItems.size() > 0);
}
}
Ran the Junit tests and captured the passed results.
5.3 Demo @RestController
In this step, I will create a DemoController.java class with two RESTful APIs that return the order line items and product profile information.
DemoController.java
package org.zheng.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.zheng.demo.entity.OrderLineItem;
import org.zheng.demo.entity.ProductProfile;
@RestController
public class DemoController {
@Autowired
private DemoService testService;
@GetMapping("/orderItems")
List<OrderLineItem> getOrderItems() {
return testService.getOrderItems();
}
@GetMapping("/productProfiles")
List<ProductProfile> getProductProfiles(){
return testService.getProductProfiles();
}
}
5.4 Start the Spring Boot Application
In this step, I will start the spring boot application and capture the server log. The log outlines the database table creation along with the foreign key constraints added by the @MapsId annotation.
server log
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.0)
2024-12-08T09:41:01.036-06:00 INFO 58456 --- [demo] [ main] org.zheng.demo.DemoApplication : Starting DemoApplication using Java 17.0.11 with PID 58456 (C:\MaryTools\workspace\demomapsid\bin\main started by azpm0 in C:\MaryTools\workspace\demomapsid)
2024-12-08T09:41:01.042-06:00 INFO 58456 --- [demo] [ main] org.zheng.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2024-12-08T09:41:01.840-06:00 INFO 58456 --- [demo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-12-08T09:41:01.931-06:00 INFO 58456 --- [demo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 80 ms. Found 4 JPA repository interfaces.
2024-12-08T09:41:02.691-06:00 INFO 58456 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-12-08T09:41:02.709-06:00 INFO 58456 --- [demo] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-12-08T09:41:02.709-06:00 INFO 58456 --- [demo] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.33]
2024-12-08T09:41:02.780-06:00 INFO 58456 --- [demo] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-12-08T09:41:02.782-06:00 INFO 58456 --- [demo] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1677 ms
2024-12-08T09:41:02.827-06:00 INFO 58456 --- [demo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2024-12-08T09:41:03.036-06:00 INFO 58456 --- [demo] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:e0372e7a-9c1e-4bda-9091-a636ecf41863 user=SA
2024-12-08T09:41:03.038-06:00 INFO 58456 --- [demo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2024-12-08T09:41:03.049-06:00 INFO 58456 --- [demo] [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:e0372e7a-9c1e-4bda-9091-a636ecf41863'
2024-12-08T09:41:03.233-06:00 INFO 58456 --- [demo] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2024-12-08T09:41:03.306-06:00 INFO 58456 --- [demo] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.6.2.Final
2024-12-08T09:41:03.352-06:00 INFO 58456 --- [demo] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled
2024-12-08T09:41:03.677-06:00 INFO 58456 --- [demo] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2024-12-08T09:41:03.742-06:00 WARN 58456 --- [demo] [ main] org.hibernate.orm.deprecation : HHH90000025: H2Dialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2024-12-08T09:41:03.770-06:00 INFO 58456 --- [demo] [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
Database driver: undefined/unknown
Database version: 2.3.232
Autocommit mode: undefined/unknown
Isolation level: undefined/unknown
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2024-12-08T09:41:04.662-06:00 INFO 58456 --- [demo] [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
Hibernate:
create table t_order (
id integer generated by default as identity,
name varchar(255),
primary key (id)
)
Hibernate:
create table t_order_item (
quantity integer,
order_id integer not null,
product_id integer not null,
primary key (order_id, product_id)
)
Hibernate:
create table t_product (
id integer generated by default as identity,
name varchar(255),
primary key (id)
)
Hibernate:
create table t_product_profile (
id integer generated by default as identity,
product_detail varchar(255),
primary key (id)
)
Hibernate:
alter table if exists t_order_item
add constraint FK2y83rerik30vumt2a1mff6606
foreign key (order_id)
references t_order
Hibernate:
alter table if exists t_order_item
add constraint FKn4krp2vsjtox6l7sj5h55inx0
foreign key (product_id)
references t_product
Hibernate:
alter table if exists t_product_profile
add constraint FKghkpnmy480sm9k7nwayvyj6c1
foreign key (id)
references t_product
2024-12-08T09:41:04.727-06:00 INFO 58456 --- [demo] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-12-08T09:41:05.175-06:00 WARN 58456 --- [demo] [ main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-12-08T09:41:05.642-06:00 INFO 58456 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2024-12-08T09:41:05.655-06:00 INFO 58456 --- [demo] [ main] org.zheng.demo.DemoApplication : Started DemoApplication in 5.074 seconds (process running for 5.506)
- Line 38-41: DDL for
T_ORDERtable. - Line 44-48: DDL for
T_ORDER_ITEMtable. - Line 51-54: DDL for
T_PRODUCTtable. - Line 57-60: DDL for
T_PRODUCT_PROFILEtable. - Line 63-66, 68-71: the two foreign key constraints created by the
@MapsIdin theT_ORDER_ITEMtable. - Line 73-76: the foreign key constraint created by
@MapsIdin theT_PRODUCT_PROFILEtable.
You can test the web application by testing both http://localhost:8080/productProfiles and http://localhost:8080/orderItems.
6. Conclusion
In this example, I created a simple Spring web application that defined entities with @MapsId annotation that mapped a field as a foreign key in a one-to-one or many-to-one relationship to another entity. The @MapsId annotation is introduced in JPA 2.0. It’s in the jakarta.persistence package starting from JakartaEE 9 and in the javax.persistence package before JakartaEE 9. Here are key notes about @MapsId annotation:
@MapsIdmaps a specific part of a composite key to a foreign key. E.g.orderIdandproductIdatOrderLineItemdefined in step 4.3.@MapsIdis used on fields that map relationships. E.g.@ManyToOneor@OneToOne.@MapsIdis always used with an@EmbeddedIdor@IdClass.
7. Download
This was an example of a gradle project which included @MapsId annotation.
You can download the full source code of this example here: @MapsId Annotation Example



