Skip to content

Commit af1487c

Browse files
feat: bulk insertion for DAOs [macata #99] (#100)
1 parent 5178677 commit af1487c

File tree

9 files changed

+253
-7
lines changed

9 files changed

+253
-7
lines changed

checkstyle.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,10 @@
219219
</module>
220220
<module name="UnusedImports"/>
221221
</module>
222+
223+
<module name="RegexpMultiline">
224+
<property name="format" value="(\n\s*){3,}"/>
225+
<property name="message" value="Too many consecutive blank lines."/>
226+
<property name="severity" value="error"/>
227+
</module>
222228
</module>

src/main/java/com/dazednconfused/catalauncher/database/base/BaseDAO.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.sql.Statement;
88
import java.util.ArrayList;
99
import java.util.Arrays;
10+
import java.util.Collection;
1011
import java.util.List;
1112
import java.util.Objects;
1213
import java.util.Optional;
@@ -27,6 +28,11 @@ public interface BaseDAO<T extends BaseEntity> {
2728
* */
2829
T insert(T t) throws DAOException;
2930

31+
/**
32+
* Inserts the given {@link BaseEntity}(ies) into the table.
33+
* */
34+
int bulkInsert(Collection<T> t) throws DAOException;
35+
3036
/**
3137
* Updates the given {@link BaseEntity}. It must have an ID set.
3238
* */

src/main/java/com/dazednconfused/catalauncher/database/h2/H2Database.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ protected static Result<Throwable, Connection> openConnection(String database) {
104104
).map(Result::success).recover(Result::failure).get();
105105
}
106106

107-
108107
/**
109108
* Completely wipes this database of any and all data, <b>including</b> the underlying schema.
110109
*

src/main/java/com/dazednconfused/catalauncher/database/mod/dao/ModH2DAOImpl.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.sql.ResultSet;
1010
import java.sql.SQLException;
1111
import java.sql.Statement;
12+
import java.util.Arrays;
13+
import java.util.Collection;
1214
import java.util.Optional;
1315

1416
import org.slf4j.Logger;
@@ -43,7 +45,7 @@ public String getDatabaseName() {
4345
public ModEntity insert(ModEntity entity) throws DAOException {
4446
LOGGER.debug("Inserting ModEntity [{}]...", entity);
4547

46-
String sql = "INSERT INTO " + MODS_TABLE_NAME + "" +
48+
String sql = "INSERT INTO " + MODS_TABLE_NAME + " " +
4749
"(name, modinfo, created_date, updated_date) " +
4850
"VALUES " +
4951
"(?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())";
@@ -61,6 +63,39 @@ public ModEntity insert(ModEntity entity) throws DAOException {
6163
}
6264
}
6365

66+
@Override
67+
public int bulkInsert(Collection<ModEntity> entities) throws DAOException {
68+
69+
if (entities == null || entities.isEmpty()) {
70+
LOGGER.warn("No entities provided for bulk insert. No operation shall be performed.");
71+
return 0;
72+
}
73+
74+
LOGGER.debug("Bulk inserting [{}] ModEntity(ies)...", entities.size());
75+
76+
String sql = "INSERT INTO " + MODS_TABLE_NAME + " " +
77+
"(name, modinfo, created_date, updated_date) " +
78+
"VALUES " +
79+
"(?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())";
80+
81+
try (Connection conn = this.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
82+
for (ModEntity entity : entities) {
83+
pstmt.setString(1, entity.getName());
84+
pstmt.setString(2, entity.getModinfo());
85+
pstmt.addBatch();
86+
}
87+
88+
int[] results = pstmt.executeBatch();
89+
90+
int insertedCount = Arrays.stream(results).filter(result -> result > 0).sum();
91+
LOGGER.debug("Bulk inserted [{}] ModEntity(ies)", insertedCount);
92+
return insertedCount;
93+
} catch (SQLException e) {
94+
LOGGER.error("An error occurred during bulk insert of ModEntity(ies)", e);
95+
throw new DAOException(e);
96+
}
97+
}
98+
6499
@Override
65100
public ModEntity update(ModEntity entity) throws DAOException {
66101
Optional<ModEntity> originalEntity = this.findById(entity.getId());

src/main/java/com/dazednconfused/catalauncher/database/mod/dao/ModfileH2DAOImpl.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import java.sql.ResultSet;
1010
import java.sql.SQLException;
1111
import java.sql.Statement;
12+
import java.util.Arrays;
13+
import java.util.Collection;
1214
import java.util.Optional;
1315

1416
import org.slf4j.Logger;
@@ -41,7 +43,7 @@ public String getDatabaseName() {
4143
public ModfileEntity insert(ModfileEntity entity) throws DAOException {
4244
LOGGER.debug("Inserting ModfileEntity [{}]...", entity);
4345

44-
String sql = "INSERT INTO " + TABLE_NAME + "" +
46+
String sql = "INSERT INTO " + TABLE_NAME + " " +
4547
"(mod_id, path, hash, created_date, updated_date) " +
4648
"VALUES " +
4749
"(?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())";
@@ -60,6 +62,40 @@ public ModfileEntity insert(ModfileEntity entity) throws DAOException {
6062
}
6163
}
6264

65+
@Override
66+
public int bulkInsert(Collection<ModfileEntity> entities) throws DAOException {
67+
68+
if (entities == null || entities.isEmpty()) {
69+
LOGGER.warn("No entities provided for bulk insert. No operation shall be performed.");
70+
return 0;
71+
}
72+
73+
LOGGER.debug("Bulk inserting [{}] ModfileEntity(ies)...", entities.size());
74+
75+
String sql = "INSERT INTO " + TABLE_NAME + " " +
76+
"(mod_id, path, hash, created_date, updated_date) " +
77+
"VALUES " +
78+
"(?, ?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP())";
79+
80+
try (Connection conn = this.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
81+
for (ModfileEntity entity : entities) {
82+
pstmt.setLong(1, entity.getModId());
83+
pstmt.setString(2, entity.getPath());
84+
pstmt.setString(3, entity.getHash());
85+
pstmt.addBatch();
86+
}
87+
88+
int[] results = pstmt.executeBatch();
89+
90+
int insertedCount = Arrays.stream(results).filter(result -> result > 0).sum();
91+
LOGGER.debug("Bulk inserted [{}] ModfileEntity(ies)", insertedCount);
92+
return insertedCount;
93+
} catch (SQLException e) {
94+
LOGGER.error("An error occurred during bulk insert of ModfileEntity(ies)", e);
95+
throw new DAOException(e);
96+
}
97+
}
98+
6399
@Override
64100
public ModfileEntity update(ModfileEntity entity) throws DAOException {
65101
Optional<ModfileEntity> originalEntity = this.findById(entity.getId());

src/main/java/com/dazednconfused/catalauncher/database/mod/repository/ModH2RepositoryImpl.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.dazednconfused.catalauncher.database.mod.entity.ModfileEntity;
99

1010
import java.util.Arrays;
11+
import java.util.Collection;
1112
import java.util.List;
1213
import java.util.Objects;
1314
import java.util.Optional;
@@ -60,6 +61,20 @@ public ModEntity insert(ModEntity entity) throws DAOException {
6061
return result;
6162
}
6263

64+
@Override
65+
public int bulkInsert(Collection<ModEntity> entities) throws DAOException {
66+
LOGGER.debug("Bulk inserting [{}] ModEntity(ies)...", entities.size());
67+
68+
int insertedCount = (int) entities.stream()
69+
.map(this::insert)
70+
.filter(Objects::nonNull)
71+
.count();
72+
73+
LOGGER.debug("Bulk inserted [{}] ModEntity(ies)", insertedCount);
74+
75+
return insertedCount;
76+
}
77+
6378
@Override
6479
public ModEntity update(ModEntity entity) throws DAOException {
6580
LOGGER.debug("Updating ModEntity: [{}]", entity);
@@ -121,10 +136,11 @@ public List<ModEntity> findAll() throws DAOException {
121136
private List<ModfileEntity> insertChildEntities(long modId, List<ModfileEntity> entities) throws DAOException {
122137
LOGGER.debug("Inserting [{}] ModfileEntity(s) associated to modId [{}]", entities.size(), modId);
123138

124-
List<ModfileEntity> result = entities.stream()
139+
this.modfileDAO.bulkInsert(entities.stream()
125140
.peek(e -> e.setModId(modId)) // set/overwrite with entity ID
126-
.map(modfileDAO::insert)
127-
.collect(Collectors.toList());
141+
.collect(Collectors.toList())
142+
);
143+
List<ModfileEntity> result = modfileDAO.findAllByModId(modId);
128144

129145
LOGGER.debug("Inserted [{}] ModfileEntity(s) associated to modId [{}]", result.size(), modId);
130146

src/test/java/com/dazednconfused/catalauncher/database/mod/dao/ModDAOTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,46 @@ void insert_success() {
9797
assertThat(result.getCreatedDate()).isEqualTo(result.getUpdatedDate());
9898
}
9999

100+
@Test
101+
void bulk_insert_success() {
102+
103+
// prepare mock data ---
104+
ModEntity entity1 = ModEntity.builder()
105+
.name("testName1")
106+
.modinfo("testModinfo1")
107+
.build();
108+
109+
ModEntity entity2 = ModEntity.builder()
110+
.name("testName2")
111+
.modinfo("testModinfo2")
112+
.build();
113+
114+
ModEntity entity3 = ModEntity.builder()
115+
.name("testName3")
116+
.modinfo("testModinfo3")
117+
.build();
118+
119+
// execute test ---
120+
int result = dao.bulkInsert(List.of(entity1, entity2, entity3));
121+
122+
// verify assertions ---
123+
assertThat(result).isEqualTo(3);
124+
125+
List<ModEntity> allEntities = dao.findAll();
126+
assertThat(allEntities).hasSize(3);
127+
128+
assertThat(allEntities)
129+
.usingRecursiveFieldByFieldElementComparatorIgnoringFields("id", "createdDate", "updatedDate")
130+
.containsExactlyInAnyOrder(entity1, entity2, entity3);
131+
132+
for (ModEntity entity : allEntities) {
133+
assertThat(entity.getId()).isNotZero();
134+
assertThat(entity.getCreatedDate()).isNotNull();
135+
assertThat(entity.getUpdatedDate()).isNotNull();
136+
assertThat(entity.getCreatedDate()).isEqualTo(entity.getUpdatedDate());
137+
}
138+
}
139+
100140
@Test
101141
void find_by_id_success() {
102142

src/test/java/com/dazednconfused/catalauncher/database/mod/dao/ModfileDAOTest.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import static org.mockito.Mockito.when;
66

77
import com.dazednconfused.catalauncher.database.base.DisposableDatabase;
8-
import com.dazednconfused.catalauncher.database.h2.H2Database;
98
import com.dazednconfused.catalauncher.database.mod.entity.ModEntity;
109
import com.dazednconfused.catalauncher.database.mod.entity.ModfileEntity;
1110

@@ -124,6 +123,49 @@ void insert_success() {
124123
assertThat(result.getCreatedDate()).isEqualTo(result.getUpdatedDate());
125124
}
126125

126+
@Test
127+
void bulk_insert_success() {
128+
129+
// prepare mock data ---
130+
ModfileEntity entity1 = ModfileEntity.builder()
131+
.modId(parentModId)
132+
.path("testPath1")
133+
.hash("testHash1")
134+
.build();
135+
136+
ModfileEntity entity2 = ModfileEntity.builder()
137+
.modId(parentModId)
138+
.path("testPath2")
139+
.hash("testHash2")
140+
.build();
141+
142+
ModfileEntity entity3 = ModfileEntity.builder()
143+
.modId(parentModId)
144+
.path("testPath3")
145+
.hash("testHash3")
146+
.build();
147+
148+
// execute test ---
149+
int result = dao.bulkInsert(List.of(entity1, entity2, entity3));
150+
151+
// verify assertions ---
152+
assertThat(result).isEqualTo(3);
153+
154+
List<ModfileEntity> allEntities = dao.findAll();
155+
assertThat(allEntities).hasSize(3);
156+
157+
assertThat(allEntities)
158+
.usingRecursiveFieldByFieldElementComparatorIgnoringFields("id", "createdDate", "updatedDate")
159+
.containsExactlyInAnyOrder(entity1, entity2, entity3);
160+
161+
for (ModfileEntity entity : allEntities) {
162+
assertThat(entity.getId()).isNotZero();
163+
assertThat(entity.getCreatedDate()).isNotNull();
164+
assertThat(entity.getUpdatedDate()).isNotNull();
165+
assertThat(entity.getCreatedDate()).isEqualTo(entity.getUpdatedDate());
166+
}
167+
}
168+
127169
@Test
128170
void find_by_id_success() {
129171

src/test/java/com/dazednconfused/catalauncher/database/mod/repository/ModRepositoryTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,72 @@ void insert_success() {
123123
assertThat(resultChildEntity3.getCreatedDate()).isEqualTo(resultChildEntity3.getUpdatedDate());
124124
}
125125

126+
@Test
127+
void bulk_insert_success() {
128+
129+
// prepare mock data ---
130+
ModEntity entity1 = ModEntity.builder()
131+
.name("testName1")
132+
.modinfo("testModinfo1")
133+
.modfiles(Arrays.asList(
134+
ModfileEntity.builder().path("testPath1_1").hash("testHash1_1").build(),
135+
ModfileEntity.builder().path("testPath1_2").hash("testHash1_2").build(),
136+
ModfileEntity.builder().path("testPath1_3").hash("testHash1_3").build()
137+
))
138+
.build();
139+
140+
ModEntity entity2 = ModEntity.builder()
141+
.name("testName2")
142+
.modinfo("testModinfo2")
143+
.modfiles(Arrays.asList(
144+
ModfileEntity.builder().path("testPath2_1").hash("testHash2_1").build(),
145+
ModfileEntity.builder().path("testPath2_2").hash("testHash2_2").build(),
146+
ModfileEntity.builder().path("testPath2_3").hash("testHash2_3").build()
147+
))
148+
.build();
149+
150+
ModEntity entity3 = ModEntity.builder()
151+
.name("testName3")
152+
.modinfo("testModinfo3")
153+
.modfiles(Arrays.asList(
154+
ModfileEntity.builder().path("testPath3_1").hash("testHash3_1").build(),
155+
ModfileEntity.builder().path("testPath3_2").hash("testHash3_2").build(),
156+
ModfileEntity.builder().path("testPath3_3").hash("testHash3_3").build()
157+
))
158+
.build();
159+
160+
// execute test ---
161+
int result = repository.bulkInsert(Arrays.asList(entity1, entity2, entity3));
162+
163+
// verify assertions ---
164+
assertThat(result).isEqualTo(3);
165+
166+
List<ModEntity> allEntities = repository.findAll();
167+
assertThat(allEntities).hasSize(3);
168+
169+
assertThat(allEntities)
170+
.usingRecursiveFieldByFieldElementComparatorIgnoringFields(
171+
"id", "createdDate", "updatedDate",
172+
"modfiles.id", "modfiles.createdDate", "modfiles.updatedDate"
173+
)
174+
.containsExactlyInAnyOrder(entity1, entity2, entity3);
175+
176+
for (ModEntity entity : allEntities) {
177+
assertThat(entity.getId()).isNotZero();
178+
assertThat(entity.getCreatedDate()).isNotNull();
179+
assertThat(entity.getUpdatedDate()).isNotNull();
180+
assertThat(entity.getCreatedDate()).isEqualTo(entity.getUpdatedDate());
181+
assertThat(entity.getModfiles()).hasSize(3);
182+
183+
for (ModfileEntity modfile : entity.getModfiles()) {
184+
assertThat(modfile.getModId()).isEqualTo(entity.getId());
185+
assertThat(modfile.getCreatedDate()).isNotNull();
186+
assertThat(modfile.getUpdatedDate()).isNotNull();
187+
assertThat(modfile.getCreatedDate()).isEqualTo(modfile.getUpdatedDate());
188+
}
189+
}
190+
}
191+
126192
@Test
127193
void find_by_id_success() {
128194

0 commit comments

Comments
 (0)