I use ormlite and I have a db with a field:
public static final String NAME = "name";
#DatabaseField (canBeNull = false, dataType = DataType.SERIALIZABLE, columnName = NAME)
private String[] name = new String[2];
And I would like to get all elements that name[0] and name[1] are "car". I try to add a where clausule like:
NAMEDB nameDB = null;
Dao<NAMEDB, Integer> daoName = this.getHelper().getDao(NAMEDB.class);
QueryBuilder<NAMEDB, Integer> queryName = daoName.queryBuilder();
Where<NAMEDB, Integer> where = queryName.where();
where.in(nameDb.NAME, "car");
But it doesn't work because it's an array string.
I have other fields:
public static final String MARK = "mark";
#DatabaseField (canBeNull = false, foreign = true, index = true, columnName = MARK)
private String mark = null;
And I can do this:
whereArticulo.in(nameDB.MARK, "aaa");
How can I solve my problem? Thanks.
It seems to me that a third option to store a string array (String[] someStringArray[]) in the database using Ormlite would be to define a data persister class that converts the string array to a single delimited string upon storage into the database and back again to a string array after taking it out of the database.
E.g., persister class would convert ["John Doe", "Joe Smith"] to "John Doe | Joe Smith" for database storage (using whatever delimiter character makes sense for your data) and converts back the other way when taking the data out of the database.
Any thoughts on this approach versus using Serializable or a foreign collection? Anyone tried this?
I just wrote my first persister class and it was pretty easy. I haven't been able to identify through web search or StackOverflow search that anyone has tried this.
Thanks.
As ronbo4610 suggested, it is a good idea to use a custom data persister in this case, to store the array as a string in the database separated by some kind of delimiter. You can then search the string in your WHERE clause just as you would any other string. (For example, using the LIKE operator)
I have implemented such a data persister. In order to use it, you must add the following annotation above your String[] object in your persisted class:
#DatabaseField(persisterClass = ArrayPersister.class)
In addition, you must create a new class called "ArrayPersister" with the following code:
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.field.SqlType;
import com.j256.ormlite.field.types.StringType;
import org.apache.commons.lang3.StringUtils;
public class ArrayPersister extends StringType {
private static final String delimiter = ",";
private static final ArrayPersister singleTon = new ArrayPersister();
private ArrayPersister() {
super(SqlType.STRING, new Class<?>[]{ String[].class });
}
public static ArrayPersister getSingleton() {
return singleTon;
}
#Override
public Object javaToSqlArg(FieldType fieldType, Object javaObject) {
String[] array = (String[]) javaObject;
if (array == null) {
return null;
}
else {
return StringUtils.join(array, delimiter);
}
}
#Override
public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) {
String string = (String)sqlArg;
if (string == null) {
return null;
}
else {
return string.split(delimiter);
}
}
}
Unfortunately ORMLite does not support querying fields that are the type SERIALIZABLE. It is storing the array as a serialized byte[] so you cannot query against the values with an IN query like:
where.in(nameDb.NAME, "car");
ORMLite does support foreign collections but you have to set it up yourself with another class holding the names. See the documentation with sample code:
http://ormlite.com/docs/foreign-collection
Related
I have this query set up to return all the records from these tables and display the information on a recyclerview in android. the DB is set up using the Room persistence library aka SQLITE.
#Query
("SELECT moodBeforetable.userId,
moodBeforetable.moodBefore,
moodBeforetable.cbtId,
cbtTable.automaticThought,
cbtTable.twistedThinkingPK,
cbtTable.challengeThought,
cbtTable.rationalThought,
cbtTable.date,
moodAfterTable.moodAfter,
twistedThinkingTable.twistedThinkingPK,
twistedThinkingTable.allOrNothing,
twistedThinkingTable.blamingOthers,
twistedThinkingTable.catastrophizing,
twistedThinkingTable.emotionalReasoning,
twistedThinkingTable.fortuneTelling,
twistedThinkingTable.labelling,
twistedThinkingTable.magnifyingTheNegative,
twistedThinkingTable.mindReading,
twistedThinkingTable.minimisingThePositive,
twistedThinkingTable.overGeneralisation,
twistedThinkingTable.selfBlaming,
twistedThinkingTable.shouldStatement
FROM moodBeforetable
JOIN cbtTable ON moodBeforetable.cbtId = cbtTable.cbtId
JOIN twistedThinkingTable ON cbtTable.cbtId = twistedThinkingTable.cbtId
JOIN moodAfterTable ON moodAfterTable.cbtId = cbtTable.cbtId
WHERE moodBeforetable.date >= datetime('now', '-1 year')
AND moodBeforetable.userId = :userId
ORDER BY :date DESC")
LiveData<List<MoodBeforeTable>> moodLogsAll (int userId, String date);
When I try to compile the app I get the following error:
The query returns some columns which are not used by com.example.feelingfit.persistence.tables.MoodBeforeTable.
You can use #ColumnInfo annotation on the fields to specify the mapping.
Could anyone help me debug this and find out why the app wont compile?
Problem is Room cannot map the result from your custom query to existing MoodBeforeTable. It is because your return type is List<MoodBeforeTable> but you have used joins using TwistedThinkingTable and MoodAfterTable and so on.
What you should do is create a new POJO like below:
public class MoodLogPojo() {
private int userId;
private String moodBefore;
zprivate int cbtId;
private String automaticThought;
private int twistedThinkingPK;
private String challengeThought;
private String rationalThought;
private String date;
private String moodAfter;
private int twistedThinkingPK;
private String allOrNothing;
private String blamingOthers;
private String catastrophizing;
private String emotionalReasoning;
private String fortuneTelling;
private String labelling;
private String magnifyingTheNegative;
private String mindReading;
private String minimisingThePositive;
private String overGeneralisation;
private String selfBlaming;
private String shouldStatement;
// generate getters and setters too
public void setSelfBlaming(Stirng selfBlming) {
this.selfBlming = selfBlming
}
public String getSelfBlaming() { return selfBlaming; }
// and so on ...
}
Then use this class as the return type like :
LiveData<List<MoodLogPojo>> moodLogsAll (int userId, String date);.
NOTE: Mind the MoodLogPojo class. Modify it accoring to the corresponding data types from each Entity.
After parsing a nested JSON array with Gson, I now need to insert the result into SQLite. I tried inserting as done when not parsing with Gson, but that didn't work. I looked for ways to do that but couldn't find a solution.
The JSON parsing:
Gson gson = new Gson();
Type listType = new TypeToken<List<Country>>(){}.getType();
List<Country> countriesList = gson.fromJson(jsonString, listType);
for(Country country : countriesList) {
ContentValues insertValues;
}
If I wasn't using Gson, I would have written the line:
JSONObject countryObject = countriesList.getJSONObject(country);
EDIT
One of the objects from the JSON
[
{
"name":"Afghanistan",
"topLevelDomain":[
".af"
],
"callingCodes":[
"93"
],
"capital":"Kabul",
"region":"Asia",
"subregion":"Southern Asia",
"population":27657145,
"latlng":[
33.0,
65.0
],
"demonym":"Afghan",
"area":652230.0,
"gini":27.8,
"timezones":[
"UTC+04:30"
],
"nativeName":"افغانستان",
"numericCode":"004",
"currencies":[
{
"name":"Afghan afghani",
"symbol":"؋"
}
],
"languages":[
{
"name":"Pashto",
"nativeName":"پښتو"
},
{
"name":"Uzbek",
"nativeName":"Oʻzbek"
},
{
"name":"Turkmen",
"nativeName":"Türkmen"
}
],
"translations":{
"de":"Afghanistan",
},
"flag":"https://restcountries.eu/data/afg.svg",
"cioc":"AFG"
},
The model classes I wrote are only for the variables objects and arrays I needed.
The model class Country.Java
public class Country implements Parcelable {
private String name;
private String capital;
private String region;
private String subregion;
private int population;
private List<Double> latlng = new ArrayList<Double>();
private double area;
private double gini;
private List<String> timezones = new ArrayList<String>();
private List<Currency> currencies = new ArrayList<Currency>();
private List<Language> languages = new ArrayList<Language>();
private String flag;
public Country() {}
//getters, setters, toString() and Parcelable methods
}
The model class Currency.Java
public class Currency implements Parcelable {
private String name;
private String symbol;
//getters, setters, toString() and Parcelable methods
}
The model class Language.Java
public class Language implements Parcelable {
private String name;
private String nativeName;
//getters, setters, toString() and Parcelable methods
}
First get property of Country and then put it to content values and insert.
List<Country> countries = gson.fromJson(jsonString, new TypeToken<List<Country>>(){}.getType());
for(Country country : countries) {
String name = country.getName();
String capital = country.getCapital();
String region = country.getRegion();
String currencies = gson.toJson(country.getCurrencies());
...
ContentValues insertValues = new ContentValues();
insertValues.put("name", name)
insertValues.put("capital", capital);
insertValues.put("region", region);
insertValues.put("currencies", currencies);
...
long res = db.insert(TABLE_NAME, null, insertValues);
}
It's hard to know where your problem is without more code. You are doing two different operations:
unmarshalling a json
persisting data in SQLite.
Unmarshalling json with Gson
Inside the for-loop, can you log each country to see that you are getting a valid object with all the fields set? It is very much possible that you need to create a factory yourself. Does Country have subtype that you need to register through RuntimeTypeAdapterFactory? Maybe something like
final RuntimeTypeAdapterFactory<City> typeFactory = RuntimeTypeAdapterFactory
of(City.class, "MyCity")
.registerSubtype(S...,...)
Save in SQLite
Once you have valid data, then you must convert per field like so
public static ContentValues getContentValues(Country country) {
ContentValues values = new ContentValues();
values.put(COLUMN_ID, country.getId());
values.put(COLUMN_NAME, country.getName());
...
return values;
}
And then, if it still doesn't work, you will need to look at your SQLite schema.
I have a RealmObject, which is used as a temporary data cache only (there will be many entries). I also wrote a static method add() so I can easily add a new entry, but it seems too complicated. Here is the whole class:
public class ExchangePairPriceCache extends RealmObject {
#Index
private String exchangeName;
#Index
private String baseCurrency;
#Index
private String quoteCurrency;
private float price;
private long lastPriceUpdate;
public ExchangePairPriceCache() {
exchangeName = "";
baseCurrency = "";
quoteCurrency = "";
price = 0;
lastPriceUpdate = 0;
}
public ExchangePairPriceCache(String exchangeName, String baseCurrency, String quoteCurrency) {
this.exchangeName = exchangeName;
this.baseCurrency = baseCurrency;
this.quoteCurrency = quoteCurrency;
price = 0;
lastPriceUpdate = 0;
}
public void setPrice(float price) {
// this needs to be called inside a Realm transaction if it's a managed object
this.price = price;
lastPriceUpdate = System.currentTimeMillis();
}
public float getPrice() {
return price;
}
/* static functions */
public static void add(String exchangeName, String baseCurrency, String quoteCurrency, float price) {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(r -> {
ExchangePairPriceCache priceCache = r.where(ExchangePairPriceCache.class)
.equalTo("exchangeName", exchangeName)
.equalTo("baseCurrency", baseCurrency)
.equalTo("quoteCurrency", quoteCurrency).findFirst();
if(priceCache != null) {
priceCache.setPrice(price);
} else {
priceCache = new ExchangePairPriceCache(exchangeName, baseCurrency, quoteCurrency);
priceCache.setPrice(price);
ExchangePairPriceCache finalPriceCache = priceCache;
r.insert(finalPriceCache);
}
});
realm.close();
}
public static ExchangePairPriceCache get(String exchangeName, String baseCurrency, String quoteCurrency) {
Realm realm = Realm.getDefaultInstance();
ExchangePairPriceCache priceCache = realm.where(ExchangePairPriceCache.class)
.equalTo("exchangeName", exchangeName)
.equalTo("baseCurrency", baseCurrency)
.equalTo("quoteCurrency", quoteCurrency)
.greaterThan("lastPriceUpdate", System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(10)).findFirst();
if(priceCache != null)
priceCache = realm.copyFromRealm(priceCache);
realm.close();
return priceCache;
}
public static void deleteAll() {
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(r -> r.delete(ExchangePairPriceCache.class));
realm.close();
}
}
Questions:
Is this a good design (having static functions for ease of use)? I like how I can insert new entries into cache like ExchangePairPriceCache.add("NASDAQ", "AAPL", "USD", 100.5); and delete all with ExchangePairPriceCache.deleteAll() when needed.
How can I simplify add() function? Right now I check if entry already exists and then update the price and if it doesn't, I create a new object and insert it into Realm. I am not able to use updateOrInsert because I don't have unique index for object.
Maybe I am just questioning myself too much and this is all good as it is. But I'd really appreciate some input from experts who use it daily.
You should use a "Repository design pattern" with a DAO object (Data Access Object), to do all your read/ write transactions in realm.
Model class should be a blind copy of objects just holding entities.
Since you do not have any unique identifiers, you can try below
Cache the Exchange pair in Shared preferences file (if they are added earlier or not)
For faster read/writes : Create a temporary unique identifier with a combination of key-value pair that you already have
eg : (exchangeName + baseCurrency + quoteCurrency) - Cast into proper formats to create some unique key with all these values.
please i am new to android, and i need to fully understand the use of POJO class for populating recyclerview in android. The way i do it is fetch data from local/API put it in a 2D ArrayList and pass it to the adapter class of recyclerview, for example the code below fetches music from device and add it to a musics 2D ArrayList :
musics.clear();
musicResolver = getActivity().getContentResolver();
Uri musicuri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor musicCursor = musicResolver.query(musicuri, null, null, null, "LOWER(" + MediaStore.Audio.Media.TITLE + ")ASC");
if (musicCursor != null) {
while (musicCursor.moveToNext()) {
ArrayList<String> tempmusic = new ArrayList<>();
tempmusic.add(0, musicCursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
long time = Integer.parseInt(musicCursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)));
tempmusic.add(1, (new SimpleDateFormat("mm:ss", Locale.getDefault())).format(new Date(time)));
tempmusic.add(2, musicCursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));
tempmusic.add(3, musicCursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
musics.add(tempmusic); //2d array
}
musicCursor.close();
}
rvAdapter.notifyDataSetChanged();
I have no problem with the above approach, it works fine, but i came across a tutorial where a POJO class was used which implements Serializable, see below:
public class Audio implements Serializable {
private String data;
private String title;
private String size;
private String duration;
public Audio(String data, String title, String duration, String size) {
this.data = data;
this.title = title;
this.duration = duration;
this.size = size;
}
public String getData() {
return data;
}
public String getTitle() {
return title;
}
public String getDuration() {
return duration;
}
public String getSize() {
return size;
}
}
Method for retrieving using the POJO class:
//musics.clear();
musicResolver = getActivity().getContentResolver();
Uri musicuri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor musicCursor = musicResolver.query(musicuri, null, null, null, "LOWER(" + MediaStore.Audio.Media.TITLE + ")ASC");
if (musicCursor != null) {
while (musicCursor.moveToNext()) {
ArrayList<String> tempmusic = new ArrayList<>();
String data =
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
String title =
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
String album =
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
String artist =
cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));
tempmusic.add(new Audio(data, title, album, artist));
}
musicCursor.close();
}
rvAdapter.notifyDataSetChanged();
Now my question is:
What is the usefulness of the pojo class, if i can achieve the whole operation with 2d arraylist.
If pojo class implements serializable, what does it mean and the advantage(s) it has over using an ordinary pojo class or just using a 2d arraylist
A clear explanation will be very much appreciated, thanks.
What is the usefulness of the pojo class
The term "POJO" initially denoted a Java object which does not follow any of the major Java object models, conventions, or frameworks; nowadays "POJO" may be used as an acronym for "Plain Old JavaScript Object" as well, in which case the term denotes a JavaScript object of similar pedigree
A POJO is usually simple so won't depend on other libraries, interfaces or annotations. This increases the chance that this can be reused in multiple project types
Read more about Plain old Java object (POJO) and from here Advantage of POJO
If pojo class implements serializable, what does it mean and the advantage(s) it has over using an ordinary pojo class or just using a 2d arraylist
Serializable
Serializability of a class is enabled by the class implementing the java.io.Serializable interface. Classes that do not implement this interface will not have any of their state serialized or deserialized. All subtypes of a serializable class are themselves serializable. The serialization interface has no methods or fields and serves only to identify the semantics of being serializable.
Read serialization - advantages and disadvantages
I have 3 tables:
#DatabaseTable(tableName="user")
public class TableUser implements Serializable{
#DatabaseField(dataType = DataType.SERIALIZABLE)
private LinkedList<TableProfile> profiles;
}
#DatabaseTable(tableName="profile")
public class TableProfile implements Serializable{
#DatabaseField(dataType = DataType.SERIALIZABLE)
private LinkedList<TableRevel> revelations;
}
#DatabaseTable(tableName="revel")
public class TableRevel implements Serializable{
private String our_profile_id;
#DatabaseField(canBeNull=false)
private String other_profile_id;
#DatabaseField(canBeNull=false)
private String info;
#DatabaseField(canBeNull=false)
}
I need update fileds in "revel" table , I get revelations from Json without object "user:{profile:...}"
I think I need use QueryBuilder, please write short example of this query if its passible.
I would force the column name to be something static so you can search on it. Something like this:
#DatabaseTable(tableName="revel")
public class TableRevel implements Serializable{
public static final String PROFILE_ID = 'id';
#DatabaseField(canBeNull=false, columnName = PROFILE_ID)
private String our_profile_id;
#DatabaseField(canBeNull=false)
private String other_profile_id;
#DatabaseField(canBeNull=false)
private String info;
}
Then I would do something like this to do the update:
public class foo extends OrmLiteSqliteOpenHelper {
...
public updateField(String jsonString) throws SQLException{
Dao<TableRevel, String> revelDAO = getDao(TableRevel.class);
QueryBuilder<TableRevel, String> queryBuilder = revelDAO.queryBuilder();
queryBuilder.where().eq(TableRevel.PROFILE_ID, getIdFromJson(jsonString) );
PreparedQuery<TableRevel> preparedQuery = queryBuilder.prepare();
TableRevel revelEntry = revelDAO.queryForFirst();
//update the entry here
...
revelDAO.update(revelEntry); //update the data
I have written a project that uses OrmLite. The link to the class that does a lot of the sql stuff is here: https://github.com/kopysoft/Chronos/blob/development/ChronosApp/src/com/kopysoft/chronos/content/Chronos.java
Hope this helps!
As I understand Ethan's answer it would mean loading the TableRevel object into memory in order to update it. Depending on the type of update you need, this might be a viable alternative:
public update(String jsonString) throws SQLException{
Dao<TableRevel, String> dao= getDao(TableRevel.class);
UpdateBuilder<TableRevel, String> updateBuilder = dao.updateBuilder();
//Update the columns you want to update for the given jsonString-match
updateBuilder.updateColumnValue(TableRevel.My_FIELD, newValue);
// ...where the json string matches
updateBuilder.where().eq(TableRevel.PROFILE_ID, getIdFromJson(jsonString));
//Execute the update
dao.update(updateBuilder.prepare());
}
Sorry if I misunderstood your question, but the info you provide is a bit sparse and I for once have no idea about the internals of json ;) . - either way this update query might suit your needs, depending on how complex your udpate is. Big plus: No need to load the object into memory.