Android: how to insert data in manyToMany relationship in Room (Java) - java

Guys I'm trying to create a database with a manyToMany relationship, I'm able to create the 2 tables of elements but I'm not able to populate the joining table. I don't know how should I insert datas.
This is Card.class:
#Entity
public class Card {
#PrimaryKey(autoGenerate = true)
private Long idCard;
#ColumnInfo(name = "title")
private String title;
#ColumnInfo(name = "taboo_word_1")
private String tabooWord1;
#ColumnInfo(name = "taboo_word_2")
private String tabooWord2;
#ColumnInfo(name = "taboo_word_3")
private String tabooWord3;
#ColumnInfo(name = "taboo_word_4")
private String tabooWord4;
#ColumnInfo(name = "taboo_word_5")
private String tabooWord5;
public Long getIdCard() {
return idCard;
}
public void setIdCard(Long idCard) {
this.idCard = idCard;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTabooWord1() {
return tabooWord1;
}
public void setTabooWord1(String tabooWord1) {
this.tabooWord1 = tabooWord1;
}
public String getTabooWord2() {
return tabooWord2;
}
public void setTabooWord2(String tabooWord2) {
this.tabooWord2 = tabooWord2;
}
public String getTabooWord3() {
return tabooWord3;
}
public void setTabooWord3(String tabooWord3) {
this.tabooWord3 = tabooWord3;
}
public String getTabooWord4() {
return tabooWord4;
}
public void setTabooWord4(String tabooWord4) {
this.tabooWord4 = tabooWord4;
}
public String getTabooWord5() {
return tabooWord5;
}
public void setTabooWord5(String tabooWord5) {
this.tabooWord5 = tabooWord5;
}
}
Tag:
#Entity
public class Tag {
#PrimaryKey(autoGenerate = true)
private long idTag;
#ColumnInfo(name = "tag")
private String tag;
public Tag(String tag) {
this.tag = tag;
}
public long getIdTag() {
return idTag;
}
public void setIdTag(long idTag) {
this.idTag = idTag;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
#Override
public String toString() {
return getTag();
}
}
This is DatabaseTaboom.class:
#Database(entities = {Card.class, Tag.class, CardTagCrossRef.class},
version = 1)
public abstract class DatabaseTaboom extends RoomDatabase {
public static final String DATABASE_NAME = "db_taboom-1";
public abstract CardDAO cardDao();
public static DatabaseTaboom db;
// Singleton pattern
public static DatabaseTaboom getDatabase(Context applicationContext) {
if (db == null) {
db = Room.databaseBuilder(applicationContext, DatabaseTaboom.class, DATABASE_NAME)
//.allowMainThreadQueries()
.build();
}
return db;
}
}
This is CardDAO.class:
#Dao
public interface CardDAO {
#Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertCard(Card card);
#Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertTag(Tag tag);
#Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertCardWithTags(CardTagCrossRef cardTagCrossRef);
// If called on an item not present in the DB it won't do anything
#Update
public void updateCard(Card card);
#Delete
public void deleteCard(Card card);
// With a query method you can also perform complex inserts/updates/deletes
// Transaction needed for relational classes
#Transaction
#Query("SELECT * FROM Card")
LiveData<List<CardWithTags>> getAllCards();
}
This is CardTagCrossRef.class:
#Entity(primaryKeys = {"idCard", "idTag"})
public class CardTagCrossRef {
public long idCard;
public long idTag;
}
CardWithTags:
public class CardWithTags {
#Embedded private Card card;
#Relation(
parentColumn = "idCard",
entityColumn = "idTag",
associateBy = #Junction(CardTagCrossRef.class)
)
private List<Tag> tagList;
public CardWithTags() {
}
public CardWithTags(Card card, List<Tag> tagList) {
this.card = card;
this.tagList = tagList;
}
public Card getCard() {
return card;
}
public void setCard(Card card) {
this.card = card;
}
public List<Tag> getTagList() {
return tagList;
}
public void setTagList(List<Tag> tagList) {
this.tagList = tagList;
}
#Override
public String toString() {
String s = getCard().toString();
s += ", TAG[";
for (Tag t: getTagList()) {
s += t + "";
}
s+="]";
return s;
}
}
And this is the method that I wrote to insert a card:
public void insertCard(CardWithTags card) {
Log.d(TAG, ">>insertCard(): " + card);
executor.execute(() -> {
cardDAO.insertCard(card.getCard());
for (Tag t: card.getTagList()) {
cardDAO.insertTag(t);
CardTagCrossRef cardTagCrossRef = new CardTagCrossRef();
cardTagCrossRef.idCard = card.getCard().getIdCard();
cardTagCrossRef.idTag = t.getIdTag();
Log.d(TAG, "CardCrossRef:" + cardTagCrossRef.idCard + cardTagCrossRef.idTag);
cardDAO.insertCardWithTags(cardTagCrossRef);
}
// Check if tags already exists
cardListIsUpdatedWithDb = false;
});
}

First you should amend the Dao's so that they return the id of the inserted row enabling you to ascertain the actual id of the inserted rows. So :-
#Insert(onConflict = OnConflictStrategy.REPLACE)
public long insertCard(Card card);
#Insert(onConflict = OnConflictStrategy.REPLACE)
public long insertTag(Tag tag);
#Insert(onConflict = OnConflictStrategy.REPLACE)
public long insertCardWithTags(CardTagCrossRef cardTagCrossRef);
This allows you to retrieve the respective id when you insert either a Card or a Tag (note that for a CardTagCrossRef insertion this will be the rowid, a normally hidden row).
So you could then have use long cardId = cardDAO.insertCard(card.getCard()); and not then need to attempt to use cardTagCrossRef.idCard = card.getCard().getIdCard(); where the card DOES NOT have the id of the inserted card (part of the issue you are facing).
And likewise for the Tag.
So you could use :-
long cardId = cardDAO.insertCard(card.getCard());
for (Tag t: card.getTagList()) {
long tagId = cardDAO.insertTag(t);
CardTagCrossRef cardTagCrossRef = new CardTagCrossRef();
cardTagCrossRef.idCard = cardId;
cardTagCrossRef.idTag = tagId;
Log.d(TAG, "CardCrossRef:" + cardTagCrossRef.idCard + cardTagCrossRef.idTag);
cardDAO.insertCardWithTags(cardTagCrossRef);
}
However, with a few changes I believe that that can make things far more flexible and have an insert that effectively does what you want within the Dao's.
SO perhaps consider the following that culminates in a working DEMO
Card
#Entity
public class Card {
#PrimaryKey/*(autoGenerate = true) SUGGESTED suppression of autogenerate as will still autogenerate but more efficiently */
private Long idCard;
#ColumnInfo(name = "title")
private String title;
#ColumnInfo(name = "taboo_word_1")
private String tabooWord1;
#ColumnInfo(name = "taboo_word_2")
private String tabooWord2;
#ColumnInfo(name = "taboo_word_3")
private String tabooWord3;
#ColumnInfo(name = "taboo_word_4")
private String tabooWord4;
#ColumnInfo(name = "taboo_word_5")
private String tabooWord5;
/* Constructors added */
public Card(){}
#Ignore
public Card(Long idCard,String title, String tabooWord1, String tabooWord2, String tabooWord3, String tabooWord4, String tabooWord5) {
this.idCard = idCard;
this.title = title;
this.tabooWord1 = tabooWord1;
this.tabooWord2 = tabooWord2;
this.tabooWord3 = tabooWord3;
this.tabooWord4 = tabooWord4;
this.tabooWord5 = tabooWord5;
}
public Long getIdCard() {
return idCard;
}
public void setIdCard(Long idCard) {
this.idCard = idCard;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getTabooWord1() {
return tabooWord1;
}
public void setTabooWord1(String tabooWord1) {
this.tabooWord1 = tabooWord1;
}
public String getTabooWord2() {
return tabooWord2;
}
public void setTabooWord2(String tabooWord2) {
this.tabooWord2 = tabooWord2;
}
public String getTabooWord3() {
return tabooWord3;
}
public void setTabooWord3(String tabooWord3) {
this.tabooWord3 = tabooWord3;
}
public String getTabooWord4() {
return tabooWord4;
}
public void setTabooWord4(String tabooWord4) {
this.tabooWord4 = tabooWord4;
}
public String getTabooWord5() {
return tabooWord5;
}
public void setTabooWord5(String tabooWord5) {
this.tabooWord5 = tabooWord5;
}
}
2 changes an more flexible constructor and not using autogenerate = true (but that does automatically generate id's BUT without the overheads of the SQLite AUTOINCREMENT which is what autogenerate = true adds).
Tag (similar changes)
#Entity
public class Tag {
#PrimaryKey/*(autoGenerate = true) SUGGESTED suppression of autogenerate*/
private Long idTag;
#ColumnInfo(name = "tag")
private String tag;
public Tag(){}
#Ignore
public Tag(Long idTag, String tag) {
this.idTag = idTag;
this.tag = tag;
}
#Ignore
public Tag(String tag) {
this.tag = tag;
}
public Long getIdTag() {
return idTag;
}
public void setIdTag(Long idTag) {
this.idTag = idTag;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
#Override
public String toString() {
return getTag();
}
}
CardTagCrossRef (added ForeignKey constraints to enforce/manage referential integrity)
#Entity(
primaryKeys = {"idCard", "idTag"}
/* SUGGESTED */
, foreignKeys = {
#ForeignKey(
entity = Card.class,
parentColumns = "idCard",
childColumns = "idCard",
/* SUGGESTED with ForeignKey */
onDelete = CASCADE,
onUpdate = CASCADE
),
#ForeignKey(
entity = Tag.class,
parentColumns = "idTag",
childColumns = "idTag",
/* SUGGESTED with ForeignKey */
onDelete = CASCADE,
onUpdate = CASCADE
)
}
)
public class CardTagCrossRef {
public long idCard;
#ColumnInfo(index = true) /* SUGGESTED */
public long idTag;
public CardTagCrossRef(){}
#Ignore
public CardTagCrossRef(long idCard, long idTag) {
this.idCard = idCard;
this.idTag = idTag;
}
}
CardWithTags
identical other than #Ignore annotation on the CardWithTags(Card card, List<Tag> tagList) constructor to supress warnings about multiple good consctructors.
i.e.
....
#Ignore /*<<<<< SUGGESTED */
public CardWithTags(Card card, List<Tag> tagList) {
this.card = card;
this.tagList = tagList;
}
....
CardDAO (new INSERT + return values)
#Dao
abstract class CardDAO {
/* public interface CardDAO { CHANGED TO abstract class to allow functions with bodies */
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract long insertCard(Card card); /* Returns long (inserted row id) */
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract long insertTag(Tag tag); /* Returns long (inserted row id) */
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract long insertCardWithTags(CardTagCrossRef cardTagCrossRef); /* Returns long (inserted row id) */
/* NEW INSERT */
#Query("")
#Transaction
long[] insert(Card card, List<Tag> tags) {
long[] rv = new long[tags.size() + 1];
int ix = 0;
rv[ix++] = insertCard(card);
if (rv[ix-1] > -1) {
for (Tag t : tags) {
rv[ix++] = insertTag(t);
if (rv[ix-1] > -1) {
insertCardWithTags(new CardTagCrossRef(rv[0],rv[ix-1]));
}
}
}
return rv;
}
// If called on an item not present in the DB it won't do anything
#Update
abstract int updateCard(Card card); /* returns number of updated rows */
#Delete
abstract int deleteCard(Card card); /* returns number of deleted rows */
// With a query method you can also perform complex inserts/updates/deletes
// Transaction needed for relational classes
#Transaction
#Query("SELECT * FROM Card")
/* abstract LiveData<List<CardWithTags>> getAllCards(); COMMENTED OUT to allow demo to run on main thread */
abstract List<CardWithTags> getAllCards(); /* same but not with LiveData */
}
DatabaseTaboom (allow main thread + exportSchema = false to suppress warning)
#Database(entities = {Card.class, Tag.class, CardTagCrossRef.class},
version = 1/* SUGGESTED */ , exportSchema = false)
public abstract class DatabaseTaboom extends RoomDatabase {
public static final String DATABASE_NAME = "db_taboom-1";
abstract CardDAO cardDao();
public static DatabaseTaboom db;
// Singleton pattern
public static DatabaseTaboom getDatabase(Context applicationContext) {
if (db == null) {
db = Room.databaseBuilder(applicationContext, DatabaseTaboom.class, DATABASE_NAME)
.allowMainThreadQueries() /* uncommented for testing */
.build();
}
return db;
}
}
Finally the DEMO MainActivity with some examples of inserting Cards, Tags and CardTagCrossRef's followed by extracting them all outputting the results to the log.
public class MainActivity extends AppCompatActivity {
DatabaseTaboom db;
CardDAO dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = DatabaseTaboom.getDatabase(this);
dao = db.cardDao();
/* Simple but long winded */
long c1id = dao.insertCard(new Card(null,"Card1","tw1","tw2","tw3","tw4","tw5"));
long t1id = dao.insertTag(new Tag(null,"TAG1"));
CardTagCrossRef ctcr1 = new CardTagCrossRef();
ctcr1.idCard = c1id;
ctcr1.idTag = t1id ;
dao.insertCardWithTags(ctcr1);
/* Using additional constructor for CardTagCrossRef */
long t2id = dao.insertTag(new Tag("TAG2"));
dao.insertCardWithTags(new CardTagCrossRef(c1id,t2id));
/* More dynamic BUT don't know the actual inserted id's of the Card and Tag */
dao.insertCardWithTags(
new CardTagCrossRef(dao.insertCard(new Card(100l,"Card2","c2tw1","c2tw2","c2tw3","c2tw4","c2tw5")),dao.insertTag(new Tag(null,"TAG3"))));
CardWithTags cwt = new CardWithTags(
new Card(null,"CARD3","c3tw1","c3tw2","c3tw3","c3tw4","c3tw5"),
Arrays.asList(
new Tag(null,"TAG4"), new Tag("TAG5"), new Tag("TAG6")
)
);
/* Amended insert function */
insertCard(cwt,dao);
/* Using new insert funciotn */
dao.insert(
new Card(1000l,"CARD4","c4tw1","c4tw2","c4tw3","c4tw4","c4tw5"),
Arrays.asList(
new Tag(null,"TAG7"), new Tag(500l,"TAG8"),new Tag(null,"TAG9")
)
);
/* Extract the results and output to the log */
for(CardWithTags cwtlist: dao.getAllCards()) {
Log.d("CWTINFO","Card is " + cwtlist.getCard().getTitle() + " TabooWord1 is " + cwtlist.getCard().getTabooWord1() + " it has " + cwtlist.getTagList().size() + " tags. They are:-");
for(Tag t: cwtlist.getTagList()) {
Log.d("CWTINFO_TAG","\tTAG is " + t.getTag());
}
}
}
public void insertCard(CardWithTags card, CardDAO cardDAO) {
final String TAG = "INSERTCARDINFO";
Log.d(TAG, ">>insertCard(): " + card);
/*
executor.execute(() -> {
*/
long currentCardId = cardDAO.insertCard(card.getCard());
for (Tag t: card.getTagList()) {
long currentTagId = cardDAO.insertTag(t);
CardTagCrossRef cardTagCrossRef = new CardTagCrossRef();
cardDAO.insertCardWithTags(new CardTagCrossRef(currentCardId,currentTagId));
/*
cardTagCrossRef.idCard = card.getCard().getIdCard();
cardTagCrossRef.idTag = t.getIdTag();
*/
/*
OR with new Contsructor
CardTagCrossRef ctcr = new CardTagCrossRef(currentCardId,currentTagId);
*/
/* AND THEN cardDAO.insertCardWithTags(cardTagCrossRef); */
Log.d(TAG, "CardCrossRef:" + cardTagCrossRef.idCard + cardTagCrossRef.idTag);
}
/*
// Check if tags already exists
cardListIsUpdatedWithDb = false;
*/
/*})*/;
}
}
When run (after new install as only designed to run the once) the Log includes:-
2022-02-04 13:29:10.569D/INSERTCARDINFO: >>insertCard(): a.a.so70979022javaroom.Card#d751e5e, TAG[TAG4TAG5TAG6]
2022-02-04 13:29:10.573D/INSERTCARDINFO: CardCrossRef:00
2022-02-04 13:29:10.578I/chatty: uid=10194(a.a.so70979022javaroom) identical 1 line
2022-02-04 13:29:10.581D/INSERTCARDINFO: CardCrossRef:00
2022-02-04 13:29:10.600D/CWTINFO: Card is Card1 TabooWord1 is tw1 it has 2 tags. They are:-
2022-02-04 13:29:10.600D/CWTINFO_TAG: TAG is TAG1
2022-02-04 13:29:10.601D/CWTINFO_TAG: TAG is TAG2
2022-02-04 13:29:10.601D/CWTINFO: Card is Card2 TabooWord1 is c2tw1 it has 1 tags. They are:-
2022-02-04 13:29:10.601D/CWTINFO_TAG: TAG is TAG3
2022-02-04 13:29:10.601D/CWTINFO: Card is CARD3 TabooWord1 is c3tw1 it has 3 tags. They are:-
2022-02-04 13:29:10.601D/CWTINFO_TAG: TAG is TAG4
2022-02-04 13:29:10.601D/CWTINFO_TAG: TAG is TAG5
2022-02-04 13:29:10.601D/CWTINFO_TAG: TAG is TAG6
2022-02-04 13:29:10.601D/CWTINFO: Card is CARD4 TabooWord1 is c4tw1 it has 3 tags. They are:-
2022-02-04 13:29:10.601D/CWTINFO_TAG: TAG is TAG7
2022-02-04 13:29:10.601D/CWTINFO_TAG: TAG is TAG8
2022-02-04 13:29:10.601D/CWTINFO_TAG: TAG is TAG9
Via App Inspection then :-
and :-
and :-

Related

foreign key constraint failed (code 787 sqlite_constraint foreign key)

I have been using the Android Studio App for about a month now,in order to create a project for a university subject.
Unfortunatelly,I came across a problem while I was trying to create a database using Room that consists of 3 tables.
Just to let you know,I have read all the threads reffering to this problem and unfortunatelly I couldn't solve it...
#Entity(tableName = "travelAgencies")
public class travelAgency {
#PrimaryKey
#ColumnInfo(name = "tACode")
private int agencyCode;
#ColumnInfo(name = "tAName")
private String agencyName;
#ColumnInfo(name = "tAAddress")
private String agencyAddress;
public int getAgencyCode() {
return agencyCode;
}
public void setAgencyCode(int agencyCode) {
this.agencyCode = agencyCode;
}
public String getAgencyName() {
return agencyName;
}
public void setAgencyName(String agencyName) {
this.agencyName = agencyName;
}
public String getAgencyAddress() {
return agencyAddress;
}
public void setAgencyAddress(String agencyAddress) {
this.agencyAddress = agencyAddress;
}
#Entity(tableName = "suggetedVacations")
public class suggestedVacation {
#PrimaryKey
#ColumnInfo(name = "sVCode")
private int suggestedVacationCode;
#ColumnInfo(name = "sVCity")
private String suggestedVacationCity;
#ColumnInfo(name = "sVCountry")
private String SuggestedVacationCountry;
#ColumnInfo(name = "sVDuration")
private String SuggestedVacationDuration;
#ColumnInfo(name = "sVType")
private String SuggestedVacationType;
public int getSuggestedVacationCode() {
return suggestedVacationCode;
}
public void setSuggestedVacationCode(int suggestedVacationCode) {
this.suggestedVacationCode = suggestedVacationCode;
}
public String getSuggestedVacationCity() {
return suggestedVacationCity;
}
public void setSuggestedVacationCity(String suggestedVacationCity) {
this.suggestedVacationCity = suggestedVacationCity;
}
public String getSuggestedVacationCountry() {
return SuggestedVacationCountry;
}
public void setSuggestedVacationCountry(String suggestedVacationCountry) {
SuggestedVacationCountry = suggestedVacationCountry;
}
public String getSuggestedVacationDuration() {
return SuggestedVacationDuration;
}
public void setSuggestedVacationDuration(String suggestedVacationDuration) {
SuggestedVacationDuration = suggestedVacationDuration;
}
public String getSuggestedVacationType() {
return SuggestedVacationType;
}
public void setSuggestedVacationType(String suggestedVacationType) {
SuggestedVacationType = suggestedVacationType;
}
}
#Entity(tableName ="theVacationPacket",
primaryKeys = {"tVPAgencyCode","tVPVacationCode","tVPCode"},
foreignKeys = {
#ForeignKey(entity = travelAgency.class,
parentColumns = "tACode",
childColumns = "tVPAgencyCode",
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE),
#ForeignKey(entity = suggestedVacation.class,
parentColumns = "sVCode",
childColumns = "tVPVacationCode",
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE)})
public class vacationPacket {
#ColumnInfo(name = "tVPCode") #NonNull
private int tableVacationPacketCode;
#ColumnInfo(name = "tVPAgencyCode") #NonNull
private int tableVacationPacketAgencyCode;
#ColumnInfo(name = "tVPVacationCode") #NonNull
private int tableVacationPacketVacationCode;
#ColumnInfo(name = "tVPDateOfDeparture")
private String tableVacationPacketDateOfDeparture;
#ColumnInfo(name = "tVPPrice")
private double tableVacationPacketPrice;
public int getTableVacationPacketCode() {
return tableVacationPacketCode;
}
public void setTableVacationPacketCode(int tableVacationPacketCode) {
this.tableVacationPacketCode = tableVacationPacketCode;
}
public int getTableVacationPacketAgencyCode() {
return tableVacationPacketAgencyCode;
}
public void setTableVacationPacketAgencyCode(int tableVacationPacketAgencyCode) {
this.tableVacationPacketAgencyCode = tableVacationPacketAgencyCode;
}
public int getTableVacationPacketVacationCode() {
return tableVacationPacketVacationCode;
}
public void setTableVacationPacketVacationCode(int tableVacationPacketVacationCode) {
this.tableVacationPacketVacationCode = tableVacationPacketVacationCode;
}
public String getTableVacationPacketDateOfDeparture() {
return tableVacationPacketDateOfDeparture;
}
public void setTableVacationPacketDateOfDeparture(String tableVacationPacketDateOfDeparture) {
this.tableVacationPacketDateOfDeparture = tableVacationPacketDateOfDeparture;
}
public double getTableVacationPacketPrice() {
return tableVacationPacketPrice;
}
public void setTableVacationPacketPrice(double tableVacationPacketPrice) {
this.tableVacationPacketPrice = tableVacationPacketPrice;
}
#Database(entities ={suggestedVacation.class,travelAgency.class,vacationPacket.class},version = 1)
public abstract class roomApiDatabase extends RoomDatabase {
public abstract RoomApiDao roomApiDaoVariable();
}
#Dao
public interface RoomApiDao {
#Insert
public void insertTravelAgency(travelAgency travelAgency);
#Update
public void updateTravelAgency(travelAgency travelAgency);
#Delete
public void deleteTravelAgency(travelAgency travelAgency);
#Insert
public void insertSuggestedVacation(suggestedVacation suggestedVacation);
#Update
public void updateSuggestedVacation(suggestedVacation suggestedVacation);
#Delete
public void deleteSuggestedVacation(suggestedVacation suggestedVacation);
#Insert
public void insertVacationPacket(vacationPacket vacationPacket);
#Update
public void updateVacationPacket(vacationPacket vacationPacket);
#Delete
public void deleteVacationPacket(vacationPacket vacationPacket);
#Query("select * from travelAgencies")
public List<travelAgency> getTravelAgency();
#Query("select * from suggetedVacations")
public List<suggestedVacation> getSuggestedVacations();
#Query("select * from theVacationPacket")
public List<vacationPacket> getVacationPackets();
}
Usually, Foreign key constraint failed happens when you try to constraint child column to an empty or null parent column, this usually happens when :
The child entity inserted to the database before the parent entity
The childColumns value doesn't match any parentColumns value

Room #Insert does not insert all records

I am running into an issue where only 1 record is being inserted into my Room SQLite DB.
When I perform a getAll(); the result only returns 1 record.
FOUND ISSUE: Genre[] genres = gson.fromJson(jsonArray.toString(), Genre[].class);
This line above is setting all "gid" values to 0, and I am not sure how to change that.
Genre.java
#Entity(indices = {#Index(value = {"id", "name"}, unique = true)})
public class Genre {
#PrimaryKey
private int gid;
//#ColumnInfo(name = "id") By Default - No need to annotate
#NonNull
private int id;
private String name;
public int getGid() {
return gid;
}
public void setGid(int gid) {
this.gid = gid;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
GenreDao.java
#Dao
public interface GenreDao {
#Query("SELECT * FROM Genre")
LiveData<List<Genre>> getAll();
#Insert(onConflict = OnConflictStrategy.REPLACE) //If there is a conflict, replace the record.
void insertAll(Genre... genres);
}
GenreRepository.java
public class GenreRepository {
private final GenreDao genreDao;
public GenreRepository(GenreDao genreDao) {
this.genreDao = genreDao;
}
//Database Methods
public void insertAll(Genre... genres) {
AsyncTask.execute(() -> { //Same as new Runnable()
genreDao.insertAll(genres);
});
}
public LiveData<List<Genre>> getAll() {
return genreDao.getAll();
}
}
APIUtil.java - getGenres() Method
This class makes an API call, returns the proper results, converts the JSONArray to a Genre[]. I can successfully loop through the Genre[] and confirm 10+ results come back.
public static void getGenres(Context context) {
APIWrapper wrapper = new APIWrapper(context, API_KEY);
Parameters params = new Parameters();
params.addFields(GENRE_FIELDS);
params.addLimit("50");
wrapper.genres(params, new onSuccessCallback() {
#Override
public void onSuccess(JSONArray jsonArray) {
Gson gson = new Gson();
Genre[] genres = gson.fromJson(jsonArray.toString(), Genre[].class);
//Insert DB
AppDatabase db = AppDatabase.getAppDatabase(context);
GenreRepository genreRepository = new GenreRepository(db.genreDao());
genreRepository.insertAll(genres);
}
#Override
public void onError(VolleyError volleyError) {
Log.e("GENRES ERROR:", volleyError.toString());
}
});
}
GenreViewModel.java
public class GenreViewModel extends ViewModel {
private GenreRepository genreRepository;
public GenreViewModel(GenreRepository genreRepository) {
this.genreRepository = genreRepository;
}
public void insertAll(Genre... genres){
genreRepository.insertAll(genres);
}
public LiveData<List<Genre>> getAll(){
return genreRepository.getAll();
}
}
SearchFragment.java
This is where I am retrieving the database values. This for loop only returns 1 result.
AppDatabase db = AppDatabase.getAppDatabase(getActivity());
GenreRepository genreRepository = new GenreRepository(db.genreDao());
GenreViewModel genreViewModel = new GenreViewModel(genreRepository);
genreViewModel.getAll().observe(this, genres -> { //new Observer<List<Genre>>()
for(Genre g : genres){
Log.e("GENRE", g.getName());
}
});
public void insertAll(Genre... genres){
genreRepository.insertAll(genres);
}
here is your mistake , what you provide as method definition and what you provide at call. see you make some thing wrong.
Solution
void insertAll(List<T> obj);
you can try with convert your array to list and put above in definition
I had this problem too.
And Solved it this way.
The problem was that the id that comes from server was mongoId and String so I should create a int primary key and pass currentTime as value to it so the database can insert all of them not replace them.
But you should consider using System.nanoTime() method instead of System.currentTimeMillis() cause sometimes it generates same value and then room replace them instead of inserting each one of them.

Room TypeConverter not working

I have an issue with Room not recognizing my converter. Error:
Cannot figure out how to save this field into database. You can consider adding a type converter for it.
I need to store some maps and sets in my database. What am I doing wrong? Does room not like interfaces or generics?
code: (sorry for all the fields and class name, they are a mixture of English and Czech to not have same names as some java classes):
Converter (only part)
public class MyConverter {
/**
* makes a string like 1;2;3;5;4;8;1;6;8;4 from a collection of integers.
*/
#TypeConverter
public static #NonNull String toString(#NonNull Collection<Integer> c) {
StringBuilder sb = new StringBuilder();
for (Integer item : c) {
sb.append(item.toString() + ";");
}
sb.delete(sb.length()-1,sb.length()-1);
return sb.toString();
}
/**
* makes a Set<Integer> from string like 1;2;3;4;5;6
* #throws NumberFormatException on incorrect input
*/
#TypeConverter
public static#NonNull Set<Integer> toIntegerSet(#NonNull String s) {
Set<Integer> set = new LinkedHashSet<>();
String[] split = s.split(";");
try {
for (String item : split) {
set.add(Integer.parseInt(item));
}
}catch (NumberFormatException e){
throw new NumberFormatException("Could not make set of integers (like 1;2;3;8;7) from \"" + s +"\"");
}
return set;
}
}
Database:
#Database(entities = {SQLUkol.class,SQLPredmet.class,SQLList.class},version = 1)
#TypeConverters({MyConverter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract MojeDAO mojeDao();
}
One of the entities (getters, setters and constructors not included):
#Entity(primaryKeys = {"id", "list_id"},
indices = {#Index("list_id")},
foreignKeys = #ForeignKey(entity = SQLList.class, parentColumns = "id",
childColumns = "list_id", onDelete = ForeignKey.CASCADE),
tableName = "ukols")
public class SQLUkol implements Comparable<SQLUkol> {
#ColumnInfo(name = "list_id")
private final int listID;
private final int id;
private String title;
#ColumnInfo(name = "title_changed")
private boolean titleChanged = false;
private String notes;
#ColumnInfo(name = "notes_changed")
private boolean notesChanged = false;
private boolean completed;
#ColumnInfo(name = "completed_changed")
private boolean completedChanged = false;
private LocalDate date;
#ColumnInfo(name = "date_changed")
private boolean dateChanged = false;
#Embedded
private final SQLData data;
}
Room does not like generics much. I had to do this:
#TypeConverter
public static String toString1(Map<String, String> m){
...
}
#TypeConverter
public static String toString2(Map<Integer, String> m){
...
}
#TypeConverter
public static String toString3(Set<Integer> s){
...
}
#TypeConverter
public static String toString4(List<Integer> l){
...
}
not just
#TypeConverter
public static String toString(Map m){
...
}
#TypeConverter
public static String toString(Collection<Integer> c){
...
}

Hibernate one to many update with Spring HibernateTemplate

public class AfpProcessSummaryDetail implements Serializable {
private String srNo;
private String fileName;
private String status;
private String location;
private String comments;
private Character convertStatus;
private AfpProcessDetail afpProcessDetail;
public AfpProcessSummaryDetail() {
}
public AfpProcessSummaryDetail(String srNo, String fileName, String status, String location, String comments,
AfpProcessDetail afpProcessDetail) {
this.srNo = srNo;
this.fileName = fileName;
this.status = status;
this.location = location;
this.comments = comments;
this.afpProcessDetail = afpProcessDetail;
}
#ManyToOne
#JoinColumn(name = "PROCESSDETAIL")
public AfpProcessDetail getAfpProcessDetail() {
return afpProcessDetail;
}
AfpProcessDetail
public class AfpProcessDetail implements Serializable {
private String processID;
private String processDate;
private Integer fileCount;
private Integer successCount;
private Integer failureCount;
private Character active;
private Set<AfpProcessSummaryDetail> processSummaryDetails = new HashSet<AfpProcessSummaryDetail>(0);
public AfpProcessDetail() {
}
public AfpProcessDetail(String processID, String processDate, Integer fileCount, Integer successCount,
Integer failureCount) {
this.processID = processID;
this.processDate = processDate;
this.fileCount = fileCount;
this.successCount = successCount;
this.failureCount = failureCount;
}
public AfpProcessDetail(String processID, String processDate, Integer fileCount, Integer successCount,
Integer failureCount, Set<AfpProcessSummaryDetail> processSummaryDetails) {
this.processID = processID;
this.processDate = processDate;
this.fileCount = fileCount;
this.successCount = successCount;
this.failureCount = failureCount;
this.processSummaryDetails = processSummaryDetails;
}
#Column(name = "FAILURECOUNT")
public Integer getFailureCount() {
return failureCount;
}
public void setFailureCount(Integer failureCount) {
this.failureCount = failureCount;
}
#Column(name = "FILECOUNT")
public Integer getFileCount() {
return fileCount;
}
public void setFileCount(Integer fileCount) {
this.fileCount = fileCount;
}
#Column(name = "PROCESSDATE")
public String getProcessDate() {
return processDate;
}
public void setProcessDate(String processDate) {
this.processDate = processDate;
}
#Id
#Column(name = "PROCESSID", unique = true, nullable = false)
public String getProcessID() {
return processID;
}
public void setProcessID(String processID) {
this.processID = processID;
}
#Column(name = "SUCESSCOUNT")
public Integer getSuccessCount() {
return successCount;
}
public void setSuccessCount(Integer successCount) {
this.successCount = successCount;
}
#JoinColumn(name="PROCESSDETAIL")
#OneToMany(cascade=CascadeType.ALL,fetch = FetchType.EAGER)
public Set<AfpProcessSummaryDetail> getProcessSummaryDetails() {
return processSummaryDetails;
}
public void setProcessSummaryDetails(Set<AfpProcessSummaryDetail> processSummaryDetails) {
this.processSummaryDetails = processSummaryDetails;
}
Code for updating
public String updateSummaryDetails(ViewFile viewFile, String codeID) {
if (viewFile != null && codeID != null) {
HibernateTemplate transactionTemplate = new HibernateTemplate(sessionFactory, true);
Object result = transactionTemplate.execute(new HibernateCallback<Object>() {
#Override
public Object doInHibernate(org.hibernate.Session session) throws HibernateException, SQLException {
AfpProcessSummaryDetail processSummary =null,newProcessSummary =null;
AfpProcessDetail processDetail = (AfpProcessDetail)session.get(AfpProcessDetail.class,codeID);
List<FileProperty> fileList = viewFile.getFileList();
Set<AfpProcessSummaryDetail> setProcessSummary=new HashSet<AfpProcessSummaryDetail>();
Set<AfpProcessSummaryDetail> modSetProcessSummary=new HashSet<AfpProcessSummaryDetail>();
setProcessSummary =processDetail.getProcessSummaryDetails();
Iterator<AfpProcessSummaryDetail> itrProcessSumm=setProcessSummary.iterator();
int srNo = 0;
while (itrProcessSumm.hasNext()){
processSummary =(AfpProcessSummaryDetail)itrProcessSumm.next();
for (FileProperty fileProperty : fileList) {
newProcessSummary =new AfpProcessSummaryDetail();
newProcessSummary.setSrNo(codeID + "" + srNo);
newProcessSummary.setFileName(fileProperty.getName());
newProcessSummary.setLocation(fileProperty.getPath());
newProcessSummary.setComments(fileProperty.getComment());
newProcessSummary.setStatus(fileProperty.getStatus());
newProcessSummary.setConvertStatus(fileProperty.getConvertStatus());
newProcessSummary.setAfpProcessDetail(processDetail);
modSetProcessSummary.add(newProcessSummary);
/*if (processSummary.getFileName().trim().equals(fileProperty.getName().trim())){
System.out.println("Element removed");
itrProcessSumm.remove();
modSetProcessSummary.add(newProcessSummary);
break;
}*/
srNo++;
}
}
// setProcessSummary.addAll(modSetProcessSummary);
processDetail.setProcessSummaryDetails(modSetProcessSummary);
processDetail.setFailureCount(viewFile.getExceptionNo());
processDetail.setSuccessCount(viewFile.getSuccessNo());
processDetail.setActive(viewFile.getActive());
transactionTemplate.flush();
session.merge(processDetail);
System.out.println("updated successfully");
return codeID;
}
});
Desired result
I want to perform a one to many update -AfpProcessSummaryDetail which is related to AfpProcessDetail via Set. When I try to replace the set values for update it tries to update the primary key to null. If I don't replace the updates don't take place. If I set cascade it gives error -a different object with the same identifier value was already associated with the session:
[com.aurionpro.convertor.dto.AfpProcessSummaryDetail#15a236f‌​fc961];
nested exception is org.hibernate.NonUniqueObjectException: a
different object with the same identifier value was already associated
with the session.
Please suggest

Spring Data JPA - Get the values of a non-entity column of a custom native query

I am using Spring Boot/MVC.
I have a custom query using JpaRepository:
public interface WorkOrderRepository extends JpaRepository<WorkOrder, Integer> {
#Query(value = "SELECT * FROM (SELECT * FROM workorder) Sub1 INNER JOIN (SELECT wo_number, GROUP_CONCAT(service_type SEPARATOR ', ') AS 'service_types' FROM service_type GROUP BY wo_number) Sub2 ON Sub1.wo_number=Sub2.wo_number WHERE fleet_company_id=?1 AND (order_status='On-Bidding' OR order_status='Draft')", nativeQuery = true)
Collection<WorkOrder> findWorkOrdersByFleet(Long fleetCompanyID);
}
It returns the following table:
http://imgur.com/Ylkc6U0
As you can see it has service_types columns which is a result of Concat, it's not part of the entity class. My problem is how can I get the value of that column. Some said I can use a separate DTO to map the service_types column? Or I can use 'new' keyword? Maybe you have other worked on me. I also tried to make a transient column service_types but it didn't work.
This is my entity class:
#Entity
#Table(name="workorder")
public class WorkOrder {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="wo_number")
private Long woNumber;
#ManyToOne(optional=false, cascade=CascadeType.ALL)
#JoinColumn(name = "vehicle_id")
private Vehicle vehicle;
#ManyToOne(optional=false, cascade=CascadeType.ALL)
#JoinColumn(name = "fleet_company_id")
private FleetCompany fleetCompany;
#Column(name="order_title")
private String orderTitle;
#Column(name="order_date")
private String orderDate;
#Column(name="order_time")
private String orderTime;
#Column(name="order_status")
private String orderStatus;
#Column(name="ref_number")
private String refNumber;
#Column(name="proposals")
private int proposals;
//#Column(name="serviceTypes")
#Transient
private int serviceTypes;
public WorkOrder() {
super();
}
public Long getWoNumber() {
return woNumber;
}
public void setWoNumber(Long woNumber) {
this.woNumber = woNumber;
}
public String getOrderTitle() {
return orderTitle;
}
public void setOrderTitle(String orderTitle) {
this.orderTitle = orderTitle;
}
public String getOrderDate() {
return orderDate;
}
public void setOrderDate(String orderDate) {
this.orderDate = orderDate;
}
public String getOrderTime() {
return orderTime;
}
public void setOrderTime(String orderTime) {
this.orderTime = orderTime;
}
public String getOrderStatus() {
return orderStatus;
}
public void setOrderStatus(String orderStatus) {
this.orderStatus = orderStatus;
}
public String getRefNumber() {
return refNumber;
}
public void setRefNumber(String refNumber) {
this.refNumber = refNumber;
}
public int getProposals() {
return proposals;
}
public void setProposals(int proposals) {
this.proposals = proposals;
}
public Vehicle getVehicle() {
return vehicle;
}
public void setVehicle(Vehicle vehicle) {
this.vehicle = vehicle;
}
public FleetCompany getFleetCompany() {
return fleetCompany;
}
public void setFleetCompany(FleetCompany fleetCompany) {
this.fleetCompany = fleetCompany;
}
public int getServiceTypes() {
return serviceTypes;
}
public void setServiceTypes(int serviceTypes) {
this.serviceTypes = serviceTypes;
}
}
Some people told me to make a DTO:
public class WorkOrderDTO extends WorkOrder {
private String service_types;
public WorkOrderDTO() {
super();
}
public WorkOrderDTO(String service_types) {
this.service_types = service_types;
}
public String getService_types() {
return service_types;
}
public void setService_types(String service_types) {
this.service_types = service_types;
}
}
and add make the repository replaced from WorkOrder to WorkOrderDTO.
public interface WorkOrderRepository extends JpaRepository<WorkOrderDTO, Integer>
but when I do that I have autowiring problems.
I solved my own problem, finally!!!
I used #SqlResultMapping
SqlResultSetMapping(
name="workorder",
classes={
#ConstructorResult(
targetClass=WorkOrderDTO.class,
columns={
#ColumnResult(name="wo_number", type = Long.class),
#ColumnResult(name="service_types", type = String.class),
#ColumnResult(name="order_title", type = String.class)
}
)
}
)
And I created a new POJO that is not an entity named WorkOrderDTO.
#PersistenceContext
private EntityManager em;
#Override
public Collection<WorkOrderDTO> getWork() {
Query query = em.createNativeQuery(
"SELECT Sub1.wo_number, Sub2.service_types, Sub1.order_title FROM (SELECT * FROM workorder) Sub1 INNER JOIN (SELECT wo_number, GROUP_CONCAT(service_type SEPARATOR ', ') AS 'service_types' FROM service_type GROUP BY wo_number) Sub2 ON Sub1.wo_number=Sub2.wo_number WHERE fleet_company_id=4 AND (order_status='On-Bidding' OR order_status='Draft')", "workorder");
#SuppressWarnings("unchecked")
Collection<WorkOrderDTO> dto = query.getResultList();
Iterable<WorkOrderDTO> itr = dto;
return (Collection<WorkOrderDTO>)itr;
}
At last, the users who hated me for posting the same problem won't be annoyed anymore.

Categories