Hibernate optimization when saving multiple many-to-one relationships - java

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.

Related

Relation between multiply databases

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 :-

Anylogic Distribution Network connection from database

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;

Hibernate Envers - request for changes in a List of the object

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.

Saving unique data in a loop for multiple many-to-one relationships in hibernate

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.

JPA - How do I map to a quick-lookup table?

I have a contact entity that has >1 phone number contact[contact, name, cell,work,home...] and want to create a lookup table phone_number[uid,contactuid,telephonenumber] so that I can search through this table by telephone number to find the contact.
With JPA - how would I configure to;
Map the phone_number entity so that the entity will populated from the contact entity
remove the phone_number record when the contacts number remove (or the contact is removed, remove all records)
change phone_number record when the contacts number changes?
I was hoping to do all this in the DAO as this isn't really domain logic..
**Update - is the contact->phone_number relationship sensible to define in JPA or just map it using SQL in the DAO?
Many Thanks in advance
If I understand correctly, you already have a Contact entity with several phone number fields:
cellPhone
workPhone
homePhone
And you would like to find a contact given a phone number. If so, you don't need any additional table to do that:
select c from Contact c
where c.cellPhone = :phoneNumber
or c.workPhone = :phoneNumber
or c.homePhone = :phoneNumber
But maybe you should have a Phone entity with a phoneNumber and a type (work, cell, etc.) fields, and have a bidirectional OneToMany association between Contact and Phone. Your query would then be
select c from Phone p
inner join p.contact c
where p.phoneNumber = :phoneNumber
which would certainly be more efficient.

Categories