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; +