Hibernate Subselect Annotation
1. Introduction
Hibernate is an open source object relational mapping (ORM) tool that provides a framework to map object-oriented domain models to relational databases for web applications. The @org.hibernate.annotations.Subselect
annotation is a Hibernate-specific feature and is used to map an immutable entity to a SQL subselect statement. Hibernate does not support insert, update, or delete operations on these immutable entities. Here is the Hibernate subselect annotation definition:
@Subselect Definition
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | /** * Maps an immutable and read-only entity to a given SQL {@code select} expression. * <p> * This is an alternative to defining a database view and mapping the entity to * the view using the {@link jakarta.persistence.Table @Table} annotation. * * @see Synchronize * * @author Sharath Reddy */ @Target (TYPE) @Retention (RUNTIME) public @interface Subselect { /** * The query. */ String value(); }</p> |
As you see from the definition. @Subselect
is an alternative way to map an entity to a database view or subquery. In this example, I will create a spring boot web application which relies on two hibernate entities: Customer
and Order
and two immutable entities: CustomerOrder
and MajorCustomer
.
2. Set up Maven Spring Boot Web Application
Navigate to Spring Initializr, select Maven, Java, and “Spring Data JPA”, “H2 Database”, and “Spring Web” dependencies as the following screenshot:
Imported the generated maven spring boot project into Eclipse IDE. Verified the pom.xml
as the following.
pom.xml
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | <? xml version = "1.0" encoding = "UTF-8" ?> xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > < modelVersion >4.0.0</ modelVersion > < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >3.3.0</ version > < relativePath /> <!-- lookup parent from repository --> </ parent > < groupId >org.zheng.demo</ groupId > < artifactId >sebselect-demo</ artifactId > < version >0.0.1-SNAPSHOT</ version > < name >sebselect-demo</ name > < description >Demo project for Spring Boot</ description > < properties > < java.version >17</ java.version > </ properties > < dependencies > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-data-jpa</ artifactId > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > < dependency > < groupId >com.h2database</ groupId > < artifactId >h2</ artifactId > < scope >runtime</ scope > </ dependency > < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > </ dependency > </ dependencies > < build > < plugins > < plugin > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-maven-plugin</ artifactId > </ plugin > </ plugins > </ build > </ project > |
3. Customer and Order Entities
In this example, I will create two entities and map them to two tables.
3.1 Customer Entity
In this step, I will create a Customer
class which annotates with the @Entity
annotation. The Customer
class has five members:
emailAddress
– the customer email address.id
– the primary key of the customer.name
– the customer’s name.type
– the customer type. If the customer’s order amount exceeds 5000, then it is a major customer.orders
– a list of orders belong to this customer.
Customer.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | package org.zheng.demo.entity; import java.util.List; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; @Entity (name = "T_Customer" ) public class Customer { private String emailAddress; @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; private String name; @OneToMany (mappedBy = "customer" ) private List<Order> orders; private String type; public Customer() { super (); } public Customer(String name, String emailAddress) { super (); this .name = name; this .emailAddress = emailAddress; } public String getEmailAddress() { return emailAddress; } public Long getId() { return id; } public String getName() { return name; } public List<Order> getOrders() { return orders; } public String getType() { return type; } public void setEmailAddress(String emailAddress) { this .emailAddress = emailAddress; } public void setId(Long id) { this .id = id; } public void setName(String name) { this .name = name; } public void setOrders(List<Order> orders) { this .orders = orders; } public void setType(String type) { this .type = type; } @Override public String toString() { return "Customer [emailAddress=" + emailAddress + ", id=" + id + ", name=" + name + ", type=" + type + "]" ; } } |
- Line 11: annotates with
@Entity
and maps to H2 tableT_Customer
. - Line 15, 16: annotates with
@Id
and@GeneratedValue
that auto generates the primary unique id. - Line 21: annotates with
@OneToMany
that sets up a 1-to-many relationship betweenCustomer
andOrder
.
3.2 Order Entity
In this step, I will create an Order
class which annotates with the @Entity
annotation. The Order
class has the following members:
amount
– the order amount.id
– the primary key of the order.name
– the order name.customer
– the customer that the order belongs to.quantity
– the order quantity.
Order.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | package org.zheng.demo.entity; import java.math.BigDecimal; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @Entity (name = "T_Order" ) public class Order { private BigDecimal amount; @ManyToOne @JoinColumn (name = "cust_id" ) private Customer customer; @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; private String name; private int quantity; public Order() { super (); } public Order(String name, int quantity, BigDecimal amount, Customer customer) { super (); this .name = name; this .quantity = quantity; this .customer = customer; this .amount = amount; } public BigDecimal getAmount() { return amount; } public Customer getCustomer() { return customer; } public Long getId() { return id; } public String getName() { return name; } public int getQuantity() { return quantity; } public void setAmount(BigDecimal amount) { this .amount = amount; } public void setCustomer(Customer customer) { this .customer = customer; } public void setId(Long id) { this .id = id; } public void setName(String name) { this .name = name; } public void setQuantity( int quantity) { this .quantity = quantity; } @Override public String toString() { return "Order [customer=" + customer + ", id=" + id + ", name=" + name + ", quantity=" + quantity + ", amount=" + amount + "]" ; } } |
- Line 12: annotates with
@Entity
and map to H2 tableT_Order
. - Line 16, 17: annotates with
@ManyToOne
and@JoinColumn
to define the foreign key column:cust_id
. - Line 20, 21: annotates with
@Id
,@GeneratedValue
for the primary key.
3.3 Customer Repository
In this step, I will create a CustomerRepo
interface which extends from JpaRepository
.
CustomerRepo.java
1 2 3 4 5 6 7 8 | package org.zheng.demo.repo; import org.springframework.data.jpa.repository.JpaRepository; import org.zheng.demo.entity.Customer; public interface CustomerRepo extends JpaRepository<Customer, Long> { } |
- Line 6: extends from
JpaRepository
.
3.4 Order Repository
In this step, I will create an OrderRepo
interfaceclass which extends from JpaRepository
.
OrderRepo.java
1 2 3 4 5 6 7 8 | package org.zheng.demo.repo; import org.springframework.data.jpa.repository.JpaRepository; import org.zheng.demo.entity.Order; public interface OrderRepo extends JpaRepository<Order, Long> { } |
- Line 6: extends from
JpaRepository
.
4. Immutable Entities
In this example, I will create two immutable entities and map to two subqueries.
4.1 CustomerOrder
In this step, I will create an immutable entity CustomerOrder
class with the @Entity
, @Immutable
, and @Subselect
annotations. Here is the sub query used with the @Subselect
.
Customer Order SubQuery
01 02 03 04 05 06 07 08 09 10 11 | SELECT t_customer.id id, t_customer. NAME NAME , a.total total FROM t_customer, ( SELECT c.id, Sum (o.amount) total FROM t_customer c, t_order o WHERE c.id = o.cust_id GROUP BY c.id) a WHERE a.id = t_customer.id |
This subquery returns the customer along with its order’s total amount.
CustomerOrder.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | package org.zheng.demo.subselect; import java.math.BigDecimal; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Subselect; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @Entity @Immutable @Subselect ( "select t_customer.id id, t_customer.name name, a.total total " + "from t_customer ," + "( select c.id, sum(o.amount) total " + "from t_customer c, t_order o " + "where c.id = o.cust_id " + "group by c.id " + ") a where a.id = t_customer.id" ) public class CustomerOrder { @Id @Column (name = "id" ) private Long id; @Column (name = "name" ) private String customerName; @Column (name = "total" ) private BigDecimal totalOrderAmount; public Long getId() { return id; } public void setId(Long id) { this .id = id; } public String getCustomerName() { return customerName; } public void setCustomerName(String customerName) { this .customerName = customerName; } public BigDecimal getTotalOrderAmount() { return totalOrderAmount; } public void setTotalOrderAmount(BigDecimal totalOrderAmount) { this .totalOrderAmount = totalOrderAmount; } } |
- Line 12,13,14: creates an immutable entity with
@Entity
,@Immutable
, and@Subselect
. Note, the subselect query is the SQL query. - Line 20, 23, 26: maps the immutable entity to the subselect query’s columns.
4.2 MajorCustomer
In this step, I will create an immutable entity MajorCustomer
class which represents a customer whose type
equals “Major
“.
Major Customer SubQuery
1 2 3 4 5 | SELECT id, NAME , email_address AS email FROM t_customer WHERE type = 'Major' |
This subquery returns the customer whose type = ‘Major
‘.
MajorCustomer.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | package org.zheng.demo.subselect; import org.hibernate.annotations.Subselect; import org.springframework.data.annotation.Immutable; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; @Entity @Immutable @Subselect ( "SELECT id, name, email_Address as email FROM t_customer where type= 'Major' " ) public class MajorCustomer { @Column (name = "email" ) private String email; @Id @Column (name = "id" ) private Long id; @Column (name = "name" ) private String name; public String getEmail() { return email; } public Long getId() { return id; } public String getName() { return name; } public void setEmail(String email) { this .email = email; } public void setId(Long id) { this .id = id; } public void setName(String name) { this .name = name; } } |
- Line 10, 11, 12: creates an immutable
MajorCustomer
entity whose type equals “Major
“. - Line 19, 22: maps the immutable entity to the subselect query’s columns.
4.3 CustomerOrderService
In this step, I will create a CustomerOrderService
class which returns a list of CustomerOrder
that contains customer data and its orders’ total amount.
CustomerOrderService.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package org.zheng.demo.service; import java.util.List; import org.springframework.stereotype.Service; import org.zheng.demo.subselect.CustomerOrder; import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; @Service public class CustomerOrderService { private EntityManager em; public CustomerOrderService(EntityManager em) { super (); this .em = em; } public List<CustomerOrder> getCustomerOrders() { TypedQuery<CustomerOrder> ret = em.createQuery( "select o from CustomerOrder o" , CustomerOrder. class ); return ret.getResultList(); } } |
- Line 16: inject
EntityManager
. - Line 22: create a JPQL query to access the immutable
CustomerOrder
entity and return the results.
4.4 MajorCustomerService
In this step, I will create a MajorCustomerService
class which returns a list of major customers.
MajorCustomerService.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package org.zheng.demo.service; import java.util.List; import org.springframework.stereotype.Service; import org.zheng.demo.subselect.MajorCustomer; import jakarta.persistence.EntityManager; import jakarta.persistence.TypedQuery; @Service public class MajorCustomerService { private EntityManager entityManager; public MajorCustomerService(EntityManager entityManager) { this .entityManager = entityManager; } public List<MajorCustomer> getMajorCustomers() { TypedQuery<MajorCustomer> query = entityManager.createQuery( "SELECT o FROM MajorCustomer o" , MajorCustomer. class ); return query.getResultList(); } } |
- Line 21: returns a list of
MajorCustomer
from the immutable entity.
5. Web Service
In this step, I will create two Restful endpoints which utilize the data and services created at step 3 and 4 to return a list of major customer and customer’s order total amounts.
5.1 ReportRest
In this step, I will create a ReportRest
class which has two get endpoints.
ReportRest.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package org.zheng.demo.rest; 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.service.MajorCustomerService; import org.zheng.demo.service.CustomerOrderService; import org.zheng.demo.subselect.MajorCustomer; import org.zheng.demo.subselect.CustomerOrder; @RestController public class ReportRest { @Autowired private MajorCustomerService custService; @Autowired private CustomerOrderService opService; @GetMapping ( "/majorCustomers" ) public List getMajorCustomers() { return custService.getMajorCustomers(); } @GetMapping ( "/customerOrders" ) public List getOrderReport() { return opService.getCustomerOrders(); } } |
- Line 22: defines a
GetMapping
to “/majorCustomers
” to return a list of major customers. - Line 27: defines a
GetMapping
to “/customerOrders
” to return a list of customers with its total order amount.
5.2 SebselectDemoApplication
In this step, I will update the generated SebselectDemoApplication
class to insert five customers and 10 orders and calculate the customer’s order total amount and set the customer as a major customer if its total order amount exceeds 5000.
SebselectDemoApplication.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | package org.zheng.demo; import java.math.BigDecimal; import java.util.Arrays; import java.util.Optional; import java.util.Random; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.zheng.demo.entity.Customer; import org.zheng.demo.entity.Order; import org.zheng.demo.repo.CustomerRepo; import org.zheng.demo.repo.OrderRepo; import org.zheng.demo.service.MajorCustomerService; import org.zheng.demo.service.CustomerOrderService; @SpringBootApplication public class SebselectDemoApplication { public static void main(String[] args) { SpringApplication.run(SebselectDemoApplication. class , args); } @Bean public CommandLineRunner demo(CustomerRepo custRep, OrderRepo orderRep, CustomerOrderService orService, MajorCustomerService ms) { return (args) -> { // save a few customers String[] custs = { "mary" , "zheng" , "john" , "bob" , "terry" }; String[] orders = { "PC" , "Phone" , "Laptop" , "TV" , "XBox" }; BigDecimal[] amount = { new BigDecimal( 1000 ), new BigDecimal( 500 ), new BigDecimal( 800 ), new BigDecimal( 3000 ), new BigDecimal( 600 ) }; Arrays.asList(custs).stream().forEach(cust -> { Customer customer = new Customer(cust, cust + "@test.com" ); custRep.save(customer); Random random = new Random(); int randomNumber = random.nextInt(orders.length); Order order = new Order(orders[randomNumber], randomNumber, randomNumber == 0 ? amount[randomNumber] : amount[randomNumber].multiply( new BigDecimal(randomNumber)), customer); orderRep.save(order); randomNumber = random.nextInt(orders.length); order = new Order(orders[randomNumber], randomNumber, randomNumber == 0 ? amount[randomNumber] : amount[randomNumber].multiply( new BigDecimal(randomNumber)), customer); orderRep.save(order); }); orderRep.findAll().forEach(order -> { System.out.println(order.toString()); }); orService.getCustomerOrders().stream().filter(orRe -> orRe.getTotalOrderAmount().intValue() > 5000 ) .forEach(major -> { Optional<Customer> majorCust = custRep.findById(major.getId()); if (majorCust.isPresent()) { Customer major2 = majorCust.get(); major2.setType( "Major" ); custRep.save(major2); } }); System.out.println( "Major customers with total exceed 5000." ); ms.getMajorCustomers().stream().forEach(m -> System.out.println(m.getName())); }; } } |
- Line 28: injects the
CustomerRepo
,OrderRepo
,CustomerOrderService
, andMajorCustomerService
created at earlier steps. - Line 32, 33, 34: initializes five customers’ name, product and and its sale amount.
- Line 39: saves the test customer.
- Line 45,50: saves the test orders for each customer.
- Line 55: prints out the test orders details, so we can use it to verify the data at step 6.
- Line 58: finds the major customer based on its total order amount exceeding 5000.
- Line 66: updates the customer’s type to “
Major
“.
5.3 SB Application Properties
In this step, I will configure H2 datasource in application.properties
.
applicatio.properties
01 02 03 04 05 06 07 08 09 10 11 | spring.application.name=sebselect-demo spring.jpa.show-sql=true spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password= spring.h2.console.enabled=true spring.jpa.hibernate.ddl-auto=update spring.datasource.initialization-mode=always |
6. Demo
Start the spring boot application, verify the application is started and capture the server log.
Server Log
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.3.0) 2024-05-25T14:05:14.845-05:00 INFO 14956 --- [sebselect-demo] [ main] o.zheng.demo.SebselectDemoApplication : Starting SebselectDemoApplication using Java 17.0.11 with PID 14956 (C:\MaryTools\workspace\sebselect-demo\target\classes started by azpm0 in C:\MaryTools\workspace\sebselect-demo) 2024-05-25T14:05:14.851-05:00 INFO 14956 --- [sebselect-demo] [ main] o.zheng.demo.SebselectDemoApplication : No active profile set, falling back to 1 default profile: "default" 2024-05-25T14:05:15.589-05:00 INFO 14956 --- [sebselect-demo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2024-05-25T14:05:15.650-05:00 INFO 14956 --- [sebselect-demo] [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 51 ms. Found 2 JPA repository interfaces. 2024-05-25T14:05:16.244-05:00 INFO 14956 --- [sebselect-demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2024-05-25T14:05:16.254-05:00 INFO 14956 --- [sebselect-demo] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2024-05-25T14:05:16.254-05:00 INFO 14956 --- [sebselect-demo] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.24] 2024-05-25T14:05:16.322-05:00 INFO 14956 --- [sebselect-demo] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2024-05-25T14:05:16.324-05:00 INFO 14956 --- [sebselect-demo] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1418 ms 2024-05-25T14:05:16.369-05:00 INFO 14956 --- [sebselect-demo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2024-05-25T14:05:16.553-05:00 INFO 14956 --- [sebselect-demo] [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:testdb user=SA 2024-05-25T14:05:16.555-05:00 INFO 14956 --- [sebselect-demo] [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2024-05-25T14:05:16.569-05:00 INFO 14956 --- [sebselect-demo] [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:testdb' 2024-05-25T14:05:16.713-05:00 INFO 14956 --- [sebselect-demo] [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2024-05-25T14:05:16.771-05:00 INFO 14956 --- [sebselect-demo] [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.5.2.Final 2024-05-25T14:05:16.809-05:00 INFO 14956 --- [sebselect-demo] [ main] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2024-05-25T14:05:17.105-05:00 INFO 14956 --- [sebselect-demo] [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2024-05-25T14:05:17.994-05:00 INFO 14956 --- [sebselect-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_customer (id bigint not null, email_address varchar(255), name varchar(255), type varchar(255), primary key (id)) Hibernate: create table t_order (id bigint not null, amount numeric(38,2), name varchar(255), quantity integer not null, cust_id bigint, primary key (id)) Hibernate: create sequence t_customer_seq start with 1 increment by 50 Hibernate: create sequence t_order_seq start with 1 increment by 50 Hibernate: alter table if exists t_order add constraint FKgt3n29ngim2bryiw3eimwjs52 foreign key (cust_id) references t_customer 2024-05-25T14:05:18.035-05:00 INFO 14956 --- [sebselect-demo] [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2024-05-25T14:05:18.309-05:00 WARN 14956 --- [sebselect-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-05-25T14:05:18.679-05:00 INFO 14956 --- [sebselect-demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2024-05-25T14:05:18.690-05:00 INFO 14956 --- [sebselect-demo] [ main] o.zheng.demo.SebselectDemoApplication : Started SebselectDemoApplication in 4.22 seconds (process running for 4.669) Hibernate: select next value for t_customer_seq Hibernate: insert into t_customer (email_address,name,type,id) values (?,?,?,?) Hibernate: select next value for t_order_seq Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: select next value for t_order_seq Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: select next value for t_customer_seq Hibernate: insert into t_customer (email_address,name,type,id) values (?,?,?,?) Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: insert into t_customer (email_address,name,type,id) values (?,?,?,?) Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: insert into t_customer (email_address,name,type,id) values (?,?,?,?) Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: insert into t_customer (email_address,name,type,id) values (?,?,?,?) Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: insert into t_order (amount,cust_id,name,quantity,id) values (?,?,?,?,?) Hibernate: select o1_0.id,o1_0.amount,o1_0.cust_id,o1_0.name,o1_0.quantity from t_order o1_0 Hibernate: select c1_0.id,c1_0.email_address,c1_0.name,c1_0.type from t_customer c1_0 where c1_0.id=? Hibernate: select c1_0.id,c1_0.email_address,c1_0.name,c1_0.type from t_customer c1_0 where c1_0.id=? Hibernate: select c1_0.id,c1_0.email_address,c1_0.name,c1_0.type from t_customer c1_0 where c1_0.id=? Hibernate: select c1_0.id,c1_0.email_address,c1_0.name,c1_0.type from t_customer c1_0 where c1_0.id=? Hibernate: select c1_0.id,c1_0.email_address,c1_0.name,c1_0.type from t_customer c1_0 where c1_0.id=? Order [customer=Customer [emailAddress=mary@test.com, id=1, name=mary, type=null], id=1, name=XBox, quantity=4, amount=2400.00] Order [customer=Customer [emailAddress=mary@test.com, id=1, name=mary, type=null], id=2, name=PC, quantity=0, amount=1000.00] Order [customer=Customer [emailAddress=zheng@test.com, id=2, name=zheng, type=null], id=3, name=TV, quantity=3, amount=9000.00] Order [customer=Customer [emailAddress=zheng@test.com, id=2, name=zheng, type=null], id=4, name=PC, quantity=0, amount=1000.00] Order [customer=Customer [emailAddress=john@test.com, id=3, name=john, type=null], id=5, name=PC, quantity=0, amount=1000.00] Order [customer=Customer [emailAddress=john@test.com, id=3, name=john, type=null], id=6, name=Phone, quantity=1, amount=500.00] Order [customer=Customer [emailAddress=bob@test.com, id=4, name=bob, type=null], id=7, name=PC, quantity=0, amount=1000.00] Order [customer=Customer [emailAddress=bob@test.com, id=4, name=bob, type=null], id=8, name=PC, quantity=0, amount=1000.00] Order [customer=Customer [emailAddress=terry@test.com, id=5, name=terry, type=null], id=9, name=XBox, quantity=4, amount=2400.00] Order [customer=Customer [emailAddress=terry@test.com, id=5, name=terry, type=null], id=10, name=PC, quantity=0, amount=1000.00] Hibernate: select co1_0.id,co1_0.name,co1_0.total from ( select t_customer.id id, t_customer.name name, a.total total from t_customer ,( select c.id, sum(o.amount) total from t_customer c, t_order o where c.id = o.cust_id group by c.id ) a where a.id = t_customer.id ) co1_0 Hibernate: select c1_0.id,c1_0.email_address,c1_0.name,c1_0.type from t_customer c1_0 where c1_0.id=? Hibernate: select c1_0.id,c1_0.email_address,c1_0.name,c1_0.type from t_customer c1_0 where c1_0.id=? Hibernate: update t_customer set email_address=?,name=?,type=? where id=? Major customers with total exceed 5000. Hibernate: select mc1_0.id,mc1_0.email,mc1_0.name from ( SELECT id, name, email_Address as email FROM t_customer where type= 'Major' ) mc1_0 zheng |
- Line 28-32: H2 table
T_Customer
,T_Order
are created. - Line 62-71: Customer order details.
- Line 76, 78: print out the major customers.
Open a web browser and navigate to the http://localhost:8080/customerOrders. You should see the following response:
Customer Orders
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | [ { "id": 1, "customerName": "mary", "totalOrderAmount": 3400 }, { "id": 2, "customerName": "zheng", "totalOrderAmount": 10000 }, { "id": 3, "customerName": "john", "totalOrderAmount": 1500 }, { "id": 4, "customerName": "bob", "totalOrderAmount": 2000 }, { "id": 5, "customerName": "terry", "totalOrderAmount": 3400 } ] |
As you see, the totalOrderAmount
for customer zheng
exceeds 5000. Navigate to the http://localhost:8080/majorCustomers. You should see the following response:
Major Customers
1 2 3 4 5 6 7 | [ { "email": "zheng@test.com", "id": 2, "name": "zheng" } ] |
7. Conclusion
@Subselect
is Hibernate featurue and not part of the JPA specification, so it may not be supported by JPA providers. Red Hat(Jboss) and Spring framework support @Subselect
, but EclipseLink, OpenJPA, and DataNucleus don’t support @Subselect
. It offers significant benefits for creating read-only views of data but comes with following constraints:
- Lack of caching: the results are not cached due to complex queries. This can impact performance if the subquery is expensive.
- Database dependence: The subquery is written in SQL, making it database-dependent.
- Initialization order: The underlying tables referenced in the subquery must be created before the
@Subselect
entity is accessed. This can lead to initialization issues in some cases. - Data consistency: since
@Subselect
entities are read-only, any changes in the underlying tables are not immediately reflected in these entities unless the session is refreshed or the query is re-executed. - Maintenance: maintaining complex subqueries within the entity definition can become challenging, especially as the database schema evolves.
- Performance trade-offs: while
@Subselect
can optimize read-heavy operations, it can also introduce performance bottlenecks if the subquery is not well-optimized.
8. Download
This was an example of Spring Boot web application which maps an immutable entity with @Subselect
annotation.
You can download the full source code of this example here: Hibernate @Subselect Annotation