Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented BookDao #416

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
</maven.checkstyle.plugin.configLocation>
</properties>

<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/mate/academy/Main.java
Original file line number Diff line number Diff line change
@@ -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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'newZapiovit' is never used. If you don't need the result of 'bookDao.create(zapovit)', you can omit assigning it to a variable.


Optional<Book> bookById2 = bookDao.findById(2L);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'bookById2' is never used. Make sure to use it or remove it if it's not necessary for the main logic.


List<Book> allBooks = bookDao.findAll();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'allBooks' is never used. Make sure to use it or remove it if it's not necessary for the main logic.


Book updateZapovit = bookDao.update(zapovit);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'updateZapovit' is never used. If you don't need the result of 'bookDao.update(zapovit)', you can omit assigning it to a variable.


boolean isDeleted = bookDao.deleteById(3L);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable 'isDeleted' is never used. Consider logging the result or handling the deletion result in some way.

}
}
17 changes: 17 additions & 0 deletions src/main/java/mate/academy/dao/BookDao.java
Original file line number Diff line number Diff line change
@@ -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<Book> findById(Long id);

List<Book> findAll();

Book update(Book book);

boolean deleteById(Long id);
}
120 changes: 120 additions & 0 deletions src/main/java/mate/academy/dao/BookDaoImpl.java
Original file line number Diff line number Diff line change
@@ -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.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;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private methods should be after all public


@Override
public Book create(Book book) {
String sql = "INSERT INTO books (title, price) VALUES (?, ?);";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For SQL keywords, use uppercase for better readability. For example, use INSERT INTO instead of insert into.

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.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to throw a DataProcessingException instead of a generic RuntimeException to keep the exception handling consistent throughout the DAO layer.

}
ResultSet generatedKeys = statement.getGeneratedKeys();
if (generatedKeys.next()) {
Long id = generatedKeys.getObject(1, Long.class);
book.setId(id);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Statement.RETURN_GENERATED_KEYS should only be used in the create method. This is correct usage, so no action is needed here. However, it's worth noting that the create method should return a boolean value depending on the result of executeUpdate, as per the checklist.

} catch (SQLException e) {
throw new DataProcessingException("Can not add a new book: " + book, e);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception message should be more informative. For example: 'Can't insert book: ' + book

}
return book;
}

@Override
public Optional<Book> findById(Long id) {
String sql = "SELECT * FROM books WHERE id = ?;";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL keywords should be in uppercase for consistency and readability.

try (Connection connection = ConnectionUtil.getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, id);
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
Book book = mapToBook(resultSet);
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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception message should be more informative and specific to the operation being performed. For example: 'Can't get a book by id ' + id

}
}

@Override
public List<Book> findAll() {
List<Book> books = new ArrayList<>();
String sql = "SELECT * FROM books;";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, use uppercase for SQL keywords to improve readability.

try (Connection connection = ConnectionUtil.getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
Book book = mapToBook(resultSet);
books.add(book);
}
} catch (SQLException e) {
throw new DataProcessingException("Can not create a connection to the database", e);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception message should be more informative. For example: 'Can't execute query: ' + sql

}
return books;
}

@Override
public Book update(Book book) {
String sql = "UPDATE books SET title = ?, price = ? WHERE id = ?;";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use uppercase for SQL keywords.

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());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
statement.setLong(3, book.getId());
statement.setString(1, book.getTitle());
statement.setBigDecimal(2, book.getPrice());
statement.setString(1, book.getTitle());
statement.setBigDecimal(2, book.getPrice());
statement.setLong(3, book.getId());

int affectedRows = statement.executeUpdate();
if (affectedRows < 1) {
throw new DataProcessingException(
"Expected to update at least one row, but updated 0 rows.");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use DataProcessingException for consistency with the rest of the DAO layer.

}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code duplication

return book;
} catch (SQLException e) {
throw new DataProcessingException("Can not create a connection to the database", e);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception message should be more informative and specific to the operation being performed. For example: 'Can't update book: ' + book

}
}

@Override
public boolean deleteById(Long id) {
String sql = "DELETE FROM books WHERE id = ?;";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use uppercase for SQL keywords.

try (Connection connection = ConnectionUtil.getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setLong(1, id);
int affectedRows = statement.executeUpdate();
return affectedRows >= 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deleteById method should return true only if one or more rows were affected. The current implementation is correct, so no action is needed here.

} catch (SQLException e) {
throw new DataProcessingException("Can not create a connection to the database", e);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception message should be more informative and specific to the operation being performed. For example: 'Can't delete book with id: ' + id

}
}
}
11 changes: 11 additions & 0 deletions src/main/java/mate/academy/exception/DataProcessingException.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
41 changes: 41 additions & 0 deletions src/main/java/mate/academy/model/Book.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
34 changes: 34 additions & 0 deletions src/main/java/mate/academy/util/ConnectionUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package mate.academy.util;

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);
}
}
3 changes: 3 additions & 0 deletions src/main/resources/config.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
db.url=jdbc:mysql://localhost:3306/intro
db.user=root
db.password=091088a+
17 changes: 17 additions & 0 deletions src/main/resources/init_db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE DATABASE IF NOT EXISTS `intro`;

USE `intro`;

CREATE TABLE `books` (
`id` INT NOT NULL AUTO_INCREMENT,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`id` INT NOT NULL AUTO_INCREMENT,
`id` BIGINT AUTO_INCREMENT,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not fixed

`title` VARCHAR(255),
`price` DECIMAL,
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);
Loading