├── .gitignore ├── README.md ├── account-db-initializer ├── README.MD ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── bobocode │ │ └── AccountDbInitializer.java │ └── test │ └── java │ └── com │ └── bobocode │ └── AccountDbInitializerTest.java ├── jdbc-account-data ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── bobocode │ ├── data │ └── Accounts.java │ └── model │ ├── Account.java │ └── Gender.java ├── jdbc-util ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── bobocode │ └── util │ ├── FileReader.java │ ├── FileReaderException.java │ └── JdbcUtil.java ├── pom.xml ├── 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 │ └── ProductDaoTest.java ├── user-profile-db-initializer ├── README.MD ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── bobocode │ │ │ └── UserProfileDbInitializer.java │ └── resources │ │ └── db │ │ └── migration │ │ └── table_initialization.sql │ └── test │ └── java │ └── com │ └── bobocode │ └── UserProfileDbInitializerTest.java └── wall-street-db-initializer ├── README.MD ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── bobocode │ │ └── WallStreetDbInitializer.java └── resources │ └── db │ └── migration │ └── table_initialization.sql └── test └── java └── com └── bobocode └── WallStreetDbInitializerTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/*.iml 3 | **/target 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JDBC API and SQL exercises 2 | The list of exercises dedicated to training your JDBC API and SQL skills 3 | 4 | ### No pain, No gain :heavy_exclamation_mark: 5 | 6 | > Skill is only developed by hours and hours and hours of beating on your craft 7 | 8 | Working on real problems, you're focused on finding a solution. Learning new things, you're trying to understand how it works. 9 | It is important to have a different type of activities, which purpose is improving your skill 10 | 11 | ***An exercise** is a predefined task that you continuously implement to improve a certain skill* :muscle: 12 | ## 13 | * [Product DAO](https://github.com/bobocode-projects/jdbc-api-exercises/tree/master/product-dao) 14 | * [Account db initializer](https://github.com/bobocode-projects/jdbc-api-exercises/tree/master/account-db-initializer) 15 | * [Wall Street db initializer](https://github.com/bobocode-projects/jdbc-api-exercises/tree/master/wall-street-db-initializer) 16 | * [User profiles db initializer](https://github.com/bobocode-projects/jdbc-api-exercises/tree/master/user-profile-db-initializer) 17 | 18 | -------------------------------------------------------------------------------- /account-db-initializer/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 | -------------------------------------------------------------------------------- /account-db-initializer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | account-db-initializer 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /account-db-initializer/src/main/java/com/bobocode/AccountDbInitializer.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import javax.sql.DataSource; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * {@link AccountDbInitializer} provides an API that allow to initialize (create) an Account table in the database 8 | */ 9 | public class AccountDbInitializer { 10 | private DataSource dataSource; 11 | 12 | public AccountDbInitializer(DataSource dataSource) { 13 | this.dataSource = dataSource; 14 | } 15 | 16 | /** 17 | * Creates an {@code account} table. That table has a identifier column {@code id} with type {@code bigint}. 18 | * It also contains an {@code email} column that is mandatory and should have unique value. This column is be able 19 | * to store any valid email. The table also has columns {@code first_name}, {@code last_name}, and {@code gender} 20 | * that are typical string columns with 255 characters, and are mandatory. Account {@code birthday} is stored 21 | * in the {@code DATE} mandatory column. The value of account balance is not mandatory, and is stored 22 | * in the {@code balance} column that is a {@code DECIMAL} number with {@code precision = 19} , 23 | * and {@code scale = 4}. A column {@code creation_time} stores a {@code TIMESTAMP}, is mandatory, and has a default 24 | * value that is set to the current timestamp using database function {@code now()}. Table primary key 25 | * is an {@code id}, and corresponding constraint is named {@code "account_pk"}. An unique constraint that 26 | * is created for {@code email column} is called "account_email_uq" 27 | * 28 | * @throws SQLException 29 | */ 30 | public void init() throws SQLException { 31 | throw new UnsupportedOperationException("It's your job to make it work!"); // todo 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /account-db-initializer/src/test/java/com/bobocode/AccountDbInitializerTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.util.JdbcUtil; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import javax.sql.DataSource; 8 | import java.sql.Connection; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | import java.sql.Statement; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | 18 | 19 | public class AccountDbInitializerTest { 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 | void testTableHasCorrectName() throws SQLException { 31 | try (Connection connection = dataSource.getConnection()) { 32 | Statement statement = connection.createStatement(); 33 | 34 | ResultSet resultSet = statement.executeQuery("SHOW TABLES"); 35 | resultSet.next(); 36 | String tableName = resultSet.getString("table_name"); 37 | 38 | assertEquals("account", tableName); 39 | } 40 | } 41 | 42 | @Test 43 | void testTableHasPrimaryKey() throws SQLException { 44 | try (Connection connection = dataSource.getConnection()) { 45 | Statement statement = connection.createStatement(); 46 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 47 | " WHERE table_name = 'account' AND constraint_type = 'PRIMARY_KEY';"); 48 | 49 | boolean resultIsNotEmpty = resultSet.next(); 50 | 51 | assertTrue(resultIsNotEmpty); 52 | } 53 | } 54 | 55 | @Test 56 | void testPrimaryKeyHasCorrectName() throws SQLException { 57 | try (Connection connection = dataSource.getConnection()) { 58 | Statement statement = connection.createStatement(); 59 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 60 | " WHERE table_name = 'account' AND constraint_type = 'PRIMARY_KEY';"); 61 | 62 | resultSet.next(); 63 | String pkConstraintName = resultSet.getString("constraint_name"); 64 | 65 | assertEquals("account_pk", pkConstraintName); 66 | } 67 | } 68 | 69 | @Test 70 | void testPrimaryKeyBasedOnIdField() throws SQLException { 71 | try (Connection connection = dataSource.getConnection()) { 72 | Statement statement = connection.createStatement(); 73 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 74 | " WHERE table_name = 'account' AND constraint_type = 'PRIMARY_KEY';"); 75 | 76 | resultSet.next(); 77 | String pkColumn = resultSet.getString("column_list"); 78 | 79 | assertEquals("id", pkColumn); 80 | } 81 | } 82 | 83 | @Test 84 | void testTableHasCorrectAlternativeKey() throws SQLException { 85 | try (Connection connection = dataSource.getConnection()) { 86 | Statement statement = connection.createStatement(); 87 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 88 | " WHERE table_name = 'account' AND constraint_type = 'UNIQUE';"); 89 | 90 | resultSet.next(); 91 | String uniqueConstraintName = resultSet.getString("constraint_name"); 92 | String uniqueConstraintColumn = resultSet.getString("column_list"); 93 | 94 | assertEquals("account_email_uq", uniqueConstraintName); 95 | assertEquals("email", uniqueConstraintColumn); 96 | } 97 | } 98 | 99 | @Test 100 | void testTableHasAllRequiredColumns() throws SQLException { 101 | try (Connection connection = dataSource.getConnection()) { 102 | Statement statement = connection.createStatement(); 103 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 104 | " WHERE table_name = 'account';"); 105 | 106 | List columns = fetchColumnsNames(resultSet); 107 | 108 | assertEquals(8, columns.size()); 109 | assertTrue(columns.containsAll(List.of("id", "first_name", "last_name", "email", "gender", "balance", "birthday", "creation_time"))); 110 | } 111 | } 112 | 113 | private List fetchColumnsNames(ResultSet resultSet) throws SQLException { 114 | List columns = new ArrayList<>(); 115 | while (resultSet.next()) { 116 | String columnName = resultSet.getString("column_name"); 117 | columns.add(columnName); 118 | } 119 | return columns; 120 | } 121 | 122 | 123 | @Test 124 | void testRequiredColumnsHaveHaveNotNullConstraint() throws SQLException { 125 | try (Connection connection = dataSource.getConnection()) { 126 | Statement statement = connection.createStatement(); 127 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 128 | " WHERE table_name = 'account' AND nullable = false;"); 129 | 130 | List notNullColumns = fetchColumnsNames(resultSet); 131 | 132 | assertEquals(7, notNullColumns.size()); 133 | assertTrue(notNullColumns.containsAll(List.of("id", "first_name", "last_name", "email", "gender", "birthday", "creation_time"))); 134 | } 135 | } 136 | 137 | @Test 138 | void testIdHasTypeBiInteger() throws SQLException { 139 | try (Connection connection = dataSource.getConnection()) { 140 | Statement statement = connection.createStatement(); 141 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 142 | " WHERE table_name = 'account' AND column_name = 'id';"); 143 | 144 | resultSet.next(); 145 | String idTypeName = resultSet.getString("type_name"); 146 | 147 | assertEquals("BIGINT", idTypeName); 148 | } 149 | } 150 | 151 | @Test 152 | void testCreationTimeHasTypeTimestamp() throws SQLException { 153 | try (Connection connection = dataSource.getConnection()) { 154 | Statement statement = connection.createStatement(); 155 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 156 | " WHERE table_name = 'account' AND column_name = 'creation_time';"); 157 | 158 | resultSet.next(); 159 | String creationTimeColumnType = resultSet.getString("type_name"); 160 | 161 | assertEquals("TIMESTAMP", creationTimeColumnType); 162 | } 163 | } 164 | 165 | @Test 166 | void testCreationTimeHasDefaultValue() throws SQLException { 167 | try (Connection connection = dataSource.getConnection()) { 168 | Statement statement = connection.createStatement(); 169 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 170 | " WHERE table_name = 'account' AND column_name = 'creation_time';"); 171 | 172 | resultSet.next(); 173 | String creationTimeColumnDefault = resultSet.getString("column_default"); 174 | 175 | assertEquals("NOW()", creationTimeColumnDefault); 176 | } 177 | } 178 | 179 | @Test 180 | void testEmailColumnHasCorrectSize() throws SQLException { 181 | try (Connection connection = dataSource.getConnection()) { 182 | Statement statement = connection.createStatement(); 183 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 184 | " WHERE table_name = 'account' AND column_name = 'email';"); 185 | 186 | resultSet.next(); 187 | String emailColumnType = resultSet.getString("type_name"); 188 | int emailColumnMaxLength = resultSet.getInt("character_maximum_length"); 189 | 190 | assertEquals("VARCHAR", emailColumnType); 191 | assertEquals(255, emailColumnMaxLength); 192 | } 193 | } 194 | 195 | @Test 196 | void testBirthdayColumnHasCorrectType() throws SQLException { 197 | try (Connection connection = dataSource.getConnection()) { 198 | Statement statement = connection.createStatement(); 199 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 200 | " WHERE table_name = 'account' AND column_name = 'birthday';"); 201 | 202 | resultSet.next(); 203 | String birthdayColumnType = resultSet.getString("type_name"); 204 | 205 | assertEquals("DATE", birthdayColumnType); 206 | } 207 | } 208 | 209 | @Test 210 | void testBalanceColumnHasCorrectType() throws SQLException { 211 | try (Connection connection = dataSource.getConnection()) { 212 | Statement statement = connection.createStatement(); 213 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 214 | " WHERE table_name = 'account' AND column_name = 'balance';"); 215 | 216 | resultSet.next(); 217 | String balanceColumnType = resultSet.getString("type_name"); 218 | int balanceColumnPrecision = resultSet.getInt("numeric_precision"); 219 | int balanceColumnScale = resultSet.getInt("numeric_scale"); 220 | 221 | assertEquals("DECIMAL", balanceColumnType); 222 | assertEquals(19, balanceColumnPrecision); 223 | assertEquals(4, balanceColumnScale); 224 | } 225 | } 226 | 227 | @Test 228 | void testBalanceIsNotMandatory() throws SQLException { 229 | try (Connection connection = dataSource.getConnection()) { 230 | Statement statement = connection.createStatement(); 231 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 232 | " WHERE table_name = 'account' AND column_name = 'balance';"); 233 | 234 | resultSet.next(); 235 | boolean balanceColumnIsNullable = resultSet.getBoolean("nullable"); 236 | 237 | assertTrue(balanceColumnIsNullable); 238 | } 239 | } 240 | 241 | @Test 242 | void testStringColumnsHaveCorrectTypeAndLength() throws SQLException { 243 | try (Connection connection = dataSource.getConnection()) { 244 | Statement statement = connection.createStatement(); 245 | ResultSet resultSet = statement.executeQuery("SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS" + 246 | " WHERE table_name = 'account' AND type_name = 'VARCHAR' AND character_maximum_length = 255;"); 247 | 248 | List stringColumns = fetchColumnsNames(resultSet); 249 | 250 | assertEquals(4, stringColumns.size()); 251 | assertTrue(stringColumns.containsAll(List.of("first_name", "last_name", "email", "gender"))); 252 | } 253 | } 254 | 255 | 256 | } 257 | -------------------------------------------------------------------------------- /jdbc-account-data/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | jdbc-account-data 13 | 14 | 15 | 16 | io.codearte.jfairy 17 | jfairy 18 | 0.5.7 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /jdbc-account-data/src/main/java/com/bobocode/data/Accounts.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.data; 2 | 3 | import com.bobocode.model.Account; 4 | import com.bobocode.model.Gender; 5 | import io.codearte.jfairy.Fairy; 6 | import io.codearte.jfairy.producer.person.Person; 7 | 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 | 14 | import static java.util.stream.Collectors.toList; 15 | import static java.util.stream.IntStream.range; 16 | 17 | public interface Accounts { 18 | static Account generateAccount(){ 19 | Fairy fairy = Fairy.create(); 20 | Person person = fairy.person(); 21 | Random random = new Random(); 22 | 23 | 24 | Account fakeAccount = new Account(); 25 | fakeAccount.setFirstName(person.getFirstName()); 26 | fakeAccount.setLastName(person.getLastName()); 27 | fakeAccount.setEmail(person.getEmail()); 28 | fakeAccount.setBirthday(LocalDate.of( 29 | person.getDateOfBirth().getYear(), 30 | person.getDateOfBirth().getMonthOfYear(), 31 | person.getDateOfBirth().getDayOfMonth())); 32 | fakeAccount.setGender(Gender.valueOf(person.getSex().name())); 33 | fakeAccount.setBalance(BigDecimal.valueOf(random.nextInt(200_000)).setScale(2)); 34 | fakeAccount.setCreationTime(LocalDateTime.now()); 35 | 36 | return fakeAccount; 37 | } 38 | 39 | static List generateAccountList(int size){ 40 | return range(0, size) 41 | .mapToObj(i -> generateAccount()) 42 | .collect(toList()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /jdbc-account-data/src/main/java/com/bobocode/model/Account.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 | 9 | @NoArgsConstructor 10 | @Getter 11 | @Setter 12 | @ToString 13 | @EqualsAndHashCode(of = "id") 14 | public class Account { 15 | private Long id; 16 | private String firstName; 17 | private String lastName; 18 | private String email; 19 | private LocalDate birthday; 20 | private Gender gender; 21 | private LocalDateTime creationTime; 22 | private BigDecimal balance = BigDecimal.ZERO; 23 | } 24 | -------------------------------------------------------------------------------- /jdbc-account-data/src/main/java/com/bobocode/model/Gender.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.model; 2 | 3 | public enum Gender { 4 | MALE, 5 | FEMALE 6 | } 7 | -------------------------------------------------------------------------------- /jdbc-util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | jdbc-util 13 | 14 | 15 | 16 | org.slf4j 17 | slf4j-simple 18 | 1.7.12 19 | 20 | 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.bobocode 8 | jdbc-api-exercises 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | jdbc-util 14 | jdbc-account-data 15 | product-dao 16 | account-db-initializer 17 | wall-street-db-initializer 18 | user-profile-db-initializer 19 | 20 | 21 | 22 | 11 23 | 11 24 | 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | 1.18.0 31 | 32 | 33 | org.postgresql 34 | postgresql 35 | 9.4-1202-jdbc4 36 | 37 | 38 | com.h2database 39 | h2 40 | 1.4.197 41 | 42 | 43 | org.slf4j 44 | slf4j-simple 45 | 1.7.24 46 | 47 | 48 | org.junit.jupiter 49 | junit-jupiter-engine 50 | 5.5.1 51 | test 52 | 53 | 54 | org.hamcrest 55 | hamcrest-all 56 | 1.3 57 | test 58 | 59 | 60 | org.apache.commons 61 | commons-lang3 62 | 3.8 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-surefire-plugin 73 | 3.0.0-M3 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /product-dao/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | product-dao 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /product-dao/src/main/java/com/bobocode/dao/ProductDao.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Product; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * {@link ProductDao} is an Data Access Object pattern (DAO) that encapsulates all database access and manipulation logic. 9 | * It provides a convenient API that allow to store, access, update and remove data working with object OO style. 10 | */ 11 | public interface ProductDao { 12 | /** 13 | * Stores a new product into the database. Sets generated id to the {@link Product} instance 14 | * 15 | * @param product new product 16 | */ 17 | void save(Product product); 18 | 19 | /** 20 | * Retrieves and returns all producrs from the database 21 | * 22 | * @return list of all products 23 | */ 24 | List findAll(); 25 | 26 | /** 27 | * Returns a product object by its id 28 | * 29 | * @param id product identifier (primary key) 30 | * @return one product by its id 31 | */ 32 | Product findOne(Long id); 33 | 34 | /** 35 | * Updates existing product. 36 | * 37 | * @param product stored product with updated fields 38 | */ 39 | void update(Product product); 40 | 41 | /** 42 | * Removes an existing product from the database 43 | * 44 | * @param product stored product 45 | */ 46 | void remove(Product product); 47 | } 48 | -------------------------------------------------------------------------------- /product-dao/src/main/java/com/bobocode/dao/ProductDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Product; 4 | 5 | import javax.sql.DataSource; 6 | import java.util.List; 7 | 8 | public class ProductDaoImpl implements ProductDao { 9 | private DataSource dataSource; 10 | 11 | public ProductDaoImpl(DataSource dataSource) { 12 | this.dataSource = dataSource; 13 | } 14 | 15 | @Override 16 | public void save(Product product) { 17 | throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo 18 | } 19 | 20 | @Override 21 | public List findAll() { 22 | throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo 23 | } 24 | 25 | @Override 26 | public Product findOne(Long id) { 27 | throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo 28 | } 29 | 30 | @Override 31 | public void update(Product product) { 32 | throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo 33 | } 34 | 35 | @Override 36 | public void remove(Product product) { 37 | throw new UnsupportedOperationException("None of these methods will work unless you implement them!");// todo 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 9 | @Getter 10 | @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @EqualsAndHashCode(of = "id") 14 | @ToString 15 | @Builder 16 | public class Product { 17 | private Long id; 18 | private String name; 19 | private String producer; 20 | private BigDecimal price; 21 | private LocalDate expirationDate; 22 | private LocalDateTime creationTime; 23 | } 24 | -------------------------------------------------------------------------------- /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 org.apache.commons.lang3.RandomStringUtils; 9 | import org.apache.commons.lang3.RandomUtils; 10 | import org.junit.jupiter.api.BeforeAll; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import javax.sql.DataSource; 14 | import java.math.BigDecimal; 15 | import java.sql.Connection; 16 | import java.sql.SQLException; 17 | import java.sql.Statement; 18 | import java.time.LocalDate; 19 | import java.time.Month; 20 | import java.util.List; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertEquals; 23 | import static org.junit.jupiter.api.Assertions.assertFalse; 24 | import static org.junit.jupiter.api.Assertions.assertNotNull; 25 | import static org.junit.jupiter.api.Assertions.assertTrue; 26 | import static org.junit.jupiter.api.Assertions.fail; 27 | 28 | public class ProductDaoTest { 29 | private static ProductDao productDao; 30 | 31 | @BeforeAll 32 | static void init() throws SQLException { 33 | DataSource h2DataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 34 | createAccountTable(h2DataSource); 35 | productDao = new ProductDaoImpl(h2DataSource); 36 | } 37 | 38 | private static void createAccountTable(DataSource dataSource) throws SQLException { 39 | try (Connection connection = dataSource.getConnection()) { 40 | Statement createTableStatement = connection.createStatement(); 41 | createTableStatement.execute("CREATE TABLE IF NOT EXISTS products (\n" + 42 | " id SERIAL NOT NULL,\n" + 43 | " name VARCHAR(255) NOT NULL,\n" + 44 | " producer VARCHAR(255) NOT NULL,\n" + 45 | " price DECIMAL(19, 4),\n" + 46 | " expiration_date TIMESTAMP NOT NULL,\n" + 47 | " creation_time TIMESTAMP NOT NULL DEFAULT now(),\n" + 48 | "\n" + 49 | " CONSTRAINT products_pk PRIMARY KEY (id)\n" + 50 | ");\n" + 51 | "\n"); 52 | } 53 | } 54 | 55 | private Product generateTestProduct() { 56 | return Product.builder() 57 | .name(RandomStringUtils.randomAlphabetic(10)) 58 | .producer(RandomStringUtils.randomAlphabetic(20)) 59 | .price(BigDecimal.valueOf(RandomUtils.nextInt(10, 100))) 60 | .expirationDate(LocalDate.ofYearDay(LocalDate.now().getYear() + RandomUtils.nextInt(1, 5), 61 | RandomUtils.nextInt(1, 365))) 62 | .build(); 63 | } 64 | 65 | @Test 66 | void testSave() { 67 | Product fanta = createTestFantaProduct(); 68 | 69 | int productsCountBeforeInsert = productDao.findAll().size(); 70 | productDao.save(fanta); 71 | List products = productDao.findAll(); 72 | 73 | assertNotNull(fanta.getId()); 74 | assertEquals(productsCountBeforeInsert + 1, products.size()); 75 | assertTrue(products.contains(fanta)); 76 | } 77 | 78 | @Test 79 | void testSaveInvalidProduct() { 80 | Product invalidTestProduct = createInvalidTestProduct(); 81 | 82 | try { 83 | productDao.save(invalidTestProduct); 84 | fail("Exception was't thrown"); 85 | } catch (Exception e) { 86 | assertEquals(DaoOperationException.class, e.getClass()); 87 | assertEquals(String.format("Error saving product: %s", invalidTestProduct), e.getMessage()); 88 | } 89 | } 90 | 91 | private Product createTestFantaProduct() { 92 | return Product.builder() 93 | .name("Fanta") 94 | .producer("The Coca-Cola Company") 95 | .price(BigDecimal.valueOf(22)) 96 | .expirationDate(LocalDate.of(2020, Month.APRIL, 14)).build(); 97 | } 98 | 99 | private Product createInvalidTestProduct() { 100 | return Product.builder() 101 | .name("INVALID") 102 | .price(BigDecimal.valueOf(22)) 103 | .expirationDate(LocalDate.of(2020, Month.APRIL, 14)).build(); 104 | } 105 | 106 | 107 | @Test 108 | void testFindAll() { 109 | List newProducts = createTestProductList(); 110 | List oldProducts = productDao.findAll(); 111 | newProducts.forEach(productDao::save); 112 | 113 | List products = productDao.findAll(); 114 | 115 | assertTrue(products.containsAll(newProducts)); 116 | assertTrue(products.containsAll(oldProducts)); 117 | assertEquals(oldProducts.size() + newProducts.size(), products.size()); 118 | 119 | } 120 | 121 | private List createTestProductList() { 122 | return List.of( 123 | Product.builder() 124 | .name("Sprite") 125 | .producer("The Coca-Cola Company") 126 | .price(BigDecimal.valueOf(18)) 127 | .expirationDate(LocalDate.of(2020, Month.MARCH, 24)).build(), 128 | Product.builder() 129 | .name("Cola light") 130 | .producer("The Coca-Cola Company") 131 | .price(BigDecimal.valueOf(21)) 132 | .expirationDate(LocalDate.of(2020, Month.JANUARY, 11)).build(), 133 | Product.builder() 134 | .name("Snickers") 135 | .producer("Mars Inc.") 136 | .price(BigDecimal.valueOf(16)) 137 | .expirationDate(LocalDate.of(2019, Month.DECEMBER, 3)).build() 138 | ); 139 | } 140 | 141 | @Test 142 | void testFindById() { 143 | Product testProduct = generateTestProduct(); 144 | productDao.save(testProduct); 145 | 146 | Product product = productDao.findOne(testProduct.getId()); 147 | 148 | assertEquals(testProduct, product); 149 | assertEquals(testProduct.getName(), product.getName()); 150 | assertEquals(testProduct.getProducer(), product.getProducer()); 151 | assertEquals(testProduct.getPrice().setScale(2), product.getPrice().setScale(2)); 152 | assertEquals(testProduct.getExpirationDate(), product.getExpirationDate()); 153 | } 154 | 155 | @Test 156 | void testFindByNotExistingId() { 157 | long invalidId = -1L; 158 | try { 159 | productDao.findOne(invalidId); 160 | fail("Exception was't thrown"); 161 | } catch (Exception e) { 162 | assertEquals(DaoOperationException.class, e.getClass()); 163 | assertEquals(String.format("Product with id = %d does not exist", invalidId), e.getMessage()); 164 | } 165 | } 166 | 167 | @Test 168 | void testUpdate() { 169 | Product testProduct = generateTestProduct(); 170 | productDao.save(testProduct); 171 | List productsBeforeUpdate = productDao.findAll(); 172 | 173 | testProduct.setName("Updated name"); 174 | testProduct.setProducer("Updated producer"); 175 | testProduct.setPrice(BigDecimal.valueOf(666)); 176 | testProduct.setExpirationDate(LocalDate.of(2020, Month.JANUARY, 1)); 177 | productDao.update(testProduct); 178 | List products = productDao.findAll(); 179 | Product updatedProduct = productDao.findOne(testProduct.getId()); 180 | 181 | assertEquals(productsBeforeUpdate.size(), products.size()); 182 | assertTrue(completelyEquals(testProduct, updatedProduct)); 183 | productsBeforeUpdate.remove(testProduct); 184 | products.remove(testProduct); 185 | assertTrue(deepEquals(productsBeforeUpdate, products)); 186 | } 187 | 188 | private boolean completelyEquals(Product productBeforeUpdate, Product productAfterUpdate) { 189 | return productBeforeUpdate.getName().equals(productAfterUpdate.getName()) 190 | && productBeforeUpdate.getProducer().equals(productAfterUpdate.getProducer()) 191 | && productBeforeUpdate.getPrice().setScale(2).equals(productAfterUpdate.getPrice().setScale(2)) 192 | && productBeforeUpdate.getExpirationDate().equals(productAfterUpdate.getExpirationDate()); 193 | } 194 | 195 | private boolean deepEquals(List productsBeforeUpdate, List productsAfterUpdate) { 196 | return productsAfterUpdate.stream() 197 | .allMatch(product -> remainedTheSame(product, productsBeforeUpdate)); 198 | } 199 | 200 | private boolean remainedTheSame(Product productAfterUpdate, List productsBeforeUpdate) { 201 | Product productBeforeUpdate = findById(productsBeforeUpdate, productAfterUpdate.getId()); 202 | return completelyEquals(productAfterUpdate, productBeforeUpdate); 203 | } 204 | 205 | private Product findById(List products, Long id) { 206 | return products.stream().filter(p -> p.getId().equals(id)).findFirst().get(); 207 | } 208 | 209 | @Test 210 | void testUpdateNotStored() { 211 | Product notStoredProduct = generateTestProduct(); 212 | 213 | try { 214 | productDao.update(notStoredProduct); 215 | fail("Exception was't thrown"); 216 | } catch (Exception e) { 217 | assertEquals(DaoOperationException.class, e.getClass()); 218 | assertEquals("Cannot find a product without ID", e.getMessage()); 219 | } 220 | } 221 | 222 | @Test 223 | void testUpdateProductWithInvalidId() { 224 | Product testProduct = generateTestProduct(); 225 | long invalidId = -1L; 226 | testProduct.setId(invalidId); 227 | 228 | try { 229 | productDao.update(testProduct); 230 | fail("Exception was't thrown"); 231 | } catch (Exception e) { 232 | assertEquals(DaoOperationException.class, e.getClass()); 233 | assertEquals(String.format("Product with id = %d does not exist", invalidId), e.getMessage()); 234 | } 235 | } 236 | 237 | @Test 238 | void testRemove() { 239 | Product testProduct = generateTestProduct(); 240 | productDao.save(testProduct); 241 | List productsBeforeRemove = productDao.findAll(); 242 | 243 | productDao.remove(testProduct); 244 | List products = productDao.findAll(); 245 | 246 | assertEquals(productsBeforeRemove.size() - 1, products.size()); 247 | assertFalse(products.contains(testProduct)); 248 | } 249 | 250 | @Test 251 | void testRemoveNotStored() { 252 | Product notStoredProduct = generateTestProduct(); 253 | 254 | try { 255 | productDao.remove(notStoredProduct); 256 | fail("Exception was't thrown"); 257 | } catch (Exception e) { 258 | assertEquals(DaoOperationException.class, e.getClass()); 259 | assertEquals("Cannot find a product without ID", e.getMessage()); 260 | } 261 | } 262 | 263 | @Test 264 | void testRemoveProductWithInvalidId() { 265 | Product testProduct = generateTestProduct(); 266 | long invalidId = -1L; 267 | testProduct.setId(invalidId); 268 | 269 | try { 270 | productDao.remove(testProduct); 271 | fail("Exception was't thrown"); 272 | } catch (Exception e) { 273 | assertEquals(DaoOperationException.class, e.getClass()); 274 | assertEquals(String.format("Product with id = %d does not exist", invalidId), e.getMessage()); 275 | } 276 | } 277 | } -------------------------------------------------------------------------------- /user-profile-db-initializer/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 | -------------------------------------------------------------------------------- /user-profile-db-initializer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | user-profile-db-initializer 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /user-profile-db-initializer/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 allow 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 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /user-profile-db-initializer/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 -------------------------------------------------------------------------------- /user-profile-db-initializer/src/test/java/com/bobocode/UserProfileDbInitializerTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.util.JdbcUtil; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import javax.sql.DataSource; 8 | import java.sql.Connection; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | import java.sql.Statement; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.hamcrest.Matchers.containsInAnyOrder; 17 | import static org.hamcrest.Matchers.equalTo; 18 | import static org.hamcrest.Matchers.is; 19 | 20 | 21 | public class UserProfileDbInitializerTest { 22 | private static DataSource dataSource; 23 | 24 | @BeforeAll 25 | static void init() throws SQLException { 26 | dataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 27 | UserProfileDbInitializer dbInitializer = new UserProfileDbInitializer(dataSource); 28 | dbInitializer.init(); 29 | } 30 | 31 | @Test 32 | void testTablesHaveCorrectNames() throws SQLException { 33 | try (Connection connection = dataSource.getConnection()) { 34 | Statement statement = connection.createStatement(); 35 | 36 | ResultSet resultSet = statement.executeQuery("SHOW TABLES"); 37 | List tableNames = fetchTableNames(resultSet); 38 | 39 | assertThat(tableNames, containsInAnyOrder("users", "profiles")); 40 | } 41 | } 42 | 43 | private List fetchTableNames(ResultSet resultSet) throws SQLException { 44 | List tableNamesList = new ArrayList<>(); 45 | while (resultSet.next()) { 46 | String tableName = resultSet.getString("table_name"); 47 | tableNamesList.add(tableName); 48 | } 49 | return tableNamesList; 50 | } 51 | 52 | 53 | @Test 54 | void testUsersTablesHasPrimaryKey() throws SQLException { 55 | try (Connection connection = dataSource.getConnection()) { 56 | Statement statement = connection.createStatement(); 57 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 58 | " WHERE table_name = 'users' AND constraint_type = 'PRIMARY_KEY';"); 59 | 60 | boolean resultIsNotEmpty = resultSet.next(); 61 | 62 | assertThat(resultIsNotEmpty, is(true)); 63 | } 64 | } 65 | 66 | @Test 67 | void testUsersTablePrimaryKeyHasCorrectName() throws SQLException { 68 | try (Connection connection = dataSource.getConnection()) { 69 | Statement statement = connection.createStatement(); 70 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 71 | " WHERE table_name = 'users' AND constraint_type = 'PRIMARY_KEY';"); 72 | 73 | resultSet.next(); 74 | String pkConstraintName = resultSet.getString("constraint_name"); 75 | 76 | assertThat(pkConstraintName, equalTo("users_PK")); 77 | } 78 | } 79 | 80 | @Test 81 | void testUsersTablePrimaryKeyBasedOnIdField() throws SQLException { 82 | try (Connection connection = dataSource.getConnection()) { 83 | Statement statement = connection.createStatement(); 84 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 85 | " WHERE table_name = 'users' AND constraint_type = 'PRIMARY_KEY';"); 86 | 87 | resultSet.next(); 88 | String pkColumn = resultSet.getString("column_list"); 89 | 90 | assertThat("id", equalTo(pkColumn)); 91 | } 92 | } 93 | 94 | @Test 95 | void testUsersTableHasCorrectAlternativeKey() throws SQLException { 96 | try (Connection connection = dataSource.getConnection()) { 97 | Statement statement = connection.createStatement(); 98 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 99 | " WHERE table_name = 'users' AND constraint_type = 'UNIQUE';"); 100 | 101 | resultSet.next(); 102 | String uniqueConstraintName = resultSet.getString("constraint_name"); 103 | String uniqueConstraintColumn = resultSet.getString("column_list"); 104 | 105 | assertThat(uniqueConstraintName, equalTo("users_email_AK")); 106 | assertThat(uniqueConstraintColumn, equalTo("email")); 107 | } 108 | } 109 | 110 | @Test 111 | void testUsersTableHasAllRequiredColumns() 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 = 'users';"); 116 | 117 | List columns = fetchColumnValues(resultSet, "column_name"); 118 | 119 | assertThat(columns.size(), equalTo(5)); 120 | assertThat(columns, containsInAnyOrder("id", "email", "first_name", "last_name", "birthday")); 121 | } 122 | } 123 | 124 | private List fetchColumnValues(ResultSet resultSet, String resultColumnName) throws SQLException { 125 | List columns = new ArrayList<>(); 126 | while (resultSet.next()) { 127 | String columnName = resultSet.getString(resultColumnName); 128 | columns.add(columnName); 129 | } 130 | return columns; 131 | } 132 | 133 | @Test 134 | void testUsersTableRequiredColumnsHaveHaveNotNullConstraint() throws SQLException { 135 | try (Connection connection = dataSource.getConnection()) { 136 | Statement statement = connection.createStatement(); 137 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 138 | " WHERE table_name = 'users' AND nullable = false;"); 139 | 140 | List notNullColumns = fetchColumnValues(resultSet, "column_name"); 141 | 142 | assertThat(notNullColumns.size(), is(5)); 143 | assertThat(notNullColumns, containsInAnyOrder("id", "email", "first_name", "last_name", "birthday")); 144 | } 145 | } 146 | 147 | @Test 148 | void testUserIdTypeIsBigint() throws SQLException { 149 | try (Connection connection = dataSource.getConnection()) { 150 | Statement statement = connection.createStatement(); 151 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 152 | " WHERE table_name = 'users' AND column_name = 'id';"); 153 | 154 | resultSet.next(); 155 | String idTypeName = resultSet.getString("type_name"); 156 | 157 | assertThat(idTypeName, is("BIGINT")); 158 | } 159 | } 160 | 161 | @Test 162 | void testUsersTableStringColumnsHaveCorrectTypeAndLength() throws SQLException { 163 | try (Connection connection = dataSource.getConnection()) { 164 | Statement statement = connection.createStatement(); 165 | ResultSet resultSet = statement.executeQuery("SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS" + 166 | " WHERE table_name = 'users' AND type_name = 'VARCHAR' AND character_maximum_length = 255;"); 167 | 168 | List stringColumns = fetchColumnValues(resultSet, "column_name"); 169 | 170 | assertThat(stringColumns.size(), is(3)); 171 | assertThat(stringColumns, containsInAnyOrder("email", "first_name", "last_name")); 172 | } 173 | } 174 | 175 | @Test 176 | void testUserBirthdayTypeIsDate() throws SQLException { 177 | try (Connection connection = dataSource.getConnection()) { 178 | Statement statement = connection.createStatement(); 179 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 180 | " WHERE table_name = 'users' AND column_name = 'birthday';"); 181 | 182 | resultSet.next(); 183 | String idTypeName = resultSet.getString("type_name"); 184 | 185 | assertThat(idTypeName, is("DATE")); 186 | } 187 | } 188 | 189 | // table sale_group test 190 | 191 | @Test 192 | void testProfilesTablesHasPrimaryKey() throws SQLException { 193 | try (Connection connection = dataSource.getConnection()) { 194 | Statement statement = connection.createStatement(); 195 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 196 | " WHERE table_name = 'profiles' AND constraint_type = 'PRIMARY_KEY';"); 197 | 198 | boolean resultIsNotEmpty = resultSet.next(); 199 | 200 | assertThat(resultIsNotEmpty, is(true)); 201 | } 202 | } 203 | 204 | @Test 205 | void testProfilesTablePrimaryKeyHasCorrectName() throws SQLException { 206 | try (Connection connection = dataSource.getConnection()) { 207 | Statement statement = connection.createStatement(); 208 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 209 | " WHERE table_name = 'profiles' AND constraint_type = 'PRIMARY_KEY';"); 210 | 211 | resultSet.next(); 212 | String pkConstraintName = resultSet.getString("constraint_name"); 213 | 214 | assertThat(pkConstraintName, equalTo("profiles_PK")); 215 | } 216 | } 217 | 218 | @Test 219 | void testProfilesTablePrimaryKeyBasedOnForeignKeyColumn() throws SQLException { 220 | try (Connection connection = dataSource.getConnection()) { 221 | Statement statement = connection.createStatement(); 222 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 223 | " WHERE table_name = 'profiles' AND constraint_type = 'PRIMARY_KEY';"); 224 | 225 | resultSet.next(); 226 | String pkColumn = resultSet.getString("column_list"); 227 | 228 | assertThat("user_id", equalTo(pkColumn)); 229 | } 230 | } 231 | 232 | @Test 233 | void testProfilesTableHasAllRequiredColumns() throws SQLException { 234 | try (Connection connection = dataSource.getConnection()) { 235 | Statement statement = connection.createStatement(); 236 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 237 | " WHERE table_name = 'profiles';"); 238 | 239 | List columns = fetchColumnValues(resultSet, "column_name"); 240 | 241 | assertThat(columns.size(), equalTo(5)); 242 | assertThat(columns, containsInAnyOrder("user_id", "job_position", "company", "education", "city")); 243 | } 244 | } 245 | 246 | @Test 247 | void testProfilesGroupIdTypeIsBigint() throws SQLException { 248 | try (Connection connection = dataSource.getConnection()) { 249 | Statement statement = connection.createStatement(); 250 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 251 | " WHERE table_name = 'profiles' AND column_name = 'user_id';"); 252 | 253 | resultSet.next(); 254 | String idTypeName = resultSet.getString("type_name"); 255 | 256 | assertThat(idTypeName, is("BIGINT")); 257 | } 258 | } 259 | 260 | @Test 261 | void testProfilesTableStringColumnsHaveCorrectTypeAndLength() throws SQLException { 262 | try (Connection connection = dataSource.getConnection()) { 263 | Statement statement = connection.createStatement(); 264 | ResultSet resultSet = statement.executeQuery("SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS" + 265 | " WHERE table_name = 'profiles' AND type_name = 'VARCHAR' AND character_maximum_length = 255;"); 266 | 267 | List stringColumns = fetchColumnValues(resultSet, "column_name"); 268 | 269 | assertThat(stringColumns.size(), is(4)); 270 | assertThat(stringColumns, containsInAnyOrder("job_position", "company", "education", "city")); 271 | } 272 | } 273 | 274 | @Test 275 | void testProfilesHasForeignKeyToUsers() throws SQLException { 276 | try (Connection connection = dataSource.getConnection()) { 277 | Statement statement = connection.createStatement(); 278 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 279 | " WHERE table_name = 'profiles' AND constraint_type = 'REFERENTIAL' AND column_list = 'user_id';"); 280 | 281 | boolean resultIsNotEmpty = resultSet.next(); 282 | 283 | assertThat(resultIsNotEmpty, is(true)); 284 | } 285 | } 286 | 287 | @Test 288 | void testProfilesForeignKeyToUsersHasCorrectName() throws SQLException { 289 | try (Connection connection = dataSource.getConnection()) { 290 | Statement statement = connection.createStatement(); 291 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 292 | " WHERE table_name = 'profiles' AND constraint_type = 'REFERENTIAL' AND column_list = 'user_id';"); 293 | 294 | resultSet.next(); 295 | String fkConstraintName = resultSet.getString("constraint_name"); 296 | 297 | assertThat(fkConstraintName, equalTo("profiles_users_FK")); 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /wall-street-db-initializer/README.MD: -------------------------------------------------------------------------------- 1 | # Wall Street db initializer exercise :muscle: 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 :heavy_exclamation_mark: 16 | You're supposed to be familiar with database design and naming convention, *JDBC API* and *SQL* 17 | 18 | ### How to start :question: 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 :information_source: 24 | * [JDBC API basics tutorial](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-basics) 25 | 26 | 27 | -------------------------------------------------------------------------------- /wall-street-db-initializer/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-exercises 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | wall-street-db-initializer 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /wall-street-db-initializer/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 allow 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 | -------------------------------------------------------------------------------- /wall-street-db-initializer/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 28 | -------------------------------------------------------------------------------- /wall-street-db-initializer/src/test/java/com/bobocode/WallStreetDbInitializerTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.util.JdbcUtil; 4 | import org.junit.jupiter.api.BeforeAll; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import javax.sql.DataSource; 8 | import java.sql.Connection; 9 | import java.sql.ResultSet; 10 | import java.sql.SQLException; 11 | import java.sql.Statement; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.hamcrest.Matchers.contains; 17 | import static org.hamcrest.Matchers.containsInAnyOrder; 18 | import static org.hamcrest.Matchers.equalTo; 19 | import static org.hamcrest.Matchers.is; 20 | 21 | 22 | public class WallStreetDbInitializerTest { 23 | private static DataSource dataSource; 24 | 25 | @BeforeAll 26 | static void init() throws SQLException { 27 | dataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 28 | WallStreetDbInitializer dbInitializer = new WallStreetDbInitializer(dataSource); 29 | dbInitializer.init(); 30 | } 31 | 32 | // table broker tests 33 | 34 | @Test 35 | void testTablesHaveCorrectNames() throws SQLException { 36 | try (Connection connection = dataSource.getConnection()) { 37 | Statement statement = connection.createStatement(); 38 | 39 | ResultSet resultSet = statement.executeQuery("SHOW TABLES"); 40 | List tableNames = fetchTableNames(resultSet); 41 | 42 | assertThat(tableNames, containsInAnyOrder("broker", "sales_group", "broker_sales_group")); 43 | } 44 | } 45 | 46 | private List fetchTableNames(ResultSet resultSet) throws SQLException { 47 | List tableNamesList = new ArrayList<>(); 48 | while (resultSet.next()) { 49 | String tableName = resultSet.getString("table_name"); 50 | tableNamesList.add(tableName); 51 | } 52 | return tableNamesList; 53 | } 54 | 55 | 56 | @Test 57 | void testBrokerTablesHasPrimaryKey() throws SQLException { 58 | try (Connection connection = dataSource.getConnection()) { 59 | Statement statement = connection.createStatement(); 60 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 61 | " WHERE table_name = 'broker' AND constraint_type = 'PRIMARY_KEY';"); 62 | 63 | boolean resultIsNotEmpty = resultSet.next(); 64 | 65 | assertThat(resultIsNotEmpty, is(true)); 66 | } 67 | } 68 | 69 | @Test 70 | void testBrokerTablePrimaryKeyHasCorrectName() throws SQLException { 71 | try (Connection connection = dataSource.getConnection()) { 72 | Statement statement = connection.createStatement(); 73 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 74 | " WHERE table_name = 'broker' AND constraint_type = 'PRIMARY_KEY';"); 75 | 76 | resultSet.next(); 77 | String pkConstraintName = resultSet.getString("constraint_name"); 78 | 79 | assertThat(pkConstraintName, equalTo("PK_broker")); 80 | } 81 | } 82 | 83 | @Test 84 | void testBrokerTablePrimaryKeyBasedOnIdField() throws SQLException { 85 | try (Connection connection = dataSource.getConnection()) { 86 | Statement statement = connection.createStatement(); 87 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 88 | " WHERE table_name = 'broker' AND constraint_type = 'PRIMARY_KEY';"); 89 | 90 | resultSet.next(); 91 | String pkColumn = resultSet.getString("column_list"); 92 | 93 | assertThat("id", equalTo(pkColumn)); 94 | } 95 | } 96 | 97 | @Test 98 | void testBrokerTableHasCorrectUniqueConstraint() throws SQLException { 99 | try (Connection connection = dataSource.getConnection()) { 100 | Statement statement = connection.createStatement(); 101 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 102 | " WHERE table_name = 'broker' AND constraint_type = 'UNIQUE';"); 103 | 104 | resultSet.next(); 105 | String uniqueConstraintName = resultSet.getString("constraint_name"); 106 | String uniqueConstraintColumn = resultSet.getString("column_list"); 107 | 108 | assertThat(uniqueConstraintName, equalTo("UQ_broker_username")); 109 | assertThat(uniqueConstraintColumn, equalTo("username")); 110 | } 111 | } 112 | 113 | @Test 114 | void testBrokerTableHasAllRequiredColumns() throws SQLException { 115 | try (Connection connection = dataSource.getConnection()) { 116 | Statement statement = connection.createStatement(); 117 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 118 | " WHERE table_name = 'broker';"); 119 | 120 | List columns = fetchColumnValues(resultSet, "column_name"); 121 | 122 | assertThat(columns.size(), equalTo(4)); 123 | assertThat(columns, containsInAnyOrder("id", "username", "first_name", "last_name")); 124 | } 125 | } 126 | 127 | private List fetchColumnValues(ResultSet resultSet, String resultColumnName) throws SQLException { 128 | List columns = new ArrayList<>(); 129 | while (resultSet.next()) { 130 | String columnName = resultSet.getString(resultColumnName); 131 | columns.add(columnName); 132 | } 133 | return columns; 134 | } 135 | 136 | @Test 137 | void testBrokerTableRequiredColumnsHaveHaveNotNullConstraint() throws SQLException { 138 | try (Connection connection = dataSource.getConnection()) { 139 | Statement statement = connection.createStatement(); 140 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 141 | " WHERE table_name = 'broker' AND nullable = false;"); 142 | 143 | List notNullColumns = fetchColumnValues(resultSet, "column_name"); 144 | 145 | assertThat(notNullColumns.size(), is(4)); 146 | assertThat(notNullColumns, containsInAnyOrder("id", "username", "first_name", "last_name")); 147 | } 148 | } 149 | 150 | @Test 151 | void testBrokerIdTypeIsBigint() throws SQLException { 152 | try (Connection connection = dataSource.getConnection()) { 153 | Statement statement = connection.createStatement(); 154 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 155 | " WHERE table_name = 'broker' AND column_name = 'id';"); 156 | 157 | resultSet.next(); 158 | String idTypeName = resultSet.getString("type_name"); 159 | 160 | assertThat(idTypeName, is("BIGINT")); 161 | } 162 | } 163 | 164 | @Test 165 | void testBrokerTableStringColumnsHaveCorrectTypeAndLength() throws SQLException { 166 | try (Connection connection = dataSource.getConnection()) { 167 | Statement statement = connection.createStatement(); 168 | ResultSet resultSet = statement.executeQuery("SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS" + 169 | " WHERE table_name = 'broker' AND type_name = 'VARCHAR' AND character_maximum_length = 255;"); 170 | 171 | List stringColumns = fetchColumnValues(resultSet, "column_name"); 172 | 173 | assertThat(stringColumns.size(), is(3)); 174 | assertThat(stringColumns, containsInAnyOrder("username", "first_name", "last_name")); 175 | } 176 | } 177 | 178 | // table sale_group test 179 | 180 | @Test 181 | void testSaleGroupTablesHasPrimaryKey() throws SQLException { 182 | try (Connection connection = dataSource.getConnection()) { 183 | Statement statement = connection.createStatement(); 184 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 185 | " WHERE table_name = 'sales_group' AND constraint_type = 'PRIMARY_KEY';"); 186 | 187 | boolean resultIsNotEmpty = resultSet.next(); 188 | 189 | assertThat(resultIsNotEmpty, is(true)); 190 | } 191 | } 192 | 193 | @Test 194 | void testSaleGroupTablePrimaryKeyHasCorrectName() throws SQLException { 195 | try (Connection connection = dataSource.getConnection()) { 196 | Statement statement = connection.createStatement(); 197 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 198 | " WHERE table_name = 'sales_group' AND constraint_type = 'PRIMARY_KEY';"); 199 | 200 | resultSet.next(); 201 | String pkConstraintName = resultSet.getString("constraint_name"); 202 | 203 | assertThat(pkConstraintName, equalTo("PK_sales_group")); 204 | } 205 | } 206 | 207 | @Test 208 | void testSaleGroupTablePrimaryKeyBasedOnIdField() throws SQLException { 209 | try (Connection connection = dataSource.getConnection()) { 210 | Statement statement = connection.createStatement(); 211 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 212 | " WHERE table_name = 'sales_group' AND constraint_type = 'PRIMARY_KEY';"); 213 | 214 | resultSet.next(); 215 | String pkColumn = resultSet.getString("column_list"); 216 | 217 | assertThat("id", equalTo(pkColumn)); 218 | } 219 | } 220 | 221 | @Test 222 | void testSaleGroupTableHasCorrectUniqueConstraint() throws SQLException { 223 | try (Connection connection = dataSource.getConnection()) { 224 | Statement statement = connection.createStatement(); 225 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 226 | " WHERE table_name = 'sales_group' AND constraint_type = 'UNIQUE';"); 227 | 228 | resultSet.next(); 229 | String uniqueConstraintName = resultSet.getString("constraint_name"); 230 | String uniqueConstraintColumn = resultSet.getString("column_list"); 231 | 232 | assertThat(uniqueConstraintName, equalTo("UQ_sales_group_name")); 233 | assertThat(uniqueConstraintColumn, equalTo("name")); 234 | } 235 | } 236 | 237 | @Test 238 | void testSaleGroupTableHasAllRequiredColumns() throws SQLException { 239 | try (Connection connection = dataSource.getConnection()) { 240 | Statement statement = connection.createStatement(); 241 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 242 | " WHERE table_name = 'sales_group';"); 243 | 244 | List columns = fetchColumnValues(resultSet, "column_name"); 245 | 246 | assertThat(columns.size(), equalTo(4)); 247 | assertThat(columns, containsInAnyOrder("id", "name", "transaction_type", "max_transaction_amount")); 248 | } 249 | } 250 | 251 | @Test 252 | void testSaleGroupTableRequiredColumnsHaveHaveNotNullConstraint() throws SQLException { 253 | try (Connection connection = dataSource.getConnection()) { 254 | Statement statement = connection.createStatement(); 255 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 256 | " WHERE table_name = 'sales_group' AND nullable = false;"); 257 | 258 | List notNullColumns = fetchColumnValues(resultSet, "column_name"); 259 | 260 | assertThat(notNullColumns.size(), is(4)); 261 | assertThat(notNullColumns, containsInAnyOrder("id", "name", "transaction_type", "max_transaction_amount")); 262 | } 263 | } 264 | 265 | @Test 266 | void testSaleGroupIdTypeIsBigint() throws SQLException { 267 | try (Connection connection = dataSource.getConnection()) { 268 | Statement statement = connection.createStatement(); 269 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 270 | " WHERE table_name = 'sales_group' AND column_name = 'id';"); 271 | 272 | resultSet.next(); 273 | String idTypeName = resultSet.getString("type_name"); 274 | 275 | assertThat(idTypeName, is("BIGINT")); 276 | } 277 | } 278 | 279 | @Test 280 | void testSaleGroupTableStringColumnsHaveCorrectTypeAndLength() throws SQLException { 281 | try (Connection connection = dataSource.getConnection()) { 282 | Statement statement = connection.createStatement(); 283 | ResultSet resultSet = statement.executeQuery("SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS" + 284 | " WHERE table_name = 'sales_group' AND type_name = 'VARCHAR' AND character_maximum_length = 255;"); 285 | 286 | List stringColumns = fetchColumnValues(resultSet, "column_name"); 287 | 288 | assertThat(stringColumns.size(), is(2)); 289 | assertThat(stringColumns, containsInAnyOrder("name", "transaction_type")); 290 | } 291 | } 292 | 293 | // table broker_sales_group tests 294 | 295 | @Test 296 | void testBrokerSaleGroupTablesHasForeignKeyToBroker() throws SQLException { 297 | try (Connection connection = dataSource.getConnection()) { 298 | Statement statement = connection.createStatement(); 299 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 300 | " WHERE table_name = 'broker_sales_group' AND constraint_type = 'REFERENTIAL' AND column_list = 'broker_id';"); 301 | 302 | boolean resultIsNotEmpty = resultSet.next(); 303 | 304 | assertThat(resultIsNotEmpty, is(true)); 305 | } 306 | } 307 | 308 | @Test 309 | void testBrokerSaleGroupTableForeignKeyToBrokerHasCorrectName() throws SQLException { 310 | try (Connection connection = dataSource.getConnection()) { 311 | Statement statement = connection.createStatement(); 312 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 313 | " WHERE table_name = 'broker_sales_group' AND constraint_type = 'REFERENTIAL' AND column_list = 'broker_id';"); 314 | 315 | resultSet.next(); 316 | String fkConstraintName = resultSet.getString("constraint_name"); 317 | 318 | assertThat(fkConstraintName, equalTo("FK_broker_sales_group_broker")); 319 | } 320 | } 321 | 322 | @Test 323 | void testBrokerSaleGroupTablesHasForeignKeyToSalesGroup() throws SQLException { 324 | try (Connection connection = dataSource.getConnection()) { 325 | Statement statement = connection.createStatement(); 326 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 327 | " WHERE table_name = 'broker_sales_group' AND constraint_type = 'REFERENTIAL' AND column_list = 'sales_group_id';"); 328 | 329 | boolean resultIsNotEmpty = resultSet.next(); 330 | 331 | assertThat(resultIsNotEmpty, is(true)); 332 | } 333 | } 334 | 335 | @Test 336 | void testBrokerSaleGroupTableForeignKeyToSalesGroupHasCorrectName() throws SQLException { 337 | try (Connection connection = dataSource.getConnection()) { 338 | Statement statement = connection.createStatement(); 339 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 340 | " WHERE table_name = 'broker_sales_group' AND constraint_type = 'REFERENTIAL' AND column_list = 'sales_group_id';"); 341 | 342 | resultSet.next(); 343 | String fkConstraintName = resultSet.getString("constraint_name"); 344 | 345 | assertThat(fkConstraintName, equalTo("FK_broker_sales_group_sales_group")); 346 | } 347 | } 348 | 349 | @Test 350 | void testBrokerSaleGroupTableHasNotNullConstraint() throws SQLException { 351 | try (Connection connection = dataSource.getConnection()) { 352 | Statement statement = connection.createStatement(); 353 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 354 | " WHERE table_name = 'broker_sales_group' AND nullable = false;"); 355 | 356 | List notNullColumns = fetchColumnValues(resultSet, "column_name"); 357 | 358 | assertThat(notNullColumns.size(), is(2)); 359 | assertThat(notNullColumns, containsInAnyOrder("broker_id", "sales_group_id")); 360 | } 361 | } 362 | 363 | @Test 364 | void testBrokerSaleGroupForeignKeysTypeAreBigint() throws SQLException { 365 | try (Connection connection = dataSource.getConnection()) { 366 | Statement statement = connection.createStatement(); 367 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.COLUMNS" + 368 | " WHERE table_name = 'broker_sales_group';"); 369 | 370 | List columnTypes = fetchColumnValues(resultSet, "type_name"); 371 | 372 | assertThat(columnTypes, contains("BIGINT", "BIGINT")); 373 | } 374 | } 375 | 376 | @Test 377 | void testBrokerSaleGroupTableHasCompositePrimaryKey() throws SQLException { 378 | try (Connection connection = dataSource.getConnection()) { 379 | Statement statement = connection.createStatement(); 380 | ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.CONSTRAINTS" + 381 | " WHERE table_name = 'broker_sales_group' AND constraint_type = 'PRIMARY_KEY';"); 382 | 383 | resultSet.next(); 384 | String uniqueConstraintName = resultSet.getString("constraint_name"); 385 | String uniqueConstraintColumn = resultSet.getString("column_list"); 386 | 387 | assertThat(uniqueConstraintName, equalTo("PK_broker_sales_group")); 388 | assertThat(uniqueConstraintColumn, equalTo("broker_id,sales_group_id")); 389 | } 390 | } 391 | } 392 | --------------------------------------------------------------------------------