Enterprise Java

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

/**
 * Maps an immutable and read-only entity to a given SQL {@code select} expression.
 * 

* 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(); }

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:

Figure 1. Create a Spring Boot Project

Imported the generated maven spring boot project into Eclipse IDE. Verified the pom.xml as the following.

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	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

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 table T_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 between Customer and Order.

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

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 table T_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

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

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

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

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

SELECT id,
       NAME,
       email_address AS email
FROM   t_customer
WHERE  type = 'Major'  

This subquery returns the customer whose type = ‘Major‘.

MajorCustomer.java

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

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

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

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

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, and MajorCustomerService 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

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

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: 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

[
  {
    "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

[
  {
    "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.

Download
You can download the full source code of this example here: Hibernate @Subselect Annotation

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button