It is easy to make relation between two tables of a database by #Relation and #ForeignKey of the Room library
And in SQLite we can join tables from different databases
But how can I do it by Room Library?
In Room you will not be able to code cross database foreign keys. The same restriction applies to SQLite. However, a Foreign Key is not required for a relationship to exist, it is a constraint(rule) used to enforce the integrity of a relationship.
Likewise in Room you will not be able to utilise cross database relationships. The #Relation annotation basically defines join criteria used the for queries that Room generates.
However, you can programmatically have relations between two room databases via the objects.
Example
As a basic example (based upon a Room database I was looking at) consider:-
The first database (already existed), whose abstract class is Database which has a single entity that is defined in the Login class and has all the Dao's in the interface AllDao.
A Login object having 4 members/fields/columns, the important one being a byte[] with the hash of the user, named userHashed.
The second database, whose abstract class is OtherDatabase which has a single entity defined in the UserLog class and has all the Dao's in the interface OtherDBDao.
A UserLog object having 3 members/fields/columns, the importane/related column being the hash of the respective user(Login) (the parent in the Login table).
With the above consider the following :-
//First Database
db = Room.databaseBuilder(this,Database.class,"mydb")
.allowMainThreadQueries()
.build();
allDao = db.allDao();
//Other Database
otherdb = Room.databaseBuilder(this,OtherDatabase.class,"myotherdb")
.allowMainThreadQueries()
.build();
otherDBDao = otherdb.otherDBDao();
// Add some user rows to first db
Login l1 = new Login(t1,t2,t3,10);
Login l2 = new Login(t2,t3,t4,20);
Login l3 = new Login(t3,t4,t1,30);
Login l4 = new Login(t4,t1,t2,40);
allDao.insertLogin(l1);
allDao.insertLogin(l2);
allDao.insertLogin(l3);
allDao.insertLogin(l4);
// Get one of the Login objects (2nd inserted)
Login[] extractedLogins = allDao.getLoginsByUserHash(t2);
// Based upon the first Login retrieved (only 1 will be)
// add some userlog rows to the other database according to the relationship
if (extractedLogins.length > 0) {
for (int i = 0;i < 10; i++) {
Log.d("USERLOG_INSRT","Inserting UserLog Entry");
otherDBDao.insertUserLog(new UserLog(extractedLogins[0].getUserHashed()));
}
}
UserLog[] extractedUserLogs = otherDBDao.getUserLogs(extractedLogins[0].getUserHashed());
for(UserLog ul : extractedUserLogs ) {
// ....
}
The above :-
builds both databases.
Adds 4 users to the first database.
extracts all of the Login objects that match a specific user (there will only be 1) from the first database.
for each Login extracted (again just the 1) it adds 10 UserLog rows to the other database.
as the TEST, uses the userhash from the first database to extract all the related UserLog rows from the other database.
to simplify showing the results a breakpoint was place on the loop that would process the extracted UserLog objects.
Of course such a design would probably never be used.
The following is a screen shot of the debug screen when the breakpoint is triggered :-
Related
I'm developing a MySQL database project using JDBC. It uses parent/child tables linked with foreign keys.
TL;DR: I want to be able to get the AUTO_INCREMENT id of a table before an INSERT statement. I am already aware of the getGeneratedKeys() method in JDBC to do this following an insert, but my application requires the ID before insertion. Maybe there's a better solution to the problem for this particular application? Details below:
In a part of this application, the user can create a new item via a form or console input to enter details - some of these details are in the form of "sub-items" within the new item.
These inputs are stored in Java objects so that each row of the table corresponds to one of these objects - here are some examples:
MainItem
- id (int)
- a bunch of other details...
MainItemTitle
- mainItemId (int)
- languageId (int)
- title (String)
ItemReference
- itemId (int) <-- this references MainItem id
- referenceId (int) <-- this references another MainItem id that is linked to the first
So essentially each Java object represents a row in the relevant table of the MySQL database.
When I store the values from the input into the objects, I use a dummy id like so:
private static final int DUMMY_ID = 0;
...
MainItem item = new MainItem(DUMMY_ID, ...);
// I read each of the titles and initialise them using the same dummy id - e.g.
MainItemTitle title = new MainItemTitle(DUMMY_ID, 2, "Here is a a title");
// I am having trouble with initialising ItemReference so I will explain this later
Once the user inputs are read, they are stored in a "holder" class:
class MainItemValuesHolder {
MainItem item;
ArrayList<MainItemTitle> titles;
ArrayList<ItemReference> references;
// These get initialised and have getters and setters, omitted here for brevity's sake
}
...
MainItemValuesHolder values = new MainItemValuesHolder();
values.setMainItem(mainItem);
values.addTitle(englishTitle);
values.addTitle(germanTitle);
// etc...
In the final layer of the application (in another class where the values holder was passed as an argument), the data from the "holder" class is read and inserted into the database:
// First insert the main item, belonging to the parent table
MainItem mainItem = values.getMainItem();
String insertStatement = mainItem.asInsertStatement(true); // true, ignore IDs
// this is an oversimplification of what actually happens, but basically constructs the SQL statement while *ignoring the ID*, because...
int actualId = DbConnection.insert(insertStatement);
// updates the database and returns the AUTO_INCREMENT id using the JDBC getGeneratedKeys() method
// Then do inserts on sub-items belonging to child tables
ArrayList<MainItemTitle> titles = values.getTitles();
for (MainItemTitle dummyTitle : titles) {
MainItemTitle actualTitle = dummyTitle.replaceForeignKey(actualId);
String insertStatement = actualTitle.asInsertStatement(false); // false, use the IDs in the object
DbConnection.insert(insertStatement);
}
Now, the issue is using this procedure for ItemReference. Because it links two MainItems, using the (or multiple) dummy IDs to construct the objects beforehand destroys these relationships.
The most obvious solution seems to be being able to get the AUTO_INCREMENT ID beforehand so that I don't need to use dummy IDs.
I suppose the other solution is inserting the data as soon as it is input, but I would prefer to keep different functions of the application in separate classes - so one class is responsible for one action. Moreover, by inserting as soon as data is input, then if the user chooses to cancel before completing entering all data for the "main item", titles, references, etc., the now invalid data would need to be deleted.
In conclusion, how would I be able to get AUTO_INCREMENT before insertion? Is there a better solution for this particular application?
You cannot get the value before the insert. You cannot know what other actions may be taken on the table. AUTO_INCREMENT may not be incrementing by one, you may have set that but it could be changed.
You could use a temporary table to store the data with keys under your control. I would suggest using a Uuid rather than an Id so you can assume it will always be unique. Then your other classes can copy data into the live tables, you can still link the data using the Uuids to find related data in your temporary table(s), but write it in the order that makes sense to the database (so the 'root' record first to get it's key and then use that where required.
I have a specific question regarding an Anylogic model that I am trying to build.
I have 3 tables:
connections with columns connecteddc and connectedcustomer
customer with columns custname and demand
dcdetails with columns dcname and dccapactiy
I am trying to write a java code that connects each dc in the first table (connecteddc) to each customer assigned (connectedcustomer) and iterates through this process multiple times to build an accurate network. I have tried using several variations of code, as shown below.
for (int i=0; i<3 ; i++){
dc.get(i).LinktoCustomers.connectTo(Locations.get(selectFirstValue(false, int.class, "SELECT connectedcustomer FROM connections WHERE connectedDC = "+i+";")));
}
This code is only connecting 1 DC to 1 customer. This problem is occurring in the 'selectFirstValue' portion of the code.
Database Query
You have to use one of the possibilies to retrieve all of the concerning database entries, instead of just the first one, as you do with selectFirstValue(). Here is one option to do so:
for (int i=0; i<dc.size() ; i++){
List<Tuple> rows = selectFrom(connection)
.where(connection.connecteddc.eq(dc.get(i).dcName))
.list();
for (Tuple row : rows) {
dc.get(i).connectTo(getCustomerByName(row.get(connection.connectedcustomer)));
}
}
Tipp: AnyLogic offers you an assistant to create such queries, that you find in the AnyLogic toolbar under "Insert Database Query". It looks like this:
AnyLogic Database Query Assistant
Other Stuff
I modified a couple of other things that catched my attention:
To add a connection you use dc.get(i).LinktoCustomers.connectTo(...). It is not neccessary to use a special variable for the connections, it is enough to just add it to the standard connections by using: dc.get(i).connectTo(...)
You go through the list of DCs with a hardcoded max index. As soon as you change the number of entries in the DC table, the code will not work anymore. I recommend something like this: for (int i=0; i<dc.size() ; i++){...}.
You gave the name "Locations" to your population of Agent type "Customer". It is confusing to use a population name that doesn't reflect the underlying agent type at all. I recommend to rename it for example "Customers".
To access your DCs you store and use the index number of the DC as an integer in the tables. In order to be on the safe side, I recommend to use unique String Ids instead, which will work even if you change to order of your table. For this to work you'll need to "parse" the Id (stored in the tables) to a Customer object.
This could be done in a function getCustomerByName(String name) like this (although this obviously lacks error handling):
for(Customer c:Customers){
if(c.custName.equals(name)){
return c;
}
}
return null;
I have a Contractor that has List of Projects he's involved with. The contract also has other lists such as Employees, payments and other fields (name, date etc).
My objective is to see which projects the contractor is associated to were changed.
example
Contractor C is involved in the following projects:
1. Furman street <Active>
2. Park West <Active>
3. Central Train Station <Active>
one day the user changes the project Park West from Active to completed etc.
So now each time I get the Contractor's revisions I get the entire information (projects, contacts, fields etc). Problem is that each time I touch the projects (list) - it goes to the db. My question, since I need to do a minimal touch to the db, how can I request projects revisions only? so I can tell what the user has done (example: add project X and mark completion for project Y)
What I have done so far is:
AuditReader reader = AuditReaderFactory.get(em);
AuditQuery query = reader.createQuery().forRevisionsOfEntity(Contractor.class, false, true).add(AuditEntity.id().eq(objID));
List<Contractor> contractors = query.getResultList();
and I also tried to ask for only projects like this (didn't work due to Null Pointer Exception )
...add(AuditEntity.property("projects").hasChanged());
public class Contractor implements Serializable
{
//fields... name, dates...
#DiffIgnore
#OneToMany(mappedBy = "contractor")
#JsonIgnoreProperties(value = {"contractor"}, allowSetters=true)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<ContractorProject> projects = new HashSet<>(); //this is a many to many relationship for a reason
}
Based on your comments given that a Project cannot be unassigned from a Contractor once that link has been made and that you're solely interested in changes made to those said projects associated with a given Contractor instance, the best way to obtain the information in question would likely be through executing several audit queries to build that view.
The first thing you'll need is a list of Project ids that are associated to that Contractor. You could acquire this information from the audit tables but I believe it would likely be better just to obtain those directly from the normal entity data instead.
SELECT p.id FROM Contractor c JOIN c.projects p WHERE c.id = :contractorId
The above query is basically a projection-based query which given a contractor identifier, you obtain all the project identifiers that are associated to the contractor through the projects association.
Should you wish to acquire this via the audit tables instead, the first thing we'd need to determine is the maximum revision number for the contractor so we fetch the right snapshot of data.
// you might want to check if this collection is empty
// it should not be assuming you aren't removing data from the audit tables
// but sometimes people archive data, so its best to be thorough
List<Number> revs = reader.getRevisions( Contractor.class, contractorId );
// The list of revisions are always in ascending order, so grab the last entry.
Number maxRevision = revs.get( revs.size() - 1 );
// Build the projection query
// This returns us the list of project ids, just like the HQL above
List projectIds = reader.createQuery()
.forEntitiesAtRevision( Contractor.class, maxRevision )
.traverseRelation( "projects", JoinType.INNER )
.addProjection( AuditEntity.property( "id" ).distinct() )
.up()
.add( AuditEntity.id().eq( contractorId ) )
.getResultList();
Once you have this information, it comes down to executing an audit query in a loop for each Project to determine the information you need.
for ( Object projectId : projectIds ) {
List results = reader.createQuery()
.forRevisionsOfEntity( Project.class, false, false )
.add( AuditEntity.id().eq( projectId ) )
.addOrder( AuditEntity.revisionNumber().asc() );
// At this point you have an list of object array values
// Index 0 - This is the instance of Project
// Index 1 - This is the revision entity, you can get the rev # and timestamp
// Index 2 - Type of revision, see RevisionType
//
// So you can basically iterate the list in ascending order keeping track of
// the prior Project and build a changeset for each project.
//
// This approach is often more efficient if you're needing to compare multiple
// attributes on an entity rather than issuing a query to the database for
// each change made to a single property.
}
In the next major release of Envers, there will be some additional query methods that will allow you to get an object array that consists of the following
// Index 0 - This is the instance of Project
// Index 1 - This is the revision entity, you can get the rev # and timestamp
// Index 2 - Type of revision, see RevisionType
// Index 3 - Array of properties changed at this revision
The key point here is index 3 where we will provide you with the properties that were modified, so you don't have to calculate those yourself.
I am currently working with tables with multiple many-to-one relationships and I'm trying to implement all of them using Hibernate.
For example, three tables I have are:
Product(id, pname), Users(id, pid, gid, uname), Group(id, gname)
Group is in an one-to-many relationship with Users
Product is also in an one-to-many relationship with Users
Users is in a many-to-one relationship with both Product and Group.
A sample of data I would receive is below:
Line 0: pname uname gname
Line 1: Razer Billy admin
Line 2: Razer Sally admin
Line 3: Razer Benji admin
Line 4: Yahoo Molly admin
...
From the above example, I want my Product table to end up with only two entries (Razer and Yahoo) with Razer associated with three entries on the Users table and Yahoo associated with one entry on the Users table.
I also want my Group table to end up with only one entry ("admin") that is associated with four entries on the Users table.
So far, whenever I receive a new line of data, how do I make sure that no duplicate entries are created for the Product and Group table but rather referencing to existing ones on there?
For example, if Line 5's data is: Razer Jacky admin, how do I make sure that the Users table adds a new entry "Jacky" while this entry is associated with the entry "Razer" and "admin" already created rather than creating duplicate entries in the Product and Group table?
My correct flawed loop for the data goes like this:
// Three lists of same size created,
// each list is a column of the sample data from above
// ...
Set<Users> users = new HashSet<Users>();
for(int i = 0; i < plist.size; i++){
Product ptemp = new Product(plist.get(i));
Group gtemp = new Group(glist.get(i));
Users utemp = new Users(ptemp, gtemp, ulist.get(i));
users.add(utemp);
ptemp.setUsers(users);
gtemp.setUsers(users);
session.save(ptemp);
session.save(gtemp);
}
Thanks!
Lets go with the example you provided. You want to insert a new row - Razer Jacky admin
Its a two step process
First of all check if product Razer and group admin is present in your database. If yes load them, If not create new objects for them.
Next step is rather straightforward you only need to persist the user data by adding references you loaded or created in first step.
Hope this helps.
I am currently working with tables with multiple many-to-one relationships and I'm trying to implement all of them using Hibernate.
For example, three tables I have are:
Product(id, pname), Users(id, pid, gid, uname), Group(id, gname)
Group is in an one-to-many relationship with Users
Product is also in an one-to-many relationship with Users
Users is in a many-to-one relationship with both Product and Group.
A sample of data I would receive is below:
Line 0: pname uname gname
Line 1: Razer Billy admin
Line 2: Razer Sally admin
Line 3: Razer Benji admin
Line 4: Yahoo Molly admin
...
My correct flawed loop for the data goes like this:
// Three lists of same size created,
// each list is a column of the sample data from above
// ...
for(int i = 0; i < plist.size; i++){
Product ptemp = new Product(plist.get(i));
Group gtemp = new Group(glist.get(i));
Users utemp = new Users(ptemp, gtemp, ulist.get(i));
Set<Users> users = new HashSet<Users>();
users.add(utemp);
ptemp.setUsers(users);
gtemp.setUsers(users);
session.save(ptemp);
session.save(gtemp);
}
Playing around with hibernate, I have also noticed that when I change my for loop to
for(int i = 0; i < plist.size; i++){
Product ptemp = new Product(plist.get(i));
Group gtemp = new Group(glist.get(i));
Users utemp = new Users(ptemp, gtemp, ulist.get(i));
session.save(ptemp);
session.save(gtemp);
session.save(utemp);
}
The results on the database are the same as before.
Is the second way a better way in hibernate? Or is the practice of storing a dependent entity directly to database something unacceptable in hibernate?
Thanks a lot for your help!
If I understand correctly what you are asking is in what order should the related objects be persisted when it comes to parent/child relationship in Hibernate?
What you have is a Group object that has a collection of User objects in a bidirectional relationship and a Product object that is related to a collection of User objects also bidirectionally. How to manage these types of relationships is documented below:
http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#example-parentchild
Hibernate recommends to manage the state of the relationship meaning the link from child to parent object from the state of the child object.
The first and second approach may work but they may result in different number of sql statements issued to insert the individual objects. It all depends on how you map the relationships.