java
41 lines · 7 steps
Projection interfaces in Spring Data JPA
A repository maps a grouped revenue query straight onto a typed interface, computed fields and all.
Explained by
highlit
1package com.example.orders.repository;
2
3import com.example.orders.domain.Order;
4import org.springframework.data.jpa.repository.JpaRepository;
5import org.springframework.data.jpa.repository.Query;
6import org.springframework.data.repository.query.Param;
7
8import java.math.BigDecimal;
9import java.time.Instant;
10import java.util.List;
11
12public interface OrderRepository extends JpaRepository<Order, Long> {
13
14 interface CustomerRevenue {
15 Long getCustomerId();
16 String getCustomerName();
17 long getOrderCount();
18 BigDecimal getTotalRevenue();
19
20 default boolean isHighValue() {
21 return getTotalRevenue().compareTo(new BigDecimal("1000")) >= 0;
22 }
23 }
24
25 @Query("""
26 select c.id as customerId,
27 c.name as customerName,
28 count(o) as orderCount,
29 coalesce(sum(o.total), 0) as totalRevenue
30 from Order o
31 join o.customer c
32 where o.placedAt >= :since
33 and o.status = com.example.orders.domain.OrderStatus.COMPLETED
34 group by c.id, c.name
35 having sum(o.total) > :threshold
36 order by sum(o.total) desc
37 """)
38 List<CustomerRevenue> findRevenueByCustomerSince(
39 @Param("since") Instant since,
40 @Param("threshold") BigDecimal threshold);
41}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Interface projections let Spring Data return exactly the columns a query selects, without a dedicated entity or DTO class.
- 2JPQL aliases must match projection getter names so Spring can bind each selected value to the right accessor.
- 3Default methods on a projection interface add derived logic that lives with the data rather than in calling code.
Related explainers
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/projection-interfaces-in-spring-data-jpa-explained-java-e484/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.