Ebean lazy load to see if resource exists - java

I am creating a REST API using play framework. I want to use lazy loading ( finder.ref(id) or Ebean.getReference(id) ) to see if an enity with a specific id exists in database. If it doesn't exist, I will return a 404.
If I try to delete using an id that doesn't exist, an OptimisticLockException is thrown. But that doesn't seem like a valid basis to see if an entity exists.
Is it possible to check if an entity exists by an id using lazy loading? I can always do finder.byId(id) and that can get me what I want. But I want to do this efficiently.
Thanks

You can just count items with specified id, while your id is unique, it will return 1 if item exists and 0 if it doesn't, so you can easily make a condition:
boolean itemExists
= (YourModel.find.where().eq("id", id).findRowCount() == 1) ? true : false;
Logger.info("Item " + ((itemExists) ? "exists" : "not found!"));
On the other hand if your intension is returning existing entity for an example in Json, you don't need to make separate checking, just check if it's not null...
YourModel entity = YourModel.find.byId(id);
if (entity == null) return notFound("No such record");
// .. rest of code for preparing API...
Edit
About costs: find.byId(id) tries to fetch whole entity, while find.ref(id) gets only reference. Unfortunately you can't determine if object exists by ref(id) as it's always not null, therefore IMHO counting elements by id is cheaper than selecting even single field to check if Db returns the entity.
Actually find.byId(id) is most expensive option as it loads whole entity, for well optimized APIs it's usually better to write dedicated methods using Ebean's select() and fetch(), like:
YourModel e = YourModel.find.select("id, name, age").where().eq("id", id).findUnique();
or
List<YourModel> le = YourModel.find.select("id, name, age").where().eq("id", id).findList();

Related

What's the advantage of finding an entity using the Hibernate #NaturalId

Is there any advantage to find an object using the Hibernate's #NaturalId?
I'm concerned about the fact that Hibernate performs two queries to get an object using #NaturalId. The first query just to get the id and the second query to load the real object.
The major advantage is that you can use the Cache to resolve the entity without hitting the database.
When the ResolveNaturalIdEvent event is thrown, Hibernate will try to:
load the associated entity id from the 1st level cache
load the associated entity id from the 2nd level cache (if enabled)
fall-back to a database query if the 1st level cache can't satisfy our request
Serializable entityId = resolveFromCache( event );
if ( entityId != null ) {
if ( traceEnabled )
LOG.tracev( "Resolved object in cache: {0}",
MessageHelper.infoString( persister, event.getNaturalIdValues(), event.getSession().getFactory() ) );
return entityId;
}
return loadFromDatasource( event );
So, it's the same benefit as with using the entity loading through the Persistence Context API (e.g. EntityManager.find()).
The only time when two queries are executed is when the entity is not already cached (1st or 2nd level cache).
At least one advantage is you will benefit from first level cache. So for example if you load User by email ( which is naturalid), you will get only the primary key id from db, and the user object from first level cache if it is already there. So faster load time as less network data transfer.
There is also another benefit of using #NaturalID and is related to the use of query cache (I'm basing this answer in Hibernate 4.3.8)
If you configure a natural ID and then you query the entity by NaturalID Restrictions#naturalId or Session#byNaturalId you could be hitting query cache even if the table has been modified.
Hibernate keeps a cache of update timestamps of tables to say if a query cache entry is upToDate.
UpdateTimestampsCache: Tracks the timestamps of the most recent updates to particular tables. It is important that the cache timeout of the underlying cache implementation be set to a higher value than the timeouts of any of the query caches. In fact, we recommend that the the underlying cache not be configured for expiry at all. Note, in particular, that an LRU cache expiry policy is never appropriate.
In plain english, if Hibernate has cached the following
*---------------------------------------------------------- *
| Query Cache |
|---------------------------------------------------------- |
| ["from Team where trophies = ", ["10"] ] -> [1,2] ] |
*---------------------------------------------------------- *
You'll want that if the Team with ID will win a new trophy the query "from Team where trophies=10" will no longer return it. To achieve this, Hibernate keeps records of when tables were last updated and then if the query cache entry is older than this timestamp it doesn't trust in it's results.
I say that it doesn't trust because the result can still be valid but Hibernate wouldn't know because the cache is not per entity, for obvious reasons. So, for example if the Team with ID=3 would be uptaded the entry for 1,2 will be invalidated even if those teams wasn't uptaded.
Even more, if the Team 1 would be uptaded but his trophies count would remain the same as before, the query cache entry will be invalidated too although it would still be valid.
If we were querying via NaturalID
List results = s.createCriteria( Citizen.class )
.add( Restrictions.naturalId().set( "ssn", "1234" ).set( "state", ste ) )
.list()
we will be enabling to Hibernate to trust in the query cache entries even if they weren't upToDate. This will be like saying: "Yes Hibernate, I know that the table was uptaded but I assure you that the NaturalID of these entities weren't changed so you can trust in the entries that correspond to queries by NaturalID".
Hibernate has to check some a couple of things to see if the query can avoid the upToDate check. This can be seen in Loader#getResultFromQueryCache
boolean isImmutableNaturalKeyLookup =
queryParameters.isNaturalKeyLookup() && // If you are querying by NaturalID
resultTypes.length == 1 &&
resultTypes[0].isEntityType() && // and you are returning an Entity (no projections)
getEntityPersister( EntityType.class.cast( resultTypes[0] ) )
.getEntityMetamodel()
.hasImmutableNaturalId(); // and that Entity has an immutable NaturalID (Is the default and I can't imagine when we could use a mutable NaturalID)
The flag isImmutableNaturalKeyLookup is passed to Query#get. Let's see how StandardQueryCache uses it.
final List cacheable = getCachedResults( key, session );
final Long timestamp = (Long) cacheable.get( 0 );
if ( !isNaturalKeyLookup && !isUpToDate( spaces, timestamp, session ) ) {
if ( DEBUGGING ) {
LOG.debug( "Cached query results were not up-to-date" );
}
return null;
}
return cacheable; // more or less
If isNaturalKeyLookup is true it doesn't check isUpToDate.
Hibernate: Cache Queries the Natural Id Way is an old but great post about query cache and NaturalID that will help to understand.

OptimisticLockException with Ebean/Play

I have a Play 2.1.3 Java app using Ebean. I am getting the OptimisticLockException below.
[OptimisticLockException: Data has changed. updated [0] rows sql[update person
set name=? where id=? and email=? and name=? and password is null and created=?
and deleted is null] bind[null]]
I understand that it is trying to tell me the record has changed between when I read it and when I tried to write it. But the only change is happening in this method.
public void updateFromForm(Map<String, String[]> form) throws Exception {
this.name = form.get("name")[0];
String password = form.get("password")[0];
if (password != null && password.length() != 0) {
String hash = Password.getSaltedHash(password);
this.password = hash;
}
this.update();
}
Am I doing this wrong? I saw similar logic in zentasks. Also, should I be able to see the the values for the bind variables?
UPDATE: I am calling updateFromForm() from inside a controller:
#RequiresAuthentication(clientName = "FormClient")
public static Result updateProfile() throws Exception {
final CommonProfile profile = getUserProfile();
String email = getEmail(profile);
Person p = Person.find.where().eq("email", email).findList().get(0);
Map<String, String[]> form = request().body().asFormUrlEncoded();
if (p == null) {
Person.createFromForm(form);
} else {
p.updateFromForm(form);
}
return ok("HI");
}
I have an alternative approach to this, where I add the annotation
#EntityConcurrencyMode(ConcurrencyMode.NONE)
to the Entity class.
This disables the optimistic locking concurrent modification check meaning the SQL becomes
update person set name=? where id=?
This is even more optimistic since it simply overwrites any intermediate changes.
Little bit late, but for your case #Version annotation should be the solution. We're using it mostly with java.util.Date, so it can be also used also for determining the date of last record update, in Play model that's just:
#Version
public java.util.Date version;
In such case update statement will be done with id and version fields only - useful especially when using with large models:
update person set name='Bob'
where id=1 and version='2014-03-03 22:07:35';
Note: you don't need/should update this field manually at each save, Ebean does it itself. version value changes ONLY when there was updated data (so using obj.update() where nothing changes doesn't update version field)
Mystery solved.
First- this public service announcement. "OptimisticLockException" is a big bucket. If you are trying to track one of these down be open to the idea that it could really be anything.
I figured out my problem by dumping SQL to the log and finding this:
update person set name='Bob'
where id=1 and email='jj#test.com'
and name='Robert' and password is null
and created=2013-12-01 and deleted is null
So I guess what happens when you do an update is that it builds a WHERE clause with all the known entities and their values as they were originally ready.
That means, if any other part of your code or another process changes something behind your back, this query will fail. I wrongly assumed that the problem was that somehow .setName('Bob') had changed the name in the DB or some object cache.
Really what was happening is that the WHERE clause includes a date while my database includes an entire timestamp with date, time, and timezone.
For now, I fixed it by just commenting out the timestamp in the model until I can figure out if/how Ebean can handle this data type.
I had the same problem,
after hours of search i found the reason..
It was of inconsistency of the parameters type in the data base (in my case string) and the object i created and tried to save -java.util.Date.
after changing the database to hold datetime object the problem was solved

Hibernate query not returning correct value

So in my database, I have 3 rows, two rows have defaultFlag as 0 and one is set to 1, now in my processing am updating defaultProperty of one object to 1 from 0 but am not saving this object yet.
Before saving I need to query database and find if any row has defaultFlag set or not, there would be only 1 default set.
So before doing update am running query to find if default is set and i get 2 values out, note here if i go and check in db then there is only 1 row with default set but query gives me two result because this.object default property has changed from 0 to 1 but note that this object is not yet saved in database.
I am really confused here as to why hibernate query is returning 2 when there is one row with default set in database and other object whose default property has changed but it is not saved.
Any thoughts would be helpful. I can provide query if need be.
Update
Following suggestions, I added session.clear() to before running the query.
session.clear();
String sql = "SELECT * FROM BANKACCOUNTS WHERE PARTYID = :partyId AND CURRENCYID = :currencySymbol AND ISDEFAULTBANKACCOUNT= :defaultbankAccount";
SQLQuery q = session.createSQLQuery(sql);
q.addEntity(BankAccount.class);
q.setParameter("partyId", partyId);
q.setParameter("currencySymbol", currencySymbol);
q.setParameter("defaultbankAccount", 1);
return q.uniqueResult();
and it returns 1 row in result as expected but now am getting
nested exception is org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session exception
Either query which row has the "default flag" set before you start changing it, or query for a list of rows with default flag set & clear all except the one you're trying to set.
Very easy, stop mucking about with your "brittle" current approach which will break in the face of concurrency or if data is ever in an inconsistent state. Use a reliable approach instead, which will always set the data to a valid state.
protected void makeAccountDefault (BankAccount acc) {
// find & clear any existing 'Default Accounts', other than specified.
//
String sql = "SELECT * FROM BANKACCOUNTS WHERE PARTYID = :partyId AND CURRENCYID = :currencySymbol AND ISDEFAULTBANKACCOUNT= :defaultbankAccount";
SQLQuery q = session.createSQLQuery(sql);
q.addEntity(BankAccount.class);
q.setParameter("partyId", partyId);
q.setParameter("currencySymbol", currencySymbol);
q.setParameter("defaultbankAccount", 1);
//
List<BackAccount> existingDefaults = q.list();
for (BankAccount existing : existingDefaults) {
if (! existing.equals( acc))
existing.setDefaultBankAccount( false);
}
// set the specified Account as Default.
acc.setDefaultBankAccount( true);
// done.
}
This is how you write proper code, do it simple & reliable. Never make or depend on weak assumptions about the reliability of data or internal state, always read & process "beforehand state" before you do the operation, just implement your code clean & right and it will serve you well.
I think that your second query won't be executed at all because the entity is already in the first level cache.
As your transaction is not yet commited, you don't see the changes in the underlying database.
(this is only a guess)
That's only a guess because you're not giving many details, but I suppose that you perform your myObject.setMyDefaultProperty(1) while your session is open.
In this case, be careful that you don't need to actually perform a session.update(myObject) to save the change. It is the nominal case when database update is transparently done by hibernate.
So, in fact, I think that your change is saved... (but not commited, of course, thus not seen when you check in db)
To verify this, you should enable the hibernate.show_sql option. You will see if an Update statement is triggered (I advise to always enable this option in development phase anyway)

Do a query for uniqueness in MongoDB and insert if unique

I'm totally new to MongoDB. I'm using Morphia to access it from Java.
I need to do something along the lines of this:
public boolean isUnique(short s){
//OPERATION 1: Check in mongo if object with field "id" = s exists
//if so, return false
//else,
//OPERATION 2: create this object in the database
//and return true
}
The problem for me to grasp is not the actual syntax, but the problem with atomicity. How do I assure that only one thread/process can have access to the document so that OP1 and OP2 are atomic?
I think this has to be managed on a database level since the Java server is in a clustered environment.
Regards,
Anders
You could replace both operations with one upsert of the {id: s} document that becomes a no-op if the document already exists, but an insert if it doesn't. Calling the getLastError command (the driver can do this for you) will tell you whether a new document was created or not.
I'm using Java and Morphia so here is what the actual code looks like, if anyone should be wanting to achieve the same thing in the future. It's the true in the call to updateFirst that tell Mongo that it's an upsert:
public boolean checkIfExistsAndInsertOtherwise(short id){
Datastore datastore = getDatastore();
Query<OrganizationId> updateQuery = datastore.createQuery(OrganizationId.class).field("identificationNumber").equal(id);
//Bogus operation, just set id to itself
UpdateOperations<OrganizationId> ops = datastore.createUpdateOperations(OrganizationId.class).set("identificationNumber", id);
UpdateResults<OrganizationId> result = datastore.updateFirst(updateQuery, ops, true,WriteConcern.SAFE);
return result.getInsertedCount() == 0;
}

What is wrong with this GeoTools FeatureId?

Using the GeoTools WFS-T plugin, I have created a new row, and after a commit, I have a FeatureId whos .getId() returns an ugly string that looks something like this:
newmy_database:my_table.9223372036854775807
Aside from the fact that the word "new" at the beginning of "my_database" is a surprise, the number in no way reflects the primary key of the new row (which in this case is "23"). Fair enough, I thought this may be some internal numbering system. However, now I want a foreign key in another table to get the primary key of the new row in this one, and I'm not sure how to get the value from this FID. Some places suggest that you can use an FID in a query like this:
Filter filter = filterFactory.id(Collections.singleton(fid));
Query query = new Query(tableName, filter);
SimpleFeatureCollection features = simpleFeatureSource.getFeatures(query);
But this fails at parsing the FID, at the underscore of all places! That underscore was there when the row was created (I had to pass "my_database:my_table" as the table to add the row to).
I'm sure that either there is something wrong with the id, or I'm using it incorrectly somehow. Can anyone shed any light?
It appears as if a couple things are going wrong - and perhaps a bug report is needed.
The FeatureId with "new" at the beginning is a temporary id; that should be replaced with the real result once commit has been called.
There are a number of way to be aware of this:
1) You can listen for a BatchFeatureEvent; this offers the information on "temp id" -> "wfs id"
2) Internally this information is parsed from the Transaction Result returned from your WFS. The result is saved in the WFSTransactionState for you to access. This was before BatchFeatureEvent was invented.
Transaction transaction = new transaction("insert");
try {
SimpleFeatureStore featureStore =
(SimpleFeatureStore) wfs.getFeatureSource( typeName );
featureStore.setTransaction( transaction );
featureStore.addFeatures( DataUtilities.collection( feature ) );
transaction.commit();
// get the final feature id
WFSTransactionState wfsts = (WFSTransactionState) transaction.getState(wfs);
// In this example there is only one fid. Get it.
String result = wfsts.getFids( typeName )[0];
}
finally {
transaction.close();
}
I have updated the documentation with the above example:
http://docs.geotools.org/latest/userguide/library/data/wfs.html

Categories