CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-starter-graphql

Starter for building GraphQL applications with Spring GraphQL

Pending
Overview
Eval results
Files

data-integration.mddocs/

Data Integration

Spring Boot GraphQL Starter provides automatic integration with Spring Data repositories, enabling seamless database access through GraphQL without manual DataFetcher configuration. The integration supports Query by Example, Querydsl, and pagination patterns.

Query by Example Integration

Automatic integration with Spring Data repositories that implement QueryByExampleExecutor.

Auto-Configuration Classes

@AutoConfiguration(after = GraphQlAutoConfiguration.class)
@ConditionalOnClass({ GraphQL.class, QueryByExampleDataFetcher.class, QueryByExampleExecutor.class })
@ConditionalOnBean(GraphQlSource.class)
public class GraphQlQueryByExampleAutoConfiguration {
    
    @Bean
    GraphQlSourceBuilderCustomizer queryByExampleDataFetcherCustomizer(
        ObjectProvider<QueryByExampleExecutor<?>> executors,
        ObjectProvider<ReactiveQueryByExampleExecutor<?>> reactiveExecutors
    ) {
        return QueryByExampleDataFetcher.autoRegistrationConfigurer(
            executors.orderedStream().toList(),
            reactiveExecutors.orderedStream().toList()
        );
    }
}

Repository Setup

// Repository with Query by Example support
public interface BookRepository extends JpaRepository<Book, String>, 
                                        QueryByExampleExecutor<Book> {
    // Spring Data will automatically generate DataFetchers
}

// Reactive repository support
public interface ReactiveBookRepository extends ReactiveCrudRepository<Book, String>,
                                                ReactiveQueryByExampleExecutor<Book> {
    // Reactive DataFetchers automatically generated
}

GraphQL Schema Integration

type Query {
    # Automatically mapped to QueryByExampleExecutor.findAll(Example)
    books(where: BookFilter): [Book]
    
    # Automatically mapped to QueryByExampleExecutor.findOne(Example)  
    book(where: BookFilter): Book
}

input BookFilter {
    title: String
    author: String
    isbn: String
    publishedYear: Int
}

Usage Example

@Controller
public class BookController {
    
    // No need to implement - auto-configured by Query by Example
    // GraphQL query: books(where: {author: "Jane Doe"}) automatically
    // becomes: bookRepository.findAll(Example.of(Book.builder().author("Jane Doe").build()))
}

Querydsl Integration

Automatic integration with Querydsl-enabled repositories for type-safe dynamic queries.

Auto-Configuration

@AutoConfiguration(after = GraphQlAutoConfiguration.class)
@ConditionalOnClass({ GraphQL.class, QuerydslDataFetcher.class, QuerydslPredicateExecutor.class })
@ConditionalOnBean(GraphQlSource.class)
public class GraphQlQuerydslAutoConfiguration {
    
    @Bean
    GraphQlSourceBuilderCustomizer querydslDataFetcherCustomizer(
        ObjectProvider<QuerydslPredicateExecutor<?>> executors,
        ObjectProvider<ReactiveQuerydslPredicateExecutor<?>> reactiveExecutors
    ) {
        return QuerydslDataFetcher.autoRegistrationConfigurer(
            executors.orderedStream().toList(),
            reactiveExecutors.orderedStream().toList()
        );
    }
}

Repository Setup

// Querydsl-enabled repository
public interface AuthorRepository extends JpaRepository<Author, String>,
                                          QuerydslPredicateExecutor<Author> {
    // Type-safe query generation
}

// Reactive Querydsl support
public interface ReactiveAuthorRepository extends ReactiveCrudRepository<Author, String>,
                                                  ReactiveQuerydslPredicateExecutor<Author> {
}

Generated Q-Types Usage

// Generated Querydsl Q-types are automatically used
// GraphQL query: authors(where: {name: {like: "John%"}})
// becomes: authorRepository.findAll(QAuthor.author.name.like("John%"))

Pagination Support

Built-in pagination support using Spring Data's pagination abstractions.

Cursor Strategy Configuration

@ConditionalOnClass(ScrollPosition.class)
@Configuration(proxyBeanMethods = false)
static class GraphQlDataAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    EncodingCursorStrategy<ScrollPosition> cursorStrategy() {
        return CursorStrategy.withEncoder(
            new ScrollPositionCursorStrategy(), 
            CursorEncoder.base64()
        );
    }
    
    @Bean
    GraphQlSourceBuilderCustomizer cursorStrategyCustomizer(CursorStrategy<?> cursorStrategy) {
        ConnectionFieldTypeVisitor visitor = ConnectionFieldTypeVisitor.create(List.of(
            new WindowConnectionAdapter(scrollCursorStrategy),
            new SliceConnectionAdapter(scrollCursorStrategy)
        ));
        return builder -> builder.typeVisitors(List.of(visitor));
    }
}

Connection Schema Types

type Query {
    books(first: Int, after: String, last: Int, before: String): BookConnection
}

type BookConnection {
    edges: [BookEdge]
    pageInfo: PageInfo!
}

type BookEdge {
    node: Book!
    cursor: String!
}

type PageInfo {
    hasNextPage: Boolean!
    hasPreviousPage: Boolean!
    startCursor: String
    endCursor: String
}

Repository Pagination

public interface BookRepository extends JpaRepository<Book, String> {
    
    // Automatically supports GraphQL Connection pagination
    Page<Book> findAll(Pageable pageable);
    
    // Slice-based pagination for large datasets
    Slice<Book> findByAuthor(String author, Pageable pageable);
    
    // Window-based pagination with ScrollPosition
    Window<Book> findByPublishedYearAfter(int year, ScrollPosition position);
}

Custom DataFetcher Integration

Manual DataFetcher registration alongside automatic repository integration.

RuntimeWiringConfigurer

@Component
public class CustomDataFetcherConfigurer implements RuntimeWiringConfigurer {
    
    @Override
    public void configure(RuntimeWiring.Builder builder) {
        builder
            .type("Query", typeWiring -> typeWiring
                .dataFetcher("searchBooks", searchBooksDataFetcher())
                .dataFetcher("recommendedBooks", recommendedBooksDataFetcher())
            )
            .type("Book", typeWiring -> typeWiring
                .dataFetcher("reviews", bookReviewsDataFetcher())
            );
    }
    
    private DataFetcher<List<Book>> searchBooksDataFetcher() {
        return environment -> {
            String query = environment.getArgument("query");
            return searchService.searchBooks(query);
        };
    }
}

Batch Loading (N+1 Problem Solution)

Integration with DataLoader for efficient batch loading.

BatchLoaderRegistry

@Component
public class BookDataLoaders {
    
    @Autowired
    private BatchLoaderRegistry batchLoaderRegistry;
    
    @PostConstruct
    public void registerDataLoaders() {
        batchLoaderRegistry.forName("authorLoader")
            .registerBatchLoader((keys, environment) -> {
                // Batch load authors by IDs
                return authorService.findAllById(keys);
            });
    }
}

Using DataLoaders in Controllers

@Controller
public class BookController {
    
    @SchemaMapping(typeName = "Book", field = "author")
    public CompletableFuture<Author> author(Book book, DataLoader<String, Author> authorLoader) {
        return authorLoader.load(book.getAuthorId());
    }
    
    @BatchMapping(typeName = "Book", field = "reviews")
    public Mono<Map<Book, List<Review>>> reviews(List<Book> books) {
        List<String> bookIds = books.stream().map(Book::getId).toList();
        return reviewService.findByBookIds(bookIds)
            .collectMultimap(review -> findBookById(books, review.getBookId()));
    }
}

Transaction Management

Automatic transaction management for GraphQL operations.

Transactional DataFetchers

@Controller
@Transactional(readOnly = true) // Default read-only transactions
public class BookController {
    
    @QueryMapping
    public List<Book> books() {
        return bookRepository.findAll(); // Read-only transaction
    }
    
    @MutationMapping
    @Transactional // Read-write transaction for mutations
    public Book addBook(@Argument BookInput input) {
        Book book = new Book(input.getTitle(), input.getAuthor());
        return bookRepository.save(book);
    }
    
    @MutationMapping
    @Transactional(rollbackFor = Exception.class)
    public Book updateBook(@Argument String id, @Argument BookInput input) {
        Book book = bookRepository.findById(id)
            .orElseThrow(() -> new BookNotFoundException(id));
        book.setTitle(input.getTitle());
        book.setAuthor(input.getAuthor());
        return bookRepository.save(book);
    }
}

Data Validation

Integration with Bean Validation for input validation.

Validation Annotations

// Input validation with Bean Validation
public class BookInput {
    @NotBlank
    @Size(min = 1, max = 255)
    private String title;
    
    @NotBlank
    @Size(min = 1, max = 100)
    private String author;
    
    @Pattern(regexp = "\\d{3}-\\d{10}")
    private String isbn;
    
    // Getters and setters
}

@Controller
public class BookController {
    
    @MutationMapping
    public Book addBook(@Argument @Valid BookInput input) {
        // Validation automatically applied
        return bookService.create(input);
    }
}

Error Handling

Data access error handling and mapping to GraphQL errors.

Exception Resolvers

@Component
public class DataAccessExceptionResolver implements DataFetcherExceptionResolver {
    
    @Override
    public Mono<List<GraphQLError>> resolveException(DataFetcherExceptionResolverEnvironment env) {
        Throwable exception = env.getException();
        
        if (exception instanceof EntityNotFoundException) {
            return Mono.just(List.of(
                GraphqlErrorBuilder.newError(env)
                    .message("Entity not found: " + exception.getMessage())
                    .errorType(ErrorType.DataFetchingException)
                    .build()
            ));
        }
        
        if (exception instanceof DataIntegrityViolationException) {
            return Mono.just(List.of(
                GraphqlErrorBuilder.newError(env)
                    .message("Data integrity violation")
                    .errorType(ErrorType.ValidationError)
                    .build()
            ));
        }
        
        return Mono.empty();
    }
}

Performance Optimization

Projection Support

@Controller
public class BookController {
    
    @QueryMapping
    public List<Book> books(DataFetchingEnvironment environment) {
        // Use GraphQL selection set for JPA projections
        Set<String> fields = getSelectedFields(environment);
        
        if (fields.contains("author")) {
            return bookRepository.findAllWithAuthor(); // Join fetch
        } else {
            return bookRepository.findAll(); // Simple query
        }
    }
}

Query Optimization

// Custom repository methods for optimized queries
public interface BookRepository extends JpaRepository<Book, String> {
    
    @Query("SELECT b FROM Book b JOIN FETCH b.author WHERE b.publishedYear > :year")
    List<Book> findRecentBooksWithAuthor(@Param("year") int year);
    
    @Query("SELECT b.id, b.title, a.name FROM Book b JOIN b.author a")
    List<BookSummary> findBookSummaries();
}

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-graphql

docs

configuration-properties.md

core-infrastructure.md

data-integration.md

index.md

observability-integration.md

security-integration.md

testing-support.md

transport-support.md

web-integration.md

tile.json