Pular para o conteúdo principal

TP6.1 - Convenções de Nomenclatura Avançadas

Objetivos​

Ao final deste exercício, você será capaz de:

  1. ✅ Utilizar as 15+ palavras-chave do Spring Data JPA
  2. ✅ Criar consultas complexas sem SQL
  3. ✅ Combinar várias palavras-chave
  4. ✅ Testar todos os seus métodos

Duração estimada: 2h


Pré-requisitos​

  • ✅ Capítulo 1 do Módulo 6 concluído
  • ✅ Maven 3.9+ e Java 17+
  • ✅ VS Code com extensões Java

Preparação: Criar o Projeto​

Etapa 1: Spring Initializr​

Acesse https://start.spring.io/

Configuração:

  • Project: Maven
  • Spring Boot: 3.2.0
  • Group: com.example
  • Artifact: jpa-conventions
  • Packaging: Jar
  • Java: 17

Dependências:

  • Spring Web
  • Spring Data JPA
  • H2 Database

Baixe, descompacte e abra no VS Code


Etapa 2: Configuração​

Arquivo: src/main/resources/application.properties

# H2 Database
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# H2 Console
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# JPA
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# Server
server.port=8080

Exercício 1: Criar a Entidade Product​

Etapa 1.1: Criar a Entidade​

Arquivo: model/Product.java

package com.example.jpa.model;

import jakarta.persistence.*;
import java.time.LocalDate;

@Entity
@Table(name = "products")
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String description;
private Double price;
private Integer stock;
private String category;
private Boolean active;
private Double discount;
private LocalDate createdAt;

// Construtores
public Product() {}

public Product(String name, Double price, Integer stock, String category, Boolean active) {
this.name = name;
this.price = price;
this.stock = stock;
this.category = category;
this.active = active;
this.createdAt = LocalDate.now();
}

// Getters e Setters (todos!)
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }

public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }

public Integer getStock() { return stock; }
public void setStock(Integer stock) { this.stock = stock; }

public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }

public Boolean getActive() { return active; }
public void setActive(Boolean active) { this.active = active; }

public Double getDiscount() { return discount; }
public void setDiscount(Double discount) { this.discount = discount; }

public LocalDate getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDate createdAt) { this.createdAt = createdAt; }
}

Exercício 2: Criar o Repository com 20 Métodos​

Etapa 2.1: Repository Básico​

Arquivo: repository/ProductRepository.java

package com.example.jpa.repository;

import com.example.jpa.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.List;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {

// NÍVEL FÁCIL (5 métodos)

// 1. Encontrar por nome exato
List<Product> findByName(String name);

// 2. Encontrar por categoria
List<Product> findByCategory(String category);

// 3. Encontrar produtos ativos
List<Product> findByActiveTrue();

// 4. Encontrar produtos inativos
List<Product> findByActiveFalse();

// 5. Encontrar por preço exato
List<Product> findByPrice(Double price);


// NÍVEL MÉDIO (10 métodos)

// 6. Preço menor que
List<Product> findByPriceLessThan(Double maxPrice);

// 7. Preço maior que
List<Product> findByPriceGreaterThan(Double minPrice);

// 8. Preço em um intervalo
List<Product> findByPriceBetween(Double min, Double max);

// 9. Estoque menor que (quase acabando)
List<Product> findByStockLessThan(Integer minStock);

// 10. Nome contendo uma palavra-chave
List<Product> findByNameContaining(String keyword);

// 11. Nome começando com
List<Product> findByNameStartingWith(String prefix);

// 12. Categoria em uma lista
List<Product> findByCategoryIn(List<String> categories);

// 13. Produtos sem desconto
List<Product> findByDiscountIsNull();

// 14. Produtos com desconto
List<Product> findByDiscountIsNotNull();

// 15. Nome sem diferenciar maiúsculas/minúsculas
List<Product> findByNameIgnoreCase(String name);


// NÍVEL AVANÇADO (5 métodos)

// 16. Combinação: Ativo E preço < max
List<Product> findByActiveTrueAndPriceLessThan(Double maxPrice);

// 17. Combinação: Categoria E estoque > min, ordenados por preço
List<Product> findByCategoryAndStockGreaterThanOrderByPriceAsc(
String category,
Integer minStock
);

// 18. Combinação: Ativo E preço no intervalo E categoria, ordenados
List<Product> findByActiveTrueAndPriceBetweenAndCategoryOrderByNameAsc(
Double min,
Double max,
String category
);

// 19. Combinação: Nome contendo OU categoria igual
List<Product> findByNameContainingOrCategory(String keyword, String category);

// 20. Criados após uma data, ordenados por data
List<Product> findByCreatedAtAfterOrderByCreatedAtDesc(LocalDate date);
}

Exercício 3: Inicializar os Dados de Teste​

Etapa 3.1: Criar DataLoader​

Arquivo: config/DataLoader.java

package com.example.jpa.config;

import com.example.jpa.model.Product;
import com.example.jpa.repository.ProductRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDate;

@Configuration
public class DataLoader {

@Bean
CommandLineRunner initDatabase(ProductRepository repository) {
return args -> {
System.out.println("=== Inicializando dados ===");

// Electronics
repository.save(new Product("MacBook Pro", 2499.99, 15, "Electronics", true));
repository.save(new Product("iPhone 15", 999.99, 50, "Electronics", true));
repository.save(new Product("iPad Pro", 1299.99, 30, "Electronics", true));

// Computers
repository.save(new Product("Dell Laptop", 899.99, 25, "Computers", true));
repository.save(new Product("HP Desktop", 799.99, 10, "Computers", true));

// Phones
repository.save(new Product("Samsung Phone", 699.99, 40, "Phones", true));
repository.save(new Product("Google Pixel", 599.99, 20, "Phones", true));

// Produtos com desconto
Product macMini = new Product("Mac Mini", 699.99, 5, "Computers", true);
macMini.setDiscount(50.0);
repository.save(macMini);

// Produtos inativos
repository.save(new Product("Old Phone", 199.99, 0, "Phones", false));

// Produtos quase acabando
repository.save(new Product("Rare Item", 1499.99, 2, "Electronics", true));

System.out.println("=== " + repository.count() + " produtos criados ===");
};
}
}

Exercício 4: Criar o Controller de Teste​

Etapa 4.1: Controller Completo​

Arquivo: controller/ProductController.java

package com.example.jpa.controller;

import com.example.jpa.model.Product;
import com.example.jpa.repository.ProductRepository;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/api/products")
public class ProductController {

private final ProductRepository productRepo;

public ProductController(ProductRepository productRepo) {
this.productRepo = productRepo;
}

// Teste 1: findByName
@GetMapping("/by-name/{name}")
public List<Product> byName(@PathVariable String name) {
return productRepo.findByName(name);
}

// Teste 2: findByCategory
@GetMapping("/by-category/{category}")
public List<Product> byCategory(@PathVariable String category) {
return productRepo.findByCategory(category);
}

// Teste 3: findByActiveTrue
@GetMapping("/active")
public List<Product> activeProducts() {
return productRepo.findByActiveTrue();
}

// Teste 4: findByActiveFalse
@GetMapping("/inactive")
public List<Product> inactiveProducts() {
return productRepo.findByActiveFalse();
}

// Teste 5: findByPriceLessThan
@GetMapping("/cheap/{maxPrice}")
public List<Product> cheapProducts(@PathVariable Double maxPrice) {
return productRepo.findByPriceLessThan(maxPrice);
}

// Teste 6: findByPriceGreaterThan
@GetMapping("/expensive/{minPrice}")
public List<Product> expensiveProducts(@PathVariable Double minPrice) {
return productRepo.findByPriceGreaterThan(minPrice);
}

// Teste 7: findByPriceBetween
@GetMapping("/price-range")
public List<Product> priceRange(
@RequestParam Double min,
@RequestParam Double max
) {
return productRepo.findByPriceBetween(min, max);
}

// Teste 8: findByStockLessThan
@GetMapping("/low-stock/{minStock}")
public List<Product> lowStock(@PathVariable Integer minStock) {
return productRepo.findByStockLessThan(minStock);
}

// Teste 9: findByNameContaining
@GetMapping("/search/{keyword}")
public List<Product> search(@PathVariable String keyword) {
return productRepo.findByNameContaining(keyword);
}

// Teste 10: findByNameStartingWith
@GetMapping("/starts-with/{prefix}")
public List<Product> startsWith(@PathVariable String prefix) {
return productRepo.findByNameStartingWith(prefix);
}

// Teste 11: findByCategoryIn
@GetMapping("/categories")
public List<Product> byCategories(@RequestParam List<String> categories) {
return productRepo.findByCategoryIn(categories);
}

// Teste 12: findByDiscountIsNull
@GetMapping("/no-discount")
public List<Product> noDiscount() {
return productRepo.findByDiscountIsNull();
}

// Teste 13: findByDiscountIsNotNull
@GetMapping("/with-discount")
public List<Product> withDiscount() {
return productRepo.findByDiscountIsNotNull();
}

// Teste 14: findByActiveTrueAndPriceLessThan
@GetMapping("/active-cheap/{maxPrice}")
public List<Product> activeCheap(@PathVariable Double maxPrice) {
return productRepo.findByActiveTrueAndPriceLessThan(maxPrice);
}

// Teste 15: Combinação complexa
@GetMapping("/complex")
public List<Product> complex(
@RequestParam Double min,
@RequestParam Double max,
@RequestParam String category
) {
return productRepo.findByActiveTrueAndPriceBetweenAndCategoryOrderByNameAsc(
min, max, category
);
}
}

Exercício 5: Testes Manuais​

Iniciar a Aplicação​

mvn clean install -DskipTests spring-boot:run

O console deve exibir:

=== Inicializando dados ===
=== 10 produtos criados ===

Teste 1: Produtos por categoria​

GET http://localhost:8080/api/products/by-category/Electronics

Resultado esperado: 3 produtos (MacBook Pro, iPhone 15, iPad Pro, Rare Item)


Teste 2: Produtos ativos​

GET http://localhost:8080/api/products/active

Resultado esperado: 9 produtos (todos exceto "Old Phone")


Teste 3: Produtos menos de 700€​

GET http://localhost:8080/api/products/cheap/700

Resultado esperado: Samsung Phone, Google Pixel, Mac Mini, Old Phone, HP Desktop


Teste 4: Preço entre 600€ e 1000€​

GET http://localhost:8080/api/products/price-range?min=600&max=1000

Resultado esperado: Samsung Phone, Mac Mini, Dell Laptop, HP Desktop, iPhone 15


Teste 5: Busca por palavra-chave​

GET http://localhost:8080/api/products/search/Phone

Resultado esperado: iPhone 15, Samsung Phone, Google Pixel, Old Phone


Teste 6: Começa com "Mac"​

GET http://localhost:8080/api/products/starts-with/Mac

Resultado esperado: MacBook Pro, Mac Mini


Teste 7: Várias categorias​

GET http://localhost:8080/api/products/categories?categories=Electronics,Phones

Resultado esperado: Todos os produtos Electronics + Phones


Teste 8: Produtos com desconto​

GET http://localhost:8080/api/products/with-discount

Resultado esperado: Mac Mini (único com desconto)


Teste 9: Produtos ativos menos de 1000€​

GET http://localhost:8080/api/products/active-cheap/1000

Resultado esperado: Todos os ativos < 1000€


Teste 10: Busca complexa​

GET http://localhost:8080/api/products/complex?min=500&max=1500&category=Electronics

Resultado esperado: iPhone 15, iPad Pro, Rare Item (ordenados por nome)


Exercício 6: Testes Unitários (Bônus)​

Criar os Testes​

Arquivo: src/test/java/ProductRepositoryTest.java

package com.example.jpa;

import com.example.jpa.model.Product;
import com.example.jpa.repository.ProductRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@DataJpaTest
public class ProductRepositoryTest {

@Autowired
private ProductRepository productRepo;

@BeforeEach
void setUp() {
productRepo.deleteAll();

productRepo.save(new Product("Laptop", 999.99, 10, "Computers", true));
productRepo.save(new Product("Phone", 599.99, 20, "Phones", true));
productRepo.save(new Product("Tablet", 399.99, 5, "Electronics", false));
}

@Test
void testFindByPriceLessThan() {
List<Product> results = productRepo.findByPriceLessThan(700.0);
assertEquals(2, results.size());
}

@Test
void testFindByActiveTrue() {
List<Product> results = productRepo.findByActiveTrue();
assertEquals(2, results.size());
}

@Test
void testFindByNameContaining() {
List<Product> results = productRepo.findByNameContaining("top");
assertEquals(1, results.size());
assertEquals("Laptop", results.get(0).getName());
}

@Test
void testFindByCategoryIn() {
List<String> categories = Arrays.asList("Computers", "Phones");
List<Product> results = productRepo.findByCategoryIn(categories);
assertEquals(2, results.size());
}

@Test
void testFindByPriceBetween() {
List<Product> results = productRepo.findByPriceBetween(400.0, 1000.0);
assertEquals(2, results.size());
}
}

Execute os testes:

mvn test

Desafio: Crie 10 Métodos Adicionais​

Agora é com você! Crie estes métodos:

  1. Encontrar por descrição contendo uma palavra-chave
  2. Encontrar produtos criados antes de uma data
  3. Encontrar por categoria E ativo, ordenados por preço DESC
  4. Encontrar produtos com estoque entre min e max
  5. Encontrar por nome terminando com um sufixo
  6. Encontrar produtos com preço >= min OU estoque < min
  7. Encontrar por categoria não em uma lista
  8. Encontrar produtos ativos sem desconto
  9. Encontrar por preço E ordenados por estoque DESC e depois nome ASC
  10. Encontrar os 5 produtos mais baratos (dica: use Pageable)

Recapitulação​

O que Você Praticou​

✅ 20 métodos com convenções de nomenclatura
✅ 9 palavras-chave diferentes (Between, LessThan, Containing, In, IsNull, True, False, IgnoreCase, OrderBy)
✅ Combinações de várias palavras-chave
✅ Testes manuais com o navegador
✅ Testes unitários com JUnit


Próximo Passo​

Você domina as convenções! Avance para Relações entre Entidades (TP2).

➡️ TP6.2: Relações Employee-Department

⬅️ Voltar ao Módulo 6