diff --git a/pom.xml b/pom.xml
index 683e84ec..f630795c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,6 +18,15 @@
+
+
+ com.mysql
+ mysql-connector-j
+ 8.3.0
+
+
+
+
diff --git a/src/main/java/mate/academy/Main.java b/src/main/java/mate/academy/Main.java
index 0058fbf9..74924878 100644
--- a/src/main/java/mate/academy/Main.java
+++ b/src/main/java/mate/academy/Main.java
@@ -1,7 +1,55 @@
package mate.academy;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Optional;
+import mate.academy.dao.BookDao;
+import mate.academy.lib.Injector;
+import mate.academy.model.Book;
+import mate.academy.service.BookService;
+import mate.academy.service.impl.BookServiceImpl;
+
public class Main {
+ private static final String PACKAGE_NAME = "mate.academy";
+ private static final Injector injector = Injector.getInstance(PACKAGE_NAME);
+
public static void main(String[] args) {
+ var bookDao = (BookDao) injector.getInstance(BookDao.class);
+ var bookService = new BookServiceImpl(bookDao);
+ var newBook = saveNewBook(bookService);
+ getBookById(bookService, newBook);
+ updateBook(bookService, newBook);
+ getAllBooks(bookService);
+ deleteBook(bookService, newBook);
+ }
+
+ private static Book saveNewBook(BookService bookService) {
+ var newBook = new Book();
+ newBook.setTitle("New Book");
+ newBook.setPrice(BigDecimal.valueOf(30.99));
+ Book savedBook = bookService.save(newBook);
+ System.out.println("Saved book: " + savedBook);
+ return savedBook;
+ }
+
+ private static void getBookById(BookService bookService, Book savedBook) {
+ Optional bookById = bookService.get(savedBook.getId());
+ System.out.println("Book by id " + savedBook.getId() + " is: " + bookById.orElse(null));
+ }
+
+ private static void updateBook(BookService bookService, Book savedBook) {
+ savedBook.setTitle("New Book.Second Edition.");
+ savedBook.setPrice(BigDecimal.valueOf(35.99));
+ System.out.println("Updated book: " + bookService.update(savedBook));
+ }
+
+ private static void getAllBooks(BookService bookService) {
+ List allBooks = bookService.getAll();
+ allBooks.forEach(book -> System.out.println("Book: " + book));
+ }
+ private static void deleteBook(BookServiceImpl bookService, Book savedBook) {
+ boolean isDeleted = bookService.delete(savedBook.getId());
+ System.out.println("Deleted book with id " + savedBook.getId() + " : " + isDeleted);
}
}
diff --git a/src/main/java/mate/academy/dao/BookDao.java b/src/main/java/mate/academy/dao/BookDao.java
new file mode 100644
index 00000000..d519d671
--- /dev/null
+++ b/src/main/java/mate/academy/dao/BookDao.java
@@ -0,0 +1,18 @@
+package mate.academy.dao;
+
+import java.util.List;
+import java.util.Optional;
+import mate.academy.model.Book;
+
+public interface BookDao {
+
+ Book save(Book book);
+
+ Optional findById(Long id);
+
+ List findAll();
+
+ Book update(Book book);
+
+ boolean deleteById(Long id);
+}
diff --git a/src/main/java/mate/academy/dao/impl/BookDaoImpl.java b/src/main/java/mate/academy/dao/impl/BookDaoImpl.java
new file mode 100644
index 00000000..c1afedb6
--- /dev/null
+++ b/src/main/java/mate/academy/dao/impl/BookDaoImpl.java
@@ -0,0 +1,131 @@
+package mate.academy.dao.impl;
+
+import static java.sql.PreparedStatement.RETURN_GENERATED_KEYS;
+
+import java.math.BigDecimal;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import mate.academy.dao.BookDao;
+import mate.academy.exception.DataProcessingException;
+import mate.academy.lib.Dao;
+import mate.academy.model.Book;
+import mate.academy.util.ConnectionUtil;
+import mate.academy.util.SqlConsumer;
+import mate.academy.util.SqlFunction;
+
+@Dao
+public class BookDaoImpl implements BookDao {
+ private static final String SAVE_SQL = """
+ INSERT INTO books (title, price)
+ VALUES(?, ?)
+ """;
+ private static final String UPDATE_SQL = """
+ UPDATE books
+ SET title = ?, price = ?
+ WHERE id = ?
+ """;
+ private static final String FIND_BY_ID_SQL = """
+ SELECT id, title, price
+ FROM books
+ WHERE id = ?
+ """;
+ private static final String FIND_ALL_SQL = """
+ SELECT id,
+ title,
+ price
+ FROM books
+ """;
+ private static final String DELETE_BY_ID_SQL = """
+ DELETE FROM books
+ WHERE id = ?
+ """;
+
+ @Override
+ public Book save(Book book) {
+ try (var connection = ConnectionUtil.getConnection();
+ var preparedStatement =
+ connection.prepareStatement(SAVE_SQL, RETURN_GENERATED_KEYS)) {
+ preparedStatement.setString(1, book.getTitle());
+ preparedStatement.setObject(2, book.getPrice());
+ preparedStatement.executeUpdate();
+ var generatedKeys = preparedStatement.getGeneratedKeys();
+ if (generatedKeys.next()) {
+ book.setId(generatedKeys.getLong(1));
+ }
+ return book;
+ } catch (SQLException e) {
+ throw new DataProcessingException("Cannot save book: " + book, e);
+ }
+ }
+
+ @Override
+ public Optional findById(Long id) {
+ return executeQuery(FIND_BY_ID_SQL, ps -> ps.setLong(1, id), rs -> {
+ if (rs.next()) {
+ return Optional.of(mapResultSetToBook(rs));
+ }
+ return Optional.empty();
+ });
+ }
+
+ @Override
+ public List findAll() {
+ return executeQuery(FIND_ALL_SQL, ps -> {
+ }, rs -> {
+ List bookList = new ArrayList<>();
+ while (rs.next()) {
+ bookList.add(mapResultSetToBook(rs));
+ }
+ return bookList;
+ });
+ }
+
+ @Override
+ public Book update(Book book) {
+ executeUpdate(UPDATE_SQL, ps -> {
+ ps.setString(1, book.getTitle());
+ ps.setObject(2, book.getPrice());
+ ps.setLong(3, book.getId());
+ });
+ return book;
+ }
+
+ @Override
+ public boolean deleteById(Long id) {
+ return executeUpdate(DELETE_BY_ID_SQL, ps -> ps.setLong(1, id));
+ }
+
+ private Book mapResultSetToBook(ResultSet resultSet) throws SQLException {
+ var book = new Book();
+ book.setId(resultSet.getLong("id"));
+ book.setTitle(resultSet.getString("title"));
+ book.setPrice(resultSet.getObject("price", BigDecimal.class));
+ return book;
+ }
+
+ private boolean executeUpdate(String sql, SqlConsumer parameterSetter) {
+ try (var connection = ConnectionUtil.getConnection();
+ var preparedStatement = connection.prepareStatement(sql)) {
+ parameterSetter.accept(preparedStatement);
+ return preparedStatement.executeUpdate() > 0;
+ } catch (SQLException e) {
+ throw new DataProcessingException("Cannot execute update from SQL: " + sql, e);
+ }
+ }
+
+ private T executeQuery(String sql, SqlConsumer parameterSetter,
+ SqlFunction resultMapper) {
+ try (var connection = ConnectionUtil.getConnection();
+ var preparedStatement = connection.prepareStatement(sql)) {
+ parameterSetter.accept(preparedStatement);
+ var resultSet = preparedStatement.executeQuery();
+ return resultMapper.apply(resultSet);
+ } catch (SQLException e) {
+ throw new DataProcessingException("Cannot execute query for SQL: " + sql, e);
+ }
+ }
+}
diff --git a/src/main/java/mate/academy/exception/DataProcessingException.java b/src/main/java/mate/academy/exception/DataProcessingException.java
new file mode 100644
index 00000000..f6fe8806
--- /dev/null
+++ b/src/main/java/mate/academy/exception/DataProcessingException.java
@@ -0,0 +1,16 @@
+package mate.academy.exception;
+
+public class DataProcessingException extends RuntimeException {
+
+ public DataProcessingException() {
+ super();
+ }
+
+ public DataProcessingException(String message) {
+ super(message);
+ }
+
+ public DataProcessingException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/mate/academy/model/Book.java b/src/main/java/mate/academy/model/Book.java
new file mode 100644
index 00000000..289371bb
--- /dev/null
+++ b/src/main/java/mate/academy/model/Book.java
@@ -0,0 +1,62 @@
+package mate.academy.model;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+
+public class Book {
+ private long id;
+ private String title;
+ private BigDecimal price;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public BigDecimal getPrice() {
+ return price;
+ }
+
+ public void setPrice(BigDecimal price) {
+ this.price = price;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Book book = (Book) o;
+ return Objects.equals(id, book.id)
+ && Objects.equals(title, book.title)
+ && Objects.equals(price, book.price);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, title, price);
+ }
+
+ @Override
+ public String toString() {
+ return "Book{"
+ + "id=" + id
+ + ", title='" + title + '\''
+ + ", price=" + price
+ + '}';
+ }
+}
diff --git a/src/main/java/mate/academy/service/BookService.java b/src/main/java/mate/academy/service/BookService.java
new file mode 100644
index 00000000..78b4f594
--- /dev/null
+++ b/src/main/java/mate/academy/service/BookService.java
@@ -0,0 +1,18 @@
+package mate.academy.service;
+
+import java.util.List;
+import java.util.Optional;
+import mate.academy.model.Book;
+
+public interface BookService {
+
+ Book save(Book book);
+
+ Optional get(Long id);
+
+ List getAll();
+
+ Book update(Book book);
+
+ boolean delete(Long id);
+}
diff --git a/src/main/java/mate/academy/service/impl/BookServiceImpl.java b/src/main/java/mate/academy/service/impl/BookServiceImpl.java
new file mode 100644
index 00000000..220537af
--- /dev/null
+++ b/src/main/java/mate/academy/service/impl/BookServiceImpl.java
@@ -0,0 +1,43 @@
+package mate.academy.service.impl;
+
+import java.util.List;
+import java.util.Optional;
+import mate.academy.dao.BookDao;
+import mate.academy.exception.DataProcessingException;
+import mate.academy.model.Book;
+import mate.academy.service.BookService;
+
+public class BookServiceImpl implements BookService {
+
+ private final BookDao bookDao;
+
+ public BookServiceImpl(BookDao bookDao) {
+ this.bookDao = bookDao;
+ }
+
+ @Override
+ public Book save(Book book) {
+ return bookDao.save(book);
+ }
+
+ @Override
+ public Optional get(Long id) {
+ return Optional.ofNullable(bookDao.findById(id)
+ .orElseThrow(() -> new DataProcessingException("Cannot find book by id: " + id)));
+ }
+
+ @Override
+ public List getAll() {
+ return bookDao.findAll();
+ }
+
+ @Override
+ public Book update(Book book) {
+ return bookDao.update(book);
+ }
+
+ @Override
+ public boolean delete(Long id) {
+ return bookDao.deleteById(id);
+ }
+}
diff --git a/src/main/java/mate/academy/util/ConnectionUtil.java b/src/main/java/mate/academy/util/ConnectionUtil.java
new file mode 100644
index 00000000..37c5af3d
--- /dev/null
+++ b/src/main/java/mate/academy/util/ConnectionUtil.java
@@ -0,0 +1,40 @@
+package mate.academy.util;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Properties;
+import mate.academy.exception.DataProcessingException;
+
+public class ConnectionUtil {
+
+ private static Properties DB_PROPERTIES;
+ private static final String USERNAME = "root";
+ private static final String PASSWORD = "Root1234";
+ private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
+ private static final String JDBC_URL =
+ "jdbc:mysql://localhost:3306/book_storage?serverTimezone=UTC";
+
+ static {
+ loadDriver();
+ }
+
+ public static Connection getConnection() {
+ try {
+ return DriverManager.getConnection(JDBC_URL, DB_PROPERTIES);
+ } catch (SQLException e) {
+ throw new DataProcessingException("Cannot connected to JDBC_URL: " + JDBC_URL, e);
+ }
+ }
+
+ private static void loadDriver() {
+ DB_PROPERTIES = new Properties();
+ DB_PROPERTIES.put("user", USERNAME);
+ DB_PROPERTIES.put("password", PASSWORD);
+ try {
+ Class.forName(JDBC_DRIVER);
+ } catch (ClassNotFoundException e) {
+ throw new DataProcessingException("Cannot load JDBC driver", e);
+ }
+ }
+}
diff --git a/src/main/java/mate/academy/util/SqlConsumer.java b/src/main/java/mate/academy/util/SqlConsumer.java
new file mode 100644
index 00000000..92588dd4
--- /dev/null
+++ b/src/main/java/mate/academy/util/SqlConsumer.java
@@ -0,0 +1,8 @@
+package mate.academy.util;
+
+import java.sql.SQLException;
+
+@FunctionalInterface
+public interface SqlConsumer {
+ void accept(T t) throws SQLException;
+}
diff --git a/src/main/java/mate/academy/util/SqlFunction.java b/src/main/java/mate/academy/util/SqlFunction.java
new file mode 100644
index 00000000..23ebc200
--- /dev/null
+++ b/src/main/java/mate/academy/util/SqlFunction.java
@@ -0,0 +1,8 @@
+package mate.academy.util;
+
+import java.sql.SQLException;
+
+@FunctionalInterface
+public interface SqlFunction {
+ R apply(T t) throws SQLException;
+}
diff --git a/src/main/resources/init_db.sql b/src/main/resources/init_db.sql
new file mode 100644
index 00000000..ecab32e8
--- /dev/null
+++ b/src/main/resources/init_db.sql
@@ -0,0 +1,23 @@
+CREATE DATABASE IF NOT EXISTS book_storage;
+
+USE book_storage;
+
+CREATE TABLE IF NOT EXISTS books(
+id BIGINT PRIMARY KEY AUTO_INCREMENT,
+title VARCHAR(255) NOT NULL,
+price DECIMAL NOT NULL
+);
+
+INSERT INTO books(title,price) VALUES
+ ('Effective Java', 45.99),
+ ('Thinking in Java', 48.95),
+ ('Head First Java', 40.75),
+ ('Java: A Beginners Guide', 30.99),
+ ('Java SE8 for the Really Impatient', 37.99),
+ ('Java Performance: The Definitive Guide', 50.00),
+ ('Clean Code: A Handbook of Agile Software Craftsmanship', 44.50),
+ ('Spring in Action', 39.99),
+ ('Java Concurrency in Practice', 42.80);
+
+SELECT * FROM books;
+