From 837b449d9992fd5ad84d54ae09623791fd37a08f Mon Sep 17 00:00:00 2001 From: Andrii Linkov Date: Thu, 19 Sep 2024 14:40:04 +0300 Subject: [PATCH 1/4] Implemented BookDao --- pom.xml | 8 ++ .../java/mate/academy/ConnectionUtil.java | 34 +++++ src/main/java/mate/academy/Main.java | 20 +++ src/main/java/mate/academy/dao/BookDao.java | 17 +++ .../java/mate/academy/dao/BookDaoImpl.java | 120 ++++++++++++++++++ .../exception/DataProcessingException.java | 11 ++ src/main/java/mate/academy/model/Book.java | 41 ++++++ src/main/resources/config.properties | 3 + src/main/resources/init_db.sql | 17 +++ 9 files changed, 271 insertions(+) create mode 100644 src/main/java/mate/academy/ConnectionUtil.java create mode 100644 src/main/java/mate/academy/dao/BookDao.java create mode 100644 src/main/java/mate/academy/dao/BookDaoImpl.java create mode 100644 src/main/java/mate/academy/exception/DataProcessingException.java create mode 100644 src/main/java/mate/academy/model/Book.java create mode 100644 src/main/resources/config.properties create mode 100644 src/main/resources/init_db.sql diff --git a/pom.xml b/pom.xml index 683e84ec..4237e40c 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,14 @@ + + + com.mysql + mysql-connector-j + 8.0.33 + + + diff --git a/src/main/java/mate/academy/ConnectionUtil.java b/src/main/java/mate/academy/ConnectionUtil.java new file mode 100644 index 00000000..c4f14b1e --- /dev/null +++ b/src/main/java/mate/academy/ConnectionUtil.java @@ -0,0 +1,34 @@ +package mate.academy; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +public class ConnectionUtil { + private static final Properties DB_PROPERTIES = new Properties(); + private static final String CONFIG_FILE = "config.properties"; + + static { + try (InputStream input = ConnectionUtil.class.getClassLoader() + .getResourceAsStream(CONFIG_FILE)) { + if (input == null) { + throw new RuntimeException( + "Configuration file '" + CONFIG_FILE + "' not found in classpath"); + } + DB_PROPERTIES.load(input); + Class.forName("com.mysql.cj.jdbc.Driver"); + } catch (IOException | ClassNotFoundException e) { + throw new RuntimeException("Cannot load configuration or JDBC driver", e); + } + } + + public static Connection getConnection() throws SQLException { + String url = DB_PROPERTIES.getProperty("db.url"); + String user = DB_PROPERTIES.getProperty("db.user"); + String password = DB_PROPERTIES.getProperty("db.password"); + return DriverManager.getConnection(url, user, password); + } +} diff --git a/src/main/java/mate/academy/Main.java b/src/main/java/mate/academy/Main.java index 0058fbf9..fec699a1 100644 --- a/src/main/java/mate/academy/Main.java +++ b/src/main/java/mate/academy/Main.java @@ -1,7 +1,27 @@ 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; + public class Main { + private static final Injector injector = Injector.getInstance("mate.academy"); + public static void main(String[] args) { + BookDao bookDao = (BookDao) injector.getInstance(BookDao.class); + + Book zapovit = new Book("zapovit", BigDecimal.valueOf(100)); + Book newZapiovit = bookDao.create(zapovit); + + Optional bookById2 = bookDao.findById(2L); + + List allBooks = bookDao.findAll(); + + Book updateZapovit = bookDao.update(zapovit); + boolean isDeleted = bookDao.deleteById(3L); } } 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..d60c063d --- /dev/null +++ b/src/main/java/mate/academy/dao/BookDao.java @@ -0,0 +1,17 @@ +package mate.academy.dao; + +import java.util.List; +import java.util.Optional; +import mate.academy.model.Book; + +public interface BookDao { + Book create(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/BookDaoImpl.java b/src/main/java/mate/academy/dao/BookDaoImpl.java new file mode 100644 index 00000000..a0645a09 --- /dev/null +++ b/src/main/java/mate/academy/dao/BookDaoImpl.java @@ -0,0 +1,120 @@ +package mate.academy.dao; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import mate.academy.ConnectionUtil; +import mate.academy.exception.DataProcessingException; +import mate.academy.lib.Dao; +import mate.academy.model.Book; + +@Dao +public class BookDaoImpl implements BookDao { + @Override + public Book create(Book book) { + String sql = "INSERT INTO books (title, price) VALUES (?, ?);"; + try (Connection connection = ConnectionUtil.getConnection(); + PreparedStatement statement = connection.prepareStatement(sql, + Statement.RETURN_GENERATED_KEYS)) { + statement.setString(1, book.getTitle()); + statement.setBigDecimal(2, book.getPrice()); + int affectedRows = statement.executeUpdate(); + if (affectedRows < 1) { + throw new RuntimeException( + "Expected to insert at least one row, but inserted 0 rows."); + } + ResultSet generatedKeys = statement.getGeneratedKeys(); + if (generatedKeys.next()) { + Long id = generatedKeys.getObject(1, Long.class); + book.setId(id); + } + } catch (SQLException e) { + throw new DataProcessingException("Can not add a new book: " + book, e); + } + return book; + } + + @Override + public Optional findById(Long id) { + String sql = "SELECT * FROM books WHERE id = ?;"; + try (Connection connection = ConnectionUtil.getConnection(); + PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setLong(1, id); + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + String title = resultSet.getString("title"); + BigDecimal price = resultSet.getObject("price", BigDecimal.class); + Book book = new Book(); + book.setId(id); + book.setTitle(title); + book.setPrice(price); + return Optional.of(book); + } else { + throw new RuntimeException("Can not find book in database by id = " + id); + } + } catch (SQLException e) { + throw new DataProcessingException("Can not create a connection to the database", e); + } + } + + @Override + public List findAll() { + List books = new ArrayList<>(); + String sql = "SELECT * FROM books;"; + try (Connection connection = ConnectionUtil.getConnection(); + PreparedStatement statement = connection.prepareStatement(sql)) { + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + Long id = resultSet.getObject("id", Long.class); + String title = resultSet.getString("title"); + BigDecimal price = resultSet.getObject("price", BigDecimal.class); + Book book = new Book(); + book.setId(id); + book.setTitle(title); + book.setPrice(price); + books.add(book); + } + } catch (SQLException e) { + throw new DataProcessingException("Can not create a connection to the database", e); + } + return books; + } + + @Override + public Book update(Book book) { + String sql = "UPDATE books SET title = ?, price = ? WHERE id = ?;"; + try (Connection connection = ConnectionUtil.getConnection(); + PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setLong(3, book.getId()); + statement.setString(1, book.getTitle()); + statement.setBigDecimal(2, book.getPrice()); + int affectedRows = statement.executeUpdate(); + if (affectedRows < 1) { + throw new DataProcessingException( + "Expected to update at least one row, but updated 0 rows."); + } + return book; + } catch (SQLException e) { + throw new DataProcessingException("Can not create a connection to the database", e); + } + } + + @Override + public boolean deleteById(Long id) { + String sql = "DELETE FROM books WHERE id = ?;"; + try (Connection connection = ConnectionUtil.getConnection(); + PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setLong(1, id); + int affectedRows = statement.executeUpdate(); + return affectedRows >= 1; + } catch (SQLException e) { + throw new DataProcessingException("Can not create a connection to the database", 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..90231bc8 --- /dev/null +++ b/src/main/java/mate/academy/exception/DataProcessingException.java @@ -0,0 +1,11 @@ +package mate.academy.exception; + +public class DataProcessingException extends RuntimeException { + public DataProcessingException(String message, Throwable cause) { + super(message, cause); + } + + public DataProcessingException(String message) { + super(message); + } +} 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..083b4915 --- /dev/null +++ b/src/main/java/mate/academy/model/Book.java @@ -0,0 +1,41 @@ +package mate.academy.model; + +import java.math.BigDecimal; + +public class Book { + private Long id; + private String title; + private BigDecimal price; + + public Book(String title, BigDecimal price) { + this.title = title; + this.price = price; + } + + public Book() { + } + + 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; + } +} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties new file mode 100644 index 00000000..192c7994 --- /dev/null +++ b/src/main/resources/config.properties @@ -0,0 +1,3 @@ +db.url=jdbc:mysql://localhost:3306/intro +db.user=root +db.password=091088a+ diff --git a/src/main/resources/init_db.sql b/src/main/resources/init_db.sql new file mode 100644 index 00000000..7e98f232 --- /dev/null +++ b/src/main/resources/init_db.sql @@ -0,0 +1,17 @@ +CREATE DATABASE IF NOT EXISTS `intro`; + +USE `intro`; + +CREATE TABLE `books` ( + `id` INT NOT NULL AUTO_INCREMENT, + `title` VARCHAR(255), + `price` INT, + PRIMARY KEY (`id`) +); + +INSERT INTO `books` (`title`, `price`) VALUES +('The Catcher in the Rye', 120), +('To Kill a Mockingbird', 150), +('1984', 200), +('The Great Gatsby', 180), +('Moby Dick', 220); From 544310a7d2f79cb6ada40117f1ebc4300568b21a Mon Sep 17 00:00:00 2001 From: Andrii Linkov Date: Thu, 19 Sep 2024 17:06:34 +0300 Subject: [PATCH 2/4] Added separate methods to map ResultSet to Book in BookDaoImpl. Minor fixes applied. --- .../java/mate/academy/dao/BookDaoImpl.java | 28 +++++++++---------- .../academy/{ => util}/ConnectionUtil.java | 2 +- src/main/resources/init_db.sql | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) rename src/main/java/mate/academy/{ => util}/ConnectionUtil.java (97%) diff --git a/src/main/java/mate/academy/dao/BookDaoImpl.java b/src/main/java/mate/academy/dao/BookDaoImpl.java index a0645a09..691c2a8f 100644 --- a/src/main/java/mate/academy/dao/BookDaoImpl.java +++ b/src/main/java/mate/academy/dao/BookDaoImpl.java @@ -9,13 +9,24 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import mate.academy.ConnectionUtil; import mate.academy.exception.DataProcessingException; import mate.academy.lib.Dao; import mate.academy.model.Book; +import mate.academy.util.ConnectionUtil; @Dao public class BookDaoImpl implements BookDao { + private Book mapToBook(ResultSet resultSet) throws SQLException { + Long id = resultSet.getObject("id", Long.class); + String title = resultSet.getString("title"); + BigDecimal price = resultSet.getObject("price", BigDecimal.class); + Book book = new Book(); + book.setId(id); + book.setTitle(title); + book.setPrice(price); + return book; + } + @Override public Book create(Book book) { String sql = "INSERT INTO books (title, price) VALUES (?, ?);"; @@ -48,12 +59,7 @@ public Optional findById(Long id) { statement.setLong(1, id); ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { - String title = resultSet.getString("title"); - BigDecimal price = resultSet.getObject("price", BigDecimal.class); - Book book = new Book(); - book.setId(id); - book.setTitle(title); - book.setPrice(price); + Book book = mapToBook(resultSet); return Optional.of(book); } else { throw new RuntimeException("Can not find book in database by id = " + id); @@ -71,13 +77,7 @@ public List findAll() { PreparedStatement statement = connection.prepareStatement(sql)) { ResultSet resultSet = statement.executeQuery(); while (resultSet.next()) { - Long id = resultSet.getObject("id", Long.class); - String title = resultSet.getString("title"); - BigDecimal price = resultSet.getObject("price", BigDecimal.class); - Book book = new Book(); - book.setId(id); - book.setTitle(title); - book.setPrice(price); + Book book = mapToBook(resultSet); books.add(book); } } catch (SQLException e) { diff --git a/src/main/java/mate/academy/ConnectionUtil.java b/src/main/java/mate/academy/util/ConnectionUtil.java similarity index 97% rename from src/main/java/mate/academy/ConnectionUtil.java rename to src/main/java/mate/academy/util/ConnectionUtil.java index c4f14b1e..597a8712 100644 --- a/src/main/java/mate/academy/ConnectionUtil.java +++ b/src/main/java/mate/academy/util/ConnectionUtil.java @@ -1,4 +1,4 @@ -package mate.academy; +package mate.academy.util; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/resources/init_db.sql b/src/main/resources/init_db.sql index 7e98f232..921e2ba3 100644 --- a/src/main/resources/init_db.sql +++ b/src/main/resources/init_db.sql @@ -5,7 +5,7 @@ USE `intro`; CREATE TABLE `books` ( `id` INT NOT NULL AUTO_INCREMENT, `title` VARCHAR(255), - `price` INT, + `price` DECIMAL, PRIMARY KEY (`id`) ); From a46eb4a3535f54610a4de8a3223aff7fee530201 Mon Sep 17 00:00:00 2001 From: Andrii Linkov Date: Fri, 20 Sep 2024 12:29:45 +0300 Subject: [PATCH 3/4] Added separate methods to validate PreparedStatement execution to Book in BookDaoImpl. --- .../java/mate/academy/dao/BookDaoImpl.java | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/main/java/mate/academy/dao/BookDaoImpl.java b/src/main/java/mate/academy/dao/BookDaoImpl.java index 691c2a8f..fa4943a3 100644 --- a/src/main/java/mate/academy/dao/BookDaoImpl.java +++ b/src/main/java/mate/academy/dao/BookDaoImpl.java @@ -16,17 +16,6 @@ @Dao public class BookDaoImpl implements BookDao { - private Book mapToBook(ResultSet resultSet) throws SQLException { - Long id = resultSet.getObject("id", Long.class); - String title = resultSet.getString("title"); - BigDecimal price = resultSet.getObject("price", BigDecimal.class); - Book book = new Book(); - book.setId(id); - book.setTitle(title); - book.setPrice(price); - return book; - } - @Override public Book create(Book book) { String sql = "INSERT INTO books (title, price) VALUES (?, ?);"; @@ -35,11 +24,7 @@ public Book create(Book book) { Statement.RETURN_GENERATED_KEYS)) { statement.setString(1, book.getTitle()); statement.setBigDecimal(2, book.getPrice()); - int affectedRows = statement.executeUpdate(); - if (affectedRows < 1) { - throw new RuntimeException( - "Expected to insert at least one row, but inserted 0 rows."); - } + validateStatementExecution(statement); ResultSet generatedKeys = statement.getGeneratedKeys(); if (generatedKeys.next()) { Long id = generatedKeys.getObject(1, Long.class); @@ -91,14 +76,10 @@ public Book update(Book book) { String sql = "UPDATE books SET title = ?, price = ? WHERE id = ?;"; try (Connection connection = ConnectionUtil.getConnection(); PreparedStatement statement = connection.prepareStatement(sql)) { - statement.setLong(3, book.getId()); statement.setString(1, book.getTitle()); statement.setBigDecimal(2, book.getPrice()); - int affectedRows = statement.executeUpdate(); - if (affectedRows < 1) { - throw new DataProcessingException( - "Expected to update at least one row, but updated 0 rows."); - } + statement.setLong(3, book.getId()); + validateStatementExecution(statement); return book; } catch (SQLException e) { throw new DataProcessingException("Can not create a connection to the database", e); @@ -117,4 +98,24 @@ public boolean deleteById(Long id) { throw new DataProcessingException("Can not create a connection to the database", e); } } + + private Book mapToBook(ResultSet resultSet) throws SQLException { + Long id = resultSet.getObject("id", Long.class); + String title = resultSet.getString("title"); + BigDecimal price = resultSet.getObject("price", BigDecimal.class); + Book book = new Book(); + book.setId(id); + book.setTitle(title); + book.setPrice(price); + return book; + } + + private void validateStatementExecution(PreparedStatement statement) + throws SQLException { + int affectedRows = statement.executeUpdate(); + if (affectedRows < 1) { + throw new DataProcessingException( + "Expected to modify at least one row, but modified 0 rows."); + } + } } From d83d20e1443fc5aaf4a549f69081255262449546 Mon Sep 17 00:00:00 2001 From: Andrii Linkov Date: Fri, 20 Sep 2024 14:39:41 +0300 Subject: [PATCH 4/4] Fixed CREATE TABLE `id`INT NOT NULL to BIGINT --- src/main/resources/init_db.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/init_db.sql b/src/main/resources/init_db.sql index 921e2ba3..318693e7 100644 --- a/src/main/resources/init_db.sql +++ b/src/main/resources/init_db.sql @@ -3,7 +3,7 @@ CREATE DATABASE IF NOT EXISTS `intro`; USE `intro`; CREATE TABLE `books` ( - `id` INT NOT NULL AUTO_INCREMENT, + `id` BIGINT AUTO_INCREMENT, `title` VARCHAR(255), `price` DECIMAL, PRIMARY KEY (`id`)