<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=user
spring.datasource.password=pass
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
@Entity maps a Java class to a database table. @Id marks the primary key and @GeneratedValue lets the database auto-generate it.
import jakarta.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String name;
private Double price;
@Column(name = "created_at")
private LocalDateTime createdAt;
@PrePersist
private void prePersist() {
createdAt = LocalDateTime.now();
}
// Getters and setters
public Long getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
}
Extend JpaRepository<T, ID> and Spring Data generates the implementation at startup — no SQL required for basic operations.
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
// Built-in: save, findById, findAll, deleteById, count, existsById, etc.
}
@Service
public class ProductService {
private final ProductRepository repo;
public ProductService(ProductRepository repo) { this.repo = repo; }
public Product create(Product p) { return repo.save(p); }
public List<Product> findAll() { return repo.findAll(); }
public void delete(Long id) { repo.deleteById(id); }
public Product findById(Long id) {
return repo.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Product", id));
}
}
Spring Data parses method names and generates the JPQL query automatically. Follow the naming convention: findBy[FieldName][Condition].
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByName(String name);
List<Product> findByPriceLessThan(Double maxPrice);
List<Product> findByNameContainingIgnoreCase(String keyword);
Optional<Product> findFirstByOrderByCreatedAtDesc();
boolean existsByName(String name);
}
Use @Query for complex queries that are hard to express through method names. You can write JPQL (object-based) or native SQL.
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface ProductRepository extends JpaRepository<Product, Long> {
// JPQL
@Query("SELECT p FROM Product p WHERE p.price BETWEEN :min AND :max ORDER BY p.price")
List<Product> findInPriceRange(@Param("min") double min, @Param("max") double max);
// Native SQL
@Query(value = "SELECT * FROM products WHERE name ILIKE %:kw%", nativeQuery = true)
List<Product> searchNative(@Param("kw") String keyword);
// Modifying (UPDATE/DELETE)
@Modifying
@Transactional
@Query("UPDATE Product p SET p.price = p.price * :factor WHERE p.id IN :ids")
int bulkUpdatePrice(@Param("factor") double factor, @Param("ids") List<Long> ids);
}