diff --git a/config/resumes.properties b/config/resumes.properties index a36649bd..d5b791c1 100644 --- a/config/resumes.properties +++ b/config/resumes.properties @@ -1,4 +1,4 @@ storage.dir=/Users/vladimirsafronov/Desktop/java/projects/basejava/storage -db.url=jdbc:postgresql://localhost:5432/resumes +db.url=jdbc:postgresql://localhost:5433/resumes db.user=postgres db.password=admin \ No newline at end of file diff --git a/src/ru/javawebinar/basejava/sql/SqlHelper.java b/src/ru/javawebinar/basejava/sql/SqlHelper.java index 81e5d9d8..2d21805e 100644 --- a/src/ru/javawebinar/basejava/sql/SqlHelper.java +++ b/src/ru/javawebinar/basejava/sql/SqlHelper.java @@ -6,6 +6,7 @@ import ru.javawebinar.basejava.exception.StorageException; public class SqlHelper { + private final ConnectionFactory connectionFactory; public SqlHelper(ConnectionFactory connectionFactory) { @@ -24,4 +25,20 @@ public T execute(String query, SqlExecutor sqlExecutor) { throw new StorageException(e); } } + + public T transactionalExecute(SqlTransaction executor) { + try (Connection conn = connectionFactory.getConnection()) { + try { + conn.setAutoCommit(false); + T res = executor.execute(conn); + conn.commit(); + return res; + } catch (SQLException e) { + conn.rollback(); + throw ExceptionUtil.convertException(e); + } + } catch (SQLException e) { + throw new StorageException(e); + } + } } diff --git a/src/ru/javawebinar/basejava/sql/SqlTransaction.java b/src/ru/javawebinar/basejava/sql/SqlTransaction.java new file mode 100644 index 00000000..bd23a125 --- /dev/null +++ b/src/ru/javawebinar/basejava/sql/SqlTransaction.java @@ -0,0 +1,9 @@ +package ru.javawebinar.basejava.sql; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface SqlTransaction { + + T execute(Connection conn) throws SQLException; +} diff --git a/src/ru/javawebinar/basejava/storage/SqlStorage.java b/src/ru/javawebinar/basejava/storage/SqlStorage.java index e82f685b..dc61fd87 100644 --- a/src/ru/javawebinar/basejava/storage/SqlStorage.java +++ b/src/ru/javawebinar/basejava/storage/SqlStorage.java @@ -1,8 +1,12 @@ package ru.javawebinar.basejava.storage; +import java.sql.Connection; import java.sql.DriverManager; +import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import ru.javawebinar.basejava.exception.NotExistStorageException; @@ -25,33 +29,33 @@ public void clear() { @Override public void update(Resume r) { - sqlHelper.execute("UPDATE resume SET full_name = ? WHERE uuid = ?", ps -> { - ps.setString(1, r.getFullName()); - ps.setString(2, r.getUuid()); - if (ps.executeUpdate() == 0) { - throw new NotExistStorageException(r.getUuid()); + sqlHelper.transactionalExecute(conn -> { + try (PreparedStatement ps = conn.prepareStatement( + "UPDATE resume SET full_name = ? WHERE uuid = ?")) { + ps.setString(1, r.getFullName()); + ps.setString(2, r.getUuid()); + if (ps.executeUpdate() == 0) { + throw new NotExistStorageException(r.getUuid()); + } + deleteContacts(conn, r); + insertContacts(conn, r); + return null; } - return null; }); } @Override public void save(Resume r) { - sqlHelper.execute("INSERT INTO resume (uuid, full_name) VALUES (?,?)", ps -> { - ps.setString(1, r.getUuid()); - ps.setString(2, r.getFullName()); - ps.execute(); + sqlHelper.transactionalExecute(conn -> { + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO resume (uuid, full_name) VALUES (?,?)")) { + ps.setString(1, r.getUuid()); + ps.setString(2, r.getFullName()); + ps.execute(); + } + insertContacts(conn, r); return null; }); - for (Map.Entry e : r.getContacts().entrySet()) { - sqlHelper.execute("INSERT INTO contact (resume_uuid, type, value) VALUES (?,?,?)", - ps -> { - ps.setString(1, r.getUuid()); - ps.setString(2, e.getKey().name()); - ps.setString(3, e.getValue()); - return null; - }); - } } @Override @@ -67,9 +71,7 @@ public Resume get(String uuid) { } Resume resume = new Resume(uuid, rs.getString("full_name")); do { - String value = rs.getString("value"); - ContactType type = ContactType.valueOf(rs.getString("type")); - resume.addContact(type, value); + addContact(rs, resume); } while (rs.next()); return resume; }); @@ -88,13 +90,22 @@ public void delete(String uuid) { @Override public List getAllSorted() { - return sqlHelper.execute("SELECT * FROM resume ORDER BY full_name, uuid", ps -> { + return sqlHelper.execute("SELECT * FROM resume r " + + " LEFT JOIN contact c " + + " ON r.uuid = c.resume_uuid" + + " ORDER BY full_name, uuid", ps -> { ResultSet rs = ps.executeQuery(); - List list = new ArrayList<>(); + Map map = new LinkedHashMap<>(); while (rs.next()) { - list.add(new Resume(rs.getString("uuid"), rs.getString("full_name"))); + String uuid = rs.getString("uuid"); + Resume r = map.get(uuid); + if (r == null) { + r = new Resume(uuid, rs.getString("full_name")); + map.put(uuid, r); + } + addContact(rs, r); } - return list; + return new ArrayList<>(map.values()); }); } @@ -105,4 +116,32 @@ public int size() { return rs.next() ? rs.getInt("count") : 0; }); } + + private void deleteContacts(Connection conn, Resume r) { + sqlHelper.execute("DELETE FROM contact WHERE resume_uuid = ?", ps -> { + ps.setString(1, r.getUuid()); + ps.execute(); + return null; + }); + } + + private void insertContacts(Connection conn, Resume r) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement + ("INSERT INTO contact (resume_uuid, type, value) VALUES (?,?,?)")) { + for (Map.Entry e : r.getContacts().entrySet()) { + ps.setString(1, r.getUuid()); + ps.setString(2, e.getKey().name()); + ps.setString(3, e.getValue()); + ps.addBatch(); + } + ps.executeBatch(); + } + } + + private void addContact(ResultSet rs, Resume r) throws SQLException { + String value = rs.getString("value"); + if (value != null) { + r.addContact(ContactType.valueOf(rs.getString("type")), value); + } + } } diff --git a/storage/d7e81904-29f4-462e-ae33-6f89f2b365bb b/storage/357d7d32-d69d-4bc8-bc6d-e1423544d4d6 similarity index 71% rename from storage/d7e81904-29f4-462e-ae33-6f89f2b365bb rename to storage/357d7d32-d69d-4bc8-bc6d-e1423544d4d6 index 0075ba29..5e54053a 100644 --- a/storage/d7e81904-29f4-462e-ae33-6f89f2b365bb +++ b/storage/357d7d32-d69d-4bc8-bc6d-e1423544d4d6 @@ -1,6 +1,6 @@ - d7e81904-29f4-462e-ae33-6f89f2b365bb + 357d7d32-d69d-4bc8-bc6d-e1423544d4d6 Name3 diff --git a/storage/d487ad02-280e-488a-b25d-ac45f1686728 b/storage/84d3d6d8-ed87-4f4a-85d0-fd7b64af8518 similarity index 71% rename from storage/d487ad02-280e-488a-b25d-ac45f1686728 rename to storage/84d3d6d8-ed87-4f4a-85d0-fd7b64af8518 index 5168be2a..9a96d25d 100644 --- a/storage/d487ad02-280e-488a-b25d-ac45f1686728 +++ b/storage/84d3d6d8-ed87-4f4a-85d0-fd7b64af8518 @@ -1,6 +1,6 @@ - d487ad02-280e-488a-b25d-ac45f1686728 + 84d3d6d8-ed87-4f4a-85d0-fd7b64af8518 Name2 diff --git a/storage/9ae5ec0a-5255-4885-9867-660ed5a57e1b b/storage/9ae5ec0a-5255-4885-9867-660ed5a57e1b deleted file mode 100644 index 69befae2..00000000 --- a/storage/9ae5ec0a-5255-4885-9867-660ed5a57e1b +++ /dev/null @@ -1,7 +0,0 @@ - - - 9ae5ec0a-5255-4885-9867-660ed5a57e1b - Name1 - - - diff --git a/storage/9be342f4-a735-4fef-a4c9-64970eee716c b/storage/9be342f4-a735-4fef-a4c9-64970eee716c new file mode 100644 index 00000000..293b3974 --- /dev/null +++ b/storage/9be342f4-a735-4fef-a4c9-64970eee716c @@ -0,0 +1,20 @@ + + + 9be342f4-a735-4fef-a4c9-64970eee716c + Name1 + + + MOBILE_PHONE + +79877654321 + + + SKYPE + newSkype + + + EMAIL + m1@google.com + + + + diff --git a/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java b/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java index 06cfabfd..fd4bb5b1 100644 --- a/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java +++ b/test/ru/javawebinar/basejava/storage/AbstractStorageTest.java @@ -4,6 +4,7 @@ import java.io.File; import java.time.Month; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.UUID; import org.junit.jupiter.api.Test; @@ -39,8 +40,12 @@ protected AbstractStorageTest(Storage storage) { R3 = new Resume(UUID_3, "Name3"); R4 = new Resume(UUID_4, "Name4"); -// R1.addContact(ContactType.MOBILE_PHONE, "+79001234567"); -// R1.addContact(ContactType.EMAIL, "abc@mail.ru"); + R1.addContact(ContactType.MOBILE_PHONE, "+79001234567"); + R1.addContact(ContactType.EMAIL, "abc@mail.ru"); + R4.addContact(ContactType.SKYPE, "Skype"); + R4.addContact(ContactType.MOBILE_PHONE, "+76666666666"); + + // R1.addSection(SectionType.OBJECTIVE, new SectionLine("Objective")); // R1.addSection(SectionType.PERSONAL, new SectionLine("Personal data")); // R1.addSection(SectionType.ACHIEVEMENT, @@ -80,6 +85,9 @@ public void setUp() { @Test public void update() { Resume newResume = new Resume(UUID_1, "New Name"); + R1.addContact(ContactType.EMAIL, "m1@google.com"); + R1.addContact(ContactType.SKYPE, "newSkype"); + R1.addContact(ContactType.MOBILE_PHONE, "+79877654321"); storage.update(newResume); Assertions.assertEquals(newResume, storage.get(UUID_1)); } @@ -139,7 +147,10 @@ public void getNotExist() throws Exception { public void getAllSorted() throws Exception { List testStorage = storage.getAllSorted(); Assertions.assertEquals(3, testStorage.size()); - Assertions.assertEquals(testStorage, Arrays.asList(R1, R2, R3)); + List sortedList = Arrays.asList(R1, R2, R3); + Collections.sort(sortedList); + Assertions.assertEquals(sortedList, testStorage); + } @Test