An example project demonstrating how to integrate Blaze Persistence Entity Views with Spring GraphQL to achieve optimal query performance by fetching only the fields requested in the GraphQL query.
When building GraphQL APIs with JPA, a common problem is over-fetching data from the database. Even if the client requests only specific fields, traditional approaches often load entire entities with all their relationships.
This project solves that problem by combining:
- Spring GraphQL for the GraphQL layer
- Blaze Persistence Entity Views for projections that map directly to GraphQL selections
- Custom Repository Infrastructure that automatically applies GraphQL field selections to database queries
The result: if a client requests only id and name fields, the generated SQL will select only those columns.
| Technology | Version |
|---|---|
| Java | 25 |
| Spring Boot | 4.0.0 |
| Blaze Persistence | 1.6.17 |
| MySQL | 8.x |
- Selective Field Fetching: Only requested GraphQL fields are fetched from the database
- Entity Views as GraphQL Types: Blaze Persistence Entity Views map directly to GraphQL types
- Pagination Support: Built-in offset-based pagination with sorting
- Criteria Filtering: Type-safe filtering using Blaze Persistence CriteriaBuilder
- ThreadLocal DataFetchingEnvironment: Access GraphQL context at repository level without passing it through service layers
src/main/java/com/lprevidente/spring_graphql_blaze/
├── author/ # Author domain
│ ├── Author.java # JPA Entity
│ ├── AuthorView.java # Blaze Entity View (GraphQL type)
│ ├── AuthorGraphQLController.java
│ ├── AuthorService.java
│ └── AuthorRepository.java
├── book/ # Book domain
│ ├── Book.java # JPA Entity
│ ├── BookView.java # Blaze Entity View (GraphQL type)
│ ├── BookCriteria.java # Search criteria
│ ├── BookGraphQLController.java
│ ├── BookService.java
│ └── BookRepository.java
├── common/ # Shared components
│ ├── Criteria.java # Generic criteria interface
│ └── MyPageRequest.java # Pagination DTO
├── graphql/ # GraphQL infrastructure
│ ├── DataFetchingEnvironmentHolder.java
│ └── DataFetchingEnvironmentInstrumentation.java
└── persistence/ # Custom repository infrastructure
├── EntityViewGraphQLRepository.java
├── EntityViewGraphQLRepositoryImpl.java
├── EntityViewGraphQLRepositoryFactoryBean.java
├── EntityViewRepositoryFactory.java
└── PersistenceConfig.java
Entity Views define projections that mirror GraphQL types:
@EntityView(Book.class)
public interface BookView {
@IdMapping
Long getId();
String getName();
int getPageCount();
@Mapping("authors")
Set<AuthorView> getAuthors();
}type BookView {
id: ID!
name: String!
pageCount: Int!
authors: [AuthorView!]!
}
extend type Query {
books: [BookView!]!
bookById(id: ID!): BookView
booksPaginated(pageRequest: PageRequest!, criteria: BookCriteria): BookPage!
}The DataFetchingEnvironmentInstrumentation captures the GraphQL context and stores it in a ThreadLocal, making it accessible at the repository level:
@Component
public class DataFetchingEnvironmentInstrumentation extends SimplePerformantInstrumentation {
@Override
public DataFetcher<?> instrumentDataFetcher(
DataFetcher<?> dataFetcher,
InstrumentationFieldFetchParameters parameters,
InstrumentationState state) {
return environment -> {
DataFetchingEnvironmentHolder.set(environment);
return dataFetcher.get(environment);
};
}
@Override
public InstrumentationContext<ExecutionResult> beginExecution(
InstrumentationExecutionParameters parameters, InstrumentationState state) {
return SimpleInstrumentationContext.whenCompleted((_, _) -> DataFetchingEnvironmentHolder.clear());
}
}The repository uses Blaze Persistence's GraphQLEntityViewSupport to create settings based on requested fields:
@Override
public <V> List<V> findAll(Class<V> viewClass) {
final var environment = DataFetchingEnvironmentHolder.get();
final var setting = graphQLEntityViewSupport.createSetting(viewClass, environment);
final var criteriaBuilder = criteriaBuilderFactory.create(entityManager, getDomainClass());
return entityViewManager.applySetting(setting, criteriaBuilder).getResultList();
}GraphQL Query:
query {
books {
id
name
}
}Generated SQL:
SELECT b.id, b.name FROM books bGraphQL Query with relations:
query {
books {
id
name
authors {
firstName
}
}
}Generated SQL:
SELECT b.id, b.name, a.first_name
FROM books b
LEFT JOIN books_authors ba ON b.id = ba.book_id
LEFT JOIN authors a ON ba.author_id = a.idGet all books:
query {
books {
id
name
pageCount
authors {
id
firstName
lastName
}
}
}Get book by ID:
query {
bookById(id: 1) {
id
name
authors {
firstName
}
}
}Paginated books with filtering:
query {
booksPaginated(
pageRequest: { page: 0, pageSize: 10, orders: [{ property: "name", order: ASC }] }
criteria: { minPageCount: 100, authorId: 1 }
) {
totalElements
totalPages
hasNext
content {
id
name
pageCount
}
}
}Create an author:
mutation {
createAuthor(input: { firstName: "John", lastName: "Doe" }) {
id
firstName
lastName
}
}Create a book:
mutation {
createBook(input: { name: "My Book", pageCount: 350, authorIds: [1, 2] }) {
id
name
authors {
firstName
}
}
}Configure your MySQL connection in application.yaml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/graphql_blaze
username: root
password: your_password
jpa:
hibernate:
ddl-auto: updateGraphQL endpoint is available at /graphql by default. GraphiQL UI can be enabled:
spring:
graphql:
graphiql:
enabled: true- Ensure you have Java 25 installed
- Start a MySQL database
- Configure database connection in
application.yaml - Run the application:
./mvnw spring-boot:run- Access GraphiQL at
http://localhost:8080/graphiql
<!-- Blaze Persistence Core -->
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-core-api-jakarta</artifactId>
</dependency>
<!-- Blaze Persistence Entity Views -->
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-entity-view-api-jakarta</artifactId>
</dependency>
<!-- Blaze Persistence GraphQL Integration -->
<dependency>
<groupId>com.blazebit</groupId>
<artifactId>blaze-persistence-integration-graphql-jakarta</artifactId>
</dependency>
<!-- Spring GraphQL -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>- Performance: No over-fetching, only selected fields are queried
- Type Safety: Entity Views provide compile-time safety
- Clean Architecture: GraphQL context doesn't pollute service layer signatures
- Pagination: Built-in support with total count and sorting
- Filtering: Type-safe criteria using Blaze Persistence CriteriaBuilder
This project is provided as an example for educational purposes.