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

HM JDBC #418

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
69e8344
created a init_db.sql and put a script for creating table
dlvsn Sep 18, 2024
c097cef
Implemented class Book
dlvsn Sep 18, 2024
4c687e8
added NOT NULL to tittle and price in initDb.sql
dlvsn Sep 18, 2024
b87ce47
Implemented ConnectionUtil
dlvsn Sep 18, 2024
f18845a
Created bookDao interface and added CRUD methods
dlvsn Sep 18, 2024
f61b102
added mySql connector at pom.xml
dlvsn Sep 20, 2024
e0c219d
Implemented findAll
dlvsn Sep 20, 2024
c05cc8a
Changed initDb.sql
dlvsn Sep 21, 2024
346f873
fix mistake with BookDao class name
dlvsn Sep 21, 2024
260cd90
Init DataProcessingException
dlvsn Sep 21, 2024
58e06a9
Changed throwable type exception from RunTime to DataProccException
dlvsn Sep 21, 2024
39510da
Removed id field from constructor
dlvsn Sep 21, 2024
801737f
Add static fabric method
dlvsn Sep 21, 2024
39326ca
used a static fabric method instead of a setter + constructor
dlvsn Sep 21, 2024
70ae9ad
Implemented getRowsCount/clear/create/findById/findAll/update/deleteById
dlvsn Sep 21, 2024
2016722
fix some mistakes
dlvsn Sep 22, 2024
d0e6c99
Deleted methods clear and getRowCount
dlvsn Sep 23, 2024
950d728
Changed field type of id/price from long to Long Wrapper. from int to…
dlvsn Sep 23, 2024
2016a25
Changed field type in book table
dlvsn Sep 23, 2024
e8924fc
added separated methods. Refactored code
dlvsn Sep 23, 2024
426dd56
Fix some mistakes in initDbSql
dlvsn Sep 23, 2024
878c6f9
Task redo
dlvsn Sep 23, 2024
7e7dec3
fix mistakes accodring checkstyle
dlvsn Sep 23, 2024
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
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@
https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
</maven.checkstyle.plugin.configLocation>
</properties>
<dependencies>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
</dependencies>

<build>
<plugins>
Expand Down
31 changes: 31 additions & 0 deletions src/main/java/mate/academy/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
package mate.academy;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import mate.academy.bookdao.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 firstBook = Book.of("Effective Java", new BigDecimal(700));
Book secondBook = Book.of("Clean Code", new BigDecimal(900));
Book thirdBook = Book.of("Head of Java", new BigDecimal(600));

bookDao.create(firstBook);
bookDao.create(secondBook);
bookDao.create(thirdBook);

List<Book> booksFromDb = bookDao.findAll();
System.out.println(booksFromDb);

Optional<Book> bookById = bookDao.findById(3L);
bookById.ifPresent(System.out::println);

Book book = Book.of(3L, "Harry Potter", new BigDecimal(1000));
Book updated = bookDao.update(book);
System.out.println(updated);

bookDao.findById(3L).ifPresent(System.out::println);

boolean isBookDeleted = bookDao.deleteById(3L);
System.out.println(isBookDeleted);
}
}
18 changes: 18 additions & 0 deletions src/main/java/mate/academy/bookdao/BookDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package mate.academy.bookdao;

import java.util.List;
import java.util.Optional;
import mate.academy.model.Book;

public interface BookDao {

Choose a reason for hiding this comment

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

Suggested change

Choose a reason for hiding this comment

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

remove 8 line

Book create(Book book);

Optional<Book> findById(Long id);

List<Book> findAll();

Book update(Book book);

boolean deleteById(Long id);
}
122 changes: 122 additions & 0 deletions src/main/java/mate/academy/bookdao/BookDaoImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package mate.academy.bookdao;

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.dbconnection.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(?, ?)";

Choose a reason for hiding this comment

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

Use Statement.RETURN_GENERATED_KEYS only in create statement. Since this is the create method, it's correctly used here. However, ensure it's not used in other methods where it's not needed.

try (Connection connection = ConnectionUtil.getConnection();
PreparedStatement preparedStatement = connection
.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
if (storeBook(preparedStatement, book, false)) {
throw new RuntimeException("Book could not be created");
}
Comment on lines +25 to +27

Choose a reason for hiding this comment

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

Suggested change
if (storeBook(preparedStatement, book, false)) {
throw new RuntimeException("Book could not be created");
}
statement.setString(1, book.getTitle());
statement.setBigDecimal(2, book.getPrice());
if (statement.executeUpdate() < 1) {

ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
if (generatedKeys.next()) {
Long id = generatedKeys.getObject(1, Long.class);
book.setId(id);
}
} catch (SQLException e) {
throw new DataProcessingException("Can't add a book " + book, e);
}
return book;
}

@Override
public Optional<Book> findById(Long id) {
String sql = "SELECT * FROM books WHERE id = ?";
try (Connection connection = ConnectionUtil.getConnection();
PreparedStatement preparedStatement = connection
.prepareStatement(sql)) {

Choose a reason for hiding this comment

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

Use PreparedStatement over Statement. Since you're already using PreparedStatement, this is just a reminder to maintain this practice.

preparedStatement.setLong(1, id);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
return Optional.of(mapToBook(resultSet));
}
} catch (SQLException e) {
throw new DataProcessingException("Can't get book by id " + id, e);
}
return Optional.empty();
}

@Override
public List<Book> findAll() {
String sql = "SELECT * FROM books";
List<Book> books = new ArrayList<>();
try (Connection connection = ConnectionUtil.getConnection();
Statement statement = connection.createStatement();

Choose a reason for hiding this comment

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

Use PreparedStatement over Statement, even for a static query with no parameters in findAll() method. It's the best practice, and it's slightly faster.

ResultSet resultSet = statement.executeQuery(sql)) {
while (resultSet.next()) {
books.add(mapToBook(resultSet));
}
} catch (SQLException e) {
throw new DataProcessingException("Can't get all books", e);
}
return books;
}

@Override
public Book update(Book book) {
String sql = "UPDATE books SET title = ?, price = ? WHERE id = ?";
try (Connection connection = ConnectionUtil.getConnection();
PreparedStatement preparedStatement = connection
.prepareStatement(sql)) {
if (storeBook(preparedStatement, book, true)) {
throw new RuntimeException("Book not found");
}
Comment on lines +78 to +80

Choose a reason for hiding this comment

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

Suggested change
if (storeBook(preparedStatement, book, true)) {
throw new RuntimeException("Book not found");
}
statement.setString(1, book.getTitle());
statement.setBigDecimal(2, book.getPrice());
statement.setLong(3, book.getId());
if (statement.executeUpdate() < 1) {
throw new DataProcessingException("Update failed for book with id:" + book.getId());
}

} catch (SQLException e) {
throw new DataProcessingException("Can't update book " + book.getTitle(), e);
}
return book;
}

@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 affectedRow = statement.executeUpdate();
Comment on lines +91 to +93

Choose a reason for hiding this comment

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

Don't return true all the time in the deleteById method. Let's return boolean value depending on statement.executeUpdate() result.

Choose a reason for hiding this comment

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

Suggested change
int affectedRow = statement.executeUpdate();
return statement.executeUpdate() > 0;

if (affectedRow < 1) {

Choose a reason for hiding this comment

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

Don't use affectedRow < 1 to check if the book was not found. Use affectedRow == 0 to check if no rows were affected, which means the book was not found. This is more explicit and understandable.

throw new RuntimeException("Book not found by id " + id);
}
} catch (SQLException e) {
throw new DataProcessingException("Can't delete book by " + id, e);
}
return true;
}

private Book mapToBook(ResultSet resultSet) throws SQLException {
Long id = resultSet.getLong("id");
String title = resultSet.getString("title");
BigDecimal price = resultSet.getBigDecimal("price");
return Book.of(id, title, price);
}

private boolean storeBook(PreparedStatement preparedStatement,

Choose a reason for hiding this comment

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

remove

Book book, boolean idPresent) throws SQLException {
if (idPresent) {
preparedStatement.setString(1, book.getTitle());
preparedStatement.setBigDecimal(2, book.getPrice());
preparedStatement.setLong(3, book.getId());
return preparedStatement.executeUpdate() < 1;
}
preparedStatement.setString(1, book.getTitle());
preparedStatement.setBigDecimal(2, book.getPrice());
return preparedStatement.executeUpdate() < 1;
}
}
27 changes: 27 additions & 0 deletions src/main/java/mate/academy/dbconnection/ConnectionUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package mate.academy.dbconnection;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

public class ConnectionUtil {
private static final String DB_URL = "jdbc:mysql://localhost:3306/test";

Choose a reason for hiding this comment

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

The DB_URL is incomplete and missing the actual database URL. It should be a full JDBC URL of the form jdbc:mysql://:/<database_name>, for example: "jdbc:mysql://localhost:3306/my_database".

Choose a reason for hiding this comment

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

The DB_URL is incomplete. It should contain the full JDBC URL to the database, including the host, port, and database name. For example: jdbc:mysql://localhost:3306/myDatabase

private static final Properties DB_PROPERTIES;

Choose a reason for hiding this comment

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

It is not secure to store database credentials in code. It's better to use environment variables or a configuration file that is not included in the version control.


static {
DB_PROPERTIES = new Properties();
DB_PROPERTIES.put("user", "root");
DB_PROPERTIES.put("password", "databasepractice1!");

try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException("Can't load a JDBC driver!", e);
}
}

public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(DB_URL, DB_PROPERTIES);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package mate.academy.exception;

public class DataProcessingException extends RuntimeException {
public DataProcessingException(String message, Throwable cause) {
super(message, cause);
}
}
65 changes: 65 additions & 0 deletions src/main/java/mate/academy/model/Book.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package mate.academy.model;

import java.math.BigDecimal;

public class Book {
private Long id;
private String title;
private BigDecimal price;

private Book(Long id, String title, BigDecimal price) {
this.id = id;
this.title = title;
this.price = price;
}

private Book(String title, BigDecimal price) {
this.title = title;
this.price = price;
}

public static Book of(Long id, String title, BigDecimal price) {
return new Book(id, title, price);
}

public static Book of(String title, BigDecimal price) {
return new Book(title, price);
}

public void setId(Long id) {
this.id = id;
}

public void setTitle(String title) {
this.title = title;
}

public void setPrice(BigDecimal price) {
this.price = price;
}

public Long getId() {
return id;
}

public String getTitle() {
return title;
}

public BigDecimal getPrice() {
return price;
}

@Override
public String toString() {
return "Book{"
+ "id="
+ id
+ ", title='"
+ title
+ '\''
+ ", price="
+ price
+ '}';
}
}
6 changes: 6 additions & 0 deletions src/main/resources/init_db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE books
(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100) NOT NULL,
price DECIMAL NOT NULL
);
Loading