diff --git a/account-db-initializer/src/main/java/com/bobocode/AccountDbInitializer.java b/account-db-initializer/src/main/java/com/bobocode/AccountDbInitializer.java index 9f185f9..2a8ae2f 100644 --- a/account-db-initializer/src/main/java/com/bobocode/AccountDbInitializer.java +++ b/account-db-initializer/src/main/java/com/bobocode/AccountDbInitializer.java @@ -1,7 +1,9 @@ package com.bobocode; import javax.sql.DataSource; +import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; /** * {@link AccountDbInitializer} provides an API that allow to initialize (create) an Account table in the database @@ -28,6 +30,20 @@ public AccountDbInitializer(DataSource dataSource) { * @throws SQLException */ public void init() throws SQLException { - throw new UnsupportedOperationException("It's your job to make it work!"); // todo + try (Connection connection = dataSource.getConnection()) { + Statement statement = connection.createStatement(); + statement.execute("CREATE TABLE account(" + + "id BIGINT," + + " email VARCHAR(255) NOT NULL," + + " first_name VARCHAR(255) NOT NULL," + + " last_name VARCHAR(255) NOT NULL," + + " gender VARCHAR(255) NOT NULL," + + " birthday DATE NOT NULL," + + " balance DECIMAL(19,4)," + + " creation_time TIMESTAMP NOT NULL DEFAULT now()," + + " CONSTRAINT account_pk PRIMARY KEY (id)," + + " CONSTRAINT account_email_uq UNIQUE (email)" + + ");"); + } } } diff --git a/product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java b/product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java index a6e0d8c..bc62427 100644 --- a/product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java +++ b/product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java @@ -1,11 +1,21 @@ package com.bobocode.dao; +import com.bobocode.exception.DaoOperationException; import com.bobocode.model.Product; import javax.sql.DataSource; +import java.sql.*; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class ProductDaoImpl implements ProductDao { + private static final String INSERT_SQL = "INSERT INTO products(name, producer, price, expiration_date) VALUES (?, ?, ?, ?);"; + private static final String SELECT_ALL_SQL = "SELECT * FROM products;"; + private static final String SELECT_BY_ID_SQL = "SELECT * FROM products WHERE id = ?;"; + private static final String UPDATE_BY_ID_SLQ = "UPDATE products SET name = ?, producer = ?, price = ?, expiration_date = ? WHERE id = ?;"; + private static final String REMOVE_BY_ID_SQL = "DELETE FROM products WHERE id = ?;"; + private DataSource dataSource; public ProductDaoImpl(DataSource dataSource) { @@ -14,27 +24,186 @@ public ProductDaoImpl(DataSource dataSource) { @Override public void save(Product product) { - throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo + Objects.requireNonNull(product); + try (Connection connection = dataSource.getConnection()) { // try-with-resources will automatically close connection + saveProduct(product, connection); + } catch (SQLException e) { + throw new DaoOperationException(String.format("Error saving product: %s", product), e); + } + } + + private void saveProduct(Product product, Connection connection) throws SQLException { + PreparedStatement insertStatement = prepareInsertStatement(product, connection); + insertStatement.executeUpdate(); + Long id = fetchGeneratedId(insertStatement); + product.setId(id); + } + + private PreparedStatement prepareInsertStatement(Product product, Connection connection) { + try { + PreparedStatement insertStatement = connection.prepareStatement(INSERT_SQL, + PreparedStatement.RETURN_GENERATED_KEYS); // this parameter will configure query to ask db for a generated keys + fillProductStatement(product, insertStatement); + return insertStatement; + } catch (SQLException e) { + throw new DaoOperationException(String.format("Cannot prepare statement for product: %s", product), e); + } + } + + private void fillProductStatement(Product product, PreparedStatement updateStatement) throws SQLException { + updateStatement.setString(1, product.getName()); + updateStatement.setString(2, product.getProducer()); + updateStatement.setBigDecimal(3, product.getPrice()); + updateStatement.setDate(4, Date.valueOf(product.getExpirationDate())); + } + + private Long fetchGeneratedId(PreparedStatement insertStatement) throws SQLException { + // this method allows to retrieve IDs that were generated by the database during insert statement + ResultSet generatedKeys = insertStatement.getGeneratedKeys(); + if (generatedKeys.next()) { // you need to call next() because cursor is located before the first row + return generatedKeys.getLong(1); + } else { // if next() returned false it means that database didn't return any IDs + throw new DaoOperationException("Can not obtain product ID"); + } } @Override public List findAll() { - throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo + try (Connection connection = dataSource.getConnection()) { + return findAllProducts(connection); + } catch (SQLException e) { + throw new DaoOperationException("Error finding all products", e); + } + } + + private List findAllProducts(Connection connection) throws SQLException { + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(SELECT_ALL_SQL); + return collectToList(resultSet); + } + + private List collectToList(ResultSet resultSet) throws SQLException { + List products = new ArrayList<>(); + while (resultSet.next()) { + Product product = parseRow(resultSet); + products.add(product); + } + return products; + } + + private Product parseRow(ResultSet resultSet) { + try { + return createFromResultSet(resultSet); + } catch (SQLException e) { + throw new DaoOperationException("Cannot parse row to create product instance", e); + } + } + + private Product createFromResultSet(ResultSet resultSet) throws SQLException { + Product product = new Product(); + product.setId(resultSet.getLong("id")); + product.setName(resultSet.getString("name")); + product.setProducer(resultSet.getString("producer")); + product.setPrice(resultSet.getBigDecimal("price")); + product.setExpirationDate(resultSet.getDate("expiration_date").toLocalDate()); + product.setCreationTime(resultSet.getTimestamp("creation_time").toLocalDateTime()); + return product; } @Override public Product findOne(Long id) { - throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo + Objects.requireNonNull(id); + try (Connection connection = dataSource.getConnection()) { + return findProductById(id, connection); + } catch (SQLException e) { + throw new DaoOperationException(String.format("Error finding product by id = %d", id), e); + } + } + + private Product findProductById(Long id, Connection connection) throws SQLException { + PreparedStatement selectByIdStatement = prepareSelectByIdStatement(id, connection); + ResultSet resultSet = selectByIdStatement.executeQuery(); + if (resultSet.next()) {// we need to call next() since cursor is located before the first line + return parseRow(resultSet); + } else { // if next() returned false it means that database returned an empty response + throw new DaoOperationException(String.format("Product with id = %d does not exist", id)); + } + } + + private PreparedStatement prepareSelectByIdStatement(Long id, Connection connection) { + try { + PreparedStatement selectByIdStatement = connection.prepareStatement(SELECT_BY_ID_SQL); + selectByIdStatement.setLong(1, id); + return selectByIdStatement; + } catch (SQLException e) { + throw new DaoOperationException(String.format("Cannot prepare select by id statement for id = %d", id), e); + } } @Override public void update(Product product) { - throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo + Objects.requireNonNull(product); + try (Connection connection = dataSource.getConnection()) { + updateProduct(product, connection); + } catch (SQLException e) { + throw new DaoOperationException(String.format("Error updating product: %s", product), e); + } + } + + private void updateProduct(Product product, Connection connection) throws SQLException { + checkIdIsNotNull(product); + PreparedStatement updateStatement = prepareUpdateStatement(product, connection); + executeUpdateById(updateStatement, product.getId()); + } + + private void executeUpdateById(PreparedStatement insertStatement, Long productId) throws SQLException { + int rowsAffected = insertStatement.executeUpdate(); // returns number of rows that were changed + if (rowsAffected == 0) { + throw new DaoOperationException(String.format("Product with id = %d does not exist", productId)); + } + } + + private PreparedStatement prepareUpdateStatement(Product product, Connection connection) { + try { + PreparedStatement updateStatement = connection.prepareStatement(UPDATE_BY_ID_SLQ); + fillProductStatement(product, updateStatement); + updateStatement.setLong(5, product.getId()); + return updateStatement; + } catch (Exception e) { + throw new DaoOperationException(String.format("Cannot prepare update statement for product: %s", product), e); + } } @Override public void remove(Product product) { - throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo + Objects.requireNonNull(product); + try (Connection connection = dataSource.getConnection()) { + removeProduct(product, connection); + } catch (SQLException e) { + throw new DaoOperationException(String.format("Error removing product by id = %d", product.getId()), e); + } + } + + private void removeProduct(Product product, Connection connection) throws SQLException { + checkIdIsNotNull(product); + PreparedStatement removeStatement = prepareRemoveStatement(product, connection); + executeUpdateById(removeStatement, product.getId()); } + private PreparedStatement prepareRemoveStatement(Product product, Connection connection) { + try { + PreparedStatement removeStatement = connection.prepareStatement(REMOVE_BY_ID_SQL); + removeStatement.setLong(1, product.getId()); + return removeStatement; + } catch (SQLException e) { + throw new DaoOperationException(String.format("Cannot prepare statement for product: %s", product), e); + } + } + + private void checkIdIsNotNull(Product product) { + if (product.getId() == null) { + throw new DaoOperationException("Product id cannot be null"); + } + } } + diff --git a/product-dao/src/test/java/com/bobocode/ProductDaoTest.java b/product-dao/src/test/java/com/bobocode/ProductDaoTest.java index e0b70c8..6632d71 100644 --- a/product-dao/src/test/java/com/bobocode/ProductDaoTest.java +++ b/product-dao/src/test/java/com/bobocode/ProductDaoTest.java @@ -215,7 +215,7 @@ void testUpdateNotStored() { fail("Exception was't thrown"); } catch (Exception e) { assertEquals(DaoOperationException.class, e.getClass()); - assertEquals("Cannot find a product without ID", e.getMessage()); + assertEquals("Product id cannot be null", e.getMessage()); } } @@ -256,7 +256,7 @@ void testRemoveNotStored() { fail("Exception was't thrown"); } catch (Exception e) { assertEquals(DaoOperationException.class, e.getClass()); - assertEquals("Cannot find a product without ID", e.getMessage()); + assertEquals("Product id cannot be null", e.getMessage()); } } diff --git a/user-profile-db-initializer/src/main/resources/db/migration/table_initialization.sql b/user-profile-db-initializer/src/main/resources/db/migration/table_initialization.sql index 6754871..011a827 100644 --- a/user-profile-db-initializer/src/main/resources/db/migration/table_initialization.sql +++ b/user-profile-db-initializer/src/main/resources/db/migration/table_initialization.sql @@ -22,4 +22,23 @@ should have a column that stores primary key from a parent table, which is a for */ --- TODO: implement the SQL according to the description \ No newline at end of file +CREATE TABLE IF NOT EXISTS users ( + id BIGINT, + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + birthday DATE NOT NULL, + CONSTRAINT users_PK PRIMARY KEY (id), + CONSTRAINT users_email_AK UNIQUE (email) +); + +CREATE TABLE IF NOT EXISTS profiles ( + user_id BIGINT, + city VARCHAR(255), + job_position VARCHAR(255), + company VARCHAR(255), + education VARCHAR(255), + CONSTRAINT profiles_PK PRIMARY KEY (user_id), + CONSTRAINT profiles_users_FK FOREIGN KEY (user_id) REFERENCES users +); + diff --git a/wall-street-db-initializer/src/main/resources/db/migration/table_initialization.sql b/wall-street-db-initializer/src/main/resources/db/migration/table_initialization.sql index ad017fe..1e0e2d7 100644 --- a/wall-street-db-initializer/src/main/resources/db/migration/table_initialization.sql +++ b/wall-street-db-initializer/src/main/resources/db/migration/table_initialization.sql @@ -24,4 +24,31 @@ A sales group can consists of more than one broker, while each broker can be ass */ --- TODO: write SQL script to create a database tables according to the requirements +CREATE TABLE IF NOT EXISTS broker ( + id BIGINT, + username VARCHAR(255) NOT NULL, + first_name VARCHAR(255) NOT NULL, + last_name VARCHAR(255) NOT NULL, + CONSTRAINT PK_broker PRIMARY KEY (id), + CONSTRAINT UQ_broker_username UNIQUE (username) +); + + +CREATE TABLE IF NOT EXISTS sales_group ( + id BIGINT, + name VARCHAR(255) NOT NULL, + transaction_type VARCHAR(255) NOT NULL, + max_transaction_amount INT NOT NULL, + CONSTRAINT PK_sales_group PRIMARY KEY (id), + CONSTRAINT UQ_sales_group_name UNIQUE (name) +); + + +CREATE TABLE IF NOT EXISTS broker_sales_group ( + broker_id BIGINT NOT NULL, + sales_group_id BIGINT NOT NULL, + CONSTRAINT PK_broker_sales_group PRIMARY KEY (broker_id, sales_group_id), + CONSTRAINT FK_broker_sales_group_broker FOREIGN KEY (broker_id) REFERENCES broker, + CONSTRAINT FK_broker_sales_group_sales_group FOREIGN KEY (sales_group_id) REFERENCES sales_group +); +