├── .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 |
--------------------------------------------------------------------------------