From a216b523c7e6295f3f8149997d0ef29dee471955 Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 21 Jan 2026 14:41:19 +0200 Subject: [PATCH] exercises completion --- .../db/migration/table_initialization.sql | 22 ++- .../db/migration/table_initialization.sql | 28 ++- .../com/bobocode/AccountDbInitializer.java | 18 +- .../java/com/bobocode/dao/ProductDaoImpl.java | 179 +++++++++++++++++- .../main/java/com/bobocode/model/Movie.java | 8 + .../main/resources/META-INF/persistence.xml | 21 +- .../main/java/com/bobocode/QueryHelper.java | 14 +- .../java/com/bobocode/dao/AccountDaoImpl.java | 51 ++++- .../java/com/bobocode/model/Employee.java | 13 +- .../com/bobocode/model/EmployeeProfile.java | 13 ++ .../java/com/bobocode/dao/CompanyDaoImpl.java | 29 ++- .../main/java/com/bobocode/model/Company.java | 17 +- .../main/java/com/bobocode/model/Product.java | 14 +- .../main/java/com/bobocode/model/Author.java | 42 +++- .../main/java/com/bobocode/model/Book.java | 23 ++- .../java/com/bobocode/dao/PhotoDaoImpl.java | 27 ++- .../main/java/com/bobocode/model/Photo.java | 28 ++- .../java/com/bobocode/model/PhotoComment.java | 22 +++ 18 files changed, 517 insertions(+), 52 deletions(-) diff --git a/1-0-rdbms-and-sql/1-2-1-one-to-one-schema/src/main/resources/db/migration/table_initialization.sql b/1-0-rdbms-and-sql/1-2-1-one-to-one-schema/src/main/resources/db/migration/table_initialization.sql index 6754871f..18924243 100644 --- a/1-0-rdbms-and-sql/1-2-1-one-to-one-schema/src/main/resources/db/migration/table_initialization.sql +++ b/1-0-rdbms-and-sql/1-2-1-one-to-one-schema/src/main/resources/db/migration/table_initialization.sql @@ -22,4 +22,24 @@ 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 +); \ No newline at end of file diff --git a/1-0-rdbms-and-sql/1-2-3-many-to-many-schema/src/main/resources/db/migration/table_initialization.sql b/1-0-rdbms-and-sql/1-2-3-many-to-many-schema/src/main/resources/db/migration/table_initialization.sql index 1db459ed..03e1a831 100644 --- a/1-0-rdbms-and-sql/1-2-3-many-to-many-schema/src/main/resources/db/migration/table_initialization.sql +++ b/1-0-rdbms-and-sql/1-2-3-many-to-many-schema/src/main/resources/db/migration/table_initialization.sql @@ -24,4 +24,30 @@ 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 \ No newline at end of file +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 +); \ No newline at end of file diff --git a/2-0-jdbc-api/2-0-2-create-table/src/main/java/com/bobocode/AccountDbInitializer.java b/2-0-jdbc-api/2-0-2-create-table/src/main/java/com/bobocode/AccountDbInitializer.java index 8c8f52e1..adc43ad6 100644 --- a/2-0-jdbc-api/2-0-2-create-table/src/main/java/com/bobocode/AccountDbInitializer.java +++ b/2-0-jdbc-api/2-0-2-create-table/src/main/java/com/bobocode/AccountDbInitializer.java @@ -3,7 +3,9 @@ import com.bobocode.util.ExerciseNotCompletedException; 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 @@ -30,6 +32,20 @@ public AccountDbInitializer(DataSource dataSource) { * @throws SQLException */ public void init() throws SQLException { - throw new ExerciseNotCompletedException(); // todo + try (var connection = dataSource.getConnection()) { + var 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)" + + ");"); + } } } \ No newline at end of file diff --git a/2-0-jdbc-api/2-1-1-product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java b/2-0-jdbc-api/2-1-1-product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java index 33e9d155..9390181a 100644 --- a/2-0-jdbc-api/2-1-1-product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java +++ b/2-0-jdbc-api/2-1-1-product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java @@ -1,12 +1,23 @@ package com.bobocode.dao; +import com.bobocode.exception.DaoOperationException; import com.bobocode.model.Product; import com.bobocode.util.ExerciseNotCompletedException; + +import java.sql.*; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.sql.DataSource; 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 final DataSource dataSource; public ProductDaoImpl(DataSource dataSource) { @@ -15,27 +26,185 @@ public ProductDaoImpl(DataSource dataSource) { @Override public void save(Product product) { - throw new ExerciseNotCompletedException();// todo + Objects.requireNonNull(product); + try (var connection = dataSource.getConnection()) { + 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 { + var insertStatement = prepareInsertStatement(product, connection); + insertStatement.executeUpdate(); + product.setId(fetchGeneratedId(insertStatement)); + } + + private PreparedStatement prepareInsertStatement(Product product, Connection connection) { + try { + var 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 ExerciseNotCompletedException();// 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 { + var statement = connection.createStatement(); + var resultSet = statement.executeQuery(SELECT_ALL_SQL); + return collectToList(resultSet); + } + + private List collectToList(ResultSet resultSet) throws SQLException { + var 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 { + var 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 ExerciseNotCompletedException();// todo + Objects.requireNonNull(id); + try (var 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 { + var selectByIdStatement = prepareSelectByIdStatement(id, connection); + var 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 { + var 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 ExerciseNotCompletedException();// 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); + var updateStatement = prepareUpdateStatement(product, connection); + executeUpdateById(updateStatement, product.getId()); + } + + private void checkIdIsNotNull(Product product) { + if (product.getId() == null) { + throw new DaoOperationException("Product id cannot be null"); + } + } + + 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 { + var 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 ExerciseNotCompletedException();// todo + Objects.requireNonNull(product); + try (var 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); + var removeStatement = prepareRemoveStatement(product, connection); + executeUpdateById(removeStatement, product.getId()); + } + + private PreparedStatement prepareRemoveStatement(Product product, Connection connection) { + try { + var 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); + } } } diff --git a/3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/src/main/java/com/bobocode/model/Movie.java b/3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/src/main/java/com/bobocode/model/Movie.java index d402ea98..208d3330 100644 --- a/3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/src/main/java/com/bobocode/model/Movie.java +++ b/3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/src/main/java/com/bobocode/model/Movie.java @@ -1,5 +1,6 @@ package com.bobocode.model; +import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -15,12 +16,19 @@ @NoArgsConstructor @Getter @Setter +@Entity +@Table(name = "movie") public class Movie { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(name = "name", nullable = false) private String name; + @Column(name = "director", nullable = false) private String director; + @Column(name = "duration") private Integer durationSeconds; } \ No newline at end of file diff --git a/3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/src/main/resources/META-INF/persistence.xml b/3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/src/main/resources/META-INF/persistence.xml index c218803f..b2a8d325 100644 --- a/3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/src/main/resources/META-INF/persistence.xml +++ b/3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/src/main/resources/META-INF/persistence.xml @@ -1,14 +1,15 @@ + + com.bobocode.model.Song - - - - - - - - - - + + + + + + + + + diff --git a/3-0-jpa-and-hibernate/3-0-2-query-helper/src/main/java/com/bobocode/QueryHelper.java b/3-0-jpa-and-hibernate/3-0-2-query-helper/src/main/java/com/bobocode/QueryHelper.java index a30ca5dc..ce1c47da 100644 --- a/3-0-jpa-and-hibernate/3-0-2-query-helper/src/main/java/com/bobocode/QueryHelper.java +++ b/3-0-jpa-and-hibernate/3-0-2-query-helper/src/main/java/com/bobocode/QueryHelper.java @@ -32,6 +32,18 @@ public QueryHelper(EntityManagerFactory entityManagerFactory) { * @return query result specified by type T */ public T readWithinTx(Function entityManagerConsumer) { - throw new ExerciseNotCompletedException(); // todo: + var entityManager = entityManagerFactory.createEntityManager(); + entityManager.unwrap(Session.class).setDefaultReadOnly(true); + entityManager.getTransaction().begin(); + try { + T result = entityManagerConsumer.apply(entityManager); + entityManager.getTransaction().commit(); + return result; + } catch (Exception e) { + entityManager.getTransaction().rollback(); + throw new QueryHelperException("Transaction is rolled back.", e); + } finally { + entityManager.close(); + } } } \ No newline at end of file diff --git a/3-0-jpa-and-hibernate/3-0-3-account-dao/src/main/java/com/bobocode/dao/AccountDaoImpl.java b/3-0-jpa-and-hibernate/3-0-3-account-dao/src/main/java/com/bobocode/dao/AccountDaoImpl.java index 1e409f8f..861bbf13 100644 --- a/3-0-jpa-and-hibernate/3-0-3-account-dao/src/main/java/com/bobocode/dao/AccountDaoImpl.java +++ b/3-0-jpa-and-hibernate/3-0-3-account-dao/src/main/java/com/bobocode/dao/AccountDaoImpl.java @@ -1,10 +1,15 @@ package com.bobocode.dao; +import com.bobocode.exception.AccountDaoException; import com.bobocode.model.Account; -import com.bobocode.util.ExerciseNotCompletedException; +import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; +import jakarta.persistence.TypedQuery; + import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; public class AccountDaoImpl implements AccountDao { private EntityManagerFactory emf; @@ -15,32 +20,64 @@ public AccountDaoImpl(EntityManagerFactory emf) { @Override public void save(Account account) { - throw new ExerciseNotCompletedException(); // todo + performWithinPersistenceContext(em -> em.persist(account)); } @Override public Account findById(Long id) { - throw new ExerciseNotCompletedException(); // todo + return performReturningWithinPersistenceContext(entityManager -> entityManager.find(Account.class, id)); } @Override public Account findByEmail(String email) { - throw new ExerciseNotCompletedException(); // todo + return performReturningWithinPersistenceContext(entityManager -> { + TypedQuery findByEmailQuery + = entityManager.createQuery("select a from Account a where a.email = :email", Account.class); + findByEmailQuery.setParameter("email", email); + return findByEmailQuery.getSingleResult(); + }); } @Override public List findAll() { - throw new ExerciseNotCompletedException(); // todo + return performReturningWithinPersistenceContext(entityManager -> + entityManager.createQuery("select a from Account a", Account.class).getResultList()); } @Override public void update(Account account) { - throw new ExerciseNotCompletedException(); // todo + performWithinPersistenceContext(em -> em.merge(account)); } @Override public void remove(Account account) { - throw new ExerciseNotCompletedException(); // todo + performWithinPersistenceContext(entityManager -> { + Account mergedAccount = entityManager.merge(account); + entityManager.remove(mergedAccount); + }); + } + + private void performWithinPersistenceContext(Consumer entityManagerConsumer) { + performReturningWithinPersistenceContext(entityManager -> { + entityManagerConsumer.accept(entityManager); + return null; + }); + } + + private T performReturningWithinPersistenceContext(Function entityManagerFunction) { + EntityManager entityManager = emf.createEntityManager(); + entityManager.getTransaction().begin(); + T result; + try { + result = entityManagerFunction.apply(entityManager); + entityManager.getTransaction().commit(); + } catch (Exception e) { + entityManager.getTransaction().rollback(); + throw new AccountDaoException("Error performing dao operation. Transaction is rolled back!", e); + } finally { + entityManager.close(); + } + return result; } } diff --git a/3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/Employee.java b/3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/Employee.java index 58085012..68eefa12 100644 --- a/3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/Employee.java +++ b/3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/Employee.java @@ -1,5 +1,6 @@ package com.bobocode.model; +import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -16,9 +17,19 @@ @NoArgsConstructor @Getter @Setter +@Entity +@Table(name = "employee") public class Employee { + @Id + @GeneratedValue private Long id; + + @Column(nullable = false) private String email; + + @Column(nullable = false) private String fistName; + + @Column(nullable = false) private String lastName; -} +} \ No newline at end of file diff --git a/3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/EmployeeProfile.java b/3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/EmployeeProfile.java index e92d729d..0a28064d 100644 --- a/3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/EmployeeProfile.java +++ b/3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/EmployeeProfile.java @@ -1,5 +1,6 @@ package com.bobocode.model; +import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -16,9 +17,21 @@ @NoArgsConstructor @Getter @Setter +@Entity +@Table(name = "employee_profile") public class EmployeeProfile { + @Id private Long id; + + @MapsId + @OneToOne + @JoinColumn(name = "employee_id") private Employee employee; + + @Column(nullable = false) private String position; + + @Column(nullable = false) private String department; + } diff --git a/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/dao/CompanyDaoImpl.java b/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/dao/CompanyDaoImpl.java index c56818c2..1262789b 100644 --- a/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/dao/CompanyDaoImpl.java +++ b/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/dao/CompanyDaoImpl.java @@ -1,9 +1,13 @@ package com.bobocode.dao; +import com.bobocode.exception.CompanyDaoException; import com.bobocode.model.Company; import com.bobocode.util.ExerciseNotCompletedException; +import org.hibernate.Session; +import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManagerFactory; +import java.util.function.Function; public class CompanyDaoImpl implements CompanyDao { private EntityManagerFactory entityManagerFactory; @@ -14,6 +18,27 @@ public CompanyDaoImpl(EntityManagerFactory entityManagerFactory) { @Override public Company findByIdFetchProducts(Long id) { - throw new ExerciseNotCompletedException(); // todo + return readWithinTx(entityManager -> + entityManager + .createQuery("select c from Company c join fetch c.products where c.id = :id", Company.class) + .setParameter("id", id) + .getSingleResult() + ); } -} + + private T readWithinTx(Function entityManagerFunction) { + EntityManager entityManager = entityManagerFactory.createEntityManager(); + entityManager.unwrap(Session.class).setDefaultReadOnly(true); + entityManager.getTransaction().begin(); + try { + T queryResult = entityManagerFunction.apply(entityManager); + entityManager.getTransaction().commit(); + return queryResult; + } catch (Exception e) { + entityManager.getTransaction().rollback(); + throw new CompanyDaoException("Error performing read operation", e); + } finally { + entityManager.close(); + } + } +} \ No newline at end of file diff --git a/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Company.java b/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Company.java index 2dd45908..dbb9acc6 100644 --- a/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Company.java +++ b/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Company.java @@ -1,6 +1,7 @@ package com.bobocode.model; import com.bobocode.util.ExerciseNotCompletedException; +import jakarta.persistence.*; import lombok.*; import java.util.ArrayList; @@ -24,16 +25,28 @@ @NoArgsConstructor @Getter @Setter +@EqualsAndHashCode(of = "id") +@Entity +@Table(name = "company") public class Company { + @Id + @GeneratedValue private Long id; + + @Column(nullable = false) private String name; + + @Setter(AccessLevel.PRIVATE) + @OneToMany(mappedBy = "company", fetch = FetchType.LAZY) private List products = new ArrayList<>(); public void addProduct(Product product) { - throw new ExerciseNotCompletedException(); + products.add(product); + product.setCompany(this); } public void removeProduct(Product product) { - throw new ExerciseNotCompletedException(); + products.remove(product); + product.setCompany(null); } } \ No newline at end of file diff --git a/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Product.java b/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Product.java index b0772aa0..8de40d92 100644 --- a/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Product.java +++ b/3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Product.java @@ -1,5 +1,7 @@ package com.bobocode.model; +import jakarta.persistence.*; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -18,8 +20,18 @@ @NoArgsConstructor @Getter @Setter +@EqualsAndHashCode(of = "id") +@Entity +@Table(name = "product") public class Product { + @Id + @GeneratedValue private Long id; + + @Column(nullable = false) private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "company_id") private Company company; -} +} \ No newline at end of file diff --git a/3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Author.java b/3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Author.java index 28dd228c..4b4193c2 100644 --- a/3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Author.java +++ b/3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Author.java @@ -1,12 +1,14 @@ package com.bobocode.model; import com.bobocode.util.ExerciseNotCompletedException; +import jakarta.persistence.*; +import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import jakarta.persistence.CascadeType; import java.util.HashSet; +import java.util.Objects; import java.util.Set; /** @@ -32,17 +34,49 @@ @NoArgsConstructor @Getter @Setter +@Entity +@Table(name = "author") public class Author { + @Id + @GeneratedValue private Long id; + + @Column(name = "first_name", nullable = false) private String firstName; + + @Column(name = "last_name", nullable = false) private String lastName; - private Set books; + + @Setter(AccessLevel.PRIVATE) + @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) + @JoinTable(name = "author_book", + joinColumns = @JoinColumn(name = "author_id"), + inverseJoinColumns = @JoinColumn(name = "book_id") + ) + private Set books = new HashSet<>(); public void addBook(Book book) { - throw new ExerciseNotCompletedException(); + books.add(book); + book.getAuthors().add(this); } public void removeBook(Book book) { - throw new ExerciseNotCompletedException(); + books.remove(book); + book.getAuthors().remove(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Author)) return false; + + Author author = (Author) o; + + return Objects.equals(id, author.id); + } + + @Override + public int hashCode() { + return 31; } } diff --git a/3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Book.java b/3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Book.java index 21673ee4..e862dc5d 100644 --- a/3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Book.java +++ b/3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Book.java @@ -1,10 +1,10 @@ package com.bobocode.model; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import jakarta.persistence.*; +import org.hibernate.annotations.NaturalId; + import java.util.HashSet; import java.util.Set; @@ -25,9 +25,22 @@ @NoArgsConstructor @Getter @Setter +@EqualsAndHashCode(of = "isbn") +@Entity +@Table(name = "book") public class Book { + @Id + @GeneratedValue private Long id; + + @Column(nullable = false) private String name; + + @NaturalId + @Column(nullable = false, unique = true) private String isbn; - private Set authors; -} + + @Setter(AccessLevel.PRIVATE) + @ManyToMany(mappedBy = "books") + private Set authors = new HashSet<>(); +} \ No newline at end of file diff --git a/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/dao/PhotoDaoImpl.java b/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/dao/PhotoDaoImpl.java index be88196f..b503bf35 100644 --- a/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/dao/PhotoDaoImpl.java +++ b/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/dao/PhotoDaoImpl.java @@ -1,43 +1,60 @@ package com.bobocode.dao; import com.bobocode.model.Photo; +import com.bobocode.model.PhotoComment; +import com.bobocode.util.EntityManagerUtil; import com.bobocode.util.ExerciseNotCompletedException; import jakarta.persistence.EntityManagerFactory; import java.util.List; +import java.util.Objects; /** * Please note that you should not use auto-commit mode for your implementation. */ public class PhotoDaoImpl implements PhotoDao { private EntityManagerFactory entityManagerFactory; + private EntityManagerUtil emUtil; public PhotoDaoImpl(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; + this.emUtil = new EntityManagerUtil(entityManagerFactory); } @Override public void save(Photo photo) { - throw new ExerciseNotCompletedException(); // todo + Objects.requireNonNull(photo); + emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); } @Override public Photo findById(long id) { - throw new ExerciseNotCompletedException(); // todo + return emUtil.performReturningWithinTx(entityManager -> entityManager.find(Photo.class, id)); } @Override + @SuppressWarnings("unchecked") public List findAll() { - throw new ExerciseNotCompletedException(); // todo + return emUtil.performReturningWithinTx( + entityManager -> entityManager.createQuery("select p from Photo p").getResultList() + ); } @Override public void remove(Photo photo) { - throw new ExerciseNotCompletedException(); // todo + Objects.requireNonNull(photo); + emUtil.performWithinTx(entityManager -> { + Photo managedPhoto = entityManager.merge(photo); + entityManager.remove(managedPhoto); + }); } @Override public void addComment(long photoId, String comment) { - throw new ExerciseNotCompletedException(); // todo + emUtil.performWithinTx(entityManager -> { + Photo photoReference = entityManager.getReference(Photo.class, photoId);// does not call database + var photoComment = new PhotoComment(comment, photoReference); + entityManager.persist(photoComment); + }); } } diff --git a/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/Photo.java b/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/Photo.java index c81adf3e..34c35433 100644 --- a/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/Photo.java +++ b/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/Photo.java @@ -1,9 +1,10 @@ package com.bobocode.model; import com.bobocode.util.ExerciseNotCompletedException; -import lombok.Getter; -import lombok.Setter; +import jakarta.persistence.*; +import lombok.*; +import java.util.ArrayList; import java.util.List; /** @@ -22,19 +23,34 @@ * - enable cascade type {@link jakarta.persistence.CascadeType#ALL} for field {@link Photo#comments} * - enable orphan removal */ +@NoArgsConstructor @Getter @Setter +@EqualsAndHashCode(of = "id") +@Entity +@Table(name = "photo") public class Photo { + @Id + @GeneratedValue private Long id; + + @Column(nullable = false, unique = true) private String url; + + @Column(nullable = false) private String description; - private List comments; + + @Setter(AccessLevel.PRIVATE) + @OneToMany(mappedBy = "photo", cascade = CascadeType.ALL, orphanRemoval = true) + private List comments = new ArrayList<>(); public void addComment(PhotoComment comment) { - throw new ExerciseNotCompletedException(); + comments.add(comment); + comment.setPhoto(this); } public void removeComment(PhotoComment comment) { - throw new ExerciseNotCompletedException(); + comments.remove(comment); + comment.setPhoto(null); } -} +} \ No newline at end of file diff --git a/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/PhotoComment.java b/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/PhotoComment.java index 83edf2f7..9e453188 100644 --- a/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/PhotoComment.java +++ b/3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/PhotoComment.java @@ -1,6 +1,9 @@ package com.bobocode.model; +import jakarta.persistence.*; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import java.time.LocalDateTime; @@ -17,11 +20,30 @@ * - map relation between Photo and PhotoComment using foreign_key column: "photo_id" * - configure relation as mandatory (not optional) */ +@NoArgsConstructor @Getter @Setter +@EqualsAndHashCode(of = "id") +@Entity +@Table(name = "photo_comment") public class PhotoComment { + @Id + @GeneratedValue private Long id; + + @Column(nullable = false) private String text; + + @Column(nullable = false) private LocalDateTime createdOn; + + @ManyToOne(optional = false) + @JoinColumn(name = "photo_id") private Photo photo; + + public PhotoComment(String text, Photo photo) { + this.text = text; + this.photo = photo; + this.createdOn = LocalDateTime.now(); + } }