From e9e1da41b7dd32d136e789ef02c667bcd0ccf540 Mon Sep 17 00:00:00 2001 From: dohoonkim Date: Sun, 10 Nov 2024 15:58:58 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20=EC=B6=94=EC=83=81=ED=99=94?= =?UTF-8?q?=EB=90=9C=20=EB=B3=80=EC=88=98=EB=AA=85=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/persistence/entity/EntityQueryHandler.java | 4 ++-- src/main/java/persistence/sql/dml/UpdateQuery.java | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/persistence/entity/EntityQueryHandler.java b/src/main/java/persistence/entity/EntityQueryHandler.java index 0dbb6b9e4..2ed216fe2 100644 --- a/src/main/java/persistence/entity/EntityQueryHandler.java +++ b/src/main/java/persistence/entity/EntityQueryHandler.java @@ -35,9 +35,9 @@ public T findById(Object primaryKey) { return jdbcTemplate.queryForObject(sql, entityLoader); } - public void update(Object entity, List changedColumns) throws IllegalAccessException { + public void update(Object entity, List updateColumns) throws IllegalAccessException { UpdateQuery updateQuery = sqlQueries.getSqlQuery(UPDATE); - String sql = updateQuery.generateQuery(entity, changedColumns); + String sql = updateQuery.generateQuery(entity, updateColumns); jdbcTemplate.execute(sql); } diff --git a/src/main/java/persistence/sql/dml/UpdateQuery.java b/src/main/java/persistence/sql/dml/UpdateQuery.java index 80d274977..ce4ea02f1 100644 --- a/src/main/java/persistence/sql/dml/UpdateQuery.java +++ b/src/main/java/persistence/sql/dml/UpdateQuery.java @@ -6,23 +6,22 @@ public class UpdateQuery implements SqlQuery { - public String generateQuery(Object entity, List changedColumns) throws IllegalAccessException { - List changedValues = new ColumnValues<>(entity).getValuesByColumns(changedColumns); + public String generateQuery(Object entity, List updateColumns) throws IllegalAccessException { String tableName = new EntityTableMetadata(entity.getClass()).getTableName(); - + List values = new ColumnValues<>(entity).getValuesByColumns(updateColumns); Long id = new EntityId(entity).getId(); - return generateUpdateQuery(tableName, changedValues, id); + return generateUpdateQuery(tableName, values, id); } private String generateUpdateQuery( String tableName, - List changedValues, + List values, Long id ) { return new QueryBuilder() .update(tableName) - .setValues(changedValues) + .setValues(values) .where("id = " + id) .build(); } From 503ff7bd72d91f85ee584b6a8fe93100b672825e Mon Sep 17 00:00:00 2001 From: dohoonkim Date: Mon, 11 Nov 2024 12:38:34 +0900 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20insert=20=EC=8B=9C=20id?= =?UTF-8?q?=EA=B0=80=20=ED=8F=AC=ED=95=A8=EB=90=9C=20entity=EA=B0=80=20?= =?UTF-8?q?=EB=A6=AC=ED=84=B4=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/jdbc/JdbcTemplate.java | 15 +++++++++++++++ .../persistence/entity/EntityPersister.java | 17 +++++++++++++++-- .../persistence/entity/EntityQueryHandler.java | 4 ++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/main/java/jdbc/JdbcTemplate.java b/src/main/java/jdbc/JdbcTemplate.java index 45217fb14..f86919fac 100644 --- a/src/main/java/jdbc/JdbcTemplate.java +++ b/src/main/java/jdbc/JdbcTemplate.java @@ -25,6 +25,21 @@ public void execute(final String sql) { } } + public Long executeAndReturnGeneratedKey(final String sql) { + try (final Statement statement = connection.createStatement()) { + statement.execute(sql, Statement.RETURN_GENERATED_KEYS); + try (ResultSet generatedKeys = statement.getGeneratedKeys()) { + if (generatedKeys.next()) { + return generatedKeys.getLong(1); + } else { + throw new RuntimeException("No ID generated for the insert."); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public T queryForObject(final String sql, final RowMapper rowMapper) { final List results = query(sql, rowMapper); if (results.size() != 1) { diff --git a/src/main/java/persistence/entity/EntityPersister.java b/src/main/java/persistence/entity/EntityPersister.java index f3d163eb1..ce62fd73d 100644 --- a/src/main/java/persistence/entity/EntityPersister.java +++ b/src/main/java/persistence/entity/EntityPersister.java @@ -1,5 +1,6 @@ package persistence.entity; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,9 +26,10 @@ public void update(Object entity, List changedColumns) throws IllegalAcc .update(entity, changedColumns); } - public void insert(Object entity) throws IllegalAccessException { - getEntityQueryHandler(entity.getClass()) + public Object insert(Object entity) throws IllegalAccessException { + long id = getEntityQueryHandler(entity.getClass()) .insert(entity); + return setId(entity, id); } private EntityQueryHandler getEntityQueryHandler(Class entityClass) { @@ -37,4 +39,15 @@ private EntityQueryHandler getEntityQueryHandler(Class entityClass) { return entityQueryHandlerMap.get(entityClass); } + private Object setId(Object entity, long id) throws IllegalAccessException { + try { + Field idField = entity.getClass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(entity, id); + return entity; + } catch (NoSuchFieldException e) { + throw new IllegalStateException("Entity does not have an 'id' field", e); + } + } + } diff --git a/src/main/java/persistence/entity/EntityQueryHandler.java b/src/main/java/persistence/entity/EntityQueryHandler.java index 2ed216fe2..1b60465db 100644 --- a/src/main/java/persistence/entity/EntityQueryHandler.java +++ b/src/main/java/persistence/entity/EntityQueryHandler.java @@ -41,10 +41,10 @@ public void update(Object entity, List updateColumns) throws IllegalAcce jdbcTemplate.execute(sql); } - public void insert(Object entity) throws IllegalAccessException { + public long insert(Object entity) throws IllegalAccessException { InsertQuery insertQuery = sqlQueries.getSqlQuery(INSERT); String sql = insertQuery.generateQuery(entity); - jdbcTemplate.execute(sql); + return jdbcTemplate.executeAndReturnGeneratedKey(sql); } public void delete(Object entity) { From 6d2fddf6e6aad1fb1b89d8f481739b09271ad2b4 Mon Sep 17 00:00:00 2001 From: dohoonkim Date: Mon, 11 Nov 2024 12:39:55 +0900 Subject: [PATCH 3/5] =?UTF-8?q?refactor:=20id=EA=B0=80=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20entity=20=EC=A0=80=EC=9E=A5=EC=86=8C=EC=97=90?= =?UTF-8?q?=EC=84=9C=20id=EA=B0=80=20=ED=8F=AC=ED=95=A8=EB=90=9C=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=86=8C=EB=A1=9C=20=EC=8A=B9=EA=B2=A9?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/persistence/entity/PersistenceContext.java | 2 +- .../persistence/entity/impl/EntityManagerImpl.java | 6 ++++-- .../entity/impl/PersistenceContextImpl.java | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/persistence/entity/PersistenceContext.java b/src/main/java/persistence/entity/PersistenceContext.java index cf9c9f431..85ff26b10 100644 --- a/src/main/java/persistence/entity/PersistenceContext.java +++ b/src/main/java/persistence/entity/PersistenceContext.java @@ -19,6 +19,6 @@ public interface PersistenceContext { Object getDatabaseSnapshot(Object entity); - void reset(); + void promotePendingToPersistent(Object entity) throws IllegalAccessException; } diff --git a/src/main/java/persistence/entity/impl/EntityManagerImpl.java b/src/main/java/persistence/entity/impl/EntityManagerImpl.java index 069e12f18..ab8bc053c 100644 --- a/src/main/java/persistence/entity/impl/EntityManagerImpl.java +++ b/src/main/java/persistence/entity/impl/EntityManagerImpl.java @@ -49,15 +49,17 @@ public void remove(Object entity) { @Override public void flush() throws IllegalAccessException { for (Object entity : persistenceContext.getPendingEntities()) { - entityPersister.insert(entity); + Object entityWithId = entityPersister.insert(entity); + persistenceContext.captureDatabaseSnapshot(entityWithId); + persistenceContext.promotePendingToPersistent(entityWithId); } for (Object entity : persistenceContext.getPersistedEntities()) { List changedColumns = dirtyCheck.findDirtyColumns(entity); if (!changedColumns.isEmpty()) { entityPersister.update(entity, changedColumns); + persistenceContext.captureDatabaseSnapshot(entity); } } - persistenceContext.reset(); } @Override diff --git a/src/main/java/persistence/entity/impl/PersistenceContextImpl.java b/src/main/java/persistence/entity/impl/PersistenceContextImpl.java index e058ef6e9..e1f443b4e 100644 --- a/src/main/java/persistence/entity/impl/PersistenceContextImpl.java +++ b/src/main/java/persistence/entity/impl/PersistenceContextImpl.java @@ -64,10 +64,10 @@ public Object getDatabaseSnapshot(Object entity) { } @Override - public void reset() { - pendingEntities.clear(); - persistedEntities.clear(); - databaseSnapshots.clear(); + public void promotePendingToPersistent(Object entity) { + pendingEntities.removeEntity(entity); + long id = new LongTypeId(entity).getId(); + persistedEntities.persistEntity(new EntityKey(id, entity.getClass().getName()), entity); } private EntityKey getEntityKey(Object entity) { @@ -77,4 +77,6 @@ private EntityKey getEntityKey(Object entity) { ); } + + } From 1358672f4ef956fd64ddd6965a751c0ccb8f90ed Mon Sep 17 00:00:00 2001 From: dohoonkim Date: Mon, 11 Nov 2024 12:40:25 +0900 Subject: [PATCH 4/5] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/CustomJpaRepositoryTest.java | 8 +--- .../entity/impl/EntityManagerImplTest.java | 37 ++++++++++++++----- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/test/java/persistence/entity/CustomJpaRepositoryTest.java b/src/test/java/persistence/entity/CustomJpaRepositoryTest.java index 40a56b82f..bf5f0b6b4 100644 --- a/src/test/java/persistence/entity/CustomJpaRepositoryTest.java +++ b/src/test/java/persistence/entity/CustomJpaRepositoryTest.java @@ -59,9 +59,7 @@ void saveWithDirty() throws SQLException, IllegalAccessException { em.persist(person); em.flush(); - Person personOne = em.find(Person.class, 1L); - personOne.setName("Jane"); - + person.setName("Jane"); em.getTransaction().commit(); Person updatedPerson = em.find(Person.class, 1L); @@ -84,9 +82,7 @@ void saveWithDirtyWithNullColumn() throws SQLException, IllegalAccessException { em.persist(person); em.flush(); - Person personOne = em.find(Person.class, 1L); - personOne.setAge(null); - + person.setAge(null); em.getTransaction().commit(); Person updatedPerson = em.find(Person.class, 1L); diff --git a/src/test/java/persistence/entity/impl/EntityManagerImplTest.java b/src/test/java/persistence/entity/impl/EntityManagerImplTest.java index 11f6f858e..8e9a6fcea 100644 --- a/src/test/java/persistence/entity/impl/EntityManagerImplTest.java +++ b/src/test/java/persistence/entity/impl/EntityManagerImplTest.java @@ -2,6 +2,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import database.DatabaseServer; import database.H2; @@ -47,7 +48,7 @@ void createTable() { @Test @DisplayName("find 구현해보기") - void findTest() throws SQLException, IllegalAccessException { + void findTest() throws SQLException { EntityManagerFactory entityManagerFactory = new EntityManagerFactoryImpl(server); EntityManager em = entityManagerFactory.createEntityManager(); @@ -58,9 +59,7 @@ void findTest() throws SQLException, IllegalAccessException { .age(20) .email("john@naver.com") .build(); - em.persist(person); - em.flush(); em.getTransaction().commit(); @@ -74,7 +73,7 @@ void findTest() throws SQLException, IllegalAccessException { @Test @DisplayName("remove 구현해보기") - void removeTest() throws SQLException, IllegalAccessException { + void removeTest() throws SQLException { EntityManagerFactory entityManagerFactory = new EntityManagerFactoryImpl(server); EntityManager em = entityManagerFactory.createEntityManager(); @@ -85,10 +84,9 @@ void removeTest() throws SQLException, IllegalAccessException { .age(20) .email("john@naver.com") .build(); - em.persist(person); + em.remove(person); - em.flush(); em.getTransaction().commit(); @@ -97,6 +95,26 @@ void removeTest() throws SQLException, IllegalAccessException { .hasMessageContaining("Expected 1 result, got 0"); } + @Test + @DisplayName("flush 이후 entity id 조회 가능 여부 테스트") + void flushAndFindId() throws SQLException, IllegalAccessException { + EntityManagerFactory entityManagerFactory = new EntityManagerFactoryImpl(server); + EntityManager em = entityManagerFactory.createEntityManager(); + + Person person = Person.builder() + .name("John") + .age(20) + .email("john@naver.com") + .build(); + + assertNull(person.getId()); + + em.persist(person); + em.flush(); + + assertEquals(1L, person.getId()); + } + @Test @DisplayName("update 구현해보기") void updateTest() throws SQLException, IllegalAccessException { @@ -113,14 +131,13 @@ void updateTest() throws SQLException, IllegalAccessException { em.persist(person); em.flush(); - Person personOne = em.find(Person.class, 1L); - personOne.setName("Jane"); - em.flush(); - + person.setName("Jane"); + person.setAge(30); em.getTransaction().commit(); Person updatedPerson = em.find(Person.class, 1L); assertEquals("Jane", updatedPerson.getName()); + assertEquals(30, updatedPerson.getAge()); } @AfterEach From 3b7057a77bf7466a00184ff1542080b990ff1efd Mon Sep 17 00:00:00 2001 From: dohoonkim Date: Mon, 11 Nov 2024 20:45:00 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=EC=9A=94=EA=B5=AC=20=EC=82=AC?= =?UTF-8?q?=ED=95=AD=201=20-=20Entity=20=EC=9D=98=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=ED=94=84=20=EC=82=AC=EC=9D=B4=ED=81=B4=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20=EC=88=98=ED=96=89=20=EC=8B=9C=20=EC=97=94?= =?UTF-8?q?=ED=84=B0=ED=8B=B0=EC=9D=98=20=EC=83=81=ED=83=9C=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=B4=EB=B3=B4=EC=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++- .../persistence/entity/DatabaseSnapshots.java | 34 ------------ .../java/persistence/entity/DirtyCheck.java | 19 ------- .../java/persistence/entity/EntityEntry.java | 36 +++++++++++++ .../persistence/entity/EntityPersister.java | 5 ++ .../persistence/entity/PendingEntities.java | 21 ++++---- .../persistence/entity/PersistedEntities.java | 52 +++++++++++++++---- .../entity/PersistenceContext.java | 19 ++++--- src/main/java/persistence/entity/Status.java | 10 ++++ .../entity/impl/EntityManagerImpl.java | 27 +++++----- .../entity/impl/PersistenceContextImpl.java | 42 ++++++++------- .../entity/impl/EntityManagerImplTest.java | 25 +++++++++ 12 files changed, 183 insertions(+), 113 deletions(-) delete mode 100644 src/main/java/persistence/entity/DatabaseSnapshots.java delete mode 100644 src/main/java/persistence/entity/DirtyCheck.java create mode 100644 src/main/java/persistence/entity/EntityEntry.java create mode 100644 src/main/java/persistence/entity/Status.java diff --git a/README.md b/README.md index 5e6c2d36d..32f77f5a5 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,8 @@ ## 3단계 ### 요구 사항1 - 만들었던 PersistenceContext 에서 효율적인 메모리 관리를 위한 기능 구현 (1차 캐싱)[O] -### 요구 사항2 - 더티체킹 구현[] +### 요구 사항2 - 더티체킹 구현[O] + +## 4단계 +### 요구 사항 1 - Entity 의 라이프 사이클 관리 작업 수행 시 엔터티의 상태를 추가[O] + diff --git a/src/main/java/persistence/entity/DatabaseSnapshots.java b/src/main/java/persistence/entity/DatabaseSnapshots.java deleted file mode 100644 index f1e05421b..000000000 --- a/src/main/java/persistence/entity/DatabaseSnapshots.java +++ /dev/null @@ -1,34 +0,0 @@ -package persistence.entity; - -import static persistence.util.ReflectionCopy.copy; - -import java.util.HashMap; -import java.util.Map; - -public class DatabaseSnapshots { - - private final Map databaseSnapshots; - - public DatabaseSnapshots() { - this.databaseSnapshots = new HashMap<>(); - } - - public void addDatabaseSnapshot(Object entity) { - long id = new LongTypeId(entity).getId(); - EntityKey entityKey = new EntityKey(id, entity.getClass().getName()); - Object copyEntity = copy(entity); - - databaseSnapshots.put(entityKey, copyEntity); - } - - public Object getDatabaseSnapshot(Object entity) { - long id = new LongTypeId(entity).getId(); - EntityKey entityKey = new EntityKey(id, entity.getClass().getName()); - return databaseSnapshots.get(entityKey); - } - - public void clear() { - databaseSnapshots.clear(); - } - -} diff --git a/src/main/java/persistence/entity/DirtyCheck.java b/src/main/java/persistence/entity/DirtyCheck.java deleted file mode 100644 index 5ca4af1a4..000000000 --- a/src/main/java/persistence/entity/DirtyCheck.java +++ /dev/null @@ -1,19 +0,0 @@ -package persistence.entity; - -import java.util.List; -import persistence.sql.dml.ColumnValues; - -public class DirtyCheck { - - private final PersistenceContext persistenceContext; - - public DirtyCheck(PersistenceContext persistenceContext) { - this.persistenceContext = persistenceContext; - } - - public List findDirtyColumns(Object entity) throws IllegalAccessException { - Object snapshot = persistenceContext.getDatabaseSnapshot(entity); - return new ColumnValues<>(snapshot).findDifferentColumns(new ColumnValues<>(entity)); - } - -} diff --git a/src/main/java/persistence/entity/EntityEntry.java b/src/main/java/persistence/entity/EntityEntry.java new file mode 100644 index 000000000..b8e2f58b7 --- /dev/null +++ b/src/main/java/persistence/entity/EntityEntry.java @@ -0,0 +1,36 @@ +package persistence.entity; + +import static persistence.util.ReflectionCopy.copy; + +import java.util.List; +import lombok.Getter; +import persistence.sql.dml.ColumnValues; + +public class EntityEntry { + @Getter + private final Object entity; + @Getter + private Status status; + private Object snapshot; + + public EntityEntry(Object entity, Status status) { + this.entity = entity; + this.status = status; + } + + public void updateStatus(Status newStatus) { + this.status = newStatus; + } + + public void updateSnapshot(Object entity) { + this.snapshot = copy(entity); + } + + public List findDirtyColumns(Object entity) throws IllegalAccessException { + if (status != Status.MANAGED) { + throw new IllegalAccessException("Entity is not managed"); + } + return new ColumnValues<>(snapshot).findDifferentColumns(new ColumnValues<>(entity)); + } + +} diff --git a/src/main/java/persistence/entity/EntityPersister.java b/src/main/java/persistence/entity/EntityPersister.java index ce62fd73d..03ec6ca8e 100644 --- a/src/main/java/persistence/entity/EntityPersister.java +++ b/src/main/java/persistence/entity/EntityPersister.java @@ -32,6 +32,11 @@ public Object insert(Object entity) throws IllegalAccessException { return setId(entity, id); } + public void delete(Object entity) { + getEntityQueryHandler(entity.getClass()) + .delete(entity); + } + private EntityQueryHandler getEntityQueryHandler(Class entityClass) { if (!entityQueryHandlerMap.containsKey(entityClass)) { entityQueryHandlerMap.put(entityClass, new EntityQueryHandler<>(entityClass, jdbcTemplate)); diff --git a/src/main/java/persistence/entity/PendingEntities.java b/src/main/java/persistence/entity/PendingEntities.java index fa7581554..014f232e7 100644 --- a/src/main/java/persistence/entity/PendingEntities.java +++ b/src/main/java/persistence/entity/PendingEntities.java @@ -1,26 +1,27 @@ package persistence.entity; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class PendingEntities { - private final Set pendingEntities = new HashSet<>(); + private final Map entityEntries = new HashMap<>(); public void persistEntity(Object entity) { - pendingEntities.add(entity); + entityEntries.put(getKey(entity), new EntityEntry(entity, Status.SAVING)); } - public Set getEntities() { - return pendingEntities; + public List getEntities() { + return entityEntries.values().stream().map(EntityEntry::getEntity).toList(); } - public void removeEntity(Object entity) { - pendingEntities.remove(entity); + public void evict(Object entity) { + entityEntries.remove(getKey(entity)); } - public void clear() { - pendingEntities.clear(); + private int getKey(Object entity) { + return System.identityHashCode(entity); } } diff --git a/src/main/java/persistence/entity/PersistedEntities.java b/src/main/java/persistence/entity/PersistedEntities.java index 3227218be..c025b2065 100644 --- a/src/main/java/persistence/entity/PersistedEntities.java +++ b/src/main/java/persistence/entity/PersistedEntities.java @@ -1,31 +1,63 @@ package persistence.entity; -import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; public class PersistedEntities { - private final Map persistedEntities = new HashMap<>(); + private final Map entityEntries = new HashMap<>(); public Object findEntity(EntityKey entityKey) { - return persistedEntities.getOrDefault(entityKey, null); + if (entityEntries.containsKey(entityKey)) { + return entityEntries.get(entityKey).getEntity(); + } + return null; } public void persistEntity(EntityKey entityKey, Object entity) { - persistedEntities.put(entityKey, entity); + var entityEntry = new EntityEntry(entity, Status.MANAGED); + entityEntry.updateSnapshot(entity); + entityEntries.put(entityKey, entityEntry); } - public void removeEntity(EntityKey entityKey) { - persistedEntities.remove(entityKey); + public void evict(Object entity) { + var entityKey = getKey(entity); + entityEntries.remove(entityKey); } - public Collection getEntities() { - return persistedEntities.values(); + public void changeToDeleteState(EntityKey entityKey) { + var entityEntry = entityEntries.get(entityKey); + if (entityEntry == null) { + return; + } + entityEntry.updateStatus(Status.DELETED); } - public void clear() { - persistedEntities.clear(); + public List getDeletedEntities() { + return entityEntries.values().stream().filter(e -> e.getStatus() == Status.DELETED).map(EntityEntry::getEntity) + .toList(); + } + + public List getManagedEntities() { + return entityEntries.values().stream().filter(e -> e.getStatus() == Status.MANAGED).map(EntityEntry::getEntity) + .toList(); + } + + public List findDirtyColumns(Object entity) throws IllegalAccessException { + var entityKey = getKey(entity); + var entityEntry = entityEntries.get(entityKey); + return entityEntry.findDirtyColumns(entity); + } + + public void updateDatabaseSnapshot(Object entity) { + var entityKey = getKey(entity); + var entityEntry = entityEntries.get(entityKey); + entityEntry.updateSnapshot(entity); + } + + private EntityKey getKey(Object entity) { + return new EntityKey(new LongTypeId(entity).getId(), entity.getClass().getName()); } } diff --git a/src/main/java/persistence/entity/PersistenceContext.java b/src/main/java/persistence/entity/PersistenceContext.java index 85ff26b10..3fdb7f55b 100644 --- a/src/main/java/persistence/entity/PersistenceContext.java +++ b/src/main/java/persistence/entity/PersistenceContext.java @@ -1,24 +1,27 @@ package persistence.entity; -import java.util.Collection; -import java.util.Set; +import java.util.List; public interface PersistenceContext { T getEntity(Class entityClass, Object primaryKey); - void attachEntity(Object entity); + void addEntity(Object entity); + + void removeEntity(Object entity); void detachEntity(Object entity); - Set getPendingEntities(); + List getSavingEntities(); + + List getDeletedEntities(); - Collection getPersistedEntities(); + List getManagedEntities(); - void captureDatabaseSnapshot(Object entity); + List findDirtyColumns(Object entity) throws IllegalAccessException; - Object getDatabaseSnapshot(Object entity); + void updateDatabaseSnapshot(Object entity); - void promotePendingToPersistent(Object entity) throws IllegalAccessException; + void mangeEntity(Object entity) throws IllegalAccessException; } diff --git a/src/main/java/persistence/entity/Status.java b/src/main/java/persistence/entity/Status.java new file mode 100644 index 000000000..fc17474df --- /dev/null +++ b/src/main/java/persistence/entity/Status.java @@ -0,0 +1,10 @@ +package persistence.entity; + +public enum Status { + MANAGED, + READ_ONLY, + DELETED, + GONE, + LOADING, + SAVING +} diff --git a/src/main/java/persistence/entity/impl/EntityManagerImpl.java b/src/main/java/persistence/entity/impl/EntityManagerImpl.java index ab8bc053c..fb9055b80 100644 --- a/src/main/java/persistence/entity/impl/EntityManagerImpl.java +++ b/src/main/java/persistence/entity/impl/EntityManagerImpl.java @@ -3,7 +3,6 @@ import java.sql.Connection; import java.util.List; import jdbc.TransactionalJdbcTemplate; -import persistence.entity.DirtyCheck; import persistence.entity.EntityManager; import persistence.entity.EntityPersister; import persistence.entity.PersistenceContext; @@ -13,13 +12,11 @@ public class EntityManagerImpl implements EntityManager { private final PersistenceContext persistenceContext; private final TransactionalJdbcTemplate transactionalJdbcTemplate; private final EntityPersister entityPersister; - private final DirtyCheck dirtyCheck; public EntityManagerImpl(Connection connection) { this.transactionalJdbcTemplate = new TransactionalJdbcTemplate(connection); this.persistenceContext = new PersistenceContextImpl(); this.entityPersister = new EntityPersister(transactionalJdbcTemplate); - this.dirtyCheck = new DirtyCheck(persistenceContext); } @Override @@ -29,8 +26,7 @@ public T find(Class entityClass, Object primaryKey) { entity = entityClass.cast( entityPersister.findById(entityClass, primaryKey) ); - persistenceContext.captureDatabaseSnapshot(entity); - persistenceContext.attachEntity(entity); + persistenceContext.addEntity(entity); } return entity; @@ -38,26 +34,31 @@ public T find(Class entityClass, Object primaryKey) { @Override public void persist(Object entity) { - persistenceContext.attachEntity(entity); + persistenceContext.addEntity(entity); } @Override public void remove(Object entity) { - persistenceContext.detachEntity(entity); + persistenceContext.removeEntity(entity); } @Override public void flush() throws IllegalAccessException { - for (Object entity : persistenceContext.getPendingEntities()) { + for (Object entity : persistenceContext.getSavingEntities()) { Object entityWithId = entityPersister.insert(entity); - persistenceContext.captureDatabaseSnapshot(entityWithId); - persistenceContext.promotePendingToPersistent(entityWithId); + persistenceContext.mangeEntity(entityWithId); } - for (Object entity : persistenceContext.getPersistedEntities()) { - List changedColumns = dirtyCheck.findDirtyColumns(entity); + + for (Object entity : persistenceContext.getDeletedEntities()) { + entityPersister.delete(entity); + persistenceContext.detachEntity(entity); + } + + for (Object entity : persistenceContext.getManagedEntities()) { + List changedColumns = persistenceContext.findDirtyColumns(entity); if (!changedColumns.isEmpty()) { entityPersister.update(entity, changedColumns); - persistenceContext.captureDatabaseSnapshot(entity); + persistenceContext.updateDatabaseSnapshot(entity); } } } diff --git a/src/main/java/persistence/entity/impl/PersistenceContextImpl.java b/src/main/java/persistence/entity/impl/PersistenceContextImpl.java index e1f443b4e..e6e333b18 100644 --- a/src/main/java/persistence/entity/impl/PersistenceContextImpl.java +++ b/src/main/java/persistence/entity/impl/PersistenceContextImpl.java @@ -1,8 +1,6 @@ package persistence.entity.impl; -import java.util.Collection; -import java.util.Set; -import persistence.entity.DatabaseSnapshots; +import java.util.List; import persistence.entity.EntityKey; import persistence.entity.LongTypeId; import persistence.entity.PendingEntities; @@ -13,12 +11,10 @@ public class PersistenceContextImpl implements PersistenceContext { private final PersistedEntities persistedEntities; private final PendingEntities pendingEntities; - private final DatabaseSnapshots databaseSnapshots; public PersistenceContextImpl() { persistedEntities = new PersistedEntities(); pendingEntities = new PendingEntities(); - databaseSnapshots = new DatabaseSnapshots(); } @Override @@ -29,7 +25,7 @@ public T getEntity(Class entityClass, Object primaryKey) { @Override - public void attachEntity(Object entity) { + public void addEntity(Object entity) { if (new LongTypeId(entity).isEntityIdNull()) { pendingEntities.persistEntity(entity); return; @@ -37,35 +33,46 @@ public void attachEntity(Object entity) { persistedEntities.persistEntity(getEntityKey(entity), entity); } + @Override + public void removeEntity(Object entity) { + pendingEntities.evict(entity); + persistedEntities.changeToDeleteState(getEntityKey(entity)); + } + @Override public void detachEntity(Object entity) { - pendingEntities.removeEntity(entity); - persistedEntities.removeEntity(getEntityKey(entity)); + persistedEntities.evict(entity); } @Override - public Set getPendingEntities() { + public List getSavingEntities() { return pendingEntities.getEntities(); } @Override - public Collection getPersistedEntities() { - return persistedEntities.getEntities(); + public List getDeletedEntities() { + return persistedEntities.getDeletedEntities(); + } + + @Override + public List getManagedEntities() { + return persistedEntities.getManagedEntities(); } @Override - public void captureDatabaseSnapshot(Object entity) { - databaseSnapshots.addDatabaseSnapshot(entity); + public List findDirtyColumns(Object entity) throws IllegalAccessException { + return persistedEntities.findDirtyColumns(entity); } @Override - public Object getDatabaseSnapshot(Object entity) { - return databaseSnapshots.getDatabaseSnapshot(entity); + public void updateDatabaseSnapshot(Object entity) { + persistedEntities.updateDatabaseSnapshot(entity); } + @Override - public void promotePendingToPersistent(Object entity) { - pendingEntities.removeEntity(entity); + public void mangeEntity(Object entity) { + pendingEntities.evict(entity); long id = new LongTypeId(entity).getId(); persistedEntities.persistEntity(new EntityKey(id, entity.getClass().getName()), entity); } @@ -78,5 +85,4 @@ private EntityKey getEntityKey(Object entity) { } - } diff --git a/src/test/java/persistence/entity/impl/EntityManagerImplTest.java b/src/test/java/persistence/entity/impl/EntityManagerImplTest.java index 8e9a6fcea..8dcdff6a4 100644 --- a/src/test/java/persistence/entity/impl/EntityManagerImplTest.java +++ b/src/test/java/persistence/entity/impl/EntityManagerImplTest.java @@ -87,7 +87,32 @@ void removeTest() throws SQLException { em.persist(person); em.remove(person); + em.getTransaction().commit(); + + assertThatThrownBy(() -> em.find(Person.class, 1L)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining("Expected 1 result, got 0"); + } + + @Test + @DisplayName("managed entity 삭제 테스트") + void removeManagedEntityTest() throws SQLException { + EntityManagerFactory entityManagerFactory = new EntityManagerFactoryImpl(server); + EntityManager em = entityManagerFactory.createEntityManager(); + + em.getTransaction().beginTransaction(); + Person person = Person.builder() + .name("John") + .age(20) + .email("john@naver.com") + .build(); + em.persist(person); + + em.getTransaction().commit(); + + em.getTransaction().beginTransaction(); + em.remove(person); em.getTransaction().commit(); assertThatThrownBy(() -> em.find(Person.class, 1L))