99import lombok .NonNull ;
1010import lombok .RequiredArgsConstructor ;
1111
12+ import java .util .Collection ;
13+ import java .util .List ;
14+ import java .util .Map ;
15+ import java .util .Objects ;
16+ import java .util .stream .Collectors ;
17+
18+ import static it .aboutbits .springboot .toolbox .util .CollectUtil .collectToSet ;
1219import static org .assertj .core .api .Assertions .assertThat ;
1320
1421public final class PersistenceAssert {
@@ -23,115 +30,224 @@ private static EntityManager getEntityManager() {
2330 public static <ID extends EntityId <?>, E extends Identified <ID > & ChangeAware , M extends Identified <ID > & ChangeAware > WriteOperationAsserter <ID , E , M > assertThatEntity (
2431 @ NonNull E before ,
2532 @ NonNull Class <M > modelClass
33+ ) {
34+ return new WriteOperationAsserter <>(getEntityManager (), List .of (before ), modelClass );
35+ }
36+
37+ @ SuppressWarnings ("unused" )
38+ public static <ID extends EntityId <?>, E extends Identified <ID > & ChangeAware , M extends Identified <ID > & ChangeAware > WriteOperationAsserter <ID , E , M > assertThatEntity (
39+ @ NonNull Collection <E > before ,
40+ @ NonNull Class <M > modelClass
2641 ) {
2742 return new WriteOperationAsserter <>(getEntityManager (), before , modelClass );
2843 }
2944
45+ /**
46+ * @deprecated Use {@link #assertThatEntityId(EntityId, Class)} instead.
47+ */
48+ @ Deprecated
3049 @ SuppressWarnings ("unused" )
3150 public static <ID extends EntityId <?>, M extends Identified <ID >> WriteOperationIdAsserter <ID , M > assertThatEntity (
3251 @ NonNull ID id ,
3352 @ NonNull Class <M > modelClass
53+ ) {
54+ return new WriteOperationIdAsserter <>(getEntityManager (), List .of (id ), modelClass );
55+ }
56+
57+ @ SuppressWarnings ("unused" )
58+ public static <ID extends EntityId <?>, M extends Identified <ID >> WriteOperationIdAsserter <ID , M > assertThatEntityId (
59+ @ NonNull ID id ,
60+ @ NonNull Class <M > modelClass
61+ ) {
62+ return new WriteOperationIdAsserter <>(getEntityManager (), List .of (id ), modelClass );
63+ }
64+
65+ @ SuppressWarnings ("unused" )
66+ public static <ID extends EntityId <?>, M extends Identified <ID >> WriteOperationIdAsserter <ID , M > assertThatEntityId (
67+ @ NonNull Collection <ID > id ,
68+ @ NonNull Class <M > modelClass
3469 ) {
3570 return new WriteOperationIdAsserter <>(getEntityManager (), id , modelClass );
3671 }
3772
73+ /**
74+ * Batch query entities by their IDs using JPQL
75+ */
76+ private static <ID extends EntityId <?>, M extends Identified <ID >> List <M > batchFindByIds (
77+ EntityManager entityManager ,
78+ Collection <ID > ids ,
79+ Class <M > modelClass
80+ ) {
81+ if (ids .isEmpty ()) {
82+ return List .of ();
83+ }
84+
85+ return ids .stream ()
86+ .map (
87+ id -> entityManager .find (modelClass , id )
88+ )
89+ .filter (Objects ::nonNull )
90+ .toList ();
91+ }
92+
93+ /**
94+ * Check if entities with given IDs exist in the database using a count query
95+ */
96+ private static <ID extends EntityId <?>, M extends Identified <ID >> Map <ID , Boolean > batchCheckExistence (
97+ EntityManager entityManager ,
98+ Collection <ID > ids ,
99+ Class <M > modelClass
100+ ) {
101+ if (ids .isEmpty ()) {
102+ return Map .of ();
103+ }
104+
105+ // Get existing entities
106+ var existingEntities = batchFindByIds (entityManager , ids , modelClass );
107+ var existingIds = collectToSet (existingEntities , Identified ::getId );
108+
109+ // Map each ID to its existence status
110+ return ids .stream ()
111+ .collect (Collectors .toMap (
112+ id -> id ,
113+ existingIds ::contains
114+ ));
115+ }
116+
38117 @ RequiredArgsConstructor (access = AccessLevel .PRIVATE )
39118 public static final class WriteOperationAsserter <ID extends EntityId <?>, E extends Identified <ID > & ChangeAware , M extends Identified <ID > & ChangeAware > {
40119 private final EntityManager entityManager ;
41- private final E entity ;
120+ private final Collection < E > entity ;
42121 private final Class <M > modelClass ;
43122
44123 @ SuppressWarnings ("unused" )
45124 public void hasBeenCreatedInDatabase () {
46125 entityManager .clear ();
47- var savedInstance = getSavedInstance ();
48126
49- assertThat (
50- savedInstance .getId ()
51- ).isNotNull ();
127+ var ids = collectToSet (entity , Identified ::getId );
128+ var savedInstances = batchFindByIds (entityManager , ids , modelClass );
129+
130+ for (var savedInstance : savedInstances ) {
131+ assertThat (
132+ savedInstance .getId ()
133+ ).isNotNull ();
134+
135+ assertThat (
136+ savedInstance .getCreatedAt ()
137+ ).isNotNull ();
138+ }
52139
53- assertThat (
54- savedInstance .getCreatedAt ()
55- ).isNotNull ();
140+ // Verify all entities were found
141+ assertThat (savedInstances ).hasSize (entity .size ());
56142 }
57143
58144 @ SuppressWarnings ("unused" )
59145 public void hasBeenUpdatedInDatabase () {
60146 entityManager .clear ();
61- var savedInstance = getSavedInstance ();
62147
63- assertThat (
64- savedInstance .getUpdatedAt ()
65- ).isAfter (
66- entity .getUpdatedAt ()
67- );
148+ var ids = collectToSet (entity , Identified ::getId );
149+ var savedInstances = batchFindByIds (entityManager , ids , modelClass );
150+
151+ // Create a map for easy lookup of original entities by ID
152+ var originalByIdMap = entity .stream ()
153+ .collect (Collectors .toMap (Identified ::getId , e -> e ));
154+
155+ for (var savedInstance : savedInstances ) {
156+ var originalEntity = originalByIdMap .get (savedInstance .getId ());
157+ assertThat (originalEntity ).isNotNull ();
158+
159+ assertThat (
160+ savedInstance .getUpdatedAt ()
161+ ).isAfter (
162+ originalEntity .getUpdatedAt ()
163+ );
164+ }
68165 }
69166
70167 @ SuppressWarnings ("unused" )
71168 public void hasNotChangedInDatabase () {
72169 entityManager .clear ();
73- var savedInstance = getSavedInstance ();
74170
75- assertThat (savedInstance ).isNotNull ();
171+ var ids = collectToSet (entity , Identified ::getId );
172+ var savedInstances = batchFindByIds (entityManager , ids , modelClass );
173+
174+ // Create a map for easy lookup of original entities by ID
175+ var originalByIdMap = entity .stream ()
176+ .collect (Collectors .toMap (Identified ::getId , e -> e ));
177+
178+ for (var savedInstance : savedInstances ) {
179+ var originalEntity = originalByIdMap .get (savedInstance .getId ());
76180
77- assertThat (
78- savedInstance .getUpdatedAt ()
79- ).isEqualTo (
80- entity .getUpdatedAt ()
81- );
181+ assertThat (savedInstance ).isNotNull ();
182+ assertThat (originalEntity ).isNotNull ();
183+
184+ assertThat (
185+ savedInstance .getUpdatedAt ()
186+ ).isEqualTo (
187+ originalEntity .getUpdatedAt ()
188+ );
189+ }
82190 }
83191
84192 @ SuppressWarnings ("unused" )
85193 public void isAbsentInDatabase () {
86194 entityManager .clear ();
87- var savedInstance = getSavedInstance ();
88195
89- assertThat (savedInstance ).isNull ();
196+ var ids = collectToSet (entity , Identified ::getId );
197+ var existenceMap = batchCheckExistence (entityManager , ids , modelClass );
198+
199+ for (var id : ids ) {
200+ assertThat (existenceMap .get (id ))
201+ .as ("Entity with ID %s should be absent from database" , id )
202+ .isFalse ();
203+ }
90204 }
91205
92206 @ SuppressWarnings ("unused" )
93207 public void isPresentInDatabase () {
94208 entityManager .clear ();
95- var savedInstance = getSavedInstance ();
96209
97- assertThat ( savedInstance ). isNotNull ( );
98- }
210+ var ids = collectToSet ( entity , Identified :: getId );
211+ var existenceMap = batchCheckExistence ( entityManager , ids , modelClass );
99212
100- private M getSavedInstance ( ) {
101- return entityManager . find (
102- modelClass ,
103- entity . getId ()
104- );
213+ for ( var id : ids ) {
214+ assertThat ( existenceMap . get ( id ))
215+ . as ( "Entity with ID %s should be present in database" , id )
216+ . isTrue ();
217+ }
105218 }
106219 }
107220
108221 @ RequiredArgsConstructor (access = AccessLevel .PRIVATE )
109222 public static final class WriteOperationIdAsserter <ID extends EntityId <?>, M extends Identified <ID >> {
110223 private final EntityManager entityManager ;
111- private final ID id ;
224+ private final Collection < ID > id ;
112225 private final Class <M > modelClass ;
113226
114227 @ SuppressWarnings ("unused" )
115228 public void isAbsentInDatabase () {
116229 entityManager .clear ();
117- var savedInstance = getSavedInstance ();
118230
119- assertThat (savedInstance ).isNull ();
231+ var existenceMap = batchCheckExistence (entityManager , id , modelClass );
232+
233+ for (var entityId : id ) {
234+ assertThat (existenceMap .get (entityId ))
235+ .as ("Entity with ID %s should be absent from database" , entityId )
236+ .isFalse ();
237+ }
120238 }
121239
122240 @ SuppressWarnings ("unused" )
123241 public void isPresentInDatabase () {
124242 entityManager .clear ();
125- var savedInstance = getSavedInstance ();
126243
127- assertThat (savedInstance ).isNotNull ();
128- }
244+ var existenceMap = batchCheckExistence (entityManager , id , modelClass );
129245
130- private M getSavedInstance ( ) {
131- return entityManager . find (
132- modelClass ,
133- id
134- );
246+ for ( var entityId : id ) {
247+ assertThat ( existenceMap . get ( entityId ))
248+ . as ( "Entity with ID %s should be present in database" , entityId )
249+ . isTrue ();
250+ }
135251 }
136252 }
137253}
0 commit comments