├── .gitignore ├── README.md ├── jdbc-account-data ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── bobocode │ ├── data │ └── Accounts.java │ ├── exception │ └── JdbcAccountUtilException.java │ ├── model │ ├── Account.java │ └── Gender.java │ └── util │ └── JdbcAccountUtil.java ├── jdbc-basics ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── com.bobocode │ └── SimpleJdbcExample.java ├── jdbc-batch-insert ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── bobocode │ ├── AccountBatchInsertExample.java │ └── exception │ └── AccountBatchInsertException.java ├── jdbc-dao ├── README.md ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── bobocode │ │ │ ├── dao │ │ │ ├── AccountDao.java │ │ │ └── AccountDaoImpl.java │ │ │ └── exception │ │ │ └── DaoOperationException.java │ └── resources │ │ └── account.sql │ └── test │ └── java │ └── com │ └── bobocode │ └── AccountDaoTest.java ├── jdbc-transaction ├── README.md ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── bobocode │ ├── JdbcTransactionManagementExample.java │ └── exception │ └── JdbcTransactionExampleException.java ├── jdbc-util ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── bobocode │ └── util │ └── JdbcUtil.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/*.iml 3 | **/target 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JDBC API, and SQL tutorial 2 | 3 | The list of tutorials on JDBC API features and SQL 4 | * [JDBC API Basics](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-basics) 5 | * [Data Access Objects (DAO)](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-dao) 6 | * [Batch Insert with JDBC API](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-batch-insert) 7 | -------------------------------------------------------------------------------- /jdbc-account-data/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-tutorial 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/exception/JdbcAccountUtilException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class JdbcAccountUtilException extends RuntimeException { 4 | public JdbcAccountUtilException(String message) { 5 | super(message); 6 | } 7 | 8 | public JdbcAccountUtilException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /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 | UNKNOWN (0), 5 | MALE(1), 6 | FEMALE(2); 7 | 8 | private final int value; 9 | 10 | Gender(int value) { 11 | this.value = value; 12 | } 13 | 14 | public int getValue() { 15 | return value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jdbc-account-data/src/main/java/com/bobocode/util/JdbcAccountUtil.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.util; 2 | 3 | import com.bobocode.exception.JdbcAccountUtilException; 4 | 5 | import javax.sql.DataSource; 6 | import java.sql.Connection; 7 | import java.sql.SQLException; 8 | import java.sql.Statement; 9 | 10 | public class JdbcAccountUtil { 11 | private final static String CREATE_ACCOUNT_TABLE_SQL = 12 | "CREATE TABLE IF NOT EXISTS account (\n" + 13 | " id SERIAL NOT NULL,\n" + 14 | " first_name VARCHAR(255) NOT NULL,\n" + 15 | " last_name VARCHAR(255) NOT NULL,\n" + 16 | " email VARCHAR(255) NOT NULL,\n" + 17 | " birthday TIMESTAMP NOT NULL,\n" + 18 | " sex SMALLINT NOT NULL DEFAULT 0 CHECK (sex >= 0 AND sex <=2) ,\n" + 19 | " balance DECIMAL(19, 4),\n" + 20 | " creation_time TIMESTAMP NOT NULL DEFAULT now(),\n" + 21 | " CONSTRAINT account_pk PRIMARY KEY (id)\n" + 22 | ");"; 23 | 24 | public static void createAccountTable(DataSource dataSource) { 25 | try (Connection connection = dataSource.getConnection()) { 26 | Statement createTableStatement = connection.createStatement(); 27 | createTableStatement.execute(CREATE_ACCOUNT_TABLE_SQL); 28 | } catch (SQLException e) { 29 | throw new JdbcAccountUtilException("Cannot create account table", e); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /jdbc-basics/README.md: -------------------------------------------------------------------------------- 1 | # JDBC API basics tutorial 2 | 3 | The tutorial on JDBC API essential features and basic configurations 4 | 5 | ### Pre-conditions :heavy_exclamation_mark: 6 | You're supposed to be familiar with SQL and relational databases, have basic knowledge of JDK, and be able to write Java code. 7 | ## 8 | ***JDBC API*** is the only part of *JDK* that provides an ability to **connect to a relational database from Java.** 9 | Since it's just an API, in order to call a real database, you need a specific implementation of that API for each database. 10 | *JDBC API* implementation is called **JDBC Driver**. Each driver is provided by its database vendor. 11 | 12 | The basic flow of working with database is **getting connection, performing SQL query, and getting results.** 13 | Here's the list of most important *JDBC API* classes needed for calling db and getting results: 14 | 15 | JDBC API class | Description 16 | --- | --- 17 | `DataSource` | Represents a concrete database server 18 | `Connection` | Represents a real physical network connection to the db 19 | `Statement` | Represents a SQL query 20 | `ResultSet` | Represents a query result received from the db 21 | 22 | Check out the `SimpleJdbcExample.java` class to see the real working example that uses all of the classes listed above. 23 | 24 | ### Best practices 25 | * use *try-with-resources* to handle database connection 26 | * prefer `PreparedStatement` for *SQL* queries with parameters 27 | * avoid mixing SQL queries with Java code withing one method 28 | * avoid returning JDBC API classes (like `Connection`, or `ResultSet`) form `public` methods 29 | -------------------------------------------------------------------------------- /jdbc-basics/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-tutorial 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | jdbc-basics 13 | 14 | 15 | com.bobocode 16 | jdbc-util 17 | 1.0-SNAPSHOT 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /jdbc-basics/src/main/java/com.bobocode/SimpleJdbcExample.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.util.JdbcUtil; 4 | 5 | import javax.sql.DataSource; 6 | import java.sql.*; 7 | 8 | public class SimpleJdbcExample { 9 | private static final String CREATE_TABLE_SQL = "CREATE TABLE message(" + 10 | "body VARCHAR(255)," + 11 | "creation_date TIMESTAMP DEFAULT now()" + 12 | ");"; 13 | private static final String INSERT_SQL = "INSERT INTO message(body) VALUES (?)"; 14 | private static final String SELECT_ALL_SQL = "SELECT * FROM message"; 15 | 16 | 17 | private static DataSource dataSource; 18 | 19 | public static void main(String[] args) throws SQLException { // exception handling is omitted 20 | init(); 21 | createMessageTable(); 22 | saveSomeMessagesIntoDB(); 23 | printMessagesFromDB(); 24 | } 25 | 26 | private static void init() { 27 | dataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 28 | } 29 | 30 | private static void createMessageTable() throws SQLException { 31 | try (Connection connection = dataSource.getConnection()) { 32 | Statement statement = connection.createStatement(); 33 | statement.execute(CREATE_TABLE_SQL); 34 | } 35 | 36 | } 37 | 38 | private static void saveSomeMessagesIntoDB() throws SQLException { 39 | try (Connection connection = dataSource.getConnection()) { 40 | PreparedStatement insertStatement = connection.prepareStatement(INSERT_SQL); 41 | insertSomeMessages(insertStatement); 42 | } 43 | } 44 | 45 | private static void insertSomeMessages(PreparedStatement insertStatement) throws SQLException { 46 | insertStatement.setString(1, "Hello!"); 47 | insertStatement.executeUpdate(); 48 | 49 | insertStatement.setString(1, "How are you?"); 50 | insertStatement.executeUpdate(); 51 | } 52 | 53 | private static void printMessagesFromDB() throws SQLException { 54 | //try-with-resource will automatically close Connection resource 55 | try (Connection connection = dataSource.getConnection()) { 56 | Statement statement = connection.createStatement(); 57 | ResultSet resultSet = statement.executeQuery(SELECT_ALL_SQL); 58 | printAllMessages(resultSet); 59 | } 60 | } 61 | 62 | private static void printAllMessages(ResultSet resultSet) throws SQLException { 63 | while (resultSet.next()) { 64 | String messageText = resultSet.getString(1); 65 | Timestamp timestamp = resultSet.getTimestamp(2); 66 | System.out.println(" - " + messageText + " [" + timestamp + "]"); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /jdbc-batch-insert/README.md: -------------------------------------------------------------------------------- 1 | # BATCH INSERT tutorial 2 | 3 | The tutorial on SQL BATCH INSERT using JDBC API 4 | 5 | ### Pre-conditions :heavy_exclamation_mark: 6 | You're supposed to be familiar with SQL and relational databases, and be able to write Java code. 7 | ## 8 | 9 | **Batch insert** it's an operation that allows to **insert more than one row using one INSERT statement.** :star: 10 | 11 | #### SQL 12 | A typical insert sql query looks like this: 13 | 14 | ```sql 15 | INSERT INTO products(name, producer) VALUES('Snickers', 'Mars Inc'); 16 | ``` 17 | 18 | We usually perform one INSERT statement to store one row. However, in some cases such approach is not efficient :-1: 19 | 20 | **Suppose you need to store large amount of products** at the same time. Like you need to store 100 000 products. It means 21 | that you need to call database 100 000 which is super inefficient, because each SQL query needs to at least go through 22 | the network from the server to the database, and it needs to do it 100 000 times :scream_cat: 23 | 24 | In order to make such operation more efficient relational database and SQL provide an ability to **insert multiple rows 25 | in one INSERT statement.** :thumbsup: The sql query looks like the following: 26 | 27 | ```sql 28 | INSERT INTO products(name, producer) VALUES ('Snickers', 'Mars Inc'), ('Fanta', 'The Coca-Cola company'), ('Bueno', 'Ferrero S.p.A.'); 29 | ``` 30 | 31 | This approach allow to tremendously reduce the amount of database calls :+1: 32 | 33 | Using batch INSERT you can split all products, and insert them using batches. In case a **batch size is 1000**, the **number 34 | of database calls would be only 100** :smiley_cat: 35 | 36 | #### JDBC API 37 | JDBC API provides two basic methods to perform batch insert: 38 | - `PreparedStatement#addBatch()` that adds new row data to the existing batch 39 | - `Statement#executeBatch()` that calls the database to perform an SQL and insert all data stored in the batch 40 | 41 | Instead of executing query each time 42 | ```java 43 | for (int i = 0; i < PRODUCTS_NUMBER; i++) { 44 | // get prepared statement 45 | // get product 46 | // set prepared statemnt parameters 47 | preparedStatemnt.executeUpdate(); // calls the database 48 | } 49 | ``` 50 | 51 | You can add new data to the batch: 52 | ```java 53 | for (int i = 0; i < PRODUCTS_NUMBER; i++) { 54 | // get prepared statement 55 | // get product 56 | // set prepared statemnt parameters 57 | preparedStatemnt.addBatch(); // doesn't call the database 58 | 59 | if (i % BATCH_SIZE == 0) { 60 | preparedStatemnt.executeBatch(); // calls the database 61 | } 62 | } 63 | 64 | // in case batch in not empty at the end of the for loop 65 | if (i % BATCH_SIZE == 0) { 66 | preparedStatemnt.executeBatch(); // calls the database 67 | } 68 | ``` 69 | 70 | ### Best practices 71 | * use batch INSERT for saving large amount of data 72 | * always measure performance 73 | * change batch size depending on situation -------------------------------------------------------------------------------- /jdbc-batch-insert/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-tutorial 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | jdbc-batch-insert 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-account-data 18 | 1.0-SNAPSHOT 19 | 20 | 21 | com.bobocode 22 | jdbc-util 23 | 1.0-SNAPSHOT 24 | 25 | 26 | com.bobocode 27 | jdbc-dao 28 | 1.0-SNAPSHOT 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /jdbc-batch-insert/src/main/java/com/bobocode/AccountBatchInsertExample.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.exception.AccountBatchInsertException; 4 | import com.bobocode.model.Account; 5 | import com.bobocode.model.Gender; 6 | import com.bobocode.util.JdbcAccountUtil; 7 | import com.bobocode.util.JdbcUtil; 8 | import org.apache.commons.lang3.RandomStringUtils; 9 | import org.apache.commons.lang3.RandomUtils; 10 | 11 | import javax.sql.DataSource; 12 | import java.math.BigDecimal; 13 | import java.sql.Connection; 14 | import java.sql.Date; 15 | import java.sql.PreparedStatement; 16 | import java.sql.SQLException; 17 | import java.time.LocalDate; 18 | 19 | /** 20 | * {@link AccountBatchInsertExample} provides an example of BATCH INSERT using JDBC API. 21 | *

22 | * {@link AccountBatchInsertExample#init()} creates in-memory database. 23 | *

24 | * Then there are two methods that do completely the 25 | * same. Both methods store generated accounts into the database. The first one {@link AccountBatchInsertExample#saveAccountsUsingRegularInsert()} 26 | * is a simple insert that call the database each time to perform INSERT query for each account. 27 | *

28 | * The second one {@link AccountBatchInsertExample#saveAccountsUsingBatchInsert()} stores generated accounts using 29 | * BATCH INSERT. E.g. it creates batch insert query, and sends it to the database every each {@link AccountBatchInsertExample#BATCH_SIZE} 30 | * accounts. 31 | *

32 | * It means that if ACCOUNT_NUMBER = 100_000, and BATCH_SIZE = 1000, 33 | * regular insert will execute 100_000 SQL queries, 34 | * batch insert will execute 100 SQL queries 35 | */ 36 | public class AccountBatchInsertExample { 37 | private static DataSource dataSource; 38 | private static final int ACCOUNT_NUMBER = 100_000; 39 | private static final int BATCH_SIZE = 1000; 40 | private static final String ACCOUNT_INSERT_SQL = 41 | "INSERT INTO account(first_name, last_name, email, birthday, sex, balance) VALUES(?,?,?,?,?,?);"; 42 | 43 | public static void main(String[] args) throws SQLException { 44 | init(); 45 | saveAccountsUsingRegularInsert(); 46 | saveAccountsUsingBatchInsert(); 47 | } 48 | 49 | public static void init() { 50 | dataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 51 | JdbcAccountUtil.createAccountTable(dataSource); 52 | } 53 | 54 | private static void saveAccountsUsingRegularInsert() throws SQLException { 55 | try (Connection connection = dataSource.getConnection()) { 56 | System.out.printf("Insert %d accounts into the database using regular INSERT: ", ACCOUNT_NUMBER); 57 | Runnable saveAccountUsingRegularInsert = saveAccountsUsingRegularInsertRunnable(connection); 58 | long millis = performCountingTimeInMillis(saveAccountUsingRegularInsert); 59 | System.out.printf("%d ms%n", millis); 60 | } 61 | } 62 | 63 | /** 64 | * Creates a {@link Runnable} that holds the logic of generating and saving accounts using regular insert. 65 | * We need {@link Runnable} to pass it to {@link AccountBatchInsertExample#performCountingTimeInMillis(Runnable)} 66 | * because we want to calculate the execution time. 67 | * 68 | * @return runnable object that does regular insert 69 | */ 70 | private static Runnable saveAccountsUsingRegularInsertRunnable(Connection connection) { 71 | return () -> { 72 | try { 73 | PreparedStatement insertStatement = connection.prepareStatement(ACCOUNT_INSERT_SQL); 74 | performRegularInsert(insertStatement); 75 | } catch (SQLException e) { 76 | throw new AccountBatchInsertException("Cannot perform regular account insert", e); 77 | } 78 | 79 | }; 80 | } 81 | 82 | private static long performCountingTimeInMillis(Runnable runnable) { 83 | long startingTime = System.nanoTime(); 84 | runnable.run(); 85 | return (System.nanoTime() - startingTime) / 1_000_000; 86 | } 87 | 88 | private static void performRegularInsert(PreparedStatement insertStatement) throws SQLException { 89 | for (int i = 0; i < ACCOUNT_NUMBER; i++) { 90 | Account account = generateAccount(); 91 | fillStatementParameters(insertStatement, account); 92 | insertStatement.executeUpdate(); // on each step we call the database sending INSERT query 93 | } 94 | } 95 | 96 | private static Account generateAccount() { 97 | Account fakeAccount = new Account(); 98 | fakeAccount.setFirstName(RandomStringUtils.randomAlphabetic(20)); 99 | fakeAccount.setLastName(RandomStringUtils.randomAlphabetic(20)); 100 | fakeAccount.setEmail(RandomStringUtils.randomAlphabetic(20)); 101 | fakeAccount.setGender(Gender.values()[RandomUtils.nextInt(1, 3)]); 102 | fakeAccount.setBalance(BigDecimal.valueOf(RandomUtils.nextInt(500, 200_000))); 103 | fakeAccount.setBirthday(LocalDate.now().minusDays(RandomUtils.nextInt(6000, 18000))); 104 | return fakeAccount; 105 | } 106 | 107 | private static void fillStatementParameters(PreparedStatement ps, Account account) throws SQLException { 108 | ps.setString(1, account.getFirstName()); 109 | ps.setString(2, account.getLastName()); 110 | ps.setString(3, account.getEmail()); 111 | ps.setDate(4, Date.valueOf(account.getBirthday())); 112 | ps.setInt(5, account.getGender().getValue()); 113 | ps.setBigDecimal(6, account.getBalance()); 114 | } 115 | 116 | private static void saveAccountsUsingBatchInsert() throws SQLException { 117 | try (Connection connection = dataSource.getConnection()) { 118 | System.out.printf("Insert %d accounts into the database using BATCH INSERT: ", ACCOUNT_NUMBER); 119 | Runnable saveAccountUsingBatch = saveAccountsUsingBatchInsertRunnable(connection); 120 | long millis = performCountingTimeInMillis(saveAccountUsingBatch); 121 | System.out.printf("%d ms%n", millis); 122 | } 123 | 124 | } 125 | 126 | /** 127 | * Creates a {@link Runnable} that holds the logic of generating and saving accounts using batch insert. 128 | * We need {@link Runnable} to pass it to {@link AccountBatchInsertExample#performCountingTimeInMillis(Runnable)} 129 | * because we want to calculate the execution time. 130 | * 131 | * @return runnable object that does regular insert 132 | */ 133 | private static Runnable saveAccountsUsingBatchInsertRunnable(Connection connection) { 134 | return () -> { 135 | try { 136 | PreparedStatement insertStatement = connection.prepareStatement(ACCOUNT_INSERT_SQL); 137 | performBatchInsert(insertStatement); 138 | } catch (SQLException e) { 139 | throw new AccountBatchInsertException("Cannot perform account batch insert", e); 140 | } 141 | }; 142 | } 143 | 144 | private static void performBatchInsert(PreparedStatement insertStatement) throws SQLException { 145 | for (int i = 1; i <= ACCOUNT_NUMBER; i++) { 146 | Account account = generateAccount(); 147 | fillStatementParameters(insertStatement, account); 148 | insertStatement.addBatch(); // on each step we add a new account to the batch (IT DOESN'T CALL THE DATABASE) 149 | 150 | if (i % BATCH_SIZE == 0) { // every BATCH_SIZE accounts 151 | insertStatement.executeBatch(); // we perform the real SQL query to insert all account to the database 152 | } 153 | } 154 | executeRemaining(insertStatement); 155 | } 156 | 157 | private static void executeRemaining(PreparedStatement insertStatement) throws SQLException { 158 | if (ACCOUNT_NUMBER % BATCH_SIZE != 0) { 159 | insertStatement.executeBatch(); 160 | } 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /jdbc-batch-insert/src/main/java/com/bobocode/exception/AccountBatchInsertException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class AccountBatchInsertException extends RuntimeException { 4 | public AccountBatchInsertException(String message) { 5 | super(message); 6 | } 7 | 8 | public AccountBatchInsertException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jdbc-dao/README.md: -------------------------------------------------------------------------------- 1 | # Data Access Objects (DAO) tutorial 2 | 3 | The tutorial on JDBC API and Data Access Objects 4 | 5 | ### Pre-conditions :heavy_exclamation_mark: 6 | You're supposed to be familiar with SQL and relational databases, have basic knowledge of JDK and JUnit, and be able to write Java code. 7 | ### Related exercises :muscle: 8 | * [Product DAO](https://github.com/bobocode-projects/jdbc-api-exercises/tree/master/product-dao) 9 | ### See also :point_down: 10 | * [Tutorial on JDBC API basics](https://github.com/bobocode-projects/jdbc-api-tutorial/tree/master/jdbc-basics) 11 | ## 12 | ***Data Access Object (DAO)*** is an object that encapsulates all **database configuration, data access and manipulation logic.** 13 | It hides all database queries, and provides a convenient API based on object-oriented model. So you can access and manipulate 14 | data in you business logic using your business objects (models). 15 | 16 | ***A model (entity)*** is a class that represents a **business entity**, **stores data** and **does not contain any business logic.** 17 | 18 | 19 | ### Best practices 20 | * create a separate *DAO* for each *entity (model)* 21 | * separate declaration (interface) and its implementation 22 | * keep all database-related details behind the *DAO* 23 | * wrap database-related exception with custom ones, providing more data and meaningful messages 24 | * avoid mixing *Java* code with *SQL* 25 | -------------------------------------------------------------------------------- /jdbc-dao/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-tutorial 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | jdbc-dao 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | com.bobocode 22 | jdbc-account-data 23 | 1.0-SNAPSHOT 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /jdbc-dao/src/main/java/com/bobocode/dao/AccountDao.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.model.Account; 4 | 5 | import java.util.List; 6 | 7 | public interface AccountDao { 8 | void save(Account account); 9 | 10 | Account findOne(Long id); 11 | 12 | List findAll(); 13 | 14 | void update(Account account); 15 | } 16 | -------------------------------------------------------------------------------- /jdbc-dao/src/main/java/com/bobocode/dao/AccountDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.dao; 2 | 3 | import com.bobocode.exception.DaoOperationException; 4 | import com.bobocode.model.Account; 5 | import com.bobocode.model.Gender; 6 | 7 | import javax.sql.DataSource; 8 | import java.sql.*; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class AccountDaoImpl implements AccountDao { 13 | private final static String INSERT_ACCOUNT_SQL = "INSERT INTO account(first_name, last_name, email, birthday, sex, balance) VALUES(?,?,?,?,?,?);"; 14 | private final static String SELECT_ACCOUNT_BY_ID_SQL = "SELECT * FROM account WHERE account.id = ?;"; 15 | private final static String SELECT_ALL_ACCOUNTS_SQL = "SELECT * FROM account;"; 16 | private final static String UPDATE_ACCOUNT_SQL = "UPDATE account SET first_name =?, last_name = ?, email = ?, birthday = ?, sex = ?, balance = ? WHERE id = ?;"; 17 | private DataSource dataSource; 18 | 19 | public AccountDaoImpl(DataSource dataSource) { 20 | this.dataSource = dataSource; 21 | } 22 | 23 | @Override 24 | public void save(Account account) { 25 | try (Connection connection = dataSource.getConnection()) { 26 | saveAccount(account, connection); 27 | } catch (SQLException e) { 28 | throw new DaoOperationException(e.getMessage(), e); 29 | } 30 | } 31 | 32 | private void saveAccount(Account account, Connection connection) throws SQLException { 33 | PreparedStatement insertStatement = prepareInsertStatement(connection, account); 34 | executeUpdate(insertStatement, "Account was not created"); 35 | Long id = fetchGeneratedId(insertStatement); 36 | account.setId(id); 37 | } 38 | 39 | private PreparedStatement prepareInsertStatement(Connection connection, Account account) { 40 | try { 41 | PreparedStatement insertStatement = connection.prepareStatement(INSERT_ACCOUNT_SQL, PreparedStatement.RETURN_GENERATED_KEYS); 42 | return fillStatementWithAccountData(insertStatement, account); 43 | } catch (SQLException e) { 44 | throw new DaoOperationException("Cannot prepare statement to insert account", e); 45 | } 46 | } 47 | 48 | private PreparedStatement fillStatementWithAccountData(PreparedStatement insertStatement, Account account) 49 | throws SQLException { 50 | insertStatement.setString(1, account.getFirstName()); 51 | insertStatement.setString(2, account.getLastName()); 52 | insertStatement.setString(3, account.getEmail()); 53 | insertStatement.setDate(4, Date.valueOf(account.getBirthday())); 54 | insertStatement.setInt(5, account.getGender().getValue()); 55 | insertStatement.setBigDecimal(6, account.getBalance()); 56 | return insertStatement; 57 | } 58 | 59 | private void executeUpdate(PreparedStatement insertStatement, String errorMessage) throws SQLException { 60 | int rowsAffected = insertStatement.executeUpdate(); 61 | if (rowsAffected == 0) { 62 | throw new DaoOperationException(errorMessage); 63 | } 64 | } 65 | 66 | private Long fetchGeneratedId(PreparedStatement insertStatement) throws SQLException { 67 | ResultSet generatedKeys = insertStatement.getGeneratedKeys(); 68 | 69 | if (generatedKeys.next()) { 70 | return generatedKeys.getLong(1); 71 | } else { 72 | throw new DaoOperationException("Can not obtain an account ID"); 73 | } 74 | } 75 | 76 | @Override 77 | public Account findOne(Long id) { 78 | try (Connection connection = dataSource.getConnection()) { 79 | return findAccountById(id, connection); 80 | } catch (SQLException e) { 81 | throw new DaoOperationException(String.format("Cannot find Account by id = %d", id), e); 82 | } 83 | } 84 | 85 | private Account findAccountById(Long id, Connection connection) throws SQLException { 86 | PreparedStatement selectByIdStatement = prepareSelectByIdStatement(id, connection); 87 | ResultSet resultSet = selectByIdStatement.executeQuery(); 88 | resultSet.next(); 89 | return parseRow(resultSet); 90 | } 91 | 92 | private PreparedStatement prepareSelectByIdStatement(Long id, Connection connection) { 93 | try { 94 | PreparedStatement selectByIdStatement = connection.prepareStatement(SELECT_ACCOUNT_BY_ID_SQL); 95 | selectByIdStatement.setLong(1, id); 96 | return selectByIdStatement; 97 | } catch (SQLException e) { 98 | throw new DaoOperationException("Cannot prepare statement to select account by id", e); 99 | } 100 | } 101 | 102 | private Account parseRow(ResultSet rs) throws SQLException { 103 | Account account = new Account(); 104 | account.setId(rs.getLong(1)); 105 | account.setFirstName(rs.getString(2)); 106 | account.setLastName(rs.getString(3)); 107 | account.setEmail(rs.getString(4)); 108 | account.setBirthday(rs.getDate(5).toLocalDate()); 109 | account.setGender(Gender.values()[rs.getInt(6)]); 110 | account.setBalance(rs.getBigDecimal(7)); 111 | account.setCreationTime(rs.getTimestamp(8).toLocalDateTime()); 112 | return account; 113 | } 114 | 115 | @Override 116 | public List findAll() { 117 | try (Connection connection = dataSource.getConnection()) { 118 | Statement statement = connection.createStatement(); 119 | ResultSet rs = statement.executeQuery(SELECT_ALL_ACCOUNTS_SQL); 120 | return collectToList(rs); 121 | } catch (SQLException e) { 122 | throw new DaoOperationException(e.getMessage()); 123 | } 124 | } 125 | 126 | @Override 127 | public void update(Account account) { 128 | try (Connection connection = dataSource.getConnection()) { 129 | PreparedStatement updateStatement = prepareUpdateStatement(account, connection); 130 | executeUpdate(updateStatement, "Account was not updated"); 131 | } catch (SQLException e) { 132 | throw new DaoOperationException(String.format("Cannot update Account with id = %d", account.getId()), e); 133 | } 134 | } 135 | 136 | private PreparedStatement prepareUpdateStatement(Account account, Connection connection) { 137 | try { 138 | PreparedStatement updateStatement = connection.prepareStatement(UPDATE_ACCOUNT_SQL); 139 | fillStatementWithAccountData(updateStatement, account); 140 | updateStatement.setLong(7, account.getId()); 141 | return updateStatement; 142 | } catch (SQLException e) { 143 | throw new DaoOperationException(String.format("Cannot prepare update statement for account id = %d", account.getId()), e); 144 | } 145 | } 146 | 147 | private List collectToList(ResultSet rs) throws SQLException { 148 | List accountList = new ArrayList<>(); 149 | while (rs.next()) { 150 | Account account = parseRow(rs); 151 | accountList.add(account); 152 | } 153 | 154 | return accountList; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /jdbc-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 | -------------------------------------------------------------------------------- /jdbc-dao/src/main/resources/account.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS account ( 2 | id SERIAL NOT NULL, 3 | first_name VARCHAR(255) NOT NULL, 4 | last_name VARCHAR(255) NOT NULL, 5 | email VARCHAR(255) NOT NULL, 6 | birthday TIMESTAMP NOT NULL, 7 | balance DECIMAL(19, 4), 8 | creation_time TIMESTAMP NOT NULL DEFAULT now(), 9 | 10 | CONSTRAINT account_pk PRIMARY KEY (id) 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /jdbc-dao/src/test/java/com/bobocode/AccountDaoTest.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.dao.AccountDao; 4 | import com.bobocode.dao.AccountDaoImpl; 5 | import com.bobocode.data.Accounts; 6 | import com.bobocode.model.Account; 7 | import com.bobocode.util.JdbcUtil; 8 | import org.junit.BeforeClass; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.junit.runners.JUnit4; 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.util.List; 19 | 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.assertNotNull; 22 | import static org.junit.Assert.assertTrue; 23 | 24 | @RunWith(JUnit4.class) 25 | public class AccountDaoTest { 26 | 27 | private static AccountDao accountDao; 28 | 29 | @BeforeClass 30 | public static void init() throws SQLException { 31 | DataSource h2DataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 32 | createAccountTable(h2DataSource); 33 | accountDao = new AccountDaoImpl(h2DataSource); 34 | } 35 | 36 | private static void createAccountTable(DataSource dataSource) throws SQLException { 37 | try (Connection connection = dataSource.getConnection()) { 38 | Statement createTableStatement = connection.createStatement(); 39 | createTableStatement.execute("CREATE TABLE IF NOT EXISTS account (\n" + 40 | " id SERIAL NOT NULL,\n" + 41 | " first_name VARCHAR(255) NOT NULL,\n" + 42 | " last_name VARCHAR(255) NOT NULL,\n" + 43 | " email VARCHAR(255) NOT NULL,\n" + 44 | " birthday TIMESTAMP NOT NULL,\n" + 45 | " sex TINYINT NOT NULL DEFAULT 0 CHECK (sex >= 0 AND sex <=2) ,\n" + 46 | " balance DECIMAL(19, 4),\n" + 47 | " creation_time TIMESTAMP NOT NULL DEFAULT now(),\n" + 48 | "\n" + 49 | " CONSTRAINT account_pk PRIMARY KEY (id)\n" + 50 | ");\n" + 51 | "\n"); 52 | } 53 | } 54 | 55 | @Test 56 | public void testSave() { 57 | Account account = Accounts.generateAccount(); 58 | 59 | int accountsCountBeforeInsert = accountDao.findAll().size(); 60 | accountDao.save(account); 61 | List accountList = accountDao.findAll(); 62 | 63 | assertNotNull(account.getId()); 64 | assertEquals(accountsCountBeforeInsert + 1, accountList.size()); 65 | assertTrue(accountList.contains(account)); 66 | } 67 | 68 | @Test 69 | public void testFindAll() { 70 | List generatedAccounts = Accounts.generateAccountList(10); 71 | generatedAccounts.stream().forEach(accountDao::save); 72 | 73 | List accountList = accountDao.findAll(); 74 | 75 | assertTrue(accountList.containsAll(generatedAccounts)); 76 | } 77 | 78 | @Test 79 | public void testFindById() { 80 | Account generatedAccount = Accounts.generateAccount(); 81 | accountDao.save(generatedAccount); 82 | 83 | Account account = accountDao.findOne(generatedAccount.getId()); 84 | 85 | assertEquals(generatedAccount, account); 86 | } 87 | 88 | @Test 89 | public void testUpdate() { 90 | Account generateAccount = Accounts.generateAccount(); 91 | accountDao.save(generateAccount); 92 | BigDecimal balanceBeforeUpdate = generateAccount.getBalance(); 93 | BigDecimal newBalance = balanceBeforeUpdate.add(BigDecimal.valueOf(15000)).setScale(2); 94 | 95 | generateAccount.setBalance(newBalance); 96 | accountDao.update(generateAccount); 97 | Account account = accountDao.findOne(generateAccount.getId()); 98 | 99 | assertEquals(newBalance, generateAccount.getBalance().setScale(2)); 100 | assertEquals(newBalance, account.getBalance().setScale(2)); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /jdbc-transaction/README.md: -------------------------------------------------------------------------------- 1 | # JDBC transaction tutorial 2 | 3 | The tutorial on JDBC API transaction management features 4 | 5 | ### Pre-conditions :heavy_exclamation_mark: 6 | You're supposed to be familiar with SQL and relational databases, have basic knowledge of JDK, and be able to write Java code. 7 | ## 8 | 9 | #### SQL 10 | Relational databases provide different commands to handle transactions. For isntance, PostgreSQL provides three self-descriptive SQL commands: 11 | 12 | ```sql 13 | START TRANSACTION 14 | ``` 15 | ```sql 16 | COMMIT 17 | ``` 18 | ```sql 19 | ROLLBACK 20 | ``` 21 | 22 | **All SQL queries that you perform after transaction started and before it was closed, is done in the scope of one transaction.** 23 | 24 | #### JDBC API 25 | JDBC encapsulates transaction management in class `java.sql.Connection`. **By default, it uses auto-commit mode.** E.g. 26 | all changes are committed automatically when you execute your statement. 27 | 28 | In order, to handle transaction manually you need to **turn off auto-commit mode**. 29 | ```java 30 | connection.setAutoCommit(false); 31 | ``` 32 | it will start new transaction, and all following statements will be executed in the scope of that transaction, 33 | until you call one of the following methods: 34 | 35 | ```java 36 | connection.commit(); 37 | ``` 38 | ```java 39 | connection.rollback(); 40 | ``` 41 | 42 | ### Best practices 43 | * prefer *try-with-resources* to `final` in order to close the resource 44 | * avoid using `auto-commit` mode 45 | * always catch exception and `rollback` a transaction as early as possible in case of error 46 | * avoid mixing transaction management with other logic 47 | 48 | -------------------------------------------------------------------------------- /jdbc-transaction/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-tutorial 7 | com.bobocode 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | jdbc-transaction 13 | 14 | 15 | 16 | com.bobocode 17 | jdbc-util 18 | 1.0-SNAPSHOT 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /jdbc-transaction/src/main/java/com/bobocode/JdbcTransactionManagementExample.java: -------------------------------------------------------------------------------- 1 | package com.bobocode; 2 | 3 | import com.bobocode.exception.JdbcTransactionExampleException; 4 | import com.bobocode.util.JdbcUtil; 5 | import org.apache.commons.lang3.RandomStringUtils; 6 | 7 | import javax.sql.DataSource; 8 | import java.sql.*; 9 | 10 | public class JdbcTransactionManagementExample { 11 | private static final String CREATE_POST_TABLE_SQL = "CREATE TABLE post(id SERIAL PRIMARY KEY, message VARCHAR(255) NOT NULL)"; 12 | private static final String INSERT_POST_SQL = "INSERT INTO post(message) VALUES (?)"; 13 | private static final String COUNT_POSTS_SQL = "SELECT count(*) FROM post"; 14 | private static final int POSTS_TO_INSERT_PER_OPERATION = 10; 15 | private static DataSource dataSource; 16 | 17 | public static void main(String[] args) { 18 | init(); 19 | savePostsWithCommit(); // will generate posts insert them into the db, and commit changes 20 | savePostsWithRollBack(); // will generate posts insert them into the db, and revert changes 21 | printNumberOfPostsInThDb(); 22 | } 23 | 24 | /** 25 | * Creates default in-memory H2 database and creates post table 26 | */ 27 | private static void init() { 28 | dataSource = JdbcUtil.createDefaultInMemoryH2DataSource(); 29 | createPostTable(); 30 | } 31 | 32 | private static void createPostTable() { 33 | try (Connection connection = dataSource.getConnection()) { 34 | Statement statement = connection.createStatement(); 35 | statement.execute(CREATE_POST_TABLE_SQL); 36 | } catch (SQLException e) { 37 | throw new JdbcTransactionExampleException("Error creating post database", e); 38 | } 39 | } 40 | 41 | /** 42 | * Generates posts and inserts it to the database. It turns of auto-commit mode at the beginning. It means that each 43 | * insert statement is performed in the scope of a single transaction. When all statements are executed it commits 44 | * the transaction 45 | */ 46 | private static void savePostsWithCommit() { 47 | try (Connection connection = dataSource.getConnection()) { 48 | connection.setAutoCommit(false); 49 | try (PreparedStatement insertStatement = connection.prepareStatement(INSERT_POST_SQL)) { 50 | insertRandomPosts(insertStatement); 51 | } 52 | connection.commit(); 53 | connection.setAutoCommit(true); 54 | } catch (SQLException e) { 55 | throw new JdbcTransactionExampleException("Error saving random posts with commit", e); 56 | } 57 | } 58 | 59 | private static void insertRandomPosts(PreparedStatement insertStatement) throws SQLException { 60 | for (int i = 0; i < POSTS_TO_INSERT_PER_OPERATION; i++) { 61 | String randomText = RandomStringUtils.randomAlphabetic(20); 62 | insertStatement.setString(1, randomText); 63 | insertStatement.executeUpdate(); 64 | } 65 | } 66 | 67 | /** 68 | * Generates posts and inserts it to the database. It turns of auto-commit mode at the beginning. It means that each 69 | * insert statement is performed in the scope of a single transaction. When all statements are executed it rollbacks 70 | * the transaction. The rollback will revert all changes made in the scope of this transaction, so no posts will 71 | * be stored. 72 | */ 73 | private static void savePostsWithRollBack() { 74 | try (Connection connection = dataSource.getConnection()) { 75 | connection.setAutoCommit(false); 76 | try (PreparedStatement insertStatement = connection.prepareStatement(INSERT_POST_SQL)) { 77 | insertRandomPosts(insertStatement); 78 | } 79 | connection.rollback(); 80 | } catch (SQLException e) { 81 | throw new JdbcTransactionExampleException("Error saving random posts with rollback", e); 82 | } 83 | } 84 | 85 | /** 86 | * Call the database to get the number of records in the post table. Prints the number of posts 87 | */ 88 | private static void printNumberOfPostsInThDb() { 89 | try (Connection connection = dataSource.getConnection()) { 90 | int postsCount = countPosts(connection); 91 | System.out.printf("Number of posts in the database is %d%n", postsCount); 92 | } catch (SQLException e) { 93 | throw new JdbcTransactionExampleException("Error selecting number of posts in the database", e); 94 | } 95 | } 96 | 97 | private static int countPosts(Connection connection) throws SQLException { 98 | Statement statement = connection.createStatement(); 99 | ResultSet countResultSet = statement.executeQuery(COUNT_POSTS_SQL); 100 | countResultSet.next(); 101 | return countResultSet.getInt(1); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /jdbc-transaction/src/main/java/com/bobocode/exception/JdbcTransactionExampleException.java: -------------------------------------------------------------------------------- 1 | package com.bobocode.exception; 2 | 3 | public class JdbcTransactionExampleException extends RuntimeException { 4 | public JdbcTransactionExampleException(String message) { 5 | super(message); 6 | } 7 | 8 | public JdbcTransactionExampleException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /jdbc-util/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | jdbc-api-tutorial 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/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", 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-tutorial 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | jdbc-basics 13 | jdbc-dao 14 | jdbc-util 15 | jdbc-account-data 16 | jdbc-batch-insert 17 | jdbc-transaction 18 | 19 | 20 | 21 | 1.10 22 | 1.10 23 | 24 | 25 | 26 | 27 | org.projectlombok 28 | lombok 29 | 1.18.0 30 | 31 | 32 | org.postgresql 33 | postgresql 34 | 9.4-1202-jdbc4 35 | 36 | 37 | com.h2database 38 | h2 39 | 1.4.197 40 | 41 | 42 | org.slf4j 43 | slf4j-simple 44 | 1.7.24 45 | 46 | 47 | junit 48 | junit 49 | 4.12 50 | 51 | 52 | org.apache.commons 53 | commons-lang3 54 | 3.8 55 | 56 | 57 | net.ttddyy 58 | datasource-proxy 59 | 1.4.9 60 | 61 | 62 | --------------------------------------------------------------------------------