Spring Boot Postgresql Crud Uygulama
Spring Initalizr ile backend kısmını oluşturalım
- Spring initalizr uygulamayı oluşturalım.
- Project: Maven
- Language: Java
- Spring Boot: 3.4.1
- Java: 21
- Dependencies
- Spring Web
- PostgreSQL Driver
- Spring Data JPA
Not: Neden lombok kullanmadık bu podcastte 20.dk dan sonra anlatılıyor, detaylı bakabilirsiniz.
Zip dosyasını indirelim. Zipten çıkaralım. Intellij Idea da projeyi açalım.
İntellij Idea
İntellij Idea Kurulum
- İntellij Idea Ultimate veya ücretsiz sürümü olan Community burdan indirebilirsiniz.
Proje Konfigurasyonları
- İntellij idea uygulamasında aşağıdaki kontrolleri yapalım.
- File>Project Structure SDK: 21 seçili olmalıdır.
- Eğer 21 sürümü kurulu değilse burdan JDK 21 seçelim. Installer yazan kısımdan indirip kuralım.
- CMD’den java sürümünü kontrol edelim.
- java -version
- Proje de anatosyonlar bulunamıyorsa ve “Cannot resolve symbol” bu hatayı veriyorsa aşağıdaki işlemleri yapalım.
- File>Invalidate Caches çıkan seçeneklerin hepsini seçip Invalidate and Restart butonuna basalım.
- File>Project Structure SDK: 21 seçili olmalıdır.
Package Yapısını Oluşturalım
CrudAppApplication.java dizinin olduğu yere aşağıdaki gibi dosyaları ekleyelim
1
2
3
4
5
6
7
8
9
10
crud_app/
├── controller/
│ └── ProductController.java
├── entity/
│ └── Product.java
├── repository/
│ └── ProductRepository.java
├── service/
│ └── ProductService.java
└── CrudAppApplicationMyProjectApplication.java
Katmanlı Mimari
Controller Katmanı
Kullanıcı ile uygulama arasındaki iletişimi yönetir. API uç noktaları burada tanımlanır.
ProductController.java
, ürünle ilgili istekleri alır ve işlemleri başlatır.
DTO Katmanı
Veri iletimi için kullanılan sınıfları içerir. İstek ve yanıt objeleri burada tanımlanır.
ProductRequestDto.java
Ürün oluşturma veya güncelleme işlemleri için gelen veriyi taşır.ProductResponseDto.java
Ürün verisinin dışarıya dönüştürülmüş halini taşır.
Entity Katmanı
Uygulamanın temel veri yapısını tanımlar. Genellikle veritabanı tablolarını temsil eden entity sınıflarından oluşur.
Product.java
, ürün verisinin yapısını ve veritabanı ile etkileşim için gerekli anotasyonları içerir.
Repository Katmanı
Veritabanı ile etkileşim kurar. CRUD işlemleri ve özel sorgular burada gerçekleştirilir.
ProductRepository.java
, Spring Data JPA kullanarak ürün verisine erişim sağlar.
Service Katmanı
İş kurallarını uygular ve mantıksal işlemleri yürütür. Controller ve Repository katmanları arasında aracıdır.
ProductService.java
, ürünle ilgili işlemleri yönetir, örneğin ürün ekleme, silme, güncelleme gibi işlemleri içerir.
CrudAppApplicationMyProjectApplication.java
Uygulamanın başlangıç noktasıdır. Spring Boot uygulamasını başlatır.
Product.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Entity
@Table(name = "product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private String brand;
private Double price;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public Product() {
}
public Product(Long id, String name, String description, String brand, Double price, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.description = description;
this.brand = brand;
this.price = price;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
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 String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
}
ProductRepository.java
1
2
3
4
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}
ProductService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public List<Product> getProducts() {
return productRepository.findAll();
}
public Product getProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
public Product addProduct(Product product) {
return productRepository.save(product);
}
public Product updateProduct(Product product) {
return productRepository.save(product);
}
public void deleteProduct(Long id) {
productRepository.deleteById(id);
}
}
ProductController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@RestController
@RequestMapping("/api")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/get-products")
public List<Product> getProducts() {
return productService.getProducts();
}
@GetMapping("/get-product")
public Product getProduct(@RequestParam("id") Long id) {
return productService.getProduct(id);
}
@PutMapping("/update-product")
public Product updateProduct(@RequestBody Product product, @RequestParam("id") Long id) {
return productService.updateProduct(product);
}
@PostMapping("/save-product")
public ResponseEntity<Product> saveProduct(@RequestBody Product product) {
Product newProduct = productService.saveProduct(product);
return ResponseEntity.status(HttpStatus.CREATED).body(newProduct);
}
@DeleteMapping("/delete-product")
public void deleteProduct(@RequestParam("id") Long id) {
productService.deleteProduct(id);
}
}
Pom.xml
Swagger ui için aşağıdaki OpenAPI dependecy ekleyelim.
1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.8.0</version>
</dependency>
Postgresql
Postgresql Kurulum
- Burdan Postgresql indirme sitesine gidelim. İşletim sistemini seçelim.
- Windows için;
- “Windows” yazana tıklayalım.
- “Download the installer” tıklayalım. Son sürümü indirelim. Windows 64 Bit kullananlar “Windows x86-64” yazan kısımdan indirebilir.
Postgresql Veritabanı
- Databases kısmından sağ tık yaparak productDb adında bir veritabanını ekleyelim.
- PostgreSQL 17 yazan kısıma sağ tık “properties” yazan kısımdan username bilgisine bakalım.
- İntellij idea uygulamasına veritabanını ekleyelim.
- İntellij idea sağ tarafta veritabanı simgesine tıklayalım.
- Artı işaretine tıklayalım. Veritabanı olarak Postgresql seçelim
- Username ve password bilgilerini Postgresql kurulumunda verdiğimiz isimlerle aynı olacak şekilde girelim.
- Database kısmına productDb veritabanı ismini yazalım.
- Aşağıda Missing File yazıyorsa tıklayalım ve veritabanı için gerekli dosyaları indirsin.
- Test Connection tıklayarak test edelim.
- Apply & Ok.
Veritabanı Konfigurasyonu
- application.properties
1
2
3
4
5
6
7
8
9
spring.application.name=crud-app
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.password=123456
spring.datasource.username=postgres
spring.datasource.url=jdbc:postgresql://localhost:5432/productDb
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
- Spring boot projesini çalıştıralım. Veritabanı tablosu oluşturulsun.
Postgresql Veritabanına Veri Ekleme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
INSERT INTO product (price, created_at, updated_at, brand, description, name)
VALUES
(299.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Apple', 'Latest model of iPhone', 'iPhone 14'),
(199.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Samsung', 'Latest model of Galaxy', 'Galaxy S23'),
(99.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Google', 'Latest model of Pixel', 'Pixel 7'),
(399.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'OnePlus', 'Latest model of OnePlus', 'OnePlus 12'),
(499.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Xiaomi', 'Latest model of Xiaomi', 'Xiaomi 12'),
(599.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Sony', 'Latest model of Xperia', 'Xperia 5'),
(699.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'LG', 'Latest model of LG', 'LG V90'),
(799.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Motorola', 'Latest model of Motorola', 'Motorola Edge'),
(899.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Nokia', 'Latest model of Nokia', 'Nokia 9'),
(999.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Huawei', 'Latest model of Huawei', 'Huawei P50'),
(1099.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'HTC', 'Latest model of HTC', 'HTC U20'),
(1199.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Asus', 'Latest model of Asus', 'Asus Zenfone 8'),
(1299.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Oppo', 'Latest model of Oppo', 'Oppo Find X5'),
(1399.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Vivo', 'Latest model of Vivo', 'Vivo X90'),
(1499.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Realme', 'Latest model of Realme', 'Realme GT 2'),
(1599.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Lenovo', 'Latest model of Lenovo', 'Lenovo Legion 3'),
(1699.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'ZTE', 'Latest model of ZTE', 'ZTE Axon 40'),
(1799.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'TCL', 'Latest model of TCL', 'TCL 30'),
(1899.99, '2023-01-03 12:00:00', '2023-01-03 12:00:00', 'Meizu', 'Latest model of Meizu', 'Meizu 19');
Postman
Postman uygulamasını burdan indirelim.
Postman İstek Atalım
Aşağıdaki gibi swagger-ui dan eklediğimiz apilerin isteklerine bakalım, postman uygulamasında bir request oluşturup products apisini çalıştıralım.
- http://localhost:8080/swagger-ui/index.html
- http://localhost:8080/v3/api-docs
1
2
curl --location 'http://localhost:8080/api/products' \
--header 'accept: */*'
saveProduct
1
2
3
4
5
6
7
8
9
10
@Transactional
public Product saveProduct(ProductRequestDto productRequestDto) {
Product product = new Product();
product.setName(productRequestDto.getName());
product.setDescription(productRequestDto.getDescription());
product.setBrand(productRequestDto.getBrand());
product.setPrice(productRequestDto.getPrice());
product.setCreatedAt(LocalDateTime.now());
return productRepository.save(product);
}
- objectMapper kullanalım.
1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.fasterxml.jackson.databind.ObjectMapper;
public class ProductService {
private final ObjectMapper objectMapper = new ObjectMapper();
@Transactional
public Product saveProduct(ProductRequestDto productRequestDto) {
// Dto'yu Product nesnesine dönüştür
Product product = objectMapper.convertValue(productRequestDto, Product.class);
product.setCreatedAt(LocalDateTime.now());
return productRepository.save(product);
}
}
Exception
1
2
3
crud_app/
├── exception/
│ └── GlobalExceptionHandler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleAllExceptions(Exception e) {
Map<String, Object> errorResponse = new HashMap<>();
errorResponse.put("timestamp", LocalDateTime.now());
errorResponse.put("status", determineHttpStatus(e).value());
errorResponse.put("error", determineHttpStatus(e).getReasonPhrase());
errorResponse.put("message", e.getMessage());
errorResponse.put("path", getRequestPath());
return new ResponseEntity<>(errorResponse, determineHttpStatus(e));
}
private HttpStatus determineHttpStatus(Exception e) {
if (e instanceof EntityNotFoundException) {
return HttpStatus.NOT_FOUND;
} else if (e instanceof IllegalArgumentException || e instanceof IllegalStateException) {
return HttpStatus.BAD_REQUEST;
} else {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
private String getRequestPath() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
return attributes.getRequest().getRequestURI();
}
return "Unknown Path";
}
}