16
16
17
17
package de .greenrobot .dao ;
18
18
19
- import java .util .ArrayList ;
20
- import java .util .Arrays ;
21
- import java .util .Collection ;
22
- import java .util .List ;
23
-
24
19
import android .database .CrossProcessCursor ;
25
20
import android .database .Cursor ;
26
21
import android .database .CursorWindow ;
27
22
import android .database .DatabaseUtils ;
28
23
import android .database .sqlite .SQLiteDatabase ;
29
24
import android .database .sqlite .SQLiteStatement ;
25
+
26
+ import java .util .ArrayList ;
27
+ import java .util .Arrays ;
28
+ import java .util .Collection ;
29
+ import java .util .Collections ;
30
+ import java .util .List ;
31
+
30
32
import de .greenrobot .dao .identityscope .IdentityScope ;
31
33
import de .greenrobot .dao .identityscope .IdentityScopeLong ;
32
34
import de .greenrobot .dao .internal .DaoConfig ;
37
39
38
40
/**
39
41
* Base class for all DAOs: Implements entity operations like insert, load, delete, and query.
40
- *
42
+ * <p>
41
43
* This class is thread-safe.
42
44
*
45
+ * @param <T> Entity type
46
+ * @param <K> Primary key (PK) type; use Void if entity does not have exactly one PK
43
47
* @author Markus
44
- *
45
- * @param <T>
46
- * Entity type
47
- * @param <K>
48
- * Primary key (PK) type; use Void if entity does not have exactly one PK
49
48
*/
50
49
/*
51
50
* When operating on TX, statements, or identity scope the following locking order must be met to avoid deadlocks:
@@ -118,8 +117,7 @@ public String[] getNonPkColumns() {
118
117
/**
119
118
* Loads and entity for the given PK.
120
119
*
121
- * @param key
122
- * a PK value or null
120
+ * @param key a PK value or null
123
121
* @return The entity or null, if no entity matched the PK value
124
122
*/
125
123
public T load (K key ) {
@@ -134,13 +132,13 @@ public T load(K key) {
134
132
}
135
133
}
136
134
String sql = statements .getSelectByKey ();
137
- String [] keyArray = new String [] { key .toString () };
135
+ String [] keyArray = new String []{ key .toString ()};
138
136
Cursor cursor = db .rawQuery (sql , keyArray );
139
137
return loadUniqueAndCloseCursor (cursor );
140
138
}
141
139
142
140
public T loadByRowId (long rowId ) {
143
- String [] idArray = new String [] { Long .toString (rowId ) };
141
+ String [] idArray = new String []{ Long .toString (rowId )};
144
142
Cursor cursor = db .rawQuery (statements .getSelectByRowId (), idArray );
145
143
return loadUniqueAndCloseCursor (cursor );
146
144
}
@@ -190,8 +188,7 @@ protected List<T> loadAllAndCloseCursor(Cursor cursor) {
190
188
/**
191
189
* Inserts the given entities in the database using a transaction.
192
190
*
193
- * @param entities
194
- * The entities to insert.
191
+ * @param entities The entities to insert.
195
192
*/
196
193
public void insertInTx (Iterable <T > entities ) {
197
194
insertInTx (entities , isEntityUpdateable ());
@@ -200,8 +197,7 @@ public void insertInTx(Iterable<T> entities) {
200
197
/**
201
198
* Inserts the given entities in the database using a transaction.
202
199
*
203
- * @param entities
204
- * The entities to insert.
200
+ * @param entities The entities to insert.
205
201
*/
206
202
public void insertInTx (T ... entities ) {
207
203
insertInTx (Arrays .asList (entities ), isEntityUpdateable ());
@@ -211,10 +207,8 @@ public void insertInTx(T... entities) {
211
207
* Inserts the given entities in the database using a transaction. The given entities will become tracked if the PK
212
208
* is set.
213
209
*
214
- * @param entities
215
- * The entities to insert.
216
- * @param setPrimaryKey
217
- * if true, the PKs of the given will be set after the insert; pass false to improve performance.
210
+ * @param entities The entities to insert.
211
+ * @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve performance.
218
212
*/
219
213
public void insertInTx (Iterable <T > entities , boolean setPrimaryKey ) {
220
214
SQLiteStatement stmt = statements .getInsertStatement ();
@@ -225,10 +219,8 @@ public void insertInTx(Iterable<T> entities, boolean setPrimaryKey) {
225
219
* Inserts or replaces the given entities in the database using a transaction. The given entities will become
226
220
* tracked if the PK is set.
227
221
*
228
- * @param entities
229
- * The entities to insert.
230
- * @param setPrimaryKey
231
- * if true, the PKs of the given will be set after the insert; pass false to improve performance.
222
+ * @param entities The entities to insert.
223
+ * @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve performance.
232
224
*/
233
225
public void insertOrReplaceInTx (Iterable <T > entities , boolean setPrimaryKey ) {
234
226
SQLiteStatement stmt = statements .getInsertOrReplaceStatement ();
@@ -238,8 +230,7 @@ public void insertOrReplaceInTx(Iterable<T> entities, boolean setPrimaryKey) {
238
230
/**
239
231
* Inserts or replaces the given entities in the database using a transaction.
240
232
*
241
- * @param entities
242
- * The entities to insert.
233
+ * @param entities The entities to insert.
243
234
*/
244
235
public void insertOrReplaceInTx (Iterable <T > entities ) {
245
236
insertOrReplaceInTx (entities , isEntityUpdateable ());
@@ -248,8 +239,7 @@ public void insertOrReplaceInTx(Iterable<T> entities) {
248
239
/**
249
240
* Inserts or replaces the given entities in the database using a transaction.
250
241
*
251
- * @param entities
252
- * The entities to insert.
242
+ * @param entities The entities to insert.
253
243
*/
254
244
public void insertOrReplaceInTx (T ... entities ) {
255
245
insertOrReplaceInTx (Arrays .asList (entities ), isEntityUpdateable ());
@@ -369,12 +359,18 @@ protected void updateKeyAfterInsertAndAttach(T entity, long rowId, boolean lock)
369
359
/** Reads all available rows from the given cursor and returns a list of entities. */
370
360
protected List <T > loadAllFromCursor (Cursor cursor ) {
371
361
int count = cursor .getCount ();
362
+ if (count == 0 ) {
363
+ return Collections .EMPTY_LIST ;
364
+ }
372
365
List <T > list = new ArrayList <T >(count );
366
+ CursorWindow window = null ;
367
+ boolean useFastCursor = false ;
373
368
if (cursor instanceof CrossProcessCursor ) {
374
- CursorWindow window = ((CrossProcessCursor ) cursor ).getWindow ();
375
- if (window != null ) { // E.g. Roboelectric has no Window at this point
369
+ window = ((CrossProcessCursor ) cursor ).getWindow ();
370
+ if (window != null ) { // E.g. Robolectric has no Window at this point
376
371
if (window .getNumRows () == count ) {
377
372
cursor = new FastCursor (window );
373
+ useFastCursor = true ;
378
374
} else {
379
375
DaoLog .d ("Window vs. result size: " + window .getNumRows () + "/" + count );
380
376
}
@@ -386,10 +382,15 @@ protected List<T> loadAllFromCursor(Cursor cursor) {
386
382
identityScope .lock ();
387
383
identityScope .reserveRoom (count );
388
384
}
385
+
389
386
try {
390
- do {
391
- list .add (loadCurrent (cursor , 0 , false ));
392
- } while (cursor .moveToNext ());
387
+ if (!useFastCursor && window != null && identityScope != null ) {
388
+ loadAllUnlockOnWindowBounds (cursor , window , list );
389
+ } else {
390
+ do {
391
+ list .add (loadCurrent (cursor , 0 , false ));
392
+ } while (cursor .moveToNext ());
393
+ }
393
394
} finally {
394
395
if (identityScope != null ) {
395
396
identityScope .unlock ();
@@ -399,6 +400,42 @@ protected List<T> loadAllFromCursor(Cursor cursor) {
399
400
return list ;
400
401
}
401
402
403
+ private void loadAllUnlockOnWindowBounds (Cursor cursor , CursorWindow window , List <T > list ) {
404
+ int windowEnd = window .getStartPosition () + window .getNumRows ();
405
+ for (int row = 0 ; ; row ++) {
406
+ list .add (loadCurrent (cursor , 0 , false ));
407
+ row ++;
408
+ if (row >= windowEnd ) {
409
+ window = moveToNextUnlocked (cursor );
410
+ if (window == null ) {
411
+ break ;
412
+ }
413
+ windowEnd = window .getStartPosition () + window .getNumRows ();
414
+ } else {
415
+ if (!cursor .moveToNext ()) {
416
+ break ;
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Unlock identityScope during cursor.moveToNext() when it is about to fill the window (needs a db connection):
424
+ * We should not hold the lock while trying to acquire a db connection to avoid deadlocks.
425
+ */
426
+ private CursorWindow moveToNextUnlocked (Cursor cursor ) {
427
+ identityScope .unlock ();
428
+ try {
429
+ if (cursor .moveToNext ()) {
430
+ return ((CrossProcessCursor ) cursor ).getWindow ();
431
+ } else {
432
+ return null ;
433
+ }
434
+ } finally {
435
+ identityScope .lock ();
436
+ }
437
+ }
438
+
402
439
/** Internal use only. Considers identity scope. */
403
440
final protected T loadCurrent (Cursor cursor , int offset , boolean lock ) {
404
441
if (identityScopeLong != null ) {
@@ -580,8 +617,7 @@ private void deleteInTxInternal(Iterable<T> entities, Iterable<K> keys) {
580
617
/**
581
618
* Deletes the given entities in the database using a transaction.
582
619
*
583
- * @param entities
584
- * The entities to delete.
620
+ * @param entities The entities to delete.
585
621
*/
586
622
public void deleteInTx (Iterable <T > entities ) {
587
623
deleteInTxInternal (entities , null );
@@ -590,8 +626,7 @@ public void deleteInTx(Iterable<T> entities) {
590
626
/**
591
627
* Deletes the given entities in the database using a transaction.
592
628
*
593
- * @param entities
594
- * The entities to delete.
629
+ * @param entities The entities to delete.
595
630
*/
596
631
public void deleteInTx (T ... entities ) {
597
632
deleteInTxInternal (Arrays .asList (entities ), null );
@@ -600,8 +635,7 @@ public void deleteInTx(T... entities) {
600
635
/**
601
636
* Deletes all entities with the given keys in the database using a transaction.
602
637
*
603
- * @param keys
604
- * Keys of the entities to delete.
638
+ * @param keys Keys of the entities to delete.
605
639
*/
606
640
public void deleteByKeyInTx (Iterable <K > keys ) {
607
641
deleteInTxInternal (null , keys );
@@ -610,8 +644,7 @@ public void deleteByKeyInTx(Iterable<K> keys) {
610
644
/**
611
645
* Deletes all entities with the given keys in the database using a transaction.
612
646
*
613
- * @param keys
614
- * Keys of the entities to delete.
647
+ * @param keys Keys of the entities to delete.
615
648
*/
616
649
public void deleteByKeyInTx (K ... keys ) {
617
650
deleteInTxInternal (null , Arrays .asList (keys ));
@@ -622,7 +655,7 @@ public void refresh(T entity) {
622
655
assertSinglePk ();
623
656
K key = getKeyVerified (entity );
624
657
String sql = statements .getSelectByKey ();
625
- String [] keyArray = new String [] { key .toString () };
658
+ String [] keyArray = new String []{ key .toString ()};
626
659
Cursor cursor = db .rawQuery (sql , keyArray );
627
660
try {
628
661
boolean available = cursor .moveToFirst ();
@@ -683,11 +716,9 @@ protected void updateInsideSynchronized(T entity, SQLiteStatement stmt, boolean
683
716
/**
684
717
* Attaches the entity to the identity scope. Calls attachEntity(T entity).
685
718
*
686
- * @param key
687
- * Needed only for identity scope, pass null if there's none.
688
- * @param entity
689
- * The entitiy to attach
690
- * */
719
+ * @param key Needed only for identity scope, pass null if there's none.
720
+ * @param entity The entitiy to attach
721
+ */
691
722
protected final void attachEntity (K key , T entity , boolean lock ) {
692
723
attachEntity (entity );
693
724
if (identityScope != null && key != null ) {
@@ -703,17 +734,15 @@ protected final void attachEntity(K key, T entity, boolean lock) {
703
734
* Sub classes with relations additionally set the DaoMaster here. Must be called before the entity is attached to
704
735
* the identity scope.
705
736
*
706
- * @param entity
707
- * The entitiy to attach
708
- * */
737
+ * @param entity The entitiy to attach
738
+ */
709
739
protected void attachEntity (T entity ) {
710
740
}
711
741
712
742
/**
713
743
* Updates the given entities in the database using a transaction.
714
744
*
715
- * @param entities
716
- * The entities to insert.
745
+ * @param entities The entities to insert.
717
746
*/
718
747
public void updateInTx (Iterable <T > entities ) {
719
748
SQLiteStatement stmt = statements .getUpdateStatement ();
@@ -754,8 +783,7 @@ public void updateInTx(Iterable<T> entities) {
754
783
/**
755
784
* Updates the given entities in the database using a transaction.
756
785
*
757
- * @param entities
758
- * The entities to update.
786
+ * @param entities The entities to update.
759
787
*/
760
788
public void updateInTx (T ... entities ) {
761
789
updateInTx (Arrays .asList (entities ));
0 commit comments