GreenDAO entities with ToOne relation and Foreign Key - android

I'm trying to create entities in my database using GreenDAO. My object is defined by this class:
public class Container {
String id; //GUID
String name;
Product product;
}
public class Product {
String urlImage;
String productName;
String description;
}
The following code is defining my entities:
Entity container = schema.addEntity("Container");
Property productsForeignKey = container.addStringProperty("id").notNull().unique().getProperty();
container.addStringProperty("name");
Entity product = schema.addEntity("Product");
product.addStringProperty("productName");
product.addStringProperty("urlImage");
product.addStringProperty("description");
containerToProduct = container.addToOne(product, productsForeignKey);
In the end I get this exception:
java.lang.RuntimeException: Currently only single FK columns are supported: ToOne 'product' from Container to Product
What I'm doing wrong?

I solved it by using Container's primary key as Product's foreign key. It turns out that String Foreign Key is not a good choice. So, my solution is more like this:
Entity container = schema.addEntity("Container");
Property productsForeignKey = container.addLongProperty("newId").primaryKey().autoincrement().getProperty();
container.addStringProperty("name");
Entity product = schema.addEntity("Product");
product.addStringProperty("productName");
product.addStringProperty("urlImage");
product.addStringProperty("description");
containerToProduct = container.addToOne(product, productsForeignKey);
Where newId is just random field used for convenience.

Related

Android Room: How to return rows from a joined table

I have the following tables:
category:
id integer primary key autoincrement;
name text not null;
transaction:
id integer primary key autoincrement;
amount real not null;
category_id integer references category(id);
And have the following entity classes
class Category {
#PrimaryKey int cat_id;
String name;
}
class Transaction {
#PrimaryKey int tx_id;
double amount;
#ForeignKey(entity = Category.class, parentColumns = "category_id", childColumns = "cat_id")
int category_id;
#Embedded Category category;
}
When I run the following query, Transaction.category is always null
select t.* from transaction t JOIN category c ON t.category_id = c.cat_id
So far, most of the tutorials online don't show how to handle this situation. In fact, it also turns out room inserts the #Embedded fields.
How can I structure the entities to make sure a single query to retrieve transactions returns with related categories? The relationship is always one-to-one.
I'm using RxJava and do not want to do second queries. I would like to have a single query return everything because it's displayed in a ReycyclerView.
Thanks
Ok, so I found the answer from here: https://medium.com/androiddevelopers/database-relations-with-room-544ab95e4542 and https://developer.android.com/training/data-storage/room/relationships
So basically, the solution is to use the #Relation annotation , which can only be used on POJOs only as described here:
Please note this is for 1:1 relationships.
A one-to-one relationship between two entities is a relationship where
each instance of the parent entity corresponds to exactly one instance
of the child entity, and vice-versa.
For example, consider a music streaming app where the user has a
library of songs that they own. Each user has only one library, and
each library corresponds to exactly one user. Therefore, there should
be a one-to-one relationship between the User entity and the Library
entity.
First, create a class for each of your two entities. One of the
entities must include a variable that is a reference to the primary
key of the other entity.
#Entity
public class User {
#PrimaryKey public long userId;
public String name;
public int age;
}
#Entity
public class Library {
#PrimaryKey public long libraryId;
public long userOwnerId;
}
In order to query the list of users and corresponding libraries, you
must first model the one-to-one relationship between the two entities.
To do this, create a new data class where each instance holds an
instance of the parent entity and the corresponding instance of the
child entity. Add the #Relation annotation to the instance of the
child entity, with parentColumn set to the name of the primary key
column of the parent entity and entityColumn set to the name of the
column of the child entity that references the parent entity's primary
key.
public class UserAndLibrary {
#Embedded public User user;
#Relation(
parentColumn = "userId",
entityColumn = "userOwnerId"
)
public Library library;
}
Finally, add a method to the DAO class that returns all instances of
the data class that pairs the parent entity and the child entity. This
method requires Room to run two queries, so add the #Transaction
annotation to this method to ensure that the whole operation is
performed atomically.
#Transaction
#Query("SELECT * FROM User")
public List<UserAndLibrary> getUsersAndLibraries();

Query type-converted list of objects in Room

I have a table that looks like following
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Product
{
#PrimaryKey
#ColumnInfo(name = "ID")
#JsonProperty("ID")
public int id;
#ColumnInfo(name = "Name")
#JsonProperty("Name")
public String name;
#ColumnInfo(name = "Documents")
#JsonProperty("Documents")
#TypeConverters(DocumentConverter.class)
public List<Document> documents;
}
//...
#TypeConverters(DocumentConverter.class)
#JsonIgnoreProperties( ignoreUnknown = true )
#JsonTypeName("Documents")
public class Document
{
#JsonProperty("Name")
public String name;
#JsonProperty("URL")
public String url;
}
I am able to retrieve a product based on its name by doing something like this
#Query("SELECT * FROM Product WHERE Name = :name")
List<Product> getProducts(String name);
And I would then be able to access the list of documents from each Product object. However I would also like to only deal with Products that has certain documents. I could get all Products via a query like above, then manually filter for the documents that I want, but it becomes quite a pain when I'm only looking for very specific documents.
Is it possible to also query based on Document variables without it being a separate table?
Something like...
#Query("SELECT * FROM Product WHERE Name = :name AND Document.name = :documentName")
List<Product> getProducts(String name, String documentName);
Thanks.
You could use LIKE sql statement to search inside your json column with converted documents list. Example:
Assume that we have document converted like this for storing in db:
{
name: "Title",
url: "Your_url"
}
So your query for product with such document in list should be like this:
SELECT * FROM Product WHERE Name = :name AND Documents LIKE :documentLikeExpr
Where
String documentLikeExpr = "%name: \"Title\"%";
% in expression mean zero, one or multiple characters.
So the only thing we are doing here - is searching for part of string inside column using SQL language features.
You cannot query a Document class variables as it is not stored as a separate table. #TypeConverter annotation converts your Document list to some predefined data types such as String. Basically it stores list of Document as a string Gson in a column of Product table, So we cannot access the field name of Document class in SQL query like Document.name.
Read the Option #2 given by #CommonsWare here
So, to access it you have to create a separate table for Document.

How to make many-to-many relation query in GreenDAO with source property other than primary key?

Let's assume we have following entities:
Item:
class Item {
...
#Index(unique=true)
private String guid;
...
#ToMany
#JoinEntity(entity = JoinItemsWithTags.class, sourceProperty = "itemGuid", targetProperty = "tagName")
private List<Tag> tagsWithThisItem;
...
}
Tag:
class Tag {
#Id
private Long localId;
#Index(unique = true)
private String name;
...
}
and we need to join them. Here is my join entity class:
#Entity(nameInDb = "item_tag_relations")
class JoinItemsWithTags {
#Id
private Long id;
private String itemGuid;
private String tagName;
...
}
I want to use tag name as a join property instead of Long id, because it's easier to support consistency when syncing with server.
But currently tags getter in Item class always return an empty list. I've looked into log and found generated query which using internally in that getter:
SELECT * <<-- there were a long sequence of fields
FROM "tags" T JOIN item_tag_relations J1
ON T."_id"=J1."TAG_NAME" <<-- here is the problem, must be `T."NAME"=J1."TAG_NAME"`
WHERE J1."ITEM_GUID"=?
So the problem is that join is base on tag's _id field. Generated List<Tag> _queryItem_TagsWithThisItem(String itemGuid) method implicitly uses that id to make a join:
// this `join` nethod is overloaded and pass tag's id as source property
queryBuilder.join(JoinItemsWithTags.class, JoinItemsWithTagsDao.Properties.TagName)
.where(JoinItemsWithTagsDao.Properties.ItemGuid.eq(itemGuid));
Correct approach is this case might be following, I suppose:
// source property is passed explicitly
queryBuilder.join(/* Desired first parameter -->> */ TagDao.Properties.Name,
JoinItemsWithTags.class, JoinItemsWithTagsDao.Properties.TagName)
.where(JoinItemsWithTagsDao.Properties.ItemGuid.eq(itemGuid));
But this code is in generated dao, and I don't know how to do anything with it. Is there any way to workaround this?

Persisting array of strings with greenDao

I'm trying to map an object to database with greenDao. But when it comes to arrays, I don't know how to do it. After receiving JSON from network and deserializing it with GSON, I have objects defined by this class:
public class Car {
Long carId;
String name;
ArrayList<String> listOfLinks;
}
In case of a a different architecture, like this:
public class Car {
Long carId;
String name;
ArrayList<Link> listOfLinks;
}
public class Link {
Long carId;
String link;
}
----
Entity cars = schema.addEntity("Car");
cars.addLongProperty("carId").primaryKey();
cars.addStringProperty("name");
Entity links = schema.addEntity("Link");
links.addStringProperty("name");
links.addIdProperty().primaryKey().notNull().autoincrement();
Property linkProperty = links.addLongProperty("carId").getProperty();
ToMany carToLinks = cars.addToMany(link, linkProperty);
It would is easy. Define some relations, define properties, add foreign key and your done. With arrays I have no clue what to do. Ideas?
That approach is not common when using relational databases.
This is commonly done using to-many relations : instead of using a list of String, you can create a Link entity and then use a list of Link.
Relation toMany is useful when you have a list of your not primitive object, that you can declare like entity that have its own id etc etc etc, and make list of entities (with toMeny). By doing that greenDao makes another table in the base for you new entity with the foreign key of the base entity that contains list. When you have list of primitive type the only way to do is to make converter that converts List into one of the primitive types that greenDao works naturally. You have to do something like this `
import org.greenrobot.greendao.converter.PropertyConverter;
import java.util.Arrays;
import java.util.List;
/**
*DOLE BREEE SQLITE BREEEEEE!!!**
*i choosed to convert List into one string
*that is going to be saved in database, and vice versa
*/
public class GreenConverter implements PropertyConverter, String> {
#Override
public List convertToEntityProperty(String databaseValue) {
if (databaseValue == null) {
return null;
}
else {
List<String> lista = Arrays.asList(databaseValue.split(","));
return lista;
}
}
#Override
public String convertToDatabaseValue(List<String> entityProperty) {
if(entityProperty==null){
return null;
}
else{
StringBuilder sb= new StringBuilder();
for(String link:entityProperty){
sb.append(link);
sb.append(",");
}
return sb.toString();
}
}
}
now above all the properties that are List you have to put
#Convert(converter=yourconverterclass.class, columnType = String.class)
#Entity
public class ShipEntry {
#Id(autoincrement = true)
private long ship_id;
private String name;
private String model;
private String manufacturer;
private String starship_class;
#Convert(converter = GreenConverter.class, columnType = String.class)
private List<String> pilots;
#Convert(converter = GreenConverter.class, columnType = String.class)
private List<String> films ;
}
you can create Converter as a inner class of entitiy, and in that case it has to be declared as staticthat is the only way i have found, but the bad side is that you can not use property that you are converting into query. There might me some typo, but i hope this helps to solve your problem
I also have the same issue, and there no answer (not in official docs, not in google). Please explain how to map List to Entity?
public class Car {
Long carId;
String name;
ArrayList<String> listOfLinks;
}
Can I do something like this?
#Entity(active = true, nameInDb = "CARS")
public class Car {
#Id
private Long id;
#NotNull
#Unique
private String remoteId;
#ToMany(joinProperties = {
#JoinProperty(name = "remoteId", referencedName = "carRemoteId")
})
private List<Links> listOfLinks;
}
#Entity(active = true, nameInDb = "LISTOFLINKS")
public class Links{
#Id
private Long id;
#NotNull
#Unique
private String remoteId;
#SerializedName("listOfLinks")
#Expose
private String listOfLinks;//is it possible?????
private String carRemoteId;
}
Since JPA 2.0, you can use an element collection to persist a Collection of value types. You just need to annotate the attribute with #ElementCollection and the persistence provider will persist the elements of the Collection in an additional database table.
#Entity
public class Author {
#ElementCollection
private List<String> phoneNumbers = new ArrayList<String>();
}
The element collection might seem easier to use than an entity with a one-to-many association. But it has one major drawback: The elements of the collection have no id and Hibernate can’t address them individually.
When you add a new Object to the List or remove an existing one, Hibernate deletes all elements and inserts a new record for each item in the List.
Let’s take a quick look at an example. The following code snippet selects an Author entity and adds a second phoneNumber to the element collection.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Author a = em.find(Author.class, 1L);
a.getPhoneNumbers().add("42424242");
em.getTransaction().commit();
em.close();
an element collection is an easy but not the most efficient option to store a list of value types in the database. You should, therefore, only use it for very small collections so that Hibernate doesn’t perform too many SQL statements. In all other cases, a one-to-many association is the better approach.

Implementing a SQLite Database with List<String> as Column type using ORMLite

I have a requirement where I need to store a List in a column in the database. Serializing the list might be an option, but i am not sure if it is the right one.
Also, i want to avoid creating another table to store the list elements and a reference to the original table row.
I am using ORMLite for the database operations.
Its a concept of foreign collection.
You need to create an entity that wraps the String. Something looks like:
#DatabaseTable
public class Student {
#DatabaseField(generatedId = true)
print int id;
#DatabaseField
private String fname;
#DatabaseField
private String lname;
#DatabaseField(foreign = true)
private Address address;
}
Then your Address class would have a ForeignCollection of these Student.
#DatabaseTable
public class Address {
#DatabaseField(generatedId = true)
print int id;
#ForeignCollectionField()
private ForeignCollection<Student> student;
}
Also refer this link , may it will help you.

Categories

Resources