Saving new entity

Configuring database setup

In this lesson we work with a bit more complex KittyORM example. This example database contains four models and one extended CRUD controller.

Firstly, create KittyDatabase implementation class annotated with @KITTY_DATABASE annotation.

Click to view this lesson KittyORM database implemetation:
 1@KITTY_DATABASE(
 2        databaseName = "basic_database",
 3        domainPackageNames = {"net.akaish.kittyormdemo.sqlite.basicdb"},
 4        logTag = LOG_TAG,
 5        isLoggingOn = true,
 6        isProductionOn = true,
 7        isPragmaOn = true
 8)
 9@KITTY_DATABASE_REGISTRY(
10        domainPairs = {
11                @KITTY_REGISTRY_PAIR(model = ComplexRandomModel.class, mapper = ComplexRandomMapper.class),
12                @KITTY_REGISTRY_PAIR(model = IndexesAndConstraintsModel.class),
13                @KITTY_REGISTRY_PAIR(model = RandomModel.class, mapper = RandomMapper.class)
14        }
15)
16public class BasicDatabase extends KittyDatabase {
17
18    public static final String LOG_TAG = "BASIC DB DEMO";
19
20    /**
21     * KittyORM main database class that represents bootstrap and holder for all related with database
22     * components.
23     * <br> See {@link KittyDatabase#KittyDatabase(Context, String)} for more info.
24     *
25     * @param ctx
26     */
27    public BasicDatabase(Context ctx) {
28        super(ctx);
29    }
30
31}

Secondly, create abstract AbstractRandomModel.class POJO that defines some basic POJO fields.

Click to view AbstractRandomModel.class:
 1public abstract class AbstractRandomModel extends KittyModel {
 2
 3    public static final String RND_INTEGER_CNAME = "rnd_int_custom_column_name";
 4    public static final String RND_ANIMAL_CNAME = "rndanimal";
 5
 6    @KITTY_COLUMN(
 7            isIPK = true,
 8            columnOrder = 0
 9    )
10    public Long id;
11
12    @KITTY_COLUMN(
13            columnOrder = 1
14    )
15    public int randomInt;
16
17    @KITTY_COLUMN(
18            columnOrder = 2,
19            columnName = RND_INTEGER_CNAME
20    )
21    public Integer randomInteger;
22
23    @KITTY_COLUMN(
24            columnOrder = 3,
25            columnName = RND_ANIMAL_CNAME
26    )
27    public Animals randomAnimal;
28
29    @KITTY_COLUMN(
30            columnOrder = 4,
31            columnAffinity = TypeAffinities.TEXT
32    )
33    public String randomAnimalName;
34}

Fields that annotated with @KITTY_COLUMN would be used for mapping. Notice, that only columnOrder property is neсessary to be set. This is the only value you have to set manually and this is done in that way because fields received via reflection not guaranteed to be ordered in same order as they were defined in model class.

Thirdly, define three regular POJO classes, two of them extend AbstractRandomModel.class.

Click to view POJO classes used in this tutorial: RandomModel.class:
 1@KITTY_TABLE
 2@KITTY_EXTENDED_CRUD(extendedCrudController = RandomMapper.class)
 3@INDEX(
 4        indexName = "random_animal_index",
 5        indexColumns = {AbstractRandomModel.RND_ANIMAL_CNAME}
 6)
 7public class RandomModel extends AbstractRandomModel {
 8
 9
10    public RandomModel() {
11        super();
12    }
13
14    @KITTY_COLUMN(columnOrder = 5)
15    public String randomAnimalSays;
16
17    @Override
18    public String toString() {
19        return new StringBuffer(64).append("[ id = ")
20                                            .append(id)
21                                            .append("; randomInt = ")
22                                            .append(Integer.toString(randomInt))
23                                            .append("; randomInteger = ")
24                                            .append(randomInteger)
25                                            .append("; randomAnimal = ")
26                                            .append(randomAnimal)
27                                            .append("; randomAnimnalLocalizedName = ")
28                                            .append(randomAnimalName)
29                                            .append("; randomAnimalSays = ")
30                                            .append(randomAnimalSays).append(" ]").toString();
31    }
32}
IndexesAndConstraintsModel.class:
 1@KITTY_TABLE(tableName = "cai")
 2@FOREIGN_KEY_T(
 3        name = "CAI_FK",
 4        columns = {IndexesAndConstraintsModel.RANDOM_ID_CNAME},
 5        reference = @FOREIGN_KEY_REFERENCE(
 6                foreignTableName = "random",
 7                foreignTableColumns = {"id"},
 8                onUpdate = OnUpdateDeleteActions.CASCADE,
 9                onDelete = OnUpdateDeleteActions.CASCADE
10        )
11)
12@INDEX(indexColumns = {"creation_date"})
13public class IndexesAndConstraintsModel extends KittyModel {
14    static final String RANDOM_ID_CNAME = "rnd_id";
15
16    @KITTY_COLUMN(columnOrder = 0)
17    @PRIMARY_KEY
18    @NOT_NULL
19    public Long id;
20
21    @KITTY_COLUMN(columnOrder = 1)
22    @NOT_NULL
23    @UNIQUE
24    public Long rndId;
25
26    @KITTY_COLUMN(columnOrder = 2)
27    @CHECK(checkExpression = "animal IN (\"CAT\", \"TIGER\", \"LION\")") // only cats allowed to this party
28    public Animals animal;
29
30    @KITTY_COLUMN(columnOrder = 3)
31    @DEFAULT(signedInteger = 28) // You can choose for options for default declaration, if nothing set than 0 value would be used
32    @NOT_NULL
33    public Integer defaultNumber;
34
35    @KITTY_COLUMN(columnOrder = 4)
36    @DEFAULT(
37            predefinedLiteralValue = LiteralValues.CURRENT_DATE
38    )
39    @NOT_NULL
40    public String creationDate;
41
42    @KITTY_COLUMN(columnOrder = 5)
43    @DEFAULT(
44            predefinedLiteralValue = LiteralValues.CURRENT_TIMESTAMP
45    )
46    @ONE_COLUMN_INDEX(unique = true, indexName = "IAC_unique_index_creation_timestamp")
47    @NOT_NULL
48    public Timestamp creationTmstmp;
49
50    @Override
51    public String toString() {
52        StringBuilder sb = new StringBuilder(64);
53        sb.append("[ RowID = ").append(getRowID())
54                .append(" ; id = ").append(id)
55                .append(" ; rndId = ").append(rndId)
56                .append(" ; animal = ").append(animal)
57                .append(" ; defaultNumber = ").append(defaultNumber)
58                .append(" ; creationDate = ").append(creationDate)
59                .append(" ; creationTmstmp = ").append(creationTmstmp).append(" ]");
60        return sb.toString();
61    }
62}
ComplexRandomModel.class:
  1@KITTY_TABLE
  2@KITTY_EXTENDED_CRUD(extendedCrudController = ComplexRandomMapper.class)
  3public class ComplexRandomModel extends AbstractRandomModel {
  4
  5    public ComplexRandomModel() {
  6        super();
  7    }
  8
  9
 10    // Primitives
 11    // (boolean, int, byte, double, long, short, float)
 12    @KITTY_COLUMN(columnOrder = 5)
 13    public boolean boolF;
 14
 15
 16    @KITTY_COLUMN(columnOrder = 6)
 17    public byte byteF;
 18
 19    @KITTY_COLUMN(columnOrder = 7)
 20    public double doubleF;
 21
 22    @KITTY_COLUMN(columnOrder = 8)
 23    public long longF;
 24
 25    @KITTY_COLUMN(columnOrder = 9)
 26    public short shortF;
 27
 28    @KITTY_COLUMN(columnOrder = 10)
 29    public float floatF;
 30
 31    // Byte array
 32    @KITTY_COLUMN(columnOrder = 11)
 33    public byte[] byteArray;
 34
 35    // String (TEXT) (String, BigDecimal, BigInteger, Enum)
 36    @KITTY_COLUMN(columnOrder = 12)
 37    public String stringF;
 38
 39    @KITTY_COLUMN(columnOrder = 13)
 40    public BigDecimal bigDecimalF;
 41
 42    @KITTY_COLUMN(columnOrder = 14)
 43    public BigInteger bigIntegerF;
 44
 45    @KITTY_COLUMN(columnOrder = 15)
 46    public Uri uriF;
 47
 48    @KITTY_COLUMN(columnOrder = 16)
 49    public File fileF;
 50
 51    @KITTY_COLUMN(columnOrder = 17)
 52    public Currency currencyF;
 53
 54    // SD
 55    @KITTY_COLUMN(
 56            columnOrder = 18,
 57            columnAffinity = TypeAffinities.TEXT
 58    )
 59    @KITTY_COLUMN_SERIALIZATION
 60    public AnimalSounds stringSDF;
 61
 62    @KITTY_COLUMN(columnOrder = 19)
 63    public SomeColours bitmapColour;
 64
 65    @KITTY_COLUMN(
 66            columnOrder = 20,
 67            columnAffinity = TypeAffinities.BLOB
 68    )
 69    @KITTY_COLUMN_SERIALIZATION
 70    public Bitmap byteArraySDF;
 71
 72    String stringSDFSerialize() {
 73        if(stringSDF == null) return null;
 74        return new GsonBuilder().create().toJson(stringSDF);
 75    }
 76
 77    AnimalSounds stringSDFDeserialize(String cvData) {
 78        if(cvData == null) return null;
 79        if(cvData.length() == 0) return null;
 80        return new GsonBuilder().create().fromJson(cvData, AnimalSounds.class);
 81    }
 82
 83    public byte[] byteArraySDFSerialize() {//byteArraySDFSerialize
 84        if(byteArraySDF == null) return null;
 85        ByteArrayOutputStream bmpStream = new ByteArrayOutputStream();
 86        byteArraySDF.compress(Bitmap.CompressFormat.PNG, 100, bmpStream);
 87        return bmpStream.toByteArray();
 88    }
 89
 90    public Bitmap byteArraySDFDeserialize(byte[] cursorData) {
 91        if(cursorData == null) return null;
 92        if(cursorData.length == 0) return null;
 93        return BitmapFactory.decodeByteArray(cursorData, 0, cursorData.length);
 94    }
 95
 96    // Primitive wrappers Boolean, Integer, Byte, Double, Short or Float
 97    @KITTY_COLUMN(columnOrder = 21)
 98    public Boolean boolFF;
 99
100
101    @KITTY_COLUMN(columnOrder = 22)
102    public Byte byteFF;
103
104    @KITTY_COLUMN(columnOrder = 23)
105    public Double doubleFF;
106
107    @KITTY_COLUMN(columnOrder = 24)
108    public Short shortFF;
109
110    @KITTY_COLUMN(columnOrder = 25)
111    public Float floatFF;
112
113
114    // Long represented types Long, Date, Calendar, Timestamp
115    @KITTY_COLUMN(columnOrder = 26)
116    public Long longFF;
117
118    @KITTY_COLUMN(columnOrder = 27)
119    public Date dateF;
120
121    @KITTY_COLUMN(columnOrder = 28)
122    public Calendar calendarF;
123
124    @KITTY_COLUMN(columnOrder = 29)
125    public Timestamp timestampF;
126
127    @Override
128    public String toString() {
129        StringBuffer out = new StringBuffer(256);
130        out.append("Long id : "+id+"\r\n");
131        out.append("int randomInt : "+randomInt+"\r\n");
132        out.append("String stringF : "+stringF+"\r\n");
133        out.append("BigInteger bigIntegerF : "+bigIntegerF+"\r\n");
134        out.append("SomeColours bitmapColour : "+bitmapColour+"\r\n");
135        out.append("Short shortFF : "+shortFF+"\r\n");
136        out.append("Timestamp timestampF (HReadable) : "+timestampF+"\r\n");
137        out.append("AnimalSounds stringSDF (HReadable) : "+stringSDFSerialize()+"\r\n");
138        out.append("Uri uriF : " + uriF+"\r\n");
139        out.append("Currency currencyF : " + currencyF.getSymbol()+"\r\n");
140        out.append("... \r\n");
141        return out.toString();
142    }
143
144    public String toShortString() {
145        StringBuffer out = new StringBuffer(256);
146        out.append("[ Long id : "+id+"; ");
147        out.append("int randomInt : "+randomInt+"; ");
148        out.append("String stringF : "+stringF+"; ");
149        out.append("BigInteger bigIntegerF : "+bigIntegerF+"; ");
150        out.append("SomeColours bitmapColour : "+bitmapColour+"; ");
151        out.append("Short shortFF : "+shortFF+"; ");
152        out.append("Timestamp timestampF (HReadable) : "+timestampF+"; ... ]");
153        return out.toString();
154    }
155
156    @Deprecated
157    public String toHTMLString() {
158        StringBuffer out = new StringBuffer(2048);
159        out.append("<br>Long id : "+id.toString()+"\r\n");
160        out.append("<br><b>PRIMITIVES</b>"+"\r\n");
161        out.append("<br>boolean boolF : "+Boolean.toString(boolF)+"\r\n");
162        out.append("<br>int randomInt : "+Integer.toString(randomInt)+"\r\n");
163        out.append("<br>byte byteF : "+Byte.toString(byteF)+"\r\n");
164        out.append("<br>double doubleF : "+Double.toString(doubleF)+"\r\n");
165        out.append("<br>long longF : "+Long.toString(longF)+"\r\n");
166        out.append("<br>short shortF : "+Short.toString(shortF)+"\r\n");
167        out.append("<br>float floatF : "+Float.toString(floatF)+"\r\n");
168        out.append("<br>byte[] byteArray : "+byteArrayToString(byteArray)+"\r\n");
169        out.append("<br><b>STRING AFFINITIES</b>"+"\r\n");
170        out.append("<br>String randomAnimalName : "+randomAnimalName+"\r\n");
171        out.append("<br>String stringF : "+stringF+"\r\n");
172        out.append("<br>BigDecimal bigDecimalF : "+bigDecimalF.toEngineeringString()+"\r\n");
173        out.append("<br>BigInteger bigIntegerF : "+bigIntegerF.toString()+"\r\n");
174        out.append("<br>Animals randomAnimal : "+randomAnimal.toString()+"\r\n");
175        out.append("<br><b>SERIALIZATION AND DESERIALIZATION</b>"+"\r\n");
176        out.append("<br>AnimalSounds stringSDF : "+stringSDFSerialize()+"\r\n");
177        out.append("<br>SomeColours bitmapColour : "+bitmapColour.toString()+"\r\n");
178        out.append("<br><b>PRIMITIVE WRAPPERS</b>"+"\r\n");
179        out.append("<br>Boolean boolFF : "+boolFF.toString()+"\r\n");
180        out.append("<br>Integer randomInteger : "+randomInteger.toString()+"\r\n");
181        out.append("<br>Byte byteFF : "+byteFF.toString()+"\r\n");
182        out.append("<br>Double doubleFF : "+doubleFF.toString()+"\r\n");
183        out.append("<br>Short shortFF : "+shortFF.toString()+"\r\n");
184        out.append("<br>Float floatFF :"+floatFF.toString()+"\r\n");
185        out.append("<br><b>LONG REPRESENTED TYPES</b>"+"\r\n");
186        out.append("<br>Long longFF : "+longFF.toString()+"\r\n");
187        out.append("<br>Date dateF : "+Long.toString(dateF.getTime())+"\r\n");
188        out.append("<br>Calendar calendarF : "+Long.toString(calendarF.getTimeInMillis())+"\r\n");
189        out.append("<br>Timestamp timestampF : "+Long.toString(timestampF.getTime())+"\r\n");
190        out.append("<br>Date dateF (HReadable) : "+dateF.toString()+"\r\n");
191        out.append("<br>Calendar calendarF (HReadable) : "+calendarF.getTime().toString()+"\r\n");
192        out.append("<br>Timestamp timestampF (HReadable) : "+timestampF.toString()+"\r\n");
193        return out.toString();
194    }
195
196    public String byteArrayToString(byte[] toString) {
197        String[] strings = new String[toString.length];
198        for(int i = 0; i < toString.length; i++) {
199            strings[i] = Byte.toString(toString[i]);
200        }
201        return KittyUtils.implodeWithCommaInBKT(strings);
202    }
203}

And, finally, create extended CRUD controller for usage with RandomModel.class POJO.

Click to view RandomMapper.class:
 1public class RandomMapper extends KittyMapper {
 2
 3    public <M extends KittyModel> RandomMapper(KittyTableConfiguration tableConfiguration,
 4                                              M blankModelInstance,
 5                                              String databasePassword) {
 6        super(tableConfiguration, blankModelInstance, databasePassword);
 7    }
 8
 9    protected SQLiteCondition getAnimalCondition(Animals animal) {
10        return new SQLiteConditionBuilder()
11                .addColumn(RND_ANIMAL_CNAME)
12                .addSQLOperator("=")
13                .addObjectValue(animal)
14                .build();
15    }
16
17    public long deleteByRandomIntegerRange(int start, int end) {
18        return deleteWhere("#?randomInt; >= ? AND #?randomInt; <= ?", start, end);
19    }
20
21    public long deleteByAnimal(Animals animal) {
22        return deleteWhere(getAnimalCondition(animal));
23    }
24
25    public List<RandomModel> findByAnimal(Animals animal, long offset, long limit, boolean groupingOn) {
26        SQLiteCondition condition = getAnimalCondition(animal);
27        QueryParameters qparam = new QueryParameters();
28        qparam.setLimit(limit).setOffset(offset);
29        if(groupingOn)
30            qparam.setGroupByColumns(RND_ANIMAL_CNAME);
31        else
32            qparam.setGroupByColumns(KittyConstants.ROWID);
33        return findWhere(condition, qparam);
34    }
35
36    public List<RandomModel> findByIdRange(long fromId, long toId, boolean inclusive, Long offset, Long limit) {
37        SQLiteCondition condition = new SQLiteConditionBuilder()
38                .addColumn("id")
39                .addSQLOperator(inclusive ? GREATER_OR_EQUAL : GREATER_THAN)
40                .addValue(fromId)
41                .addSQLOperator(AND)
42                .addColumn("id")
43                .addSQLOperator(inclusive ? LESS_OR_EQUAL : LESS_THAN)
44                .addValue(toId)
45                .build();
46        QueryParameters qparam = new QueryParameters();
47        qparam.setLimit(limit).setOffset(offset).setGroupByColumns(KittyConstants.ROWID);
48        return findWhere(condition, qparam);
49    }
50
51    public List<RandomModel> findAllRandomModels(Long offset, Long limit) {
52        QueryParameters qparam = new QueryParameters();
53        qparam.setLimit(limit).setOffset(offset).setGroupByColumns(KittyConstants.ROWID);
54        return findAll(qparam);
55    }
56
57}

By default, all configurations of KittyORM are defined in annotations (however you can extend KittyORM for using any other configuration approach). Let’s give a look at most common KittyORM annotations, when they should be used and for what purposes.

Annotation name Annotated element Annotation purposes
@KITTY_DATABASE KittyDatabase regular implementation Used for setting such properties as package domains, schema name, schema version, log tag etc.
@KITTY_DATABASE_HELPER KittyDatabase regular implementation Used for managing default file paths for SQLite script and on database upgrade behavior.
@KITTY_EXTENDED_CRUD KittyModel regular implementation Used for exact defining of extended CRUD controller related to this particular POJO.
@KITTY_TABLE KittyModel regular implementation Used for defining a table name this POJO associated with and some other options such as setting a POJO schema model or using this POJO with the correspondent table.
@KITTY_COLUMN KittyModel implementation field that corresponds to the table column Used for defining an associated column name, type affinity, order etc
@KITTY_COLUMN_ACCEPTED_VALUES KittyModel implementation field that corresponds to the table column Used for partial CHECK constraint simulation. If model field value is not in the provided array, then KittyRuntimeException would be thrown. It works, but presents a kind of not needed functional. Sometimes, when KittyORM would have Java8 support it would use Lambda expressions.
@KITTY_COLUMN_SERIALIZATION KittyModel implementation field that corresponds to the table column Used for auto-mapping of complex objects to SQLite TEXT or NONE type affinity. For example, POJO may contain Bitmap field that would be automatically stored as BLOB in database and BLOB from database would be converted back to Bitmap.
@KITTY_DATABASE_REGISTRY KittyDatabase regular implementation Used for KittyORM database registry definition.
@KITTY_REGISTRY_PAIR KittyDatabase regular implementation Used for model and mapper pair definition apart with KittyORM database registry definition via @KITTY_DATABASE_REGISTRY.

You can find more documentation about KittyORM basic annotations at KittyORM project page.

Creating new entity and storing it in database

Use object wrappers for PK. No primitives. No exceptions.

In this lesson we would work with RandomModel.class. KittyORM can handle enumerations and wrapped primitives automatically. Enumerations would be stored as TEXT type affinity, wrappers would be stored as INTEGER or NUMERIC type affinities, boolean and Boolean stored as INTEGER type affinity.

alt text alt text alt text

As you can see, RandomModel.class contains only one model field, String randomAnimalSays. id, randomInt, randomInteger, randomAnimal and randomAnimalName were inherited from super class. So the generated table for RandomModel.class has name random and consists of columns defined both in RandomModel.class and AbstractRandomModel.class.

For inserting new RandomModel into database just get KittyMapper from RandomDatabase with method getMapper(RandomModel.class), create new instance of RandomModel and save it with save(model) or insert(model)method of acquired mapper instance.

 1// Initializing database instance
 2BasicDatabase db = new BasicDatabase(getContext());
 3// Getting mapper instance
 4RandomMapper mapper = (RandomMapper) db.getMapper(RandomModel.class);
 5// Initializing model
 6RandomModel toInsert = new RandomModel();
 7// Setting model fields
 8toInsert.randomInt = 10;
 9...
10// Saving model with save method
11mapper.save(toInsert);
12// Saving model with direct insert call
13mapper.insert(toInsert);

What is the best method to save new entity?

Actually, there is no ‘best’ method to save new entity. KittyMapper provides two main methods for saving new model. They are KittyMapper.save(M model) and KittyMapper.insert(M model).
By using KittyMapper.save(M model) you force KittyORM to define what operation should be done with provided model: update or insert. KittyORM makes this decision based on state of entity fields that can be used for unambiguous definition of associated record in database.

It checks rowid first, if rowid value is NULL or table was created with WITHOUT ROWID flag, KittyORM tries to check PRIMARY KEY field values. If table has INTEGER PRIMARY KEY as PK, KittyORM would check if it is set. If table has complex PRIMARY KEY that KittyORM would check state of all entity fields that correspond with PRIMARY KEY columns of this table that defined via @KITTY_COLUMN annotation as generated at insertion by SQLite. After all of those actions KittyORM can suppose provided model to be new or existing and ran INSERT or UPDATE query. That means that KittyMapper.save(M model) method slower than KittyMapper.insert(M model) and not suitable for tables created without PRIMARY KEY that has no auto generated column values and with WITHOUT ROWID flag.

Also, KittyMapper.save(M model) is a void and instead of KittyMapper.insert(M model) doesn’t return rowid of newly inserted record.
However, KittyMapper.save(List<M> models) allows you to save entities associated with both new and existing records in target table.
So some tip: use most suitable method for concrete task. If you want to save 100% newly created entity then just use insert(M model) method. If you for some reasons do not know is model associated with new or with existing record and model has support of rowid\IPK\PK with auto generated columns than use save(M model).

Overview of supported SQLite type affinities and Java → type affinity table located in KittyORM datatypes mapping.