Skip to content

Comprehensive Guide

Maruf Bepary edited this page Mar 12, 2025 · 1 revision

Comprehensive Setup Guide: Spring Boot 3 with MongoDB Connectivity

This guide will walk you through setting up an empty Spring Boot 3 project with MongoDB connectivity, using Java 17, Gradle 8, and Lombok. I'll provide all the necessary code and configuration files.

1. Project Structure Setup

First, let's create the basic project structure:

my-spring-project/
├── build.gradle
├── settings.gradle
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── example/
│   │   │           └── demo/
│   │   │               ├── Application.java
│   │   │               ├── controller/
│   │   │               │   └── ProductController.java
│   │   │               ├── model/
│   │   │               │   └── Product.java
│   │   │               ├── repository/
│   │   │               │   └── ProductRepository.java
│   │   │               └── service/
│   │   │                   └── ProductService.java
│   │   └── resources/
│   │       └── application.yaml
│   └── test/
│       └── java/
│           └── com/
│               └── example/
│                   └── demo/

2. Gradle Configuration

Create settings.gradle:

rootProject.name = 'my-spring-project'

Create build.gradle:

plugins {
    id 'org.springframework.boot' version '3.2.0'
    id 'io.spring.dependency-management' version '1.1.4'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '17'
}

repositories {
    mavenCentral()
}

dependencies {
    // Spring Boot Starters
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    
    // Lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    
    // Test dependencies
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

3. Application Configuration

Create application.yaml:

spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: mydatabase
      # If authentication is needed (not required for local development):
      # username: myuser
      # password: mypassword
      
server:
  port: 8080

logging:
  level:
    org:
      springframework:
        data:
          mongodb:
            core:
              MongoTemplate: DEBUG

4. Java Code

Main Application Class

Create src/main/java/com/example/demo/Application.java:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Document Model Example

Create src/main/java/com/example/demo/model/Product.java:

package com.example.demo.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;

@Document(collection = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    @Id
    private String id;
    
    private String name;
    private String description;
    private BigDecimal price;
}

Repository Example

Create src/main/java/com/example/demo/repository/ProductRepository.java:

package com.example.demo.repository;

import com.example.demo.model.Product;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import java.util.List;

@Repository
public interface ProductRepository extends MongoRepository<Product, String> {
    // Custom query methods
    List<Product> findByNameContainingIgnoreCase(String name);
    List<Product> findByPriceLessThanEqual(BigDecimal maxPrice);
}

Service Example

Create src/main/java/com/example/demo/service/ProductService.java:

package com.example.demo.service;

import com.example.demo.model.Product;
import com.example.demo.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

@Service
public class ProductService {
    private final ProductRepository productRepository;
    
    @Autowired
    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }
    
    public List<Product> findAllProducts() {
        return productRepository.findAll();
    }
    
    public Optional<Product> findProductById(String id) {
        return productRepository.findById(id);
    }
    
    public List<Product> findProductsByName(String name) {
        return productRepository.findByNameContainingIgnoreCase(name);
    }
    
    public List<Product> findProductsByMaxPrice(BigDecimal maxPrice) {
        return productRepository.findByPriceLessThanEqual(maxPrice);
    }
    
    public Product saveProduct(Product product) {
        return productRepository.save(product);
    }
    
    public void deleteProduct(String id) {
        productRepository.deleteById(id);
    }
}

Controller Example

Create src/main/java/com/example/demo/controller/ProductController.java:

package com.example.demo.controller;

import com.example.demo.model.Product;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.List;

@RestController
@RequestMapping("/api/products")
public class ProductController {
    private final ProductService productService;
    
    @Autowired
    public ProductController(ProductService productService) {
        this.productService = productService;
    }
    
    @GetMapping
    public List<Product> getAllProducts() {
        return productService.findAllProducts();
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Product> getProductById(@PathVariable String id) {
        return productService.findProductById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
    
    @GetMapping("/search")
    public List<Product> searchProducts(
            @RequestParam(required = false) String name,
            @RequestParam(required = false) BigDecimal maxPrice) {
        
        if (name != null && maxPrice != null) {
            // If both parameters are provided, you might want to implement a custom method
            return productService.findProductsByName(name);
        } else if (name != null) {
            return productService.findProductsByName(name);
        } else if (maxPrice != null) {
            return productService.findProductsByMaxPrice(maxPrice);
        } else {
            return productService.findAllProducts();
        }
    }
    
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Product createProduct(@RequestBody Product product) {
        return productService.saveProduct(product);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<Product> updateProduct(@PathVariable String id, @RequestBody Product product) {
        return productService.findProductById(id)
                .map(existingProduct -> {
                    product.setId(id);
                    return ResponseEntity.ok(productService.saveProduct(product));
                })
                .orElse(ResponseEntity.notFound().build());
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable String id) {
        return productService.findProductById(id)
                .map(product -> {
                    productService.deleteProduct(id);
                    return ResponseEntity.noContent().<Void>build();
                })
                .orElse(ResponseEntity.notFound().build());
    }
}

5. Step-by-Step Setup Instructions

Step 1: Set Up MongoDB

  1. Install MongoDB if not already installed (download from the MongoDB website or use a package manager)
  2. Start MongoDB (typically runs on port 27017)
  3. MongoDB will automatically create the database mydatabase when you first save data to it

Step 2: Create Project Structure

Create all the directories and files as outlined in the project structure section. You can do this manually or use a Spring Initializer.

Step 3: Configure Gradle

Copy the provided build.gradle and settings.gradle files to your project root.

Step 4: Configure Application Properties

Copy the provided application.yaml file to src/main/resources/.

Step 5: Add Java Classes

Copy the provided Java class files to their respective directories:

  • Application.java to src/main/java/com/example/demo/
  • Product.java to src/main/java/com/example/demo/model/
  • ProductRepository.java to src/main/java/com/example/demo/repository/
  • ProductService.java to src/main/java/com/example/demo/service/
  • ProductController.java to src/main/java/com/example/demo/controller/

Step 6: Run the Application

You can run the application using Gradle:

./gradlew bootRun

Or from your IDE by running the Application.java class.

6. Testing the Setup

Once the application is running, you can test the API endpoints:

Create a Product

curl -X POST http://localhost:8080/api/products \
  -H "Content-Type: application/json" \
  -d '{"name":"Test Product","description":"A test product","price":19.99}'

Get All Products

curl http://localhost:8080/api/products

Get Product by ID (replace with an actual ID)

curl http://localhost:8080/api/products/60f7a5d3e1b7346bc1f13b45

Search Products by Name

curl "http://localhost:8080/api/products/search?name=test"

Search Products by Max Price

curl "http://localhost:8080/api/products/search?maxPrice=50"

7. Further Customisations

Using MongoDB Atlas (Cloud)

To connect to MongoDB Atlas instead of a local MongoDB instance:

  1. Update the application.yaml to use a connection URI:
spring:
  data:
    mongodb:
      uri: mongodb+srv://username:password@cluster0.mongodb.net/mydatabase

Adding MongoDB Indexing

To improve query performance, you can add indexes to your document fields:

@Document(collection = "products")
@CompoundIndex(name = "name_price_idx", def = "{'name': 1, 'price': 1}")
public class Product {
    @Id
    private String id;
    
    @Indexed
    private String name;
    private String description;
    private BigDecimal price;
}

Using MongoDB GridFS for File Storage

To store large files (e.g., images, documents):

// Add to build.gradle dependencies
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'

Then create a configuration class:

@Configuration
public class GridFsConfig {
    @Bean
    public GridFsTemplate gridFsTemplate(MongoDatabaseFactory mongoDbFactory, 
                                         MongoTemplate mongoTemplate) {
        return new GridFsTemplate(mongoDbFactory, mongoTemplate.getConverter());
    }
}

8. Key Concepts

Document-Oriented vs Relational

MongoDB is a document-oriented database, which contrasts with relational databases:

  • Documents contain all related data in a single JSON-like structure
  • Documents in a collection can have different fields (schema flexibility)
  • No need for complex joins across multiple tables

MongoDB Data Modelling

MongoDB data modelling generally follows two patterns:

  1. Embedded documents - When data is accessed together (1:1 or 1:few relationships):
@Document
public class Order {
    @Id
    private String id;
    private String customerName;
    // Embed the items directly
    private List<OrderItem> items;
}
  1. References - When data is accessed independently (1:many or many:many):
@Document
public class Order {
    @Id
    private String id;
    private String customerName;
    // Store only IDs, not embedded documents
    private List<String> itemIds;
}

Query Methods in Spring Data MongoDB

Spring Data MongoDB supports various query creation methods:

// Find documents where a field equals a value
List<Product> findByCategory(String category);

// Using logical operators (AND, OR)
List<Product> findByCategoryAndPriceLessThan(String category, BigDecimal price);

// Using sorting
List<Product> findByNameContainingOrderByPriceAsc(String name);

// Using projections (return only specific fields)
@Query(value = "{ 'name': ?0 }", fields = "{ 'name': 1, 'price': 1 }")
List<Product> findProductNameAndPriceByName(String name);

MongoDB Aggregation Framework

For complex analytics, you can use MongoDB's aggregation framework:

@Autowired
private MongoTemplate mongoTemplate;

public List<DBObject> getProductStatsByCategory() {
    TypedAggregation<Product> aggregation = Aggregation.newAggregation(
        Product.class,
        Aggregation.group("category")
            .count().as("count")
            .avg("price").as("avgPrice")
            .max("price").as("maxPrice")
            .min("price").as("minPrice"),
        Aggregation.sort(Sort.Direction.DESC, "count")
    );
    
    AggregationResults<DBObject> results = mongoTemplate.aggregate(
        aggregation, DBObject.class);
    return results.getMappedResults();
}

Conclusion

You now have a complete Spring Boot 3 application with MongoDB connectivity using Java 17, Gradle 8, and Lombok. This empty project provides the foundation for building any document-oriented database application. Feel free to extend it with additional document models, queries, and business logic as needed.

Remember that MongoDB's flexibility allows for rapid development and iteration compared to traditional relational databases. You can easily evolve your document schemas over time without complex migration scripts.