├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── 1-0-rdbms-and-sql ├── 1-2-1-one-to-one-schema │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ └── UserProfileDbInitializer.java │ │ └── resources │ │ │ └── db │ │ │ └── migration │ │ │ └── table_initialization.sql │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── UserProfileDbInitializerTest.java ├── 1-2-3-many-to-many-schema │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ └── WallStreetDbInitializer.java │ │ └── resources │ │ │ └── db │ │ │ └── migration │ │ │ └── table_initialization.sql │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── WallStreetDbInitializerTest.java └── pom.xml ├── 2-0-jdbc-api ├── 2-0-2-create-table │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── bobocode │ │ │ └── AccountDbInitializer.java │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── AccountDbInitializerTest.java ├── 2-1-1-product-dao │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── bobocode │ │ │ ├── dao │ │ │ ├── ProductDao.java │ │ │ └── ProductDaoImpl.java │ │ │ ├── exception │ │ │ └── DaoOperationException.java │ │ │ └── model │ │ │ └── Product.java │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── bobocode │ │ │ ├── AbstractDaoTest.java │ │ │ └── ProductDaoTest.java │ │ └── resources │ │ └── db │ │ └── init.sql └── pom.xml ├── 3-0-jpa-and-hibernate ├── 3-0-0-hello-jpa-entity │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ └── model │ │ │ │ └── Movie.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── JpaEntityMovieTest.java ├── 3-0-1-hello-persistence-xml │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ └── model │ │ │ │ └── Song.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── JpaPersistenceUnitTest.java ├── 3-0-2-query-helper │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ ├── QueryHelper.java │ │ │ │ └── exception │ │ │ │ └── QueryHelperException.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── QueryHelperTest.java ├── 3-0-3-account-dao │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ ├── dao │ │ │ │ ├── AccountDao.java │ │ │ │ └── AccountDaoImpl.java │ │ │ │ └── exception │ │ │ │ └── AccountDaoException.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── dao │ │ └── AccountDaoTest.java ├── 3-1-1-employee-profile │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ └── model │ │ │ │ ├── Employee.java │ │ │ │ └── EmployeeProfile.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── EmployeeProfileMappingTest.java ├── 3-1-2-company-products │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ ├── dao │ │ │ │ ├── CompanyDao.java │ │ │ │ └── CompanyDaoImpl.java │ │ │ │ ├── exception │ │ │ │ └── CompanyDaoException.java │ │ │ │ └── model │ │ │ │ ├── Company.java │ │ │ │ └── Product.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── CompanyProductMappingTest.java ├── 3-1-3-author-book │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ └── model │ │ │ │ ├── Author.java │ │ │ │ └── Book.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── AuthorBookMappingTest.java ├── 3-2-2-photo-comment-dao │ ├── README.MD │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── bobocode │ │ │ │ ├── dao │ │ │ │ ├── PhotoDao.java │ │ │ │ └── PhotoDaoImpl.java │ │ │ │ └── model │ │ │ │ ├── Photo.java │ │ │ │ └── PhotoComment.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── bobocode │ │ ├── PhotoCommentMappingTest.java │ │ ├── PhotoDaoTest.java │ │ └── util │ │ └── PhotoTestDataGenerator.java └── pom.xml ├── 4-0-spring-data-jpa └── pom.xml ├── LICENSE ├── README.md ├── java-persistence-util ├── jdbc-util │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── util │ │ ├── FileReader.java │ │ ├── FileReaderException.java │ │ └── JdbcUtil.java ├── jpa-hibernate-model │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── model │ │ ├── Account.java │ │ └── Gender.java ├── jpa-hibernate-util │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── util │ │ ├── EntityManagerUtil.java │ │ └── TestDataGenerator.java ├── persistence-util │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── bobocode │ │ └── util │ │ └── ExerciseNotCompletedException.java └── pom.xml ├── lesson-demo ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── bobocode │ └── DemoApp.java ├── mvnw ├── mvnw.cmd └── pom.xml /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | 2 | # This workflow will build a Java project with Maven 3 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 4 | 5 | name: Build completed branch with tests 6 | 7 | on: 8 | push: 9 | branches: [ completed ] 10 | pull_request: 11 | branches: [ completed ] 12 | 13 | jobs: 14 | build: 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up JDK 17 21 | uses: actions/setup-java@v1 22 | with: 23 | java-version: 17 24 | - name: Build with Maven 25 | run: mvn -B package --file pom.xml 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/*.iml 3 | **/target 4 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobocode-projects/java-persistence-exercises/62ad97f7beba4e1dbe6e3e59d0b0b915f4ac4dda/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/1-2-1-one-to-one-schema/README.MD: -------------------------------------------------------------------------------- 1 | # User profiles db initializer exercise :muscle: 2 | Improve your database design and SQL skills 3 | ### Task 4 | `UserProfilesDbInitializer` provides an API that allows to create(initialize) a database tables to store user information, 5 | their work profiles and its relations. Each user can have one and only one profile. A profile is optional and is related 6 | to a single user. The job of initializer is to crate proper database tables. 7 | 8 | `UserProfilesDbInitializer` contains a *javadoc* that specifies database requirements. **It already contains all required Java 9 | implementation.** Your job is to **implement SQL file** `table_initialization.sql`. 10 | 11 | The purpose of the task is to **design a database table following docs and naming convention and implement it using SQL** 12 | It helps you to **learn the 1 to 1 relations**, and how to design it it the database in **the most efficient way.** 13 | 14 | To verify your implementation, run `UserProfilesDbInitializerTest.java` 15 | 16 | ### Pre-conditions :heavy_exclamation_mark: 17 | You're supposed to be familiar with database design and naming convention, *JDBC API* and *SQL* 18 | 19 | ### How to start :question: 20 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 21 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 22 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 23 | 24 | ### Related materials :information_source: 25 | * [JDBC API basics tutorial](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-basics) 26 | 27 | 28 | -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/1-2-1-one-to-one-schema/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 1-0-rdbms-and-sql 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 1-2-1-one-to-one-schema 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/1-2-1-one-to-one-schema/src/main/java/com/bobocode/UserProfileDbInitializer.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.util.FileReader; 4 | 5 | import javax.sql.DataSource; 6 | import java.sql.Connection; 7 | import java.sql.SQLException; 8 | import java.sql.Statement; 9 | 10 | /** 11 | * {@link UserProfileDbInitializer} is an API that has only one method. It allows to create a database tables to store 12 | * information about users and their profiles. 13 | */ 14 | public class UserProfileDbInitializer { 15 | private final static String TABLE_INITIALIZATION_SQL_FILE = "db/migration/table_initialization.sql"; // todo: see the file 16 | private DataSource dataSource; 17 | 18 | public UserProfileDbInitializer(DataSource dataSource) { 19 | this.dataSource = dataSource; 20 | } 21 | 22 | /** 23 | * Reads the SQL script form the file and executes it 24 | * 25 | * @throws SQLException 26 | */ 27 | public void init() throws SQLException { 28 | String createTablesSql = FileReader.readWholeFileFromResources(TABLE_INITIALIZATION_SQL_FILE); 29 | 30 | try (Connection connection = dataSource.getConnection()) { 31 | Statement statement = connection.createStatement(); 32 | statement.execute(createTablesSql); 33 | } catch (SQLException e) { 34 | throw new SQLException("INIT ERROR", e); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/1-2-1-one-to-one-schema/src/main/resources/db/migration/table_initialization.sql: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | User profile database stores information about users and their work profiles. 4 | 5 | Each user has one and only one work profile. 6 | 7 | Each user has stored first and last names, email and birthday which are mandatory. Email is a unique value. 8 | A profile for each user is optional, and consists of optional information: city, job_position, company and education. 9 | All these fields are regular strings without restrictions. 10 | 11 | TECH NOTES AND NAMING CONVENTION 12 | - All tables, columns and constraints are named using "snake case" naming convention 13 | - All table names must be plural (e.g. "companies", not "company") 14 | - All tables (except link tables) should have a single-value identifier of type BIGINT, which is a primary key 15 | - All primary key, foreign key, and unique constraint should be named according to the naming convention. 16 | - All "1 - optional 1" relations should be handled using the same primary key value for both tables. E.g. child table 17 | should have a column that stores primary key from a parent table, which is a foreign key and primary key at the same time 18 | 19 | - All primary keys should be named according to the following rule "table_name_PK" 20 | - All foreign keys should be named according to the following rule "table_name_reference_table_name_FK" 21 | - All alternative keys (unique) should be named according to the following rule "table_name_column_name_AK" 22 | 23 | */ 24 | 25 | -- TODO: implement the SQL according to the description -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/1-2-3-many-to-many-schema/README.MD: -------------------------------------------------------------------------------- 1 | # Wall Street db initializer exercise 💪 2 | Improve your database design and SQL skills 3 | ### Task 4 | `WallStreetDbInitializer` provides an API that allows to create(initialize) a database tables to store broker information, 5 | sales groups and its relations. Roughly speaking each group consists of multiple brokers, and each broker can be 6 | associated with more than one group. The job of initializer is to crate proper database tables. 7 | 8 | `WallStreetDbInitializer` contains a *javadoc* that specifies database requirements. **It already contains all required Java 9 | implementation.** Your job is to **implement SQL file** `table_initialization.sql`. 10 | 11 | The purpose of the task is to **design a database table following docs and naming convention and implement it using SQL** 12 | 13 | To verify your implementation, run `WallStreetDbInitializerTest.java` 14 | 15 | ### Pre-conditions ❗ 16 | You're supposed to be familiar with database design and naming convention, *JDBC API* and *SQL* 17 | 18 | ### How to start ❓ 19 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 20 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 21 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 22 | 23 | ### Related materials ℹ 24 | * [JDBC API basics tutorial](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-basics) 25 | 26 | 27 | -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/1-2-3-many-to-many-schema/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 1-0-rdbms-and-sql 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 1-2-3-many-to-many-schema 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/1-2-3-many-to-many-schema/src/main/java/com/bobocode/WallStreetDbInitializer.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.util.FileReader; 4 | 5 | import javax.sql.DataSource; 6 | import java.sql.Connection; 7 | import java.sql.SQLException; 8 | import java.sql.Statement; 9 | 10 | /** 11 | * {@link WallStreetDbInitializer} is an API that has only one method. It allows to create a database tables to store 12 | * information about brokers and its sales groups. 13 | */ 14 | public class WallStreetDbInitializer { 15 | private final static String TABLE_INITIALIZATION_SQL_FILE = "db/migration/table_initialization.sql"; // todo: see the file 16 | private DataSource dataSource; 17 | 18 | public WallStreetDbInitializer(DataSource dataSource) { 19 | this.dataSource = dataSource; 20 | } 21 | 22 | /** 23 | * Reads the SQL script form the file and executes it 24 | * 25 | * @throws SQLException 26 | */ 27 | public void init() throws SQLException { 28 | String createTablesSql = FileReader.readWholeFileFromResources(TABLE_INITIALIZATION_SQL_FILE); 29 | 30 | try (Connection connection = dataSource.getConnection()) { 31 | Statement statement = connection.createStatement(); 32 | statement.execute(createTablesSql); 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/1-2-3-many-to-many-schema/src/main/resources/db/migration/table_initialization.sql: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | WallStreet database should store information about brokers, sales groups and its relations. 4 | 5 | Each broker must have a unique username. First and last names are also mandatory. 6 | 7 | A sales group is a special group that has its own restrictions. Sale groups are used to organize the work of brokers. 8 | Each group mush have a unique name, transaction type (string), and max transaction amount (a number). All field are 9 | mandatory. 10 | 11 | A sales group can consists of more than one broker, while each broker can be associated with more than one sale group. 12 | 13 | TECH NOTES AND NAMING CONVENTION 14 | - All tables, columns and constraints are named using "snake case" naming convention 15 | - All table names must be singular (e.g. "user", not "users") 16 | - All tables (except link tables) should have an id of type BIGINT, which is a primary key 17 | - Link tables should have composite primary key, that consists of two other foreign key columns 18 | - All primary key, foreign key, and unique constraint should be named according to the naming convention. 19 | - All link tables should have a composite key that consists of two foreign key columns 20 | 21 | - All primary keys should be named according to the following rule "PK_table_name" 22 | - All foreign keys should be named according to the following rule "FK_table_name_reference_table_name" 23 | - All alternative keys (unique) should be named according to the following rule "UQ_table_name_column_name" 24 | 25 | */ 26 | 27 | -- TODO: write SQL script to create a database tables according to the requirements -------------------------------------------------------------------------------- /1-0-rdbms-and-sql/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 1-0-rdbms-and-sql 13 | pom 14 | 15 | 16 | 1-2-1-one-to-one-schema 17 | 1-2-3-many-to-many-schema 18 | 19 | 20 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-0-2-create-table/README.MD: -------------------------------------------------------------------------------- 1 | # Account db initializer exercise :muscle: 2 | Improve your database design and SQL skills 3 | ### Task 4 | `AccountDbInitializer` provides an API that allows to create(initialize) a database (one table). It contains a *javadoc* 5 | that specifies database requirements. `AccountDbInitializer` has a field `DataSource`. Your job is to use that 6 | `dataSource` and **implement the todo section**. E.g. implement the method `init()` that should **create an account db.** 7 | 8 | The purpose of the task is to **design a database table following docs and naming convention and implement it using SQL and 9 | JDBC API.** 10 | 11 | To verify your implementation, run `AccountDbInitializerTest.java` 12 | 13 | ### Pre-conditions :heavy_exclamation_mark: 14 | You're supposed to be familiar with database design and naming convention, *JDBC API* and *SQL* 15 | 16 | ### How to start :question: 17 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 18 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 19 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 20 | 21 | ### Related materials :information_source: 22 | * [JDBC API basics tutorial](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-basics) 23 | 24 | 25 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-0-2-create-table/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 2-0-jdbc-api 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 2-0-2-create-table 13 | 14 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-0-2-create-table/src/main/java/com/bobocode/AccountDbInitializer.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.util.ExerciseNotCompletedException; 4 | 5 | import javax.sql.DataSource; 6 | import java.sql.SQLException; 7 | 8 | /** 9 | * {@link AccountDbInitializer} provides an API that allow to initialize (create) an Account table in the database 10 | */ 11 | public class AccountDbInitializer { 12 | private DataSource dataSource; 13 | 14 | public AccountDbInitializer(DataSource dataSource) { 15 | this.dataSource = dataSource; 16 | } 17 | 18 | /** 19 | * Creates an {@code account} table. That table has an identifier column {@code id} with type {@code bigint}. 20 | * It also contains an {@code email} column that is mandatory and should have unique value. This column is able 21 | * to store any valid email. The table also has columns {@code first_name}, {@code last_name}, and {@code gender} 22 | * that are typical string columns with 255 characters, and are mandatory. Account {@code birthday} is stored 23 | * in the {@code DATE} mandatory column. The value of account balance is not mandatory, and is stored 24 | * in the {@code balance} column that is a {@code DECIMAL} number with {@code precision = 19} , 25 | * and {@code scale = 4}. A column {@code creation_time} stores a {@code TIMESTAMP}, is mandatory, and has a default 26 | * value that is set to the current timestamp using database function {@code now()}. Table primary key 27 | * is an {@code id}, and corresponding constraint is named {@code "account_pk"}. A unique constraint that 28 | * is created for {@code email column} is called "account_email_uq" 29 | * 30 | * @throws SQLException 31 | */ 32 | public void init() throws SQLException { 33 | throw new ExerciseNotCompletedException(); // todo 34 | } 35 | } -------------------------------------------------------------------------------- /2-0-jdbc-api/2-0-2-create-table/src/test/java/com/bobocode/AccountDbInitializerTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.util.JdbcUtil; 4 | import org.junit.jupiter.api.*; 5 | 6 | import javax.sql.DataSource; 7 | import java.sql.Connection; 8 | import java.sql.ResultSet; 9 | import java.sql.SQLException; 10 | import java.sql.Statement; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 15 | import static org.junit.jupiter.api.Assertions.assertTrue; 16 | 17 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 18 | class AccountDbInitializerTest { 19 | 20 | private static DataSource dataSource; 21 | 22 | @BeforeAll 23 | static void init() throws SQLException { 24 | dataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 25 | AccountDbInitializer dbInitializer = new AccountDbInitializer(dataSource); 26 | dbInitializer.init(); 27 | } 28 | 29 | @Test 30 | @Order(1) 31 | @DisplayName("The table has a correct name") 32 | void tableHasCorrectName() throws SQLException { 33 | try (Connection connection = dataSource.getConnection()) { 34 | Statement statement = connection.createStatement(); 35 | 36 | ResultSet resultSet = statement.executeQuery("SHOW TABLES"); 37 | resultSet.next(); 38 | String tableName = resultSet.getString("TABLE_NAME"); 39 | 40 | assertThat(tableName).isEqualTo("account"); 41 | } 42 | } 43 | 44 | @Test 45 | @Order(2) 46 | @DisplayName("The table has a primary key") 47 | void tableHasPrimaryKey() throws SQLException { 48 | try (Connection connection = dataSource.getConnection()) { 49 | Statement statement = connection.createStatement(); 50 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS" + 51 | " WHERE TABLE_NAME = 'account' AND CONSTRAINT_TYPE = 'PRIMARY KEY';"); 52 | 53 | boolean resultIsNotEmpty = resultSet.next(); 54 | 55 | assertTrue(resultIsNotEmpty); 56 | } 57 | } 58 | 59 | @Test 60 | @Order(3) 61 | @DisplayName("The table has all the required columns") 62 | void tableHasAllRequiredColumns() throws SQLException { 63 | try (Connection connection = dataSource.getConnection()) { 64 | Statement statement = connection.createStatement(); 65 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 66 | " WHERE TABLE_NAME = 'account';"); 67 | 68 | List columns = fetchColumnsNames(resultSet); 69 | 70 | assertThat(columns.size()).isEqualTo(8); 71 | assertTrue(columns.containsAll( 72 | List.of("id", "first_name", "last_name", "email", "gender", "balance", "birthday", "creation_time"))); 73 | } 74 | } 75 | 76 | @Test 77 | @Order(4) 78 | @DisplayName("Id column has a type of BIGINT") 79 | void idHasTypeBigInteger() throws SQLException { 80 | try (Connection connection = dataSource.getConnection()) { 81 | Statement statement = connection.createStatement(); 82 | ResultSet resultSet = statement.executeQuery("SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS" + 83 | " WHERE TABLE_NAME = 'account' AND COLUMN_NAME = 'id';"); 84 | 85 | resultSet.next(); 86 | String idTypeName = resultSet.getString(1); 87 | 88 | assertThat(idTypeName).isEqualTo("BIGINT"); 89 | } 90 | } 91 | 92 | @Test 93 | @Order(5) 94 | @DisplayName("String columns have correct type and length") 95 | void stringColumnsHaveCorrectTypeAndLength() throws SQLException { 96 | try (Connection connection = dataSource.getConnection()) { 97 | Statement statement = connection.createStatement(); 98 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 99 | " WHERE TABLE_NAME = 'account' AND DATA_TYPE = 'CHARACTER VARYING' AND CHARACTER_MAXIMUM_LENGTH = 255;"); 100 | 101 | List stringColumns = fetchColumnsNames(resultSet); 102 | 103 | assertThat(stringColumns.size()).isEqualTo(4); 104 | assertTrue(stringColumns.containsAll(List.of("first_name", "last_name", "email", "gender"))); 105 | } 106 | } 107 | 108 | @Test 109 | @Order(6) 110 | @DisplayName("Birthday column has a correct type") 111 | void birthdayColumnHasCorrectType() throws SQLException { 112 | try (Connection connection = dataSource.getConnection()) { 113 | Statement statement = connection.createStatement(); 114 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 115 | " WHERE TABLE_NAME = 'account' AND COLUMN_NAME = 'birthday';"); 116 | 117 | resultSet.next(); 118 | String birthdayColumnType = resultSet.getString("DATA_TYPE"); 119 | 120 | assertThat(birthdayColumnType).isEqualTo("DATE"); 121 | } 122 | } 123 | 124 | @Test 125 | @Order(7) 126 | @DisplayName("Balance column has a correct type") 127 | void balanceColumnHasCorrectType() throws SQLException { 128 | try (Connection connection = dataSource.getConnection()) { 129 | Statement statement = connection.createStatement(); 130 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 131 | " WHERE TABLE_NAME = 'account' AND COLUMN_NAME = 'balance';"); 132 | 133 | resultSet.next(); 134 | String balanceColumnType = resultSet.getString("DATA_TYPE"); 135 | int balanceColumnPrecision = resultSet.getInt("NUMERIC_PRECISION"); 136 | int balanceColumnScale = resultSet.getInt("NUMERIC_SCALE"); 137 | 138 | assertThat(balanceColumnType).isEqualTo("NUMERIC"); 139 | assertThat(balanceColumnPrecision).isEqualTo(19); 140 | assertThat(balanceColumnScale).isEqualTo(4); 141 | } 142 | } 143 | 144 | @Test 145 | @Order(8) 146 | @DisplayName("Balance column is not mandatory") 147 | void balanceIsNotMandatory() throws SQLException { 148 | try (Connection connection = dataSource.getConnection()) { 149 | Statement statement = connection.createStatement(); 150 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 151 | " WHERE TABLE_NAME = 'account' AND COLUMN_NAME = 'balance';"); 152 | 153 | resultSet.next(); 154 | String isNullable = resultSet.getString("IS_NULLABLE"); 155 | 156 | assertThat(isNullable).isEqualTo("YES"); 157 | } 158 | } 159 | 160 | @Test 161 | @Order(9) 162 | @DisplayName("Creation time column has type TIMESTAMP") 163 | void creationTimeHasTypeTimestamp() throws SQLException { 164 | try (Connection connection = dataSource.getConnection()) { 165 | Statement statement = connection.createStatement(); 166 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 167 | " WHERE TABLE_NAME = 'account' AND COLUMN_NAME = 'creation_time';"); 168 | 169 | resultSet.next(); 170 | String creationTimeColumnType = resultSet.getString("DATA_TYPE"); 171 | 172 | assertThat(creationTimeColumnType).isEqualTo("TIMESTAMP"); 173 | } 174 | } 175 | 176 | @Test 177 | @Order(10) 178 | @DisplayName("Creation time has a default value") 179 | void creationTimeHasDefaultValue() throws SQLException { 180 | try (Connection connection = dataSource.getConnection()) { 181 | Statement statement = connection.createStatement(); 182 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 183 | " WHERE TABLE_NAME = 'account' AND COLUMN_NAME = 'creation_time';"); 184 | 185 | resultSet.next(); 186 | String creationTimeColumnDefault = resultSet.getString("COLUMN_DEFAULT"); 187 | 188 | assertThat(creationTimeColumnDefault).isEqualTo("LOCALTIMESTAMP"); 189 | } 190 | } 191 | 192 | @Test 193 | @Order(11) 194 | @DisplayName("The required columns have NOT NULL constraint") 195 | void requiredColumnsHaveNotNullConstraint() throws SQLException { 196 | try (Connection connection = dataSource.getConnection()) { 197 | Statement statement = connection.createStatement(); 198 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 199 | " WHERE TABLE_NAME = 'account' AND IS_NULLABLE = 'NO';"); 200 | 201 | List notNullColumns = fetchColumnsNames(resultSet); 202 | 203 | assertThat(notNullColumns.size()).isEqualTo(7); 204 | assertTrue(notNullColumns.containsAll( 205 | List.of("id", "first_name", "last_name", "email", "gender", "birthday", "creation_time"))); 206 | } 207 | } 208 | 209 | @Test 210 | @Order(12) 211 | @DisplayName("The primary key has a correct name") 212 | void primaryKeyHasCorrectName() throws SQLException { 213 | try (Connection connection = dataSource.getConnection()) { 214 | Statement statement = connection.createStatement(); 215 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS" + 216 | " WHERE TABLE_NAME = 'account' AND CONSTRAINT_TYPE = 'PRIMARY KEY';"); 217 | 218 | resultSet.next(); 219 | String pkConstraintName = resultSet.getString("CONSTRAINT_NAME"); 220 | 221 | assertThat(pkConstraintName).isEqualTo("account_pk"); 222 | } 223 | } 224 | 225 | @Test 226 | @Order(13) 227 | @DisplayName("The primary key id based on the Id field") 228 | void primaryKeyBasedOnIdField() throws SQLException { 229 | try (Connection connection = dataSource.getConnection()) { 230 | Statement statement = connection.createStatement(); 231 | ResultSet resultSet = statement.executeQuery(""" 232 | SELECT ccu.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc 233 | INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu 234 | ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME 235 | WHERE tc.TABLE_NAME = 'account' AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'; 236 | """); 237 | 238 | resultSet.next(); 239 | String pkColumn = resultSet.getString(1); 240 | 241 | assertThat(pkColumn).isEqualTo("id"); 242 | } 243 | } 244 | 245 | @Test 246 | @Order(14) 247 | @DisplayName("The table has a correct alternative key") 248 | void tableHasCorrectAlternativeKey() throws SQLException { 249 | try (Connection connection = dataSource.getConnection()) { 250 | Statement statement = connection.createStatement(); 251 | ResultSet resultSet = statement.executeQuery(""" 252 | SELECT ccu.CONSTRAINT_NAME, ccu.COLUMN_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc 253 | INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu 254 | ON tc.CONSTRAINT_NAME = ccu.CONSTRAINT_NAME 255 | WHERE tc.TABLE_NAME = 'account' AND tc.CONSTRAINT_TYPE = 'UNIQUE'; 256 | """); 257 | 258 | resultSet.next(); 259 | String uniqueConstraintName = resultSet.getString(1); 260 | String uniqueConstraintColumn = resultSet.getString(2); 261 | 262 | assertThat(uniqueConstraintName).isEqualTo("account_email_uq"); 263 | assertThat(uniqueConstraintColumn).isEqualTo("email"); 264 | } 265 | } 266 | 267 | 268 | private List fetchColumnsNames(ResultSet resultSet) throws SQLException { 269 | List columns = new ArrayList<>(); 270 | while (resultSet.next()) { 271 | String columnName = resultSet.getString("COLUMN_NAME"); 272 | columns.add(columnName); 273 | } 274 | return columns; 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/README.MD: -------------------------------------------------------------------------------- 1 | # Product DAO exercise :muscle: 2 | Improve your JDBC API skills 3 | ### Task 4 | `ProductDao` provides an API that allow to access and manipulate products. Your job is to implement the *todo* section 5 | of that class using **JDBC API**. 6 | To verify your implementation, run `ProductDaoTest.java` 7 | 8 | 9 | 10 | ### Pre-conditions :heavy_exclamation_mark: 11 | You're supposed to be familiar with JDBC API, SQL and be able to write Java code 12 | 13 | ### How to start :question: 14 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 15 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 16 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 17 | 18 | ### Related materials :information_source: 19 | * [JDBC API and DAO tutorial](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-dao) 20 | 21 | 22 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 2-0-jdbc-api 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 2-1-1-product-dao 13 | 14 | 15 | 16 | org.apache.commons 17 | commons-text 18 | 1.9 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/src/main/java/com/bobocode/dao/ProductDao.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.exception.DaoOperationException; 4 | import com.bobocode.model.Product; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * {@link ProductDao} is a Data Access Object pattern (DAO) that encapsulates all database access and manipulation logic. 10 | * It provides a convenient API that allows to store, access, update and remove data working with object-oriented style. 11 | */ 12 | public interface ProductDao { 13 | /** 14 | * Stores a new product into the database. Sets the database-generated ID to {@link Product} instance 15 | * 16 | * @param product new product 17 | * @throws DaoOperationException in case of database errors 18 | */ 19 | void save(Product product); 20 | 21 | /** 22 | * Retrieves and returns all products from the database 23 | * 24 | * @return list of all products 25 | * @throws DaoOperationException in case of database errors 26 | */ 27 | List findAll(); 28 | 29 | /** 30 | * Returns a product object by provided id 31 | * 32 | * @param id product identifier (primary key) 33 | * @return single product by its id 34 | * @throws DaoOperationException in case of database errors 35 | */ 36 | Product findOne(Long id); 37 | 38 | /** 39 | * Updates existing product. 40 | * 41 | * @param product stored product with updated fields 42 | * @throws DaoOperationException in case of database errors 43 | */ 44 | void update(Product product); 45 | 46 | /** 47 | * Removes an existing product from the database 48 | * 49 | * @param product stored product 50 | * @throws DaoOperationException in case of database errors 51 | */ 52 | void remove(Product product); 53 | } 54 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Product; 4 | import com.bobocode.util.ExerciseNotCompletedException; 5 | import java.util.List; 6 | import javax.sql.DataSource; 7 | 8 | public class ProductDaoImpl implements ProductDao { 9 | 10 | private final DataSource dataSource; 11 | 12 | public ProductDaoImpl(DataSource dataSource) { 13 | this.dataSource = dataSource; 14 | } 15 | 16 | @Override 17 | public void save(Product product) { 18 | throw new ExerciseNotCompletedException();// todo 19 | } 20 | 21 | @Override 22 | public List findAll() { 23 | throw new ExerciseNotCompletedException();// todo 24 | } 25 | 26 | @Override 27 | public Product findOne(Long id) { 28 | throw new ExerciseNotCompletedException();// todo 29 | } 30 | 31 | @Override 32 | public void update(Product product) { 33 | throw new ExerciseNotCompletedException();// todo 34 | } 35 | 36 | @Override 37 | public void remove(Product product) { 38 | throw new ExerciseNotCompletedException();// todo 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/src/main/java/com/bobocode/exception/DaoOperationException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class DaoOperationException extends RuntimeException { 4 | public DaoOperationException(String message) { 5 | super(message); 6 | } 7 | 8 | public DaoOperationException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/src/main/java/com/bobocode/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.*; 4 | 5 | import java.math.BigDecimal; 6 | import java.time.LocalDate; 7 | import java.time.LocalDateTime; 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @EqualsAndHashCode(of = "id") 12 | @Builder 13 | public class Product { 14 | private Long id; 15 | private String name; 16 | private String producer; 17 | private BigDecimal price; 18 | private LocalDate expirationDate; 19 | private LocalDateTime creationTime; 20 | } 21 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/src/test/java/com/bobocode/AbstractDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.exception.DaoOperationException; 4 | import com.bobocode.model.Product; 5 | import com.bobocode.util.FileReader; 6 | import java.math.BigDecimal; 7 | import java.sql.Connection; 8 | import java.sql.Date; 9 | import java.sql.PreparedStatement; 10 | import java.sql.ResultSet; 11 | import java.sql.SQLException; 12 | import java.sql.Statement; 13 | import java.time.LocalDate; 14 | import java.time.Month; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import javax.sql.DataSource; 18 | 19 | abstract class AbstractDaoTest { 20 | 21 | static void createTable(DataSource dataSource) throws SQLException { 22 | String createTablesSql = FileReader.readWholeFileFromResources("db/init.sql"); 23 | 24 | try (Connection connection = dataSource.getConnection()) { 25 | Statement statement = connection.createStatement(); 26 | statement.execute(createTablesSql); 27 | statement.close(); 28 | } 29 | } 30 | 31 | Product findProductById(Long id, Connection connection) throws SQLException { 32 | PreparedStatement selectByIdStatement = prepareSelectByIdStatement(id, connection); 33 | ResultSet resultSet = selectByIdStatement.executeQuery(); 34 | if (resultSet.next()) { 35 | return parseRow(resultSet); 36 | } else { 37 | throw new DaoOperationException(String.format("Product with id = %d does not exist", id)); 38 | } 39 | } 40 | 41 | private Product parseRow(ResultSet resultSet) { 42 | try { 43 | return createFromResultSet(resultSet); 44 | } catch (SQLException e) { 45 | throw new DaoOperationException("Cannot parse row to create product instance", e); 46 | } 47 | } 48 | 49 | private Product createFromResultSet(ResultSet resultSet) throws SQLException { 50 | Product product = new Product(); 51 | product.setId(resultSet.getLong("id")); 52 | product.setName(resultSet.getString("name")); 53 | product.setProducer(resultSet.getString("producer")); 54 | product.setPrice(resultSet.getBigDecimal("price").stripTrailingZeros()); 55 | product.setExpirationDate(resultSet.getDate("expiration_date").toLocalDate()); 56 | product.setCreationTime(resultSet.getTimestamp("creation_time").toLocalDateTime()); 57 | return product; 58 | } 59 | 60 | List collectToList(ResultSet resultSet) throws SQLException { 61 | List products = new ArrayList<>(); 62 | while (resultSet.next()) { 63 | Product product = parseRow(resultSet); 64 | products.add(product); 65 | } 66 | return products; 67 | } 68 | 69 | void saveProduct(Product product, Connection connection) throws SQLException { 70 | PreparedStatement insertStatement = prepareInsertStatement(product, connection); 71 | insertStatement.executeUpdate(); 72 | Long id = fetchGeneratedId(insertStatement); 73 | product.setId(id); 74 | } 75 | 76 | private PreparedStatement prepareSelectByIdStatement(Long id, Connection connection) { 77 | try { 78 | PreparedStatement selectByIdStatement = connection 79 | .prepareStatement("SELECT * FROM products WHERE id = ?;"); 80 | selectByIdStatement.setLong(1, id); 81 | return selectByIdStatement; 82 | } catch (SQLException e) { 83 | throw new DaoOperationException(String.format("Cannot prepare select by id statement for id = %d", id), e); 84 | } 85 | } 86 | 87 | private PreparedStatement prepareInsertStatement(Product product, Connection connection) { 88 | try { 89 | PreparedStatement insertStatement = connection 90 | .prepareStatement("INSERT INTO products(name, producer, price, expiration_date) VALUES (?, ?, ?, ?);", 91 | PreparedStatement.RETURN_GENERATED_KEYS); 92 | fillProductStatement(product, insertStatement); 93 | return insertStatement; 94 | } catch (SQLException e) { 95 | throw new DaoOperationException(String.format("Cannot prepare statement for product: %s", product), e); 96 | } 97 | } 98 | 99 | private void fillProductStatement(Product product, PreparedStatement updateStatement) throws SQLException { 100 | updateStatement.setString(1, product.getName()); 101 | updateStatement.setString(2, product.getProducer()); 102 | updateStatement.setBigDecimal(3, product.getPrice()); 103 | updateStatement.setDate(4, Date.valueOf(product.getExpirationDate())); 104 | } 105 | 106 | private Long fetchGeneratedId(PreparedStatement insertStatement) throws SQLException { 107 | ResultSet generatedKeys = insertStatement.getGeneratedKeys(); 108 | if (generatedKeys.next()) { 109 | return generatedKeys.getLong(1); 110 | } else { 111 | throw new DaoOperationException("Can not obtain product ID"); 112 | } 113 | } 114 | 115 | List createTestProductList() { 116 | return List.of( 117 | Product.builder() 118 | .name("Sprite") 119 | .producer("The Coca-Cola Company") 120 | .price(BigDecimal.valueOf(18)) 121 | .expirationDate(LocalDate.of(2020, Month.MARCH, 24)).build(), 122 | Product.builder() 123 | .name("Cola light") 124 | .producer("The Coca-Cola Company") 125 | .price(BigDecimal.valueOf(21)) 126 | .expirationDate(LocalDate.of(2020, Month.JANUARY, 11)).build(), 127 | Product.builder() 128 | .name("Snickers") 129 | .producer("Mars Inc.") 130 | .price(BigDecimal.valueOf(16)) 131 | .expirationDate(LocalDate.of(2019, Month.DECEMBER, 3)).build() 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/src/test/java/com/bobocode/ProductDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.dao.ProductDao; 4 | import com.bobocode.dao.ProductDaoImpl; 5 | import com.bobocode.exception.DaoOperationException; 6 | import com.bobocode.model.Product; 7 | import com.bobocode.util.JdbcUtil; 8 | import lombok.SneakyThrows; 9 | import org.apache.commons.lang3.RandomStringUtils; 10 | import org.apache.commons.lang3.RandomUtils; 11 | import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration; 12 | import org.junit.jupiter.api.*; 13 | import org.mockito.Mockito; 14 | 15 | import javax.sql.DataSource; 16 | import java.math.BigDecimal; 17 | import java.sql.Connection; 18 | import java.sql.ResultSet; 19 | import java.sql.SQLException; 20 | import java.sql.Statement; 21 | import java.time.LocalDate; 22 | import java.time.Month; 23 | import java.util.List; 24 | import java.util.Objects; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 28 | import static org.junit.jupiter.api.MethodOrderer.OrderAnnotation; 29 | import static org.mockito.Mockito.doThrow; 30 | 31 | @TestMethodOrder(OrderAnnotation.class) 32 | class ProductDaoTest extends AbstractDaoTest { 33 | 34 | private static ProductDao productDao; 35 | private static DataSource spyDataSource; 36 | private static DataSource originalDataSource; 37 | 38 | @BeforeAll 39 | @SneakyThrows 40 | static void init() { 41 | originalDataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 42 | spyDataSource = Mockito.spy(originalDataSource); 43 | productDao = new ProductDaoImpl(spyDataSource); 44 | createTable(originalDataSource); 45 | } 46 | 47 | @SneakyThrows 48 | @AfterEach 49 | void reset() { 50 | try (var connection = originalDataSource.getConnection()) { 51 | try (var statement = connection.createStatement()) { 52 | statement.executeUpdate("TRUNCATE TABLE products;"); 53 | } 54 | } 55 | Mockito.reset(spyDataSource); 56 | } 57 | 58 | @Test 59 | @Order(1) 60 | @DisplayName("save stores a product to the DB") 61 | void save() { 62 | var product = createTestProduct(); 63 | 64 | productDao.save(product); 65 | var foundProducts = findAllFromDataBase(); 66 | 67 | assertThat(foundProducts) 68 | .usingRecursiveFieldByFieldElementComparatorIgnoringFields("id", "creationTime") 69 | .contains(product); 70 | } 71 | 72 | @Test 73 | @Order(2) 74 | @DisplayName("save generates product id") 75 | void saveGeneratesId() { 76 | var product = createTestProduct(); 77 | assertThat(product.getId()).isNull(); 78 | 79 | productDao.save(product); 80 | assertThat(product.getId()).isNotNull(); 81 | 82 | var foundProduct = findOneFromDatabase(product.getId()); 83 | assertThat(foundProduct).isEqualTo(product); 84 | } 85 | 86 | @Test 87 | @Order(3) 88 | @DisplayName("save wraps DB errors with a custom exception") 89 | @SneakyThrows 90 | void saveWrapsSqlException() { 91 | var product = createTestProduct(); 92 | mockDataSourceToThrowError(); 93 | 94 | assertThatExceptionOfType(DaoOperationException.class) 95 | .isThrownBy(() -> productDao.save(product)) 96 | .withMessage(String.format("Error saving product: %s", product)); 97 | } 98 | 99 | @Test 100 | @Order(4) 101 | @DisplayName("findAll loads all products from the DB") 102 | void findAll() { 103 | List products = givenStoredProductsFromDB(); 104 | 105 | List foundProducts = productDao.findAll(); 106 | 107 | assertThat(foundProducts).isEqualTo(products); 108 | } 109 | 110 | @Test 111 | @SneakyThrows 112 | @Order(5) 113 | @DisplayName("findAll wraps DB errors with a custom exception") 114 | void findAllWrapsSqlExceptions() { 115 | mockDataSourceToThrowError(); 116 | assertThatExceptionOfType(DaoOperationException.class).isThrownBy(() -> productDao.findAll()); 117 | } 118 | 119 | @Test 120 | @Order(6) 121 | @DisplayName("findOne loads a product by id") 122 | void findById() { 123 | Product testProduct = generateTestProduct(); 124 | saveToDB(testProduct); 125 | 126 | Product product = productDao.findOne(testProduct.getId()); 127 | 128 | assertThat(testProduct).isEqualTo(product); 129 | assertThat(testProduct.getName()).isEqualTo(product.getName()); 130 | assertThat(testProduct.getProducer()).isEqualTo(product.getProducer()); 131 | assertThat(testProduct.getPrice().setScale(2)).isEqualTo(product.getPrice().setScale(2)); 132 | assertThat(testProduct.getExpirationDate()).isEqualTo(product.getExpirationDate()); 133 | } 134 | 135 | @Test 136 | @Order(7) 137 | @DisplayName("findOne throws an exception when the product is not found") 138 | void findOneThrowsExceptionWhenNotFound() { 139 | long productId = 666L; 140 | 141 | assertThatExceptionOfType(DaoOperationException.class) 142 | .isThrownBy(() -> productDao.findOne(productId)); 143 | } 144 | 145 | @Test 146 | @Order(8) 147 | @DisplayName("findOne wraps DB errors with a custom exception") 148 | @SneakyThrows 149 | void findOneWrapsSqlExceptions() { 150 | mockDataSourceToThrowError(); 151 | assertThatExceptionOfType(DaoOperationException.class) 152 | .isThrownBy(() -> productDao.findOne(1L)); 153 | } 154 | 155 | @Test 156 | @Order(9) 157 | @DisplayName("update changes the product in the DB") 158 | void update() { 159 | Product testProduct = generateTestProduct(); 160 | saveToDB(testProduct); 161 | List productsBeforeUpdate = findAllFromDataBase(); 162 | 163 | testProduct.setName("Updated name"); 164 | testProduct.setProducer("Updated producer"); 165 | testProduct.setPrice(BigDecimal.valueOf(666)); 166 | testProduct.setExpirationDate(LocalDate.of(2020, Month.JANUARY, 1)); 167 | 168 | productDao.update(testProduct); 169 | List products = findAllFromDataBase(); 170 | Product updatedProduct = findOneFromDatabase(testProduct.getId()); 171 | 172 | assertThat(productsBeforeUpdate).hasSameSizeAs(products); 173 | RecursiveComparisonConfiguration recursiveComparisonConfiguration = new RecursiveComparisonConfiguration(); 174 | recursiveComparisonConfiguration.setIgnoreAllActualNullFields(true); 175 | assertThat(testProduct).usingRecursiveComparison(recursiveComparisonConfiguration).isEqualTo(updatedProduct); 176 | productsBeforeUpdate.remove(testProduct); 177 | products.remove(testProduct); 178 | assertThat(productsBeforeUpdate).usingRecursiveComparison().isEqualTo(products); 179 | } 180 | 181 | @Test 182 | @Order(10) 183 | @DisplayName("update throws an exception when a product ID is null") 184 | void updateNotStored() { 185 | Product notStoredProduct = generateTestProduct(); 186 | 187 | assertThatExceptionOfType(DaoOperationException.class) 188 | .isThrownBy(() -> productDao.update(notStoredProduct)); 189 | } 190 | 191 | @Test 192 | @Order(11) 193 | @DisplayName("update wraps DB errors with a custom exception") 194 | @SneakyThrows 195 | void updateWrapsSqlExceptions() { 196 | mockDataSourceToThrowError(); 197 | assertThatExceptionOfType(DaoOperationException.class) 198 | .isThrownBy(() -> productDao.update(new Product())); 199 | } 200 | 201 | @Test 202 | @Order(12) 203 | @DisplayName("remove deletes the product by id from the DB") 204 | void remove() { 205 | var product = givenStoredProductFromDB(); 206 | 207 | productDao.remove(product); 208 | List allProducts = findAllFromDataBase(); 209 | 210 | assertThat(allProducts).doesNotContain(product); 211 | } 212 | 213 | @Test 214 | @Order(13) 215 | @DisplayName("remove throws an exception when a product ID is null") 216 | void removeNotStored() { 217 | Product notStoredProduct = generateTestProduct(); 218 | 219 | assertThatExceptionOfType(DaoOperationException.class) 220 | .isThrownBy(() -> productDao.remove(notStoredProduct)); 221 | } 222 | 223 | @Test 224 | @Order(14) 225 | @DisplayName("remove wraps DB errors with a custom exception") 226 | @SneakyThrows 227 | void removeWrapsSqlExceptions() { 228 | mockDataSourceToThrowError(); 229 | assertThatExceptionOfType(DaoOperationException.class) 230 | .isThrownBy(() -> productDao.remove(new Product())); 231 | } 232 | 233 | private Product givenStoredProductFromDB() { 234 | Product product = generateTestProduct(); 235 | saveToDB(product); 236 | return product; 237 | } 238 | 239 | private Product generateTestProduct() { 240 | return Product.builder() 241 | .name(RandomStringUtils.randomAlphabetic(10)) 242 | .producer(RandomStringUtils.randomAlphabetic(20)) 243 | .price(BigDecimal.valueOf(RandomUtils.nextInt(10, 100))) 244 | .expirationDate(LocalDate.ofYearDay( 245 | LocalDate.now().getYear() + RandomUtils.nextInt(1, 5), 246 | RandomUtils.nextInt(1, 365)) 247 | ) 248 | .build(); 249 | } 250 | 251 | private void mockDataSourceToThrowError() throws SQLException { 252 | doThrow(new SQLException("Mock testing Exception")).when(spyDataSource).getConnection(); 253 | } 254 | 255 | private List givenStoredProductsFromDB() { 256 | List products = createTestProductList(); 257 | products.forEach(this::saveToDB); 258 | return products; 259 | } 260 | 261 | private List findAllFromDataBase() { 262 | try (Connection connection = originalDataSource.getConnection()) { 263 | Statement statement = connection.createStatement(); 264 | ResultSet resultSet = statement.executeQuery("SELECT * FROM products;"); 265 | return collectToList(resultSet); 266 | } catch (SQLException e) { 267 | throw new DaoOperationException("Error finding all products", e); 268 | } 269 | } 270 | 271 | private Product findOneFromDatabase(Long id) { 272 | Objects.requireNonNull(id); 273 | try (Connection connection = originalDataSource.getConnection()) { 274 | return findProductById(id, connection); 275 | } catch (SQLException e) { 276 | throw new DaoOperationException(String.format("Error finding product by id = %d", id), e); 277 | } 278 | } 279 | 280 | private Product createTestProduct() { 281 | return Product.builder() 282 | .name("Fanta") 283 | .producer("The Coca-Cola Company") 284 | .price(BigDecimal.valueOf(22)) 285 | .expirationDate(LocalDate.of(2020, Month.APRIL, 14)).build(); 286 | } 287 | 288 | private void saveToDB(Product product) { 289 | Objects.requireNonNull(product); 290 | try (Connection connection = originalDataSource.getConnection()) { 291 | saveProduct(product, connection); 292 | } catch (SQLException e) { 293 | throw new DaoOperationException(String.format("Error saving product: %s " + e.getMessage(), product), e); 294 | } 295 | } 296 | } -------------------------------------------------------------------------------- /2-0-jdbc-api/2-1-1-product-dao/src/test/resources/db/init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS products 2 | ( 3 | id SERIAL NOT NULL, 4 | name VARCHAR(255) NOT NULL, 5 | producer VARCHAR(255) NOT NULL, 6 | price DECIMAL(19, 4), 7 | expiration_date TIMESTAMP NOT NULL, 8 | creation_time TIMESTAMP NOT NULL DEFAULT now(), 9 | 10 | CONSTRAINT products_pk PRIMARY KEY (id) 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /2-0-jdbc-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 2-0-jdbc-api 13 | pom 14 | 15 | 16 | 2-0-2-create-table 17 | 2-1-1-product-dao 18 | 19 | 20 | 21 | 22 | org.postgresql 23 | postgresql 24 | 42.6.0 25 | 26 | 27 | com.bobocode 28 | persistence-util 29 | 1.0-SNAPSHOT 30 | compile 31 | 32 | 33 | com.bobocode 34 | jdbc-util 35 | 1.0-SNAPSHOT 36 | 37 | 38 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/README.MD: -------------------------------------------------------------------------------- 1 | # Hello JPA entity exercise :muscle: 2 | Improve your JPA entity mapping skills 3 | ### Task 4 | `Movie.java` is a business entity that stores some movie-related information like movie `name`, `director` and 5 | `duration` in second. Your job is to **implement JPA entity mapping** according to the notes in the *todo* section. 6 | 7 | To verify your implementation, run `JpaEntityMovieTest.java` 8 | 9 | 10 | ### Pre-conditions :heavy_exclamation_mark: 11 | You're supposed to be familiar with JPA and Hibernate ORM and be able to write Java code 12 | 13 | ### How to start :question: 14 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 15 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 16 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 17 | 18 | ### Related materials :information_source: 19 | * [JPA and Hibernate basics tutorial](https://github.com/boy4uck/jpa-hibernate-tutorial/tree/master/jpa-hibernate-basics) 20 | 21 | 22 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 3-0-jpa-and-hibernate 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-0-0-hello-jpa-entity 13 | 14 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/src/main/java/com/bobocode/model/Movie.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | /** 8 | * TODO: you're job is to implement mapping for JPA entity {@link Movie} 9 | * - explicitly specify the table name 10 | * - specify id 11 | * - configure id as auto-increment column, choose an Identity generation strategy 12 | * - explicitly specify each column name ("id", "name", "director", and "duration" accordingly) 13 | * - specify not null constraint for fields {@link Movie#name} and {@link Movie#director} 14 | */ 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | public class Movie { 19 | private Long id; 20 | 21 | private String name; 22 | 23 | private String director; 24 | 25 | private Integer durationSeconds; 26 | } -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.bobocode.model.Movie 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-0-hello-jpa-entity/src/test/java/com/bobocode/JpaEntityMovieTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 4 | 5 | import com.bobocode.model.Movie; 6 | import java.lang.reflect.Field; 7 | import jakarta.persistence.Column; 8 | import jakarta.persistence.Entity; 9 | import jakarta.persistence.GeneratedValue; 10 | import jakarta.persistence.GenerationType; 11 | import jakarta.persistence.Id; 12 | import jakarta.persistence.Table; 13 | import org.junit.jupiter.api.DisplayName; 14 | import org.junit.jupiter.api.MethodOrderer; 15 | import org.junit.jupiter.api.Order; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.api.TestMethodOrder; 18 | 19 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 20 | class JpaEntityMovieTest { 21 | 22 | @Test 23 | @Order(1) 24 | @DisplayName("The class is marked as JPA entity") 25 | void classIsMarkedAsJpaEntity() { 26 | assertThat(Movie.class.getAnnotation(Entity.class)).isNotNull(); 27 | } 28 | 29 | @Test 30 | @Order(2) 31 | @DisplayName("@Table name is specified") 32 | void tableIsSpecified() { 33 | Table table = Movie.class.getAnnotation(Table.class); 34 | 35 | assertThat(table).isNotNull(); 36 | assertThat(table.name()).isEqualTo("movie"); 37 | } 38 | 39 | @Test 40 | @Order(3) 41 | @DisplayName("The entity has an ID") 42 | void entityHasId() throws NoSuchFieldException { 43 | Field idField = Movie.class.getDeclaredField("id"); 44 | 45 | assertThat(idField.getAnnotation(Id.class)).isNotNull(); 46 | } 47 | 48 | @Test 49 | @Order(4) 50 | @DisplayName("Id field type is Long") 51 | void idTypeIsLong() throws NoSuchFieldException { 52 | Field idField = Movie.class.getDeclaredField("id"); 53 | String idTypeName = idField.getType().getName(); 54 | String longName = Long.class.getName(); 55 | 56 | assertThat(idTypeName).isEqualTo(longName); 57 | } 58 | 59 | @Test 60 | @Order(5) 61 | @DisplayName("Id field is marked as generated value") 62 | void idIsGenerated() throws NoSuchFieldException { 63 | Field idField = Movie.class.getDeclaredField("id"); 64 | 65 | assertThat(idField.getAnnotation(GeneratedValue.class)).isNotNull(); 66 | } 67 | 68 | @Test 69 | @Order(6) 70 | @DisplayName("Id generation strategy is Identity") 71 | void idGenerationStrategyIsIdentity() throws NoSuchFieldException { 72 | Field idField = Movie.class.getDeclaredField("id"); 73 | GeneratedValue generatedValue = idField.getAnnotation(GeneratedValue.class); 74 | 75 | assertThat(generatedValue.strategy()).isEqualTo(GenerationType.IDENTITY); 76 | } 77 | 78 | @Test 79 | @Order(7) 80 | @DisplayName("Name field is marked as Column") 81 | void movieNameIsMarkedAsColumn() throws NoSuchFieldException { 82 | Field nameField = Movie.class.getDeclaredField("name"); 83 | 84 | assertThat(nameField.getAnnotation(Column.class)).isNotNull(); 85 | } 86 | 87 | @Test 88 | @Order(8) 89 | @DisplayName("Name column name is specified explicitly") 90 | void movieNameColumnIsSpecified() throws NoSuchFieldException { 91 | Field nameField = Movie.class.getDeclaredField("name"); 92 | Column column = nameField.getAnnotation(Column.class); 93 | 94 | assertThat(column.name()).isEqualTo("name"); 95 | } 96 | 97 | @Test 98 | @Order(9) 99 | @DisplayName("Name column is not nullable") 100 | void movieNameColumnIsNotNull() throws NoSuchFieldException { 101 | Field nameField = Movie.class.getDeclaredField("name"); 102 | Column column = nameField.getAnnotation(Column.class); 103 | 104 | assertThat(column.nullable()).isFalse(); 105 | } 106 | 107 | @Test 108 | @Order(10) 109 | @DisplayName("Director field is marked as column") 110 | void directorIsMarkedAsColumn() throws NoSuchFieldException { 111 | Field declaredField = Movie.class.getDeclaredField("director"); 112 | 113 | assertThat(declaredField.getAnnotation(Column.class)).isNotNull(); 114 | } 115 | 116 | @Test 117 | @Order(11) 118 | @DisplayName("Director column name is specified explicitly") 119 | void directorColumnIsSpecified() throws NoSuchFieldException { 120 | Field declaredField = Movie.class.getDeclaredField("director"); 121 | Column column = declaredField.getAnnotation(Column.class); 122 | 123 | assertThat(column.name()).isEqualTo("director"); 124 | } 125 | 126 | @Test 127 | @Order(12) 128 | @DisplayName("Director column is not nullable") 129 | void directorColumnIsNotNull() throws NoSuchFieldException { 130 | Field declaredField = Movie.class.getDeclaredField("director"); 131 | Column column = declaredField.getAnnotation(Column.class); 132 | 133 | assertThat(column.nullable()).isFalse(); 134 | } 135 | 136 | @Test 137 | @Order(13) 138 | @DisplayName("Duration field is marked as column") 139 | void durationIsMarkedAsColumn() throws NoSuchFieldException { 140 | Field declaredField = Movie.class.getDeclaredField("durationSeconds"); 141 | 142 | assertThat(declaredField.getAnnotation(Column.class)).isNotNull(); 143 | } 144 | 145 | @Test 146 | @Order(14) 147 | @DisplayName("Duration column name is specified explicitly") 148 | void durationColumnIsSpecified() throws NoSuchFieldException { 149 | Field declaredField = Movie.class.getDeclaredField("durationSeconds"); 150 | Column column = declaredField.getAnnotation(Column.class); 151 | 152 | assertThat(column.name()).isEqualTo("duration"); 153 | } 154 | 155 | @Test 156 | @Order(15) 157 | @DisplayName("Duration column is not nullable") 158 | void durationColumnIsNullable() throws NoSuchFieldException { 159 | Field declaredField = Movie.class.getDeclaredField("durationSeconds"); 160 | Column column = declaredField.getAnnotation(Column.class); 161 | 162 | assertThat(column.nullable()).isTrue(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/README.MD: -------------------------------------------------------------------------------- 1 | # JPA persistence.xml exercise :muscle: 2 | Improve your JPA configuration skills 3 | ### Task 4 | In order to use *JPA* you need to configure its provider. In our case you need to configure *Hibernate ORM*. All properties 5 | are located in **standard JPA configuration file** `persistence.xml`. Your job is to implement the todo section 6 | providing all required configuration. 7 | 8 | To verify your implementation, run `JpaPersistenceUnitTest.java` 9 | 10 | 11 | ### Pre-conditions :heavy_exclamation_mark: 12 | You're supposed to be familiar with JPA and Hibernate ORM and be able to write Java code 13 | 14 | ### How to start :question: 15 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 16 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 17 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 18 | 19 | ### Related materials :information_source: 20 | * [JPA and Hibernate basics tutorial](https://github.com/boy4uck/jpa-hibernate-tutorial/tree/master/jpa-hibernate-basics) 21 | 22 | 23 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 3-0-jpa-and-hibernate 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-0-1-hello-persistence-xml 13 | 14 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/src/main/java/com/bobocode/model/Song.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | import jakarta.persistence.*; 8 | 9 | @NoArgsConstructor 10 | @Getter 11 | @Setter 12 | @Entity 13 | @Table(name = "song") 14 | public class Song { 15 | @Id 16 | @GeneratedValue 17 | private Long id; 18 | @Column 19 | private String name; 20 | @Column 21 | private String author; 22 | } 23 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-1-hello-persistence-xml/src/test/java/com/bobocode/JpaPersistenceUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.util.List; 6 | import java.util.Properties; 7 | import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; 8 | import org.hibernate.jpa.boot.internal.PersistenceXmlParser; 9 | import org.junit.jupiter.api.BeforeAll; 10 | import org.junit.jupiter.api.DisplayName; 11 | import org.junit.jupiter.api.MethodOrderer; 12 | import org.junit.jupiter.api.Order; 13 | import org.junit.jupiter.api.Test; 14 | import org.junit.jupiter.api.TestMethodOrder; 15 | 16 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 17 | class JpaPersistenceUnitTest { 18 | 19 | private static ParsedPersistenceXmlDescriptor persistenceUnit; 20 | 21 | @BeforeAll 22 | public static void beforeAll() { 23 | List persistenceUnits = PersistenceXmlParser 24 | .locatePersistenceUnits(new Properties()); 25 | 26 | persistenceUnit = persistenceUnits.stream() 27 | .filter(unit -> unit.getName().equals("TuttiFrutti")) 28 | .findAny().get(); 29 | } 30 | 31 | @Test 32 | @Order(1) 33 | @DisplayName("Persistence unit has a proper name") 34 | void persistenceUnit() { 35 | assertThat(persistenceUnit).isNotNull(); 36 | } 37 | 38 | @Test 39 | @Order(2) 40 | @DisplayName("Connection URL has proper value") 41 | void connectionUrl() { 42 | Properties properties = persistenceUnit.getProperties(); 43 | 44 | assertThat(properties) 45 | .containsKey("hibernate.connection.url") 46 | .containsValue("jdbc:h2:mem:tutti_frutti_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false"); 47 | } 48 | 49 | @Test 50 | @Order(3) 51 | @DisplayName("Connection driver has proper value") 52 | void driverClass() { 53 | Properties properties = persistenceUnit.getProperties(); 54 | 55 | assertThat(properties) 56 | .containsKey("hibernate.connection.driver_class") 57 | .containsValue("org.h2.Driver"); 58 | } 59 | 60 | @Test 61 | @Order(4) 62 | @DisplayName("Username has proper value") 63 | void username() { 64 | Properties properties = persistenceUnit.getProperties(); 65 | 66 | assertThat(properties) 67 | .containsKey("hibernate.connection.username") 68 | .containsValue("little_richard"); 69 | } 70 | 71 | @Test 72 | @Order(5) 73 | @DisplayName("Password has proper value") 74 | void password() { 75 | Properties properties = persistenceUnit.getProperties(); 76 | 77 | assertThat(properties) 78 | .containsKey("hibernate.connection.password") 79 | .containsValue("rock_n_roll_is_alive"); 80 | } 81 | 82 | @Test 83 | @Order(6) 84 | @DisplayName("Dialect has proper value") 85 | void dialect() { 86 | Properties properties = persistenceUnit.getProperties(); 87 | 88 | assertThat(properties) 89 | .containsKey("hibernate.dialect") 90 | .containsValue("org.hibernate.dialect.H2Dialect"); 91 | } 92 | 93 | @Test 94 | @Order(7) 95 | @DisplayName("DDL generation and database creation are configured") 96 | void ddlAndDatabaseCreation() { 97 | Properties properties = persistenceUnit.getProperties(); 98 | 99 | assertThat(properties) 100 | .containsKey("hibernate.hbm2ddl.auto") 101 | .containsValue("create"); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-2-query-helper/README.MD: -------------------------------------------------------------------------------- 1 | # Query helper exercise :muscle: 2 | Improve your Hibernate "dirty checking" skills 3 | ### Task 4 | `QueryHelper.java` is a simple API that provides a util method helps to perform read operations using `EntityManager`. 5 | Your job is to **implement the *todo* section**. The purpose of this exercise is to learn how to disable *dirty checking*. 6 | 7 | To verify your implementation, run `QueryHelperTest.java` 8 | 9 | 10 | ### Pre-conditions :heavy_exclamation_mark: 11 | You're supposed to be familiar with *JPA* and *Hibernate ORM* 12 | 13 | ### How to start :question: 14 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 15 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 16 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 17 | 18 | ### Related materials :information_source: 19 | * ["Dirty checking" tutorial](https://github.com/bobocode-projects/jpa-hibernate-tutorial/tree/master/dirty-checking-mechanism) 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-2-query-helper/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 3-0-jpa-and-hibernate 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-0-2-query-helper 13 | 14 | 15 | 16 | com.bobocode 17 | jpa-hibernate-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-2-query-helper/src/main/java/com/bobocode/QueryHelper.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.exception.QueryHelperException; 4 | import com.bobocode.util.ExerciseNotCompletedException; 5 | import org.hibernate.Session; 6 | 7 | import jakarta.persistence.EntityManager; 8 | import jakarta.persistence.EntityManagerFactory; 9 | import java.util.Collection; 10 | import java.util.function.Function; 11 | 12 | /** 13 | * {@link QueryHelper} provides an util method that allows to perform read operations in the scope of transaction 14 | */ 15 | public class QueryHelper { 16 | private EntityManagerFactory entityManagerFactory; 17 | 18 | public QueryHelper(EntityManagerFactory entityManagerFactory) { 19 | this.entityManagerFactory = entityManagerFactory; 20 | } 21 | 22 | /** 23 | * Receives a {@link Function}, creates {@link EntityManager} instance, starts transaction, 24 | * performs received function and commits the transaction, in case of exception in rollbacks the transaction and 25 | * throws a {@link QueryHelperException} with the following message: "Error performing query. Transaction is rolled back" 26 | *

27 | * The purpose of this method is to perform read operations using {@link EntityManager}, so it uses read only mode 28 | * by default. 29 | * 30 | * @param entityManagerConsumer query logic encapsulated as function that receives entity manager and returns result 31 | * @param generic type that allows to specify single entity class of some collection 32 | * @return query result specified by type T 33 | */ 34 | public T readWithinTx(Function entityManagerConsumer) { 35 | throw new ExerciseNotCompletedException(); // todo: 36 | } 37 | } -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-2-query-helper/src/main/java/com/bobocode/exception/QueryHelperException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class QueryHelperException extends RuntimeException { 4 | public QueryHelperException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-2-query-helper/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.bobocode.model.Account 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-2-query-helper/src/test/java/com/bobocode/QueryHelperTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.exception.QueryHelperException; 4 | import com.bobocode.model.Account; 5 | import com.bobocode.util.EntityManagerUtil; 6 | import com.bobocode.util.TestDataGenerator; 7 | import org.junit.jupiter.api.*; 8 | 9 | import jakarta.persistence.EntityManagerFactory; 10 | import jakarta.persistence.Persistence; 11 | 12 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 13 | import static org.junit.jupiter.api.Assertions.fail; 14 | 15 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 16 | class QueryHelperTest { 17 | 18 | private static EntityManagerFactory entityManagerFactory; 19 | private static EntityManagerUtil emUtil; 20 | private static QueryHelper queryHelper; 21 | 22 | @BeforeAll 23 | public static void setup() { 24 | entityManagerFactory = Persistence.createEntityManagerFactory("Account"); 25 | emUtil = new EntityManagerUtil(entityManagerFactory); 26 | queryHelper = new QueryHelper(entityManagerFactory); 27 | } 28 | 29 | @AfterAll 30 | static void destroy() { 31 | entityManagerFactory.close(); 32 | } 33 | 34 | @Test 35 | @Order(1) 36 | @DisplayName("Query helper returns a result") 37 | void queryHelperReturnsResult() { 38 | Account account = saveRandomAccount(); 39 | Long accountId = account.getId(); 40 | 41 | Account foundAccount = queryHelper.readWithinTx(entityManager -> entityManager.find(Account.class, accountId)); 42 | 43 | assertThat(foundAccount).isNotNull(); 44 | } 45 | 46 | @Test 47 | @Order(2) 48 | @DisplayName("Query helper uses \"Read Only\"") 49 | void queryHelperUsesReadOnly() { 50 | Account account = saveRandomAccount(); 51 | Long accountId = account.getId(); 52 | 53 | tryToUpdateFirstName(accountId); 54 | Account foundAccount = queryHelper.readWithinTx(entityManager -> entityManager.find(Account.class, accountId)); 55 | 56 | assertThat(foundAccount.getFirstName()).isNotEqualTo("XXX"); 57 | assertThat(foundAccount.getFirstName()).isEqualTo(account.getFirstName()); 58 | } 59 | 60 | @Test 61 | @Order(3) 62 | @DisplayName("Query helper throws exception") 63 | void queryHelperThrowsException() { 64 | Account account = TestDataGenerator.generateAccount(); 65 | emUtil.performWithinTx(entityManager -> entityManager.persist(account)); 66 | try { 67 | queryHelper.readWithinTx(entityManager -> { 68 | Account managedAccount = entityManager.find(Account.class, account.getId()); 69 | throwException(); 70 | return managedAccount; 71 | }); 72 | fail("Exception should be thrown"); 73 | } catch (Exception e) { 74 | assertThat(e.getClass()).isEqualTo(QueryHelperException.class); 75 | assertThat(e.getMessage()).contains("Transaction is rolled back"); 76 | } 77 | } 78 | 79 | private Account saveRandomAccount() { 80 | Account account = TestDataGenerator.generateAccount(); 81 | emUtil.performWithinTx(entityManager -> entityManager.persist(account)); 82 | return account; 83 | } 84 | 85 | private void tryToUpdateFirstName(Long accountId) { 86 | queryHelper.readWithinTx(entityManager -> { 87 | Account managedAccount = entityManager.find(Account.class, accountId); 88 | managedAccount.setFirstName("XXX"); 89 | return managedAccount; 90 | }); 91 | } 92 | 93 | private void throwException() { 94 | throw new RuntimeException("Runtime error"); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-3-account-dao/README.MD: -------------------------------------------------------------------------------- 1 | # Account DAO exercise :muscle: 2 | Improve your JPA and Hibernate ORM skills 3 | ### Task 4 | `AccountDao` provides an API that allow to access and manipulate accounts. Your job is to implement the *todo* section 5 | of that class using JPA and Hibernate. 6 | To verify your implementation, run `AccountDaoTest.java` 7 | 8 | 9 | ### Pre-conditions :heavy_exclamation_mark: 10 | You're supposed to be familiar with JPA and Hibernate ORM and be able to write Java code 11 | 12 | ### How to start :question: 13 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 14 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 15 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 16 | 17 | ### Related materials :information_source: 18 | * [JPA and Hibernate basics tutorial](https://github.com/boy4uck/jpa-hibernate-tutorial/tree/master/jpa-hibernate-basics) 19 | 20 | 21 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-3-account-dao/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 3-0-jpa-and-hibernate 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-0-3-account-dao 13 | 14 | 15 | 16 | com.bobocode 17 | jpa-hibernate-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-3-account-dao/src/main/java/com/bobocode/dao/AccountDao.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Account; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * {@link AccountDao} provides an API to access {@link Account} data. 9 | */ 10 | public interface AccountDao { 11 | /** 12 | * Receives a new instance of {@link Account} and stores it into database. Sets a generated id to account. 13 | * 14 | * @param account new instance of account 15 | */ 16 | void save(Account account); 17 | 18 | /** 19 | * Returns an {@link Account} instance by its id 20 | * 21 | * @param id account id in the database 22 | * @return account instance 23 | */ 24 | Account findById(Long id); 25 | 26 | /** 27 | * Returns {@link Account} instance by its email 28 | * 29 | * @param email account emails 30 | * @return account instance 31 | */ 32 | Account findByEmail(String email); 33 | 34 | /** 35 | * Returns all accounts stored in the database. 36 | * 37 | * @return account list 38 | */ 39 | List findAll(); 40 | 41 | /** 42 | * Receives stored {@link Account} instance and updates it in the database 43 | * 44 | * @param account stored account with updated fields 45 | */ 46 | void update(Account account); 47 | 48 | /** 49 | * Removes the stored account from the database. 50 | * 51 | * @param account stored account instance 52 | */ 53 | void remove(Account account); 54 | } 55 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-3-account-dao/src/main/java/com/bobocode/dao/AccountDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Account; 4 | import com.bobocode.util.ExerciseNotCompletedException; 5 | 6 | import jakarta.persistence.EntityManagerFactory; 7 | import java.util.List; 8 | 9 | public class AccountDaoImpl implements AccountDao { 10 | private EntityManagerFactory emf; 11 | 12 | public AccountDaoImpl(EntityManagerFactory emf) { 13 | this.emf = emf; 14 | } 15 | 16 | @Override 17 | public void save(Account account) { 18 | throw new ExerciseNotCompletedException(); // todo 19 | } 20 | 21 | @Override 22 | public Account findById(Long id) { 23 | throw new ExerciseNotCompletedException(); // todo 24 | } 25 | 26 | @Override 27 | public Account findByEmail(String email) { 28 | throw new ExerciseNotCompletedException(); // todo 29 | } 30 | 31 | @Override 32 | public List findAll() { 33 | throw new ExerciseNotCompletedException(); // todo 34 | } 35 | 36 | @Override 37 | public void update(Account account) { 38 | throw new ExerciseNotCompletedException(); // todo 39 | } 40 | 41 | @Override 42 | public void remove(Account account) { 43 | throw new ExerciseNotCompletedException(); // todo 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-3-account-dao/src/main/java/com/bobocode/exception/AccountDaoException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class AccountDaoException extends RuntimeException{ 4 | public AccountDaoException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-3-account-dao/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.bobocode.model.Account 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | com.bobocode.model.Account 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-0-3-account-dao/src/test/java/com/bobocode/dao/AccountDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.exception.AccountDaoException; 4 | import com.bobocode.model.Account; 5 | import com.bobocode.util.TestDataGenerator; 6 | import org.hibernate.Session; 7 | import org.junit.jupiter.api.*; 8 | 9 | import jakarta.persistence.EntityManager; 10 | import jakarta.persistence.EntityManagerFactory; 11 | import jakarta.persistence.Persistence; 12 | import java.math.BigDecimal; 13 | import java.math.RoundingMode; 14 | import java.sql.Date; 15 | import java.sql.PreparedStatement; 16 | import java.sql.ResultSet; 17 | import java.sql.Timestamp; 18 | import java.util.List; 19 | 20 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 21 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; 22 | import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; 23 | 24 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 25 | class AccountDaoTest { 26 | private static EntityManagerFactory emf; 27 | private static AccountDao accountDao; 28 | 29 | @BeforeAll 30 | static void init() { 31 | emf = Persistence.createEntityManagerFactory("SingleAccountEntityH2"); 32 | accountDao = new AccountDaoImpl(emf); 33 | } 34 | 35 | @AfterAll 36 | static void destroy() { 37 | emf.close(); 38 | } 39 | 40 | @Test 41 | @Order(1) 42 | @DisplayName("Save account") 43 | void saveAccount() { 44 | Account account = TestDataGenerator.generateAccount(); 45 | 46 | accountDao.save(account); 47 | boolean saved = isSaved(account); 48 | 49 | assertThat(account.getId()).isNotNull(); 50 | assertThat(saved).isTrue(); 51 | } 52 | 53 | @Test 54 | @Order(2) 55 | @DisplayName("Save throws exception when account is invalid") 56 | void testSaveInvalidAccount() { 57 | Account invalidAccount = TestDataGenerator.generateAccount(); 58 | invalidAccount.setEmail(null); 59 | assertThatThrownBy(() -> { 60 | accountDao.save(invalidAccount); 61 | }).isInstanceOf(AccountDaoException.class); 62 | } 63 | 64 | @Test 65 | @Order(3) 66 | @DisplayName("Find account by Id") 67 | void testFindAccountById() { 68 | Account account = TestDataGenerator.generateAccount(); 69 | saveTestAccount(account); 70 | 71 | Account foundAccount = accountDao.findById(account.getId()); 72 | 73 | assertThat(account).isEqualTo(foundAccount); 74 | } 75 | 76 | @Test 77 | @Order(4) 78 | @DisplayName("Find account by email") 79 | void testFindAccountByEmail() { 80 | Account account = TestDataGenerator.generateAccount(); 81 | saveTestAccount(account); 82 | 83 | Account foundAccount = accountDao.findByEmail(account.getEmail()); 84 | 85 | assertThat(account).isEqualTo(foundAccount); 86 | } 87 | 88 | @Test 89 | @Order(5) 90 | @DisplayName("Find all accounts") 91 | void testFindAllAccounts() { 92 | List accounts = TestDataGenerator.generateAccountList(3); 93 | accounts.forEach(this::saveTestAccount); 94 | 95 | List foundAccounts = accountDao.findAll(); 96 | 97 | assertThat(foundAccounts).containsAll(accounts); 98 | } 99 | 100 | @Test 101 | @Order(6) 102 | @DisplayName("Update accounts") 103 | void testUpdateAccount() { 104 | Account account = TestDataGenerator.generateAccount(); 105 | saveTestAccount(account); 106 | 107 | account.setBalance(account.getBalance().add(BigDecimal.valueOf(1000).setScale(2, RoundingMode.HALF_UP))); 108 | accountDao.update(account); 109 | boolean balanceUpdated = isBalanceUpdated(account); 110 | 111 | assertThat(balanceUpdated).isTrue(); 112 | } 113 | 114 | @Test 115 | @Order(7) 116 | @DisplayName("Update throws exception when account is invalid") 117 | void testUpdateInvalidAccount() { 118 | Account account = TestDataGenerator.generateAccount(); 119 | saveTestAccount(account); 120 | account.setFirstName(null); 121 | assertThatThrownBy(() -> { 122 | accountDao.update(account); 123 | }).isInstanceOf(AccountDaoException.class); 124 | } 125 | 126 | @Test 127 | @Order(8) 128 | @DisplayName("Remove account") 129 | void testRemoveAccount() { 130 | Account account = TestDataGenerator.generateAccount(); 131 | saveTestAccount(account); 132 | 133 | accountDao.remove(account); 134 | boolean saved = isSaved(account); 135 | 136 | assertThat(saved).isFalse(); 137 | } 138 | 139 | private boolean isBalanceUpdated(Account account) { 140 | EntityManager entityManager = emf.createEntityManager(); 141 | boolean isUpdated = entityManager.unwrap(Session.class).doReturningWork(connection -> { 142 | PreparedStatement statement = connection.prepareStatement("SELECT balance = ? FROM account WHERE id = ?"); 143 | statement.setBigDecimal(1, account.getBalance()); 144 | statement.setLong(2, account.getId()); 145 | ResultSet resultSet = statement.executeQuery(); 146 | resultSet.next(); 147 | return resultSet.getBoolean(1); 148 | }); 149 | entityManager.close(); 150 | return isUpdated; 151 | } 152 | 153 | private boolean isSaved(Account account) { 154 | EntityManager entityManager = emf.createEntityManager(); 155 | boolean isSaved = entityManager.unwrap(Session.class).doReturningWork(connection -> { 156 | PreparedStatement statement = connection.prepareStatement("SELECT * FROM account WHERE id = ?"); 157 | statement.setLong(1, account.getId()); 158 | return statement.executeQuery().next(); 159 | }); 160 | entityManager.close(); 161 | return isSaved; 162 | } 163 | 164 | private void saveTestAccount(Account account) { 165 | EntityManager entityManager = emf.createEntityManager(); 166 | entityManager.unwrap(Session.class).doWork(connection -> { 167 | String insertSql = "INSERT INTO account(first_name, last_name, email, birthday, gender, creation_time, balance) VALUES (?,?,?,?,?,?,?)"; 168 | try (PreparedStatement insertStatement = connection.prepareStatement(insertSql, PreparedStatement.RETURN_GENERATED_KEYS)) { 169 | insertStatement.setString(1, account.getFirstName()); 170 | insertStatement.setString(2, account.getLastName()); 171 | insertStatement.setString(3, account.getEmail()); 172 | insertStatement.setDate(4, Date.valueOf(account.getBirthday())); 173 | insertStatement.setString(5, account.getGender().name()); 174 | insertStatement.setTimestamp(6, Timestamp.valueOf(account.getCreationTime())); 175 | insertStatement.setBigDecimal(7, account.getBalance().setScale(2)); 176 | insertStatement.executeUpdate(); 177 | ResultSet generatedKeys = insertStatement.getGeneratedKeys(); 178 | generatedKeys.next(); 179 | long id = generatedKeys.getLong(1); 180 | account.setId(id); 181 | } 182 | }); 183 | entityManager.close(); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-1-employee-profile/README.MD: -------------------------------------------------------------------------------- 1 | # Employee profile exercise :muscle: 2 | Improve your *JPA One-To-One* relationship mapping skills 3 | ### Task 4 | `Employee.java` and `EmployeeProfile.java` are related JPA entities. The relationship between those two entities is **one 5 | to one**. Each employee can have only one profile, while each profiles is always associated with one employee. **Profile 6 | cannot exist separately without an employee, while employee can exist with no profile associated.** 7 | 8 | Your job is to **implement unidirectional mapping** between those entities using **derived identifier** (a shared primary 9 | key for both entities). 10 | 11 | To verify your implementation, run `EmployeeProfileMappingTest.java` 12 | 13 | 14 | ### Pre-conditions :heavy_exclamation_mark: 15 | You're supposed to be familiar with *JPA* mapping strategies and *Hibernate ORM* 16 | 17 | ### How to start :question: 18 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 19 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 20 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 21 | 22 | ### Related materials :information_source: 23 | * [JPA and Hibernate basics tutorial](https://github.com/boy4uck/jpa-hibernate-tutorial/tree/master/jpa-hibernate-basics) 24 | * [Derived identifiers](http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#identifiers-derived) 25 | * [The best way to map a @OneToOne relationship with JPA and Hibernate](https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/) 26 | 27 | 28 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-1-employee-profile/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 3-0-jpa-and-hibernate 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-1-1-employee-profile 13 | 14 | 15 | com.bobocode 16 | jpa-hibernate-util 17 | 1.0-SNAPSHOT 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/Employee.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | /** 8 | * todo: 9 | * - configure JPA entity 10 | * - specify table name: "employee" 11 | * - configure auto generated identifier 12 | * - configure not nullable columns: email, firstName, lastName 13 | * 14 | * - map unidirectional relation between {@link Employee} and {@link EmployeeProfile} on the child side 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | public class Employee { 20 | private Long id; 21 | private String email; 22 | private String fistName; 23 | private String lastName; 24 | } 25 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/java/com/bobocode/model/EmployeeProfile.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | /** 8 | * todo: 9 | * - configure JPA entity 10 | * - specify table name: "employee_profile" 11 | * - configure not nullable columns: position, department 12 | * 13 | * - map relation between {@link Employee} and {@link EmployeeProfile} using foreign_key column: "employee_id" 14 | * - configure a derived identifier. E.g. map "employee_id" column should be also a primary key (id) for this entity 15 | */ 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | public class EmployeeProfile { 20 | private Long id; 21 | private Employee employee; 22 | private String position; 23 | private String department; 24 | } 25 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-1-employee-profile/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.bobocode.model.Employee 6 | com.bobocode.model.EmployeeProfile 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-1-employee-profile/src/test/java/com/bobocode/EmployeeProfileMappingTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.model.Employee; 4 | import com.bobocode.model.EmployeeProfile; 5 | import com.bobocode.util.EntityManagerUtil; 6 | import org.apache.commons.lang3.RandomStringUtils; 7 | import org.junit.jupiter.api.*; 8 | 9 | import jakarta.persistence.*; 10 | import java.lang.reflect.Field; 11 | 12 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 13 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.Matchers.equalTo; 16 | 17 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 18 | class EmployeeProfileMappingTest { 19 | private static EntityManagerUtil emUtil; 20 | private static EntityManagerFactory entityManagerFactory; 21 | 22 | @BeforeAll 23 | static void setup() { 24 | entityManagerFactory = Persistence.createEntityManagerFactory("Employees"); 25 | emUtil = new EntityManagerUtil(entityManagerFactory); 26 | } 27 | 28 | @AfterAll 29 | static void destroy() { 30 | entityManagerFactory.close(); 31 | } 32 | 33 | @Test 34 | @Order(1) 35 | @DisplayName("The employee table has a correct name") 36 | void employeeTableHasCorrectName() { 37 | Table table = Employee.class.getAnnotation(Table.class); 38 | String tableName = table.name(); 39 | 40 | assertThat(tableName, equalTo("employee")); 41 | } 42 | 43 | @Test 44 | @Order(2) 45 | @DisplayName("Save an employee only") 46 | void saveEmployeeOnly() { 47 | Employee employee = createRandomEmployee(); 48 | 49 | emUtil.performWithinTx(entityManager -> entityManager.persist(employee)); 50 | 51 | assertThat(employee.getId()).isNotNull(); 52 | } 53 | 54 | @Test 55 | @Order(3) 56 | @DisplayName("Saving an employee throws an exception when the email is null") 57 | void saveEmployeeWithoutEmail() { 58 | Employee employee = createRandomEmployee(); 59 | employee.setEmail(null); 60 | 61 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 62 | emUtil.performWithinTx(entityManager -> entityManager.persist(employee))); 63 | } 64 | 65 | @Test 66 | @Order(4) 67 | @DisplayName("Saving an employee throws an exception when the first name is null") 68 | void saveEmployeeWithoutFirstName() { 69 | Employee employee = createRandomEmployee(); 70 | employee.setFistName(null); 71 | 72 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 73 | emUtil.performWithinTx(entityManager -> entityManager.persist(employee))); 74 | } 75 | 76 | @Test 77 | @Order(5) 78 | @DisplayName("Saving an employee throws an exception when the last name is null") 79 | void testSaveEmployeeWithoutLastName() { 80 | Employee employee = createRandomEmployee(); 81 | employee.setLastName(null); 82 | 83 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 84 | emUtil.performWithinTx(entityManager -> entityManager.persist(employee))); 85 | } 86 | 87 | @Test 88 | @Order(6) 89 | @DisplayName("The employee profile table has a correct name") 90 | void employeeProfileTableHasCorrectName() { 91 | Table table = EmployeeProfile.class.getAnnotation(Table.class); 92 | String tableName = table.name(); 93 | 94 | assertThat(tableName).isEqualTo("employee_profile"); 95 | } 96 | 97 | @Test 98 | @Order(7) 99 | @DisplayName("Save only an employee profile") 100 | void saveEmployeeProfileOnly() { 101 | EmployeeProfile employeeProfile = createRandomEmployeeProfile(); 102 | employeeProfile.setId(666L); 103 | 104 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 105 | emUtil.performWithinTx(entityManager -> entityManager.persist(employeeProfile))); 106 | } 107 | 108 | @Test 109 | @Order(8) 110 | @DisplayName("The foreign key column has a correct name") 111 | void foreignKeyColumnHasCorrectName() throws NoSuchFieldException { 112 | Field employee = EmployeeProfile.class.getDeclaredField("employee"); 113 | JoinColumn joinColumn = employee.getAnnotation(JoinColumn.class); 114 | String foreignKeyColumnName = joinColumn.name(); 115 | 116 | assertThat(foreignKeyColumnName).isEqualTo("employee_id"); 117 | } 118 | 119 | @Test 120 | @Order(9) 121 | @DisplayName("Save both employee and employee profile") 122 | void saveBothEmployeeAndEmployeeProfile() { 123 | Employee employee = createRandomEmployee(); 124 | EmployeeProfile employeeProfile = createRandomEmployeeProfile(); 125 | 126 | emUtil.performWithinTx(entityManager -> { 127 | entityManager.persist(employee); 128 | employeeProfile.setEmployee(employee); 129 | entityManager.persist(employeeProfile); 130 | }); 131 | 132 | assertThat(employee.getId()).isNotNull(); 133 | assertThat(employeeProfile.getId()).isNotNull(); 134 | assertThat(employeeProfile.getId()).isEqualTo(employee.getId()); 135 | } 136 | 137 | @Test 138 | @Order(10) 139 | @DisplayName("Add an employee profile") 140 | void addEmployeeProfile() { 141 | Employee employee = createRandomEmployee(); 142 | emUtil.performWithinTx(entityManager -> entityManager.persist(employee)); 143 | long employeeId = employee.getId(); 144 | 145 | EmployeeProfile employeeProfile = createRandomEmployeeProfile(); 146 | emUtil.performWithinTx(entityManager -> { 147 | Employee managedEmployee = entityManager.find(Employee.class, employeeId); 148 | employeeProfile.setEmployee(managedEmployee); 149 | entityManager.persist(employeeProfile); 150 | }); 151 | 152 | assertThat(employee.getId()).isNotNull(); 153 | assertThat(employeeProfile.getId()).isNotNull(); 154 | assertThat(employeeProfile.getId()).isEqualTo(employee.getId()); 155 | } 156 | 157 | @Test 158 | @Order(11) 159 | @DisplayName("Adding an employee profile throws an exception when the position is null") 160 | void addEmployeeWithoutPosition() { 161 | Employee employee = createRandomEmployee(); 162 | emUtil.performWithinTx(entityManager -> entityManager.persist(employee)); 163 | long employeeId = employee.getId(); 164 | 165 | EmployeeProfile profileWithoutPosition = createRandomEmployeeProfile(); 166 | profileWithoutPosition.setPosition(null); 167 | 168 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 169 | emUtil.performWithinTx(entityManager -> { 170 | Employee managedEmployee = entityManager.find(Employee.class, employeeId); 171 | profileWithoutPosition.setEmployee(managedEmployee); 172 | entityManager.persist(profileWithoutPosition); 173 | })); 174 | } 175 | 176 | @Test 177 | @Order(12) 178 | @DisplayName("Adding an employee profile throws an exception when the department is null") 179 | void addEmployeeWithoutDepartment() { 180 | Employee employee = createRandomEmployee(); 181 | emUtil.performWithinTx(entityManager -> entityManager.persist(employee)); 182 | long employeeId = employee.getId(); 183 | 184 | EmployeeProfile profileWithoutDepartment = createRandomEmployeeProfile(); 185 | profileWithoutDepartment.setDepartment(null); 186 | 187 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 188 | emUtil.performWithinTx(entityManager -> { 189 | Employee managedEmployee = entityManager.find(Employee.class, employeeId); 190 | profileWithoutDepartment.setEmployee(managedEmployee); 191 | entityManager.persist(profileWithoutDepartment); 192 | })); 193 | } 194 | 195 | private Employee createRandomEmployee() { 196 | Employee employee = new Employee(); 197 | employee.setEmail(RandomStringUtils.randomAlphabetic(15)); 198 | employee.setFistName(RandomStringUtils.randomAlphabetic(15)); 199 | employee.setLastName(RandomStringUtils.randomAlphabetic(15)); 200 | return employee; 201 | } 202 | 203 | private EmployeeProfile createRandomEmployeeProfile() { 204 | EmployeeProfile employeeProfile = new EmployeeProfile(); 205 | employeeProfile.setDepartment(RandomStringUtils.randomAlphabetic(15)); 206 | employeeProfile.setPosition(RandomStringUtils.randomAlphabetic(15)); 207 | return employeeProfile; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/README.MD: -------------------------------------------------------------------------------- 1 | # Company products exercise :muscle: 2 | Improve your *Hibernate Lazy loading* and *JPA Many-To-One* relationship mapping skills 3 | ### Task 4 | Your job is to implement correct mapping between `Company` and `Product` entities, and implement `CompanyDaoImpl` 5 | **following the instructions in the todo section** 6 | 7 | To verify your implementation, run `CompanyProductMappingTest.java` 8 | 9 | 10 | ### Pre-conditions :heavy_exclamation_mark: 11 | You're supposed to be familiar with *JPA* mapping strategies and *Hibernate ORM* 12 | 13 | ### How to start :question: 14 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 15 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 16 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 17 | 18 | 19 | ### Related materials :information_source: 20 | * [JPA and Hibernate basics tutorial](https://github.com/boy4uck/jpa-hibernate-tutorial/tree/master/jpa-hibernate-basics) 21 | * [The best way to map a @OneToMany relationship with JPA and Hibernate](https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/) 22 | 23 | 24 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 3-0-jpa-and-hibernate 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-1-2-company-products 13 | 14 | 15 | 16 | com.bobocode 17 | jpa-hibernate-util 18 | 1.0-SNAPSHOT 19 | test 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/dao/CompanyDao.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Company; 4 | 5 | public interface CompanyDao { 6 | /** 7 | * Retrieves a {@link Company} with all its products by company id 8 | * 9 | * @param id company id 10 | * @return company with all its products 11 | */ 12 | Company findByIdFetchProducts(Long id); 13 | } 14 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/dao/CompanyDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Company; 4 | import com.bobocode.util.ExerciseNotCompletedException; 5 | 6 | import jakarta.persistence.EntityManagerFactory; 7 | 8 | public class CompanyDaoImpl implements CompanyDao { 9 | private EntityManagerFactory entityManagerFactory; 10 | 11 | public CompanyDaoImpl(EntityManagerFactory entityManagerFactory) { 12 | this.entityManagerFactory = entityManagerFactory; 13 | } 14 | 15 | @Override 16 | public Company findByIdFetchProducts(Long id) { 17 | throw new ExerciseNotCompletedException(); // todo 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/exception/CompanyDaoException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class CompanyDaoException extends RuntimeException{ 4 | public CompanyDaoException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Company.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import com.bobocode.util.ExerciseNotCompletedException; 4 | import lombok.*; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * todo: 11 | * - make setter for field {@link Company#products} private 12 | * - initialize field {@link Company#products} as new {@link ArrayList} 13 | * - implement a helper {@link Company#addProduct(Product)} that establishes a relation on both sides 14 | * - implement a helper {@link Company#removeProduct(Product)} that drops a relation on both sides 15 | *

16 | * - configure JPA entity 17 | * - specify table name: "company" 18 | * - configure auto generated identifier 19 | * - configure mandatory column "name" for field {@link Company#name} 20 | *

21 | * - configure one-to-many relationship as mapped on the child side 22 | * - override equals() and hashCode() considering entity id 23 | */ 24 | @NoArgsConstructor 25 | @Getter 26 | @Setter 27 | public class Company { 28 | private Long id; 29 | private String name; 30 | private List products = new ArrayList<>(); 31 | 32 | public void addProduct(Product product) { 33 | throw new ExerciseNotCompletedException(); 34 | } 35 | 36 | public void removeProduct(Product product) { 37 | throw new ExerciseNotCompletedException(); 38 | } 39 | } -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/src/main/java/com/bobocode/model/Product.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | /** 8 | * todo: 9 | * - configure JPA entity 10 | * - specify table name: "product" 11 | * - configure auto generated identifier 12 | * - configure mandatory column "name" for field {@link Product#name} 13 | *

14 | * - configure lazy many-to-one relation between {@link Product} and {@link Company} 15 | * - configure foreign key column "company_id" references company table 16 | * - override equals() and hashCode() considering entity id 17 | */ 18 | @NoArgsConstructor 19 | @Getter 20 | @Setter 21 | public class Product { 22 | private Long id; 23 | private String name; 24 | private Company company; 25 | } 26 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.bobocode.model.Company 6 | com.bobocode.model.Product 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-2-company-products/src/test/java/com/bobocode/CompanyProductMappingTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.dao.CompanyDao; 4 | import com.bobocode.dao.CompanyDaoImpl; 5 | import com.bobocode.model.Company; 6 | import com.bobocode.model.Product; 7 | import com.bobocode.util.EntityManagerUtil; 8 | import org.apache.commons.lang3.RandomStringUtils; 9 | import org.hibernate.LazyInitializationException; 10 | import org.junit.jupiter.api.*; 11 | 12 | import jakarta.persistence.EntityManagerFactory; 13 | import jakarta.persistence.JoinColumn; 14 | import jakarta.persistence.Persistence; 15 | import jakarta.persistence.PersistenceException; 16 | import java.lang.reflect.Field; 17 | import java.lang.reflect.Modifier; 18 | import java.util.List; 19 | 20 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 21 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 22 | import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; 23 | 24 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 25 | class CompanyProductMappingTest { 26 | private static EntityManagerUtil emUtil; 27 | private static EntityManagerFactory entityManagerFactory; 28 | private static CompanyDao companyDao; 29 | 30 | @BeforeAll 31 | static void setup() { 32 | entityManagerFactory = Persistence.createEntityManagerFactory("CompanyProducts"); 33 | emUtil = new EntityManagerUtil(entityManagerFactory); 34 | companyDao = new CompanyDaoImpl(entityManagerFactory); 35 | } 36 | 37 | @AfterAll 38 | static void destroy() { 39 | entityManagerFactory.close(); 40 | } 41 | 42 | @Test 43 | @Order(1) 44 | @DisplayName("Save a company") 45 | void saveCompany() { 46 | var company = createRandomCompany(); 47 | 48 | emUtil.performWithinTx(entityManager -> entityManager.persist(company)); 49 | 50 | assertThat(company.getId()).isNotNull(); 51 | } 52 | 53 | @Test 54 | @Order(2) 55 | @DisplayName("Saving a company throws an exception when the name is null") 56 | void saveCompanyWithNullName() { 57 | var company = new Company(); 58 | 59 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 60 | emUtil.performWithinTx(entityManager -> entityManager.persist(company)) 61 | ); 62 | } 63 | 64 | @Test 65 | @Order(3) 66 | @DisplayName("Foreign key column is specified") 67 | void foreignKeyColumnIsSpecified() throws NoSuchFieldException { 68 | Field company = Product.class.getDeclaredField("company"); 69 | JoinColumn joinColumn = company.getAnnotation(JoinColumn.class); 70 | 71 | assertThat(joinColumn.name()).isEqualTo("company_id"); 72 | } 73 | 74 | @Test 75 | @Order(4) 76 | @DisplayName("Save a product") 77 | void saveProduct() { 78 | var product = createRandomProduct(); 79 | 80 | emUtil.performWithinTx(entityManager -> entityManager.persist(product)); 81 | 82 | assertThat(product.getId()).isNotNull(); 83 | } 84 | 85 | @Test 86 | @Order(5) 87 | @DisplayName("Saving a product throws an exception when the name is null") 88 | void saveProductWithNullName() { 89 | var product = new Product(); 90 | 91 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 92 | emUtil.performWithinTx(entityManager -> entityManager.persist(product)) 93 | ); 94 | } 95 | 96 | @Test 97 | @Order(6) 98 | @DisplayName("Save both a product and a company") 99 | void saveProductAndCompany() { 100 | var company = createRandomCompany(); 101 | var product = createRandomProduct(); 102 | 103 | emUtil.performWithinTx(entityManager -> entityManager.persist(company)); 104 | emUtil.performWithinTx(entityManager -> { 105 | var companyProxy = entityManager.getReference(Company.class, company.getId()); 106 | product.setCompany(companyProxy); 107 | entityManager.persist(product); 108 | }); 109 | 110 | emUtil.performWithinTx(entityManager -> { 111 | var managedCompany = entityManager.find(Company.class, company.getId()); 112 | var managedProduct = entityManager.find(Product.class, product.getId()); 113 | 114 | assertThat(managedCompany.getProducts()).contains(managedProduct); 115 | assertThat(managedProduct.getCompany()).isEqualTo(managedCompany); 116 | }); 117 | } 118 | 119 | @Test 120 | @Order(7) 121 | @DisplayName("Add a new product to an existing company") 122 | void addNewProductToExistingCompany() { 123 | var company = createRandomCompany(); 124 | emUtil.performWithinTx(entityManager -> entityManager.persist(company)); 125 | 126 | var product = createRandomProduct(); 127 | emUtil.performWithinTx(entityManager -> { 128 | entityManager.persist(product); 129 | var managedCompany = entityManager.merge(company); 130 | managedCompany.addProduct(product); 131 | assertThat(managedCompany.getProducts()).contains(product); 132 | }); 133 | 134 | assertThat(product.getCompany()).isEqualTo(company); 135 | emUtil.performWithinTx(entityManager -> { 136 | var managedCompany = entityManager.find(Company.class, company.getId()); 137 | assertThat(managedCompany.getProducts()).contains(product); 138 | 139 | }); 140 | } 141 | 142 | @Test 143 | @Order(8) 144 | @DisplayName("Remove a product from a company") 145 | void removeProductFromCompany() { 146 | var company = createRandomCompany(); 147 | emUtil.performWithinTx(entityManager -> entityManager.persist(company)); 148 | 149 | var product = createRandomProduct(); 150 | emUtil.performWithinTx(entityManager -> { 151 | product.setCompany(company); 152 | entityManager.persist(product); 153 | }); 154 | 155 | emUtil.performWithinTx(entityManager -> { 156 | var managedProduct = entityManager.find(Product.class, product.getId()); 157 | var managedCompany = entityManager.find(Company.class, company.getId()); 158 | managedCompany.removeProduct(managedProduct); 159 | assertThat(managedCompany.getProducts()).doesNotContain(managedProduct); 160 | }); 161 | 162 | emUtil.performWithinTx(entityManager -> { 163 | var managedCompany = entityManager.find(Company.class, company.getId()); 164 | assertThat(managedCompany.getProducts()).doesNotContain(product); 165 | }); 166 | } 167 | 168 | @Test 169 | @Order(9) 170 | @DisplayName("Field \"products\" is lazy in Company entity") 171 | void companyToProductsIsLazy() { 172 | var company = createRandomCompany(); 173 | emUtil.performWithinTx(entityManager -> entityManager.persist(company)); 174 | 175 | var product = createRandomProduct(); 176 | emUtil.performWithinTx(entityManager -> { 177 | product.setCompany(company); 178 | entityManager.persist(product); 179 | }); 180 | 181 | Company loadedCompany = emUtil.performReturningWithinTx(entityManager -> entityManager.find(Company.class, company.getId())); 182 | List products = loadedCompany.getProducts(); 183 | 184 | assertThatExceptionOfType(LazyInitializationException.class).isThrownBy(() -> System.out.println(products)); 185 | } 186 | 187 | @Test 188 | @Order(10) 189 | @DisplayName("Field \"company\" is lazy in Product entity") 190 | void productsToCompanyIsLazy() { 191 | var company = createRandomCompany(); 192 | emUtil.performWithinTx(entityManager -> entityManager.persist(company)); 193 | 194 | var product = createRandomProduct(); 195 | emUtil.performWithinTx(entityManager -> { 196 | product.setCompany(company); 197 | entityManager.persist(product); 198 | }); 199 | 200 | Product loadedProduct = emUtil.performReturningWithinTx(entityManager -> entityManager.find(Product.class, product.getId())); 201 | Company loadedCompany = loadedProduct.getCompany(); 202 | 203 | assertThatExceptionOfType(LazyInitializationException.class).isThrownBy(() -> System.out.println(loadedCompany)); 204 | } 205 | 206 | @Test 207 | @Order(11) 208 | @DisplayName("findByIdFetchProducts() loads company and products all together") 209 | void findByIdFetchesProducts() { 210 | var company = createRandomCompany(); 211 | emUtil.performWithinTx(entityManager -> entityManager.persist(company)); 212 | 213 | var product = createRandomProduct(); 214 | emUtil.performWithinTx(entityManager -> { 215 | product.setCompany(company); 216 | entityManager.persist(product); 217 | }); 218 | 219 | Company foundCompany = companyDao.findByIdFetchProducts(company.getId()); 220 | assertThat(foundCompany).isEqualTo(company); 221 | assertThat(foundCompany.getProducts()).contains(product); 222 | } 223 | 224 | @Test 225 | @Order(12) 226 | @DisplayName("Setter for field \"products\" is private in Company entity") 227 | void companySetProductsIsPrivate() throws NoSuchMethodException { 228 | assertThat(Company.class.getDeclaredMethod("setProducts", List.class).getModifiers()).isEqualTo(Modifier.PRIVATE); 229 | } 230 | 231 | private Company createRandomCompany() { 232 | var company = new Company(); 233 | company.setName(RandomStringUtils.randomAlphabetic(20)); 234 | return company; 235 | } 236 | 237 | private Product createRandomProduct() { 238 | var product = new Product(); 239 | product.setName(RandomStringUtils.randomAlphabetic(20)); 240 | return product; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-3-author-book/README.MD: -------------------------------------------------------------------------------- 1 | # Authors & books exercise :muscle: 2 | Improve your *JPA Many-To-Many* relationship mapping skills 3 | ### Task 4 | The relationship between `Author` and `Book` is many to many. In the database this relation is represented as two tables 5 | that are linked with a special **link table**. Your job is to **provide mapping for both entities following notes 6 | in the todo section.** 7 | 8 | To verify your implementation, run `AuthorBookMappingTest.java` 9 | 10 | 11 | ### Pre-conditions :heavy_exclamation_mark: 12 | You're supposed to be familiar with *JPA* mapping strategies and *Hibernate ORM* 13 | 14 | ### How to start :question: 15 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 16 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 17 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 18 | 19 | ### See also 20 | * [Wall street db initializer exercise](https://github.com/bobocode-projects/jdbc-api-exercises/tree/master/wall-street-db-initializer) 21 | 22 | ### Related materials :information_source: 23 | * [JPA and Hibernate basics tutorial](https://github.com/boy4uck/jpa-hibernate-tutorial/tree/master/jpa-hibernate-basics) 24 | * [@ManyToMany](http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#associations-many-to-many) 25 | * [The best way to use the @ManyToMany annotation with JPA and Hibernate](https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/) 26 | * [How to implement equals and hashCode using the JPA entity identifier](https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/) 27 | 28 | 29 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-3-author-book/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 3-0-jpa-and-hibernate 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-1-3-author-book 13 | 14 | 15 | 16 | com.bobocode 17 | jpa-hibernate-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Author.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import com.bobocode.util.ExerciseNotCompletedException; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import jakarta.persistence.CascadeType; 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | /** 13 | * todo: 14 | * - implement hashCode() that return constant value 31 15 | * - make setter for field {@link Author#books} private 16 | * - initialize field {@link Author#books} as new {@link HashSet} 17 | * - implement a helper {@link Author#addBook(Book)} that establishes a relation on both sides 18 | * - implement a helper {@link Author#removeBook(Book)} that drops a relation on both sides 19 | *

20 | * - configure JPA entity 21 | * - specify table name: "author" 22 | * - configure auto generated identifier 23 | * - configure mandatory column "first_name" for field {@link Author#firstName} 24 | * - configure mandatory column "last_name" for field {@link Author#lastName} 25 | *

26 | * - configure many-to-many relation between {@link Author} and {@link Book} 27 | * - configure cascade operations for this relations {@link CascadeType#PERSIST} and {@link CascadeType#MERGE} 28 | * - configure link (join) table "author_book" 29 | * - configure foreign key column "book_id" references book table 30 | * - configure foreign key column "author_id" references author table 31 | */ 32 | @NoArgsConstructor 33 | @Getter 34 | @Setter 35 | public class Author { 36 | private Long id; 37 | private String firstName; 38 | private String lastName; 39 | private Set books; 40 | 41 | public void addBook(Book book) { 42 | throw new ExerciseNotCompletedException(); 43 | } 44 | 45 | public void removeBook(Book book) { 46 | throw new ExerciseNotCompletedException(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-3-author-book/src/main/java/com/bobocode/model/Book.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | import jakarta.persistence.*; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | /** 12 | * todo: 13 | * - implement equals() and hashCode() based on {@link Book#isbn} 14 | * - make setter for field {@link Book#authors} private 15 | * - initialize field {@link Book#authors} as new {@link HashSet} 16 | *

17 | * - configure JPA entity 18 | * - specify table name: "book" 19 | * - configure auto generated identifier 20 | * - configure mandatory column "name" for field {@link Book#name} 21 | * - configure mandatory unique column "isbn" for field {@link Book#isbn}, it is a natural key candidate 22 | *

23 | * - configure many-to-many relation as mapped on the {@link Author} side 24 | */ 25 | @NoArgsConstructor 26 | @Getter 27 | @Setter 28 | public class Book { 29 | private Long id; 30 | private String name; 31 | private String isbn; 32 | private Set authors; 33 | } 34 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-1-3-author-book/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.bobocode.model.Book 6 | com.bobocode.model.Author 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/README.MD: -------------------------------------------------------------------------------- 1 | # Photo comment DAO exercise :muscle: 2 | Improve your *Hibernate ORM* and *JPA One-To-Many* relationship mapping skills 3 | ### Task 4 | Each `Photo` has a list of `PhotoComments`. Comments cannot exits without photo associated. You job is to provide a mapping 5 | for this **bidirectional *one to many* relationship** and **implement required DAO methods.** Please follow the instructions 6 | in the todo sections. 7 | 8 | To verify your mapping, run `PhotoCommentMappingTest.java` 9 | To verify your DAO implementation, run `PhotoDaoTest.java` 10 | 11 | 12 | ### Pre-conditions :heavy_exclamation_mark: 13 | You're supposed to be familiar with *JPA* mapping strategies and *Hibernate ORM* 14 | 15 | ### How to start :question: 16 | * Just clone the repository and start implementing the **todo** section, verify your changes by running tests 17 | * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) 18 | * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation 19 | 20 | ### Related materials :information_source: 21 | * [JPA and Hibernate basics tutorial](https://github.com/boy4uck/jpa-hibernate-tutorial/tree/master/jpa-hibernate-basics) 22 | * [@OneToMany association](http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#associations-one-to-many) 23 | * [The best way to map a @OneToMany relationship with JPA and Hibernate](https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/) 24 | 25 | 26 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 3-0-jpa-and-hibernate 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-2-2-photo-comment-dao 13 | 14 | 15 | 16 | com.bobocode 17 | jpa-hibernate-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/dao/PhotoDao.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Photo; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * {@link PhotoDao} defines and API of Data-Access Object for entity {@link Photo} 9 | */ 10 | public interface PhotoDao { 11 | 12 | /** 13 | * Saves photo into db and sets an id 14 | * 15 | * @param photo new photo 16 | */ 17 | void save(Photo photo); 18 | 19 | /** 20 | * Retrieves a photo from the database by its id 21 | * 22 | * @param id photo id 23 | * @return photo instance 24 | */ 25 | Photo findById(long id); 26 | 27 | /** 28 | * Returns a list of all stored photos 29 | * 30 | * @return list of stored photos 31 | */ 32 | List findAll(); 33 | 34 | /** 35 | * Removes a photo from the database 36 | * 37 | * @param photo an instance of stored photo 38 | */ 39 | void remove(Photo photo); 40 | 41 | /** 42 | * Adds a new comment to an existing photo. This method does not require additional SQL select methods to load 43 | * {@link Photo}. 44 | * 45 | * @param photoId 46 | * @param comment 47 | */ 48 | void addComment(long photoId, String comment); 49 | } 50 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/dao/PhotoDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Photo; 4 | import com.bobocode.util.ExerciseNotCompletedException; 5 | 6 | import jakarta.persistence.EntityManagerFactory; 7 | import java.util.List; 8 | 9 | /** 10 | * Please note that you should not use auto-commit mode for your implementation. 11 | */ 12 | public class PhotoDaoImpl implements PhotoDao { 13 | private EntityManagerFactory entityManagerFactory; 14 | 15 | public PhotoDaoImpl(EntityManagerFactory entityManagerFactory) { 16 | this.entityManagerFactory = entityManagerFactory; 17 | } 18 | 19 | @Override 20 | public void save(Photo photo) { 21 | throw new ExerciseNotCompletedException(); // todo 22 | } 23 | 24 | @Override 25 | public Photo findById(long id) { 26 | throw new ExerciseNotCompletedException(); // todo 27 | } 28 | 29 | @Override 30 | public List findAll() { 31 | throw new ExerciseNotCompletedException(); // todo 32 | } 33 | 34 | @Override 35 | public void remove(Photo photo) { 36 | throw new ExerciseNotCompletedException(); // todo 37 | } 38 | 39 | @Override 40 | public void addComment(long photoId, String comment) { 41 | throw new ExerciseNotCompletedException(); // todo 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/Photo.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import com.bobocode.util.ExerciseNotCompletedException; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * todo: 11 | * - make a setter for field {@link Photo#comments} {@code private} 12 | * - implement equals() and hashCode() based on identifier field 13 | * 14 | * - configure JPA entity 15 | * - specify table name: "photo" 16 | * - configure auto generated identifier 17 | * - configure not nullable and unique column: url 18 | * 19 | * - initialize field comments 20 | * - map relation between Photo and PhotoComment on the child side 21 | * - implement helper methods {@link Photo#addComment(PhotoComment)} and {@link Photo#removeComment(PhotoComment)} 22 | * - enable cascade type {@link jakarta.persistence.CascadeType#ALL} for field {@link Photo#comments} 23 | * - enable orphan removal 24 | */ 25 | @Getter 26 | @Setter 27 | public class Photo { 28 | private Long id; 29 | private String url; 30 | private String description; 31 | private List comments; 32 | 33 | public void addComment(PhotoComment comment) { 34 | throw new ExerciseNotCompletedException(); 35 | } 36 | 37 | public void removeComment(PhotoComment comment) { 38 | throw new ExerciseNotCompletedException(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/java/com/bobocode/model/PhotoComment.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | /** 9 | * todo: 10 | * - implement equals and hashCode based on identifier field 11 | * 12 | * - configure JPA entity 13 | * - specify table name: "photo_comment" 14 | * - configure auto generated identifier 15 | * - configure not nullable column: text 16 | * 17 | * - map relation between Photo and PhotoComment using foreign_key column: "photo_id" 18 | * - configure relation as mandatory (not optional) 19 | */ 20 | @Getter 21 | @Setter 22 | public class PhotoComment { 23 | private Long id; 24 | private String text; 25 | private LocalDateTime createdOn; 26 | private Photo photo; 27 | } 28 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.bobocode.model.Photo 6 | com.bobocode.model.PhotoComment 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/test/java/com/bobocode/PhotoCommentMappingTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import static com.bobocode.util.PhotoTestDataGenerator.createListOfRandomComments; 4 | import static com.bobocode.util.PhotoTestDataGenerator.createRandomPhoto; 5 | import static com.bobocode.util.PhotoTestDataGenerator.createRandomPhotoComment; 6 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 7 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 8 | import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; 9 | 10 | import com.bobocode.model.Photo; 11 | import com.bobocode.model.PhotoComment; 12 | import com.bobocode.util.EntityManagerUtil; 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.Method; 15 | import java.lang.reflect.Modifier; 16 | import java.util.List; 17 | import jakarta.persistence.CascadeType; 18 | import jakarta.persistence.Column; 19 | import jakarta.persistence.EntityManagerFactory; 20 | import jakarta.persistence.JoinColumn; 21 | import jakarta.persistence.ManyToOne; 22 | import jakarta.persistence.OneToMany; 23 | import jakarta.persistence.Persistence; 24 | import jakarta.persistence.PersistenceException; 25 | import jakarta.persistence.Table; 26 | import org.junit.jupiter.api.AfterAll; 27 | import org.junit.jupiter.api.BeforeAll; 28 | import org.junit.jupiter.api.DisplayName; 29 | import org.junit.jupiter.api.MethodOrderer; 30 | import org.junit.jupiter.api.Order; 31 | import org.junit.jupiter.api.Test; 32 | import org.junit.jupiter.api.TestMethodOrder; 33 | 34 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 35 | class PhotoCommentMappingTest { 36 | 37 | private static EntityManagerUtil emUtil; 38 | private static EntityManagerFactory entityManagerFactory; 39 | 40 | @BeforeAll 41 | static void setup() { 42 | entityManagerFactory = Persistence.createEntityManagerFactory("PhotoComments"); 43 | emUtil = new EntityManagerUtil(entityManagerFactory); 44 | } 45 | 46 | @AfterAll 47 | static void destroy() { 48 | entityManagerFactory.close(); 49 | } 50 | 51 | @Test 52 | @Order(1) 53 | @DisplayName("Comments list is initialized") 54 | void commentsListIsInitialized() { 55 | Photo photo = new Photo(); 56 | List comments = photo.getComments(); 57 | 58 | assertThat(comments).isNotNull(); 59 | } 60 | 61 | @Test 62 | @Order(2) 63 | @DisplayName("Setter for field \"comments\" is private in Photo entity") 64 | void commentsSetterIsPrivate() throws NoSuchMethodException { 65 | Method setComments = Photo.class.getDeclaredMethod("setComments", List.class); 66 | 67 | assertThat(setComments.getModifiers()).isEqualTo(Modifier.PRIVATE); 68 | } 69 | 70 | @Test 71 | @Order(3) 72 | @DisplayName("Photo table name is specified") 73 | void photoTableNameIsSpecified() { 74 | Table table = Photo.class.getAnnotation(Table.class); 75 | String tableName = table.name(); 76 | 77 | assertThat(tableName).isEqualTo("photo"); 78 | } 79 | 80 | @Test 81 | @Order(4) 82 | @DisplayName("Photo comment table name is specified") 83 | void photoCommentTableNameIsSpecified() { 84 | Table table = PhotoComment.class.getAnnotation(Table.class); 85 | 86 | assertThat(table.name()).isEqualTo("photo_comment"); 87 | } 88 | 89 | @Test 90 | @Order(5) 91 | @DisplayName("Photo URL is not null and unique") 92 | void photoUrlIsNotNullAndUnique() throws NoSuchFieldException { 93 | Field url = Photo.class.getDeclaredField("url"); 94 | Column column = url.getAnnotation(Column.class); 95 | 96 | assertThat(column.nullable()).isFalse(); 97 | assertThat(column.unique()).isTrue(); 98 | } 99 | 100 | @Test 101 | @Order(6) 102 | @DisplayName("Photo comment text is mandatory") 103 | void photoCommentTextIsMandatory() throws NoSuchFieldException { 104 | Field text = PhotoComment.class.getDeclaredField("text"); 105 | Column column = text.getAnnotation(Column.class); 106 | 107 | assertThat(column.nullable()).isFalse(); 108 | } 109 | 110 | @Test 111 | @Order(7) 112 | @DisplayName("Cascade type ALL is enabled for comments") 113 | void cascadeTypeAllIsEnabledForComments() throws NoSuchFieldException { 114 | Field comments = Photo.class.getDeclaredField("comments"); 115 | OneToMany oneToMany = comments.getAnnotation(OneToMany.class); 116 | CascadeType[] expectedCascade = {CascadeType.ALL}; 117 | 118 | assertThat(oneToMany.cascade()).isEqualTo(expectedCascade); 119 | } 120 | 121 | @Test 122 | @Order(8) 123 | @DisplayName("Orphan removal is enabled for comments") 124 | void orphanRemovalIsEnabledForComments() throws NoSuchFieldException { 125 | Field comments = Photo.class.getDeclaredField("comments"); 126 | OneToMany oneToMany = comments.getAnnotation(OneToMany.class); 127 | 128 | assertThat(oneToMany.orphanRemoval()).isTrue(); 129 | } 130 | 131 | @Test 132 | @Order(9) 133 | @DisplayName("Foreign key column is specified") 134 | void foreignKeyColumnIsSpecified() throws NoSuchFieldException { 135 | Field photo = PhotoComment.class.getDeclaredField("photo"); 136 | JoinColumn joinColumn = photo.getAnnotation(JoinColumn.class); 137 | 138 | assertThat(joinColumn.name()).isEqualTo("photo_id"); 139 | } 140 | 141 | @Test 142 | @Order(10) 143 | @DisplayName("Save a photo only") 144 | void savePhotoOnly() { 145 | Photo photo = createRandomPhoto(); 146 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 147 | 148 | assertThat(photo.getId()).isNotNull(); 149 | } 150 | 151 | @Test 152 | @Order(11) 153 | @DisplayName("Save a photo comment only") 154 | void savePhotoCommentOnly() { 155 | PhotoComment photoComment = createRandomPhotoComment(); 156 | 157 | assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> 158 | emUtil.performWithinTx(entityManager -> entityManager.persist(photoComment))); 159 | } 160 | 161 | @Test 162 | @Order(12) 163 | @DisplayName("A comment cannot exist without a photo") 164 | void commentCannotExistsWithoutPhoto() throws NoSuchFieldException { 165 | Field photo = PhotoComment.class.getDeclaredField("photo"); 166 | ManyToOne manyToOne = photo.getAnnotation(ManyToOne.class); 167 | 168 | assertThat(manyToOne.optional()).isFalse(); 169 | } 170 | 171 | @Test 172 | @Order(13) 173 | @DisplayName("Save a new comment") 174 | void saveNewComment() { 175 | Photo photo = createRandomPhoto(); 176 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 177 | 178 | PhotoComment photoComment = createRandomPhotoComment(); 179 | photoComment.setPhoto(photo); 180 | emUtil.performWithinTx(entityManager -> entityManager.persist(photoComment)); 181 | 182 | assertThat(photoComment.getId()).isNotNull(); 183 | emUtil.performWithinTx(entityManager -> { 184 | PhotoComment managedPhotoComment = entityManager.find(PhotoComment.class, photoComment.getId()); 185 | assertThat(managedPhotoComment.getPhoto()).isEqualTo(photo); 186 | }); 187 | emUtil.performWithinTx(entityManager -> { 188 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 189 | assertThat(managedPhoto.getComments()).contains(photoComment); 190 | }); 191 | } 192 | 193 | @Test 194 | @Order(14) 195 | @DisplayName("Add a new comment") 196 | void addNewComment() { 197 | Photo photo = createRandomPhoto(); 198 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 199 | 200 | PhotoComment photoComment = createRandomPhotoComment(); 201 | emUtil.performWithinTx(entityManager -> { 202 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 203 | managedPhoto.addComment(photoComment); 204 | }); 205 | 206 | assertThat(photoComment.getId()).isNotNull(); 207 | emUtil.performWithinTx(entityManager -> { 208 | PhotoComment managedPhotoComment = entityManager.find(PhotoComment.class, photoComment.getId()); 209 | assertThat(managedPhotoComment.getPhoto()).isEqualTo(photo); 210 | }); 211 | emUtil.performWithinTx(entityManager -> { 212 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 213 | assertThat(managedPhoto.getComments()).contains(photoComment); 214 | }); 215 | } 216 | 217 | @Test 218 | @Order(15) 219 | @DisplayName("Save new comments") 220 | void saveNewComments() { 221 | Photo photo = createRandomPhoto(); 222 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 223 | 224 | List listOfComments = createListOfRandomComments(5); 225 | listOfComments.forEach(comment -> comment.setPhoto(photo)); 226 | 227 | emUtil.performWithinTx(entityManager -> listOfComments.forEach(entityManager::persist)); 228 | 229 | emUtil.performWithinTx(entityManager -> { 230 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 231 | assertThat(managedPhoto.getComments()).containsExactlyInAnyOrderElementsOf(listOfComments); 232 | }); 233 | } 234 | 235 | @Test 236 | @Order(16) 237 | @DisplayName("Add new comments") 238 | void addNewComments() { 239 | Photo photo = createRandomPhoto(); 240 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 241 | List listOfComments = createListOfRandomComments(5); 242 | 243 | emUtil.performWithinTx(entityManager -> { 244 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 245 | listOfComments.forEach(managedPhoto::addComment); 246 | }); 247 | 248 | emUtil.performWithinTx(entityManager -> { 249 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 250 | assertThat(managedPhoto.getComments()).containsExactlyInAnyOrderElementsOf(listOfComments); 251 | }); 252 | } 253 | 254 | @Test 255 | @Order(17) 256 | @DisplayName("Remove a comment") 257 | void removeComment() { 258 | Photo photo = createRandomPhoto(); 259 | PhotoComment photoComment = createRandomPhotoComment(); 260 | List commentList = createListOfRandomComments(5); 261 | photo.addComment(photoComment); 262 | commentList.forEach(photo::addComment); 263 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 264 | 265 | emUtil.performWithinTx(entityManager -> { 266 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 267 | PhotoComment managedComment = entityManager.find(PhotoComment.class, photoComment.getId()); 268 | managedPhoto.removeComment(managedComment); 269 | }); 270 | 271 | emUtil.performWithinTx(entityManager -> { 272 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 273 | PhotoComment managedPhotoComment = entityManager.find(PhotoComment.class, photoComment.getId()); 274 | 275 | assertThat(managedPhoto.getComments()).doesNotContain(photoComment); 276 | assertThat(managedPhoto.getComments()).containsExactlyInAnyOrderElementsOf(commentList); 277 | assertThat(managedPhotoComment).isNull(); 278 | }); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/test/java/com/bobocode/PhotoDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import static com.bobocode.util.PhotoTestDataGenerator.createListOfRandomPhotos; 4 | import static com.bobocode.util.PhotoTestDataGenerator.createRandomPhoto; 5 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 6 | import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; 7 | 8 | import com.bobocode.dao.PhotoDao; 9 | import com.bobocode.dao.PhotoDaoImpl; 10 | import com.bobocode.model.Photo; 11 | import com.bobocode.util.EntityManagerUtil; 12 | import java.util.List; 13 | import jakarta.persistence.EntityManagerFactory; 14 | import jakarta.persistence.Persistence; 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.DisplayName; 18 | import org.junit.jupiter.api.MethodOrderer; 19 | import org.junit.jupiter.api.Order; 20 | import org.junit.jupiter.api.Test; 21 | import org.junit.jupiter.api.TestMethodOrder; 22 | 23 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 24 | class PhotoDaoTest { 25 | 26 | private EntityManagerUtil emUtil; 27 | private PhotoDao photoDao; 28 | private EntityManagerFactory entityManagerFactory; 29 | 30 | @BeforeEach 31 | void setup() { 32 | entityManagerFactory = Persistence.createEntityManagerFactory("PhotoComments"); 33 | emUtil = new EntityManagerUtil(entityManagerFactory); 34 | photoDao = new PhotoDaoImpl(entityManagerFactory); 35 | } 36 | 37 | @AfterEach 38 | void destroy() { 39 | entityManagerFactory.close(); 40 | } 41 | 42 | @Test 43 | @Order(1) 44 | @DisplayName("Save a photo") 45 | void savePhoto() { 46 | Photo photo = createRandomPhoto(); 47 | 48 | photoDao.save(photo); 49 | 50 | Photo fountPhoto = emUtil.performReturningWithinTx(entityManager -> entityManager.find(Photo.class, photo.getId())); 51 | assertThat(fountPhoto).isEqualTo(photo); 52 | } 53 | 54 | @Test 55 | @Order(3) 56 | @DisplayName("Find a photo by Id") 57 | void findPhotoById() { 58 | Photo photo = createRandomPhoto(); 59 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 60 | 61 | Photo foundPhoto = photoDao.findById(photo.getId()); 62 | 63 | assertThat(foundPhoto).isEqualTo(photo); 64 | } 65 | 66 | @Test 67 | @Order(3) 68 | @DisplayName("Find all photos") 69 | void findAllPhotos() { 70 | List listOfRandomPhotos = createListOfRandomPhotos(5); 71 | emUtil.performWithinTx(entityManager -> listOfRandomPhotos.forEach(entityManager::persist)); 72 | 73 | List foundPhotos = photoDao.findAll(); 74 | 75 | assertThat(foundPhotos).containsExactlyInAnyOrderElementsOf(listOfRandomPhotos); 76 | } 77 | 78 | @Test 79 | @Order(4) 80 | @DisplayName("Remove a photo") 81 | void removePhoto() { 82 | Photo photo = createRandomPhoto(); 83 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 84 | 85 | photoDao.remove(photo); 86 | 87 | Photo removedPhoto = emUtil.performReturningWithinTx(entityManager -> entityManager.find(Photo.class, photo.getId())); 88 | assertThat(removedPhoto).isNull(); 89 | } 90 | 91 | @Test 92 | @Order(5) 93 | @DisplayName("Add a photo comment") 94 | void addPhotoComment() { 95 | Photo photo = createRandomPhoto(); 96 | emUtil.performWithinTx(entityManager -> entityManager.persist(photo)); 97 | 98 | photoDao.addComment(photo.getId(), "Nice picture!"); 99 | 100 | emUtil.performWithinTx(entityManager -> { 101 | Photo managedPhoto = entityManager.find(Photo.class, photo.getId()); 102 | assertThat(managedPhoto.getComments()).extracting("text").contains("Nice picture!"); 103 | }); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/3-2-2-photo-comment-dao/src/test/java/com/bobocode/util/PhotoTestDataGenerator.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.util; 2 | 3 | import com.bobocode.model.Photo; 4 | import com.bobocode.model.PhotoComment; 5 | import org.apache.commons.lang3.RandomStringUtils; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | import java.util.stream.Stream; 10 | 11 | import static java.util.stream.Collectors.toList; 12 | 13 | public class PhotoTestDataGenerator { 14 | public static Photo createRandomPhoto(){ 15 | Photo photo = new Photo(); 16 | photo.setUrl(RandomStringUtils.randomAlphabetic(30)); 17 | photo.setDescription(RandomStringUtils.randomAlphabetic(50)); 18 | return photo; 19 | } 20 | 21 | public static PhotoComment createRandomPhotoComment() { 22 | PhotoComment photoComment = new PhotoComment(); 23 | photoComment.setCreatedOn(LocalDateTime.now()); 24 | photoComment.setText(RandomStringUtils.randomAlphabetic(50)); 25 | return photoComment; 26 | } 27 | 28 | public static List createListOfRandomPhotos(int size) { 29 | return Stream.generate(PhotoTestDataGenerator::createRandomPhoto).limit(size).collect(toList()); 30 | } 31 | public static List createListOfRandomComments(int size) { 32 | return Stream.generate(PhotoTestDataGenerator::createRandomPhotoComment).limit(size).collect(toList()); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /3-0-jpa-and-hibernate/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 3-0-jpa-and-hibernate 13 | pom 14 | 15 | 16 | 3-0-0-hello-jpa-entity 17 | 3-0-1-hello-persistence-xml 18 | 3-0-2-query-helper 19 | 3-0-3-account-dao 20 | 3-1-1-employee-profile 21 | 3-1-2-company-products 22 | 3-1-3-author-book 23 | 3-2-2-photo-comment-dao 24 | 25 | 26 | 27 | 28 | org.postgresql 29 | postgresql 30 | 42.6.0 31 | 32 | 33 | com.h2database 34 | h2 35 | 2.2.224 36 | 37 | 38 | jakarta.persistence 39 | jakarta.persistence-api 40 | 3.1.0 41 | 42 | 43 | org.hibernate 44 | hibernate-core 45 | 6.3.1.Final 46 | 47 | 48 | 49 | 50 | javax.xml.bind 51 | jaxb-api 52 | 2.3.1 53 | 54 | 55 | 56 | com.bobocode 57 | persistence-util 58 | 1.0-SNAPSHOT 59 | compile 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /4-0-spring-data-jpa/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | 4-0-spring-data-jpa 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Java Persistence exercises 2 | Build strong skills that you will need for creating persistence layer in a real-world Enterprise Java development 💪 3 | 4 | ## Why 5 | Most people don’t know how to learn Enterprise Java efficiently. So we created an **open-source education system** 6 | that helps them to **master strong skills**, learn **world best practices** and build a **successful career**. 🚀 7 | 8 | At Bobocode we have extensive experience in both building Enterprise Java applications and organizing efficient learning. 9 | Therefore, this exercises covers what you need in the most efficient way. We believe that 10 | **the key to efficient learning is practice**. 💪 And as a software engineer, you should **spend as much time as you can in the IDE writing code**. 11 | At the end of the day, this is the only place where you build software... 💻 12 | 13 | -------------------------------------------------------------------------------- /java-persistence-util/jdbc-util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-util 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | jdbc-util 13 | jar 14 | 15 | -------------------------------------------------------------------------------- /java-persistence-util/jdbc-util/src/main/java/com/bobocode/util/FileReader.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.util; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.net.URL; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | import java.util.Objects; 10 | import java.util.stream.Stream; 11 | 12 | import static java.util.stream.Collectors.joining; 13 | 14 | /** 15 | * {@link FileReader} provides an API that allow to read whole file into a {@link String} by file name. 16 | */ 17 | public class FileReader { 18 | 19 | /** 20 | * Returns a {@link String} that contains whole text from the file specified by name. 21 | * 22 | * @param fileName a name of a text file 23 | * @return string that holds whole file content 24 | */ 25 | public static String readWholeFileFromResources(String fileName) { 26 | Path filePath = createPathFromFileName(fileName); 27 | try (Stream fileLinesStream = openFileLinesStream(filePath)) { 28 | return fileLinesStream.collect(joining("\n")); 29 | } 30 | } 31 | 32 | private static Stream openFileLinesStream(Path filePath) { 33 | try { 34 | return Files.lines(filePath); 35 | } catch (IOException e) { 36 | throw new FileReaderException("Cannot create stream of file lines!", e); 37 | } 38 | } 39 | 40 | private static Path createPathFromFileName(String fileName) { 41 | Objects.requireNonNull(fileName); 42 | URL fileUrl = FileReader.class.getClassLoader().getResource(fileName); 43 | try { 44 | return Paths.get(fileUrl.toURI()); 45 | } catch (URISyntaxException e) { 46 | throw new FileReaderException("Invalid file URL",e); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /java-persistence-util/jdbc-util/src/main/java/com/bobocode/util/FileReaderException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.util; 2 | 3 | public class FileReaderException extends RuntimeException { 4 | public FileReaderException(String message, Exception e) { 5 | super(message, e); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /java-persistence-util/jdbc-util/src/main/java/com/bobocode/util/JdbcUtil.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.util; 2 | 3 | import org.h2.jdbcx.JdbcDataSource; 4 | import org.postgresql.ds.PGSimpleDataSource; 5 | 6 | import javax.sql.DataSource; 7 | import java.util.Map; 8 | 9 | public class JdbcUtil { 10 | static String DEFAULT_DATABASE_NAME = "bobocode_db"; 11 | static String DEFAULT_USERNAME = "bobouser"; 12 | static String DEFAULT_PASSWORD = "bobodpass"; 13 | 14 | public static DataSource createDefaultInMemoryH2DataSource() { 15 | String url = formatH2ImMemoryDbUrl(DEFAULT_DATABASE_NAME); 16 | return createInMemoryH2DataSource(url, DEFAULT_USERNAME, DEFAULT_PASSWORD); 17 | } 18 | 19 | public static DataSource createInMemoryH2DataSource(String url, String username, String pass) { 20 | JdbcDataSource h2DataSource = new JdbcDataSource(); 21 | h2DataSource.setUser(username); 22 | h2DataSource.setPassword(pass); 23 | h2DataSource.setUrl(url); 24 | 25 | return h2DataSource; 26 | } 27 | 28 | private static String formatH2ImMemoryDbUrl(String databaseName) { 29 | return String.format("jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false;DATABASE_TO_UPPER=false;", databaseName); 30 | } 31 | 32 | public static DataSource createDefaultPostgresDataSource() { 33 | String url = formatPostgresDbUrl(DEFAULT_DATABASE_NAME); 34 | return createPostgresDataSource(url, DEFAULT_USERNAME, DEFAULT_PASSWORD); 35 | } 36 | 37 | public static DataSource createPostgresDataSource(String url, String username, String pass) { 38 | PGSimpleDataSource dataSource = new PGSimpleDataSource(); 39 | dataSource.setUrl(url); 40 | dataSource.setUser(username); 41 | dataSource.setPassword(pass); 42 | return dataSource; 43 | } 44 | 45 | private static String formatPostgresDbUrl(String databaseName) { 46 | return String.format("jdbc:postgresql://localhost:5432/%s", databaseName); 47 | } 48 | 49 | public static Map getInMemoryDbPropertiesMap() { 50 | return Map.of( 51 | "url", String.format("jdbc:h2:mem:%s", DEFAULT_DATABASE_NAME), 52 | "username", DEFAULT_USERNAME, 53 | "password", DEFAULT_PASSWORD); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /java-persistence-util/jpa-hibernate-model/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-util 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | jpa-hibernate-model 13 | 14 | 15 | -------------------------------------------------------------------------------- /java-persistence-util/jpa-hibernate-model/src/main/java/com/bobocode/model/Account.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | import lombok.*; 4 | 5 | import jakarta.persistence.*; 6 | import java.math.BigDecimal; 7 | import java.time.LocalDate; 8 | import java.time.LocalDateTime; 9 | 10 | @NoArgsConstructor 11 | @Getter 12 | @Setter 13 | @ToString 14 | @EqualsAndHashCode(of = "id") 15 | @Entity 16 | @Table(name = "account") 17 | public class Account { 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Long id; 21 | 22 | @Column(name = "first_name", nullable = false) 23 | private String firstName; 24 | 25 | @Column(name = "last_name", nullable = false) 26 | private String lastName; 27 | 28 | @Column(name = "email", nullable = false) 29 | private String email; 30 | 31 | @Column(name = "birthday", nullable = false) 32 | private LocalDate birthday; 33 | 34 | @Column(name = "gender", nullable = false) 35 | @Enumerated(EnumType.STRING) 36 | private Gender gender; 37 | 38 | @Column(name = "creation_time", nullable = false) 39 | private LocalDateTime creationTime; 40 | 41 | @Column(name = "balance") 42 | private BigDecimal balance = BigDecimal.ZERO.setScale(2); 43 | } 44 | -------------------------------------------------------------------------------- /java-persistence-util/jpa-hibernate-model/src/main/java/com/bobocode/model/Gender.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | public enum Gender { 4 | MALE, 5 | FEMALE 6 | } 7 | -------------------------------------------------------------------------------- /java-persistence-util/jpa-hibernate-util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | com.bobocode 9 | java-persistence-util 10 | 1.0-SNAPSHOT 11 | 12 | jpa-hibernate-util 13 | 14 | 15 | 16 | com.devskiller 17 | jfairy 18 | 0.6.5 19 | 20 | 21 | com.bobocode 22 | jpa-hibernate-model 23 | 1.0-SNAPSHOT 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /java-persistence-util/jpa-hibernate-util/src/main/java/com/bobocode/util/EntityManagerUtil.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.util; 2 | 3 | import jakarta.persistence.EntityManager; 4 | import jakarta.persistence.EntityManagerFactory; 5 | import java.util.function.Consumer; 6 | import java.util.function.Function; 7 | 8 | public class EntityManagerUtil { 9 | private EntityManagerFactory entityManagerFactory; 10 | 11 | public EntityManagerUtil(EntityManagerFactory entityManagerFactory) { 12 | this.entityManagerFactory = entityManagerFactory; 13 | } 14 | 15 | public void performWithinTx(Consumer entityManagerConsumer) { 16 | EntityManager entityManager = entityManagerFactory.createEntityManager(); 17 | entityManager.getTransaction().begin(); 18 | try { 19 | entityManagerConsumer.accept(entityManager); 20 | entityManager.getTransaction().commit(); 21 | } catch (Exception e) { 22 | entityManager.getTransaction().rollback(); 23 | throw e; 24 | } finally { 25 | entityManager.close(); 26 | } 27 | } 28 | 29 | public T performReturningWithinTx(Function entityManagerFunction) { 30 | EntityManager entityManager = entityManagerFactory.createEntityManager(); 31 | entityManager.getTransaction().begin(); 32 | try { 33 | T result = entityManagerFunction.apply(entityManager); 34 | entityManager.getTransaction().commit(); 35 | return result; 36 | } catch (Exception e) { 37 | entityManager.getTransaction().rollback(); 38 | throw e; 39 | } finally { 40 | entityManager.close(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java-persistence-util/jpa-hibernate-util/src/main/java/com/bobocode/util/TestDataGenerator.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.util; 2 | 3 | 4 | import com.bobocode.model.Account; 5 | import com.bobocode.model.Gender; 6 | import com.devskiller.jfairy.Fairy; 7 | import com.devskiller.jfairy.producer.person.Person; 8 | import java.math.BigDecimal; 9 | import java.time.LocalDate; 10 | import java.time.LocalDateTime; 11 | import java.util.List; 12 | import java.util.Random; 13 | import java.util.stream.Collectors; 14 | import java.util.stream.Stream; 15 | 16 | public class TestDataGenerator { 17 | 18 | public static List generateAccountList(int size) { 19 | return Stream.generate(TestDataGenerator::generateAccount) 20 | .limit(size) 21 | .collect(Collectors.toList()); 22 | } 23 | 24 | public static Account generateAccount() { 25 | Fairy fairy = Fairy.create(); 26 | Person person = fairy.person(); 27 | Random random = new Random(); 28 | 29 | Account fakeAccount = new Account(); 30 | fakeAccount.setFirstName(person.getFirstName()); 31 | fakeAccount.setLastName(person.getLastName()); 32 | fakeAccount.setEmail(person.getEmail()); 33 | fakeAccount.setBirthday(LocalDate.of( 34 | person.getDateOfBirth().getYear(), 35 | person.getDateOfBirth().getMonth(), 36 | person.getDateOfBirth().getDayOfMonth())); 37 | fakeAccount.setGender(Gender.valueOf(person.getSex().name())); 38 | BigDecimal balance = BigDecimal.valueOf(random.nextInt(200_000), 2); 39 | fakeAccount.setBalance(balance); 40 | fakeAccount.setCreationTime(LocalDateTime.now()); 41 | 42 | return fakeAccount; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /java-persistence-util/persistence-util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-util 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | persistence-util 13 | 14 | -------------------------------------------------------------------------------- /java-persistence-util/persistence-util/src/main/java/com/bobocode/util/ExerciseNotCompletedException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.util; 2 | 3 | /** 4 | * This is a custom exception that we throw in every method which should be implemented as a part of the exercise. 5 | * If you see that it was thrown it means that you did not implement all required methods yet. 6 | */ 7 | public class ExerciseNotCompletedException extends RuntimeException { 8 | public ExerciseNotCompletedException() { 9 | super("Implement this method and remove exception OR switch to branch completed if you got stuck."); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /java-persistence-util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | java-persistence-util 13 | 1.0-SNAPSHOT 14 | pom 15 | 16 | 17 | jdbc-util 18 | persistence-util 19 | jpa-hibernate-util 20 | jpa-hibernate-model 21 | 22 | 23 | 24 | 25 | org.postgresql 26 | postgresql 27 | 42.6.0 28 | 29 | 30 | com.h2database 31 | h2 32 | 2.1.214 33 | 34 | 35 | jakarta.persistence 36 | jakarta.persistence-api 37 | 3.1.0 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /lesson-demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | java-persistence-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | lesson-demo 13 | 14 | -------------------------------------------------------------------------------- /lesson-demo/src/main/java/com/bobocode/DemoApp.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | public class DemoApp { 4 | public static void main(String[] args) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.bobocode 8 | java-persistence-exercises 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | 21 14 | 21 15 | UTF-8 16 | 17 | 18 | 19 | 1-0-rdbms-and-sql 20 | 2-0-jdbc-api 21 | 3-0-jpa-and-hibernate 22 | 4-0-spring-data-jpa 23 | java-persistence-util 24 | lesson-demo 25 | 26 | 27 | 28 | 29 | org.junit.jupiter 30 | junit-jupiter-engine 31 | 5.10.0 32 | test 33 | 34 | 35 | org.junit.jupiter 36 | junit-jupiter-params 37 | 5.10.0 38 | test 39 | 40 | 41 | org.assertj 42 | assertj-core 43 | 3.24.2 44 | test 45 | 46 | 47 | org.hamcrest 48 | hamcrest-library 49 | 2.2 50 | test 51 | 52 | 53 | org.projectlombok 54 | lombok 55 | 1.18.30 56 | 57 | 58 | com.google.code.findbugs 59 | jsr305 60 | 3.0.2 61 | 62 | 63 | org.mockito 64 | mockito-core 65 | 5.6.0 66 | test 67 | 68 | 69 | net.bytebuddy 70 | byte-buddy 71 | 72 | 73 | net.bytebuddy 74 | byte-buddy-agent 75 | 76 | 77 | 78 | 79 | net.bytebuddy 80 | byte-buddy 81 | 1.14.9 82 | 83 | 84 | net.bytebuddy 85 | byte-buddy-agent 86 | 1.14.9 87 | 88 | 89 | org.mockito 90 | mockito-inline 91 | 5.2.0 92 | test 93 | 94 | 95 | org.slf4j 96 | slf4j-simple 97 | 2.0.9 98 | 99 | 100 | org.apache.commons 101 | commons-lang3 102 | 3.12.0 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-surefire-plugin 112 | 3.1.2 113 | 114 | 115 | 116 | 117 | 118 | --------------------------------------------------------------------------------