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}
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}
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.
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.