Unique ID number in static method through concurrency JAVA - java

I want to get unique ID from my domain object (table) ID each time a method is called. So that ID's do not repeat. I have a function that returns unique ID.
public static Long generateID (Short company)
throws Exception
{
IDDAO iDDAO = SpringApplicationContext.getBean (IDDAO.class);
ID iD = iDDAO.findByNaturalKey (new IDNatKey (company);
if (iD != null)
{
// Check if ID has reached limit, then reset the ID to the first ID
if (iD.getLatestIDno ().longValue () == iD.getLastIDno ().longValue ())
{
iD.setLatestIDno (iD.getFrstIDno ());
}
// Get next ID
iD.setLatestIDno (iD.getLatestIDno () + 1);
// update database with latest id
iDDAO.update (iD);
return iD.getLatestIDno ();
}
}
The issue is that if access the application from two machines and press button from UI to generate ID exactly at the same time, there are sometimes duplicate IDs returned from this method
e.g.
Long ID = TestClass.generateID (123);
This gives me duplicate sometimes.
I made the method like this
public static synchronized Long generateID (Short company)
throws Exception
so that only one thread can go in this function at a time, but the duplicate issue is still there.
I do not want to use database sequences as I do not want gaps in the ID sequences if the transaction rolls back, in that case sequence will be incremented still, which I do not want.Gaps at the middle are OK but not at end. E.g we have 1, 2 and 3 as IDs and 2 rolls back, that is OK. But if 3 rolls back, we should get 3 again when another user comes, in case of sequence, it will give 4
Please help me tell what I am doing incorrect ? static synchronized will still cause other threads to go inside this function at same time ? I have many other static (but not synchronized) functions in the class. Will this cause issue with them too if I make it static synchronized ?
Thanks
Aiden

You can use java.util.UUID. it will generate a universal uniqueId.

Keep 2 unique IDs:
a db-provided, internal transaction ID, created by an autoincrement every time a new transaction is built. Gaps may appear if transactions are rolled back.
a pretty, gap-less "ticket ID", assigned only once the transaction commits successfully.
Assign both from the DB - it is best to keep all shared state there, as the DB will guarantee ACID, while Java concurrency is far trickier to get right.

In that case I think you try the below :
synchronized(this){if (iD != null)
{
// Check if ID has reached limit, then reset the ID to the first ID
if (iD.getLatestIDno ().longValue () == iD.getLastIDno ().longValue ())
{
iD.setLatestIDno (iD.getFrstIDno ());
}
// Get next ID
iD.setLatestIDno (iD.getLatestIDno () + 1);
// update database with latest id
iDDAO.update (iD);
return iD.getLatestIDno ();
}}

Related

How To Put If Statement Inside Transaction Firebase Firestore

When the user clicks a button it's going to check if the Document in Firestore is exists or not, if it is, then just update (increase) some value, and if it isn't create a new document with the new value.
I could just do the normal documentRef.get(), and documentRef.set(), but when I tested it with 2 user press the button at the same time. Its just put the new value of one of them. and so I used transaction.
In this transaction, first I get the documentsnapshot, and I get the value.
and then I put if statement to check if the document is exist, the first statement which is document is exist is working fine, but when I deleted the document, the else statement didn't do anything.
Is it even possible to use if statement inside Firebase Firestore transaction?
Map<String, Object> newDocumentSet = new HashMap<>();
newDocumentSet.put("sold", 1);
newDocumentSet.put("price", myProductPrice);
mDb.runTransaction(new Transaction.Function<Void>() {
#Nullable
#Override
public Void apply(#NonNull Transaction transaction) throws FirebaseFirestoreException {
DocumentSnapshot myDocumentSnapshot = transaction.get(myDocumentRef);
long newAddSold = myDocumentSnapshot.getLong("sold") + 1;
long newAddPrice = myDocumentSnapshot.getLong("price") + myProductPrice;
if(myDocumentSnapshot.exists()){
transaction.update(myDocumentRef, "sold", newAddSold);
transaction.update(myDocumentRef, "price", newAddPrice);
}else {
transaction.set(myDocumentRef, newDocumentSet);
}
return null;
}
});
I don't know what's happening, it didn't show any error, please tell me if I made some mistake or there is another way of doing this.
or there is another way of doing this..
Yes there is, even a simpler one which in fact is more efficient than a transaction because it does not require a round trip between the client and Firebase servers. Assuming that the mDb points to the correct document, to increment the sold property by 1, please use the following line of code:
mDb.update("sold", FieldValue.increment(1));
All you need is to return a Map<String, dynamic> value from the runTransaction function and inside the run transaction make sure you return a map value like
if (a) {
return "value":true
} else {
return "value":false
}
Then in the return statement of your function assign mapDynamic["value"];. Then you will now which part of your code was executed. mapDynamic is just a variable name.

How to lock table between read and write operations

I've a situation where each new record should contain a unique and readable value.
This value has a business meaning for the user and will be handled as a natural id (next to primary key) in our database.
To give you an idea of the value's structure:
- record 1 has business value 'INVOICE_AAA_001'
- record 2 has business value 'INVOICE_AAA_002'
...
- record 999 has business value 'INVOICE_AAA_999'
- record 1000 has business value 'INVOICE_BAA_001'
- record 1001 has business value 'INVOICE_BAA_002'
...
This business value is created by a factory:
class BusinessFactory {
...
public String createUniqueValue() {
String lastValue = (String) getSession().createQuery("select businessValue " +
"from Invoice as invoice " +
"order by businessValue")
.setMaxResults(1)
.setReadOnly(true)
.uniqueResult();
// generate new value
return newValue;
}
}
The Service layer will call the factory and save a new Invoice:
#Transactional
public void saveNewInvoice() {
final String newValue = businessFactory.createUniqueValue();
Invoice invoice = new Invoice(newValue);
invoiceRepository.save(invoice);
}
The problem here is that a situation might exist where trx1 and trx2 read business value 'INVOICE_BAA_002'.
What happens next is that 2 trx's are working with the same value. The trx that first commits will succeed, the 2nd will fail due to a unique constraint exception.
Therefore I need to put a lock on Invoice table when reading out the latest Business value. I think this lock should be active until the new Invoice entity is saved to DB.
How should I do this in Hibernate?
Instead of using a conflict detection concurrency control mechanism, such a relying on unique constraints or optimistic locking, you can use pessimistic locking.
You need to have:
An InvoiceSequence table with an Entity mapped to it
This table has only one row, storing the latest invoice sequence value
You acquire an exclusive lock on the record:
InvoiceSequence invoiceSequence = em.find(InvoiceSequence.class, 1L, LockModeType.PESSIMISTIC_WRITE)
You increment the sequence using your business logic and modify the entity to store the latest value:
String currentSequence = invoiceSequence.getValue();
String nextSequence = sequenceGenerator.nextValue(currentSequence);
invoiceSequence.setValue(nextSequence);
The exclusive lock will prevent both concurrent read and writes too.
A sequence is a set of integers 1, 2, 3, ... that are generated in order on demand. Sequences are frequently used in databases because many applications require each row in a table to contain a unique value, and sequences provide an easy way to generate them.
One way you can do
class BusinessFactory {
public String createUniqueValue() {
String valueNotUsed = (String) getSession().createSQLQuery("select nextval('hibernate_sequence')");
// generate new value
return newValue;
}}

Handling many requests in the same time

I have a table Users in MySQL includes 3 fields : ID (int), Name (varchar) and UpdateTime (TimeStamp).
I have a java web application and one function to get one record in table Users, after that, check if UpdateTime is before current time ---> update UpdateTime by current time plus 2 hours.
And my problem is when having about 100 users access to my app and request to that function to get the same record in the same time, all of them will update UpdateTime. But I want only first user update UpdateTime, latecomers will only get data without update UpdateTime.
How I can do it? I really have no idea.
Thanks.
P/s: Sorry about my English, it seems to be not good :(
Your update time code should be in synchronised block.
Take write lock on your object and if someone has consumed lock before going on update operation and at the same time another request comes should get the pre loaded/after update data.
Chech synchronised keyword in java also check read write lock mechanism. This might help you.
following code might help you.
import java.util.Map;
import java.util.HashMap;
public class Test {
boolean isWriteLockTaken;
Map<Integer,String> cachedUsers = new HashMap<Integer,String>();
public static void main(String[] args) throws Exception {
Test t = new Test();
for(int i=0;i<10;i++){
t.updateAndGetUser(1);
}
}
public String updateAndGetUser(Integer id) throws InterruptedException {
if(isWriteLockTaken && !cachedUsers.containsKey(id)){
this.wait();
}
if(!cachedUsers.containsKey(id)){
isWriteLockTaken = true;
synchronized(id){
if(id==1){
// do processing to retrive user
System.out.println("Updating & loading user");
cachedUsers.put(id, "Gaurav");
return "Gaurav";
}
}
isWriteLockTaken = false;
}
System.out.println("returning from cached list, not loading user details as it is already loaded");
return cachedUsers.get(id);
}
}
Thanks,
Gaurav

Retrieving a single column of last row in sqlite/Android using ORDER ID desc limit 1

I'm attempting to create a method that will allow me to retrieve a value from the last row of a database, and then insert it into an EditText field. (This is a value that the user will not change all that often, so it would be helpful if when they do set it, it stays set when they come back to it).
My method, based on a similar method I have for getting the total of a specific column, is as such:
public String getBase() {
Cursor mCursor = mDb.rawQuery(
"SELECT base FROM table constants ORDER ID desc limit 1", null);
if (mCursor.moveToFirst()) {
return mCursor.getString(0);
}
return mCursor.getString(0);
Like I said, I based this on a similar method, which I found after searching around the Internet. I understand most of it, but I have no idea what the 0's in the return statements mean (or the moveToFirst method).
Anyway, in my OnClickListener (the button the user would press to save this value to db), I have the following (editBase is the EditText field I want to populate):
editBase.setText(cDbHelper.getBase());
If I run the program without this statement, it works fine and the value saves to the db. As soon as I try to run it with this, I get a force close. Any suggestions?
Thanks.
EDIT: Thanks for the responses. With some guidance from a friend, I ended up using "ORDER BY... desc limit 1" instead. This was the final method:
public double getBase() {
final double DEFAULT_BASE = 0;
Cursor mCursor = mDb.rawQuery(
"SELECT base FROM constants ORDER BY _id desc limit 1", null);
if (mCursor.getCount() == 0)
return DEFAULT_BASE;
else
mCursor.moveToFirst();
return mCursor.getDouble(0);
1) For querying the DB may be try this one:
https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
SQLiteDatabase has query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) method.
At least with this one you don't need to construct your SQL manually.
2) Check the doc for Cursor:
http://developer.android.com/reference/android/database/Cursor.html#getString(int)
0 is a columnIndex.
You are probably trying to update a View when running a job on a separate thread, this almost always triggers a force close.
In this article, Painless Threading, you learn how to break of your long running work into another thread (and how you can "come back" to it). Basically you can tell the GUI thread (or event queue or ui thread) that you need a Runnable to run with access to your controls and views.
private void clicked() {
// called from a click of a button for instance
new Thread(new Runnable() {
public void run() {
// the long running job should be executed on a different
// thread as to not stall the ui thread
runLongWork();
}
}
}
private void runLongWork() {
// this is method running on separate thread
// so we should run a runnable on the ui thread instead to update our view's.
this.runOnUiThread(new Runnable() {
public void run() {
// Now we are back in the ui thread
editBase.setText(cDbHelper.getBase());
}
}
}
As for the other questions Arhimed already answered it partly. When you execute a query to the Sqlite database you get an instance of a class Cursor that sort of is a list of the results or rows that your query fetched. Using the moveToFirst method you position the cursor at the first row of results. You can then get the values from the columns in your query using the getXYZ() methods (for instance getString()) and pass an index of the column that you want. 0 in this case refers to base, the first column in your SQL query.
I you have more than one row you can use the moveToNext method to get the next row, and since it returns a bool stating that it actually "found" a next row, you can use it in a while loop to get all your rows.
while (cursor.moveToNext()) {
// next row of data, use getString, getInt or others
}
Your query altough will always return only one value, since you limit the results to 1, so no need to use the moveToNext. moveToFirst will, like moveToNext, return true if a row was found, and false if there are no more rows available.
Your statement is as follows; you execute a query, move to the first row, and if it was successful return the string from the first column in the query (index starts at zero). If it doesn't find a row, you return the first column as a string, which will probably fail though. You should perhaps return a default value or throw an exception in that case.

Transaction issue in java with hibernate - latest entries not pulled from database

I'm having what seems to be a transactional issue in my application. I'm using Java 1.6 and Hibernate 3.2.5.
My application runs a monthly process where it creates billing entries for a every user in the database based on their monthly activity. These billing entries are then used to create Monthly Bill object. The process is:
Get users who have activity in the past month
Create the relevant billing entries for each user
Get the set of billing entries that we've just created
Create a Monthly Bill based on these entries
Everything works fine until Step 3 above. The Billing Entries are correctly created (I can see them in the database if I add a breakpoint after the Billing Entry creation method), but they are not pulled out of the database. As a result, an incorrect Monthly Bill is generated.
If I run the code again (without clearing out the database), new Billing Entries are created and Step 3 pulls out the entries created in the first run (but not the second run). This, to me, is very confusing.
My code looks like the following:
for (User user : usersWithActivities) {
createBillingEntriesForUser(user.getId());
userBillingEntries = getLastMonthsBillingEntriesForUser(user.getId());
createXMLBillForUser(user.getId(), userBillingEntries);
}
The methods called look like the following:
#Transactional
public void createBillingEntriesForUser(Long id) {
UserManager userManager = ManagerFactory.getUserManager();
User user = userManager.getUser(id);
List<AccountEvent> events = getLastMonthsAccountEventsForUser(id);
BillingEntry entry = new BillingEntry();
if (null != events) {
for (AccountEvent event : events) {
if (event.getEventType().equals(EventType.ENABLE)) {
Calendar cal = Calendar.getInstance();
Date eventDate = event.getTimestamp();
cal.setTime(eventDate);
double startDate = cal.get(Calendar.DATE);
double numOfDaysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
double numberOfDaysInUse = numOfDaysInMonth - startDate;
double fractionToCharge = numberOfDaysInUse/numOfDaysInMonth;
BigDecimal amount = BigDecimal.valueOf(fractionToCharge * Prices.MONTHLY_COST);
amount.scale();
entry.setAmount(amount);
entry.setUser(user);
entry.setTimestamp(eventDate);
userManager.saveOrUpdate(entry);
}
}
}
}
#Transactional
public Collection<BillingEntry> getLastMonthsBillingEntriesForUser(Long id) {
if (log.isDebugEnabled())
log.debug("Getting all the billing entries for last month for user with ID " + id);
//String queryString = "select billingEntry from BillingEntry as billingEntry where billingEntry>=:firstOfLastMonth and billingEntry.timestamp<:firstOfCurrentMonth and billingEntry.user=:user";
String queryString = "select be from BillingEntry as be join be.user as user where user.id=:id and be.timestamp>=:firstOfLastMonth and be.timestamp<:firstOfCurrentMonth";
//This parameter will be the start of the last month ie. start of billing cycle
SearchParameter firstOfLastMonth = new SearchParameter();
firstOfLastMonth.setTemporalType(TemporalType.DATE);
//this parameter holds the start of the CURRENT month - ie. end of billing cycle
SearchParameter firstOfCurrentMonth = new SearchParameter();
firstOfCurrentMonth.setTemporalType(TemporalType.DATE);
Query query = super.entityManager.createQuery(queryString);
query.setParameter("firstOfCurrentMonth", getFirstOfCurrentMonth());
query.setParameter("firstOfLastMonth", getFirstOfLastMonth());
query.setParameter("id", id);
List<BillingEntry> entries = query.getResultList();
return entries;
}
public MonthlyBill createXMLBillForUser(Long id, Collection<BillingEntry> billingEntries) {
BillingHistoryManager manager = ManagerFactory.getBillingHistoryManager();
UserManager userManager = ManagerFactory.getUserManager();
MonthlyBill mb = new MonthlyBill();
User user = userManager.getUser(id);
mb.setUser(user);
mb.setTimestamp(new Date());
Set<BillingEntry> entries = new HashSet<BillingEntry>();
entries.addAll(billingEntries);
String xml = createXmlForMonthlyBill(user, entries);
mb.setXmlBill(xml);
mb.setBillingEntries(entries);
MonthlyBill bill = (MonthlyBill) manager.saveOrUpdate(mb);
return bill;
}
Help with this issue would be greatly appreciated as its been wracking my brain for weeks now!
Thanks in advance,
Gearoid.
Is your top method also transactional ? If yes most of the time i've encountered that kind of problem, it was a flush that was not done at the right time by hibernate.
Try to add a call to session.flush() at the beginning of the getLastMonthsBillingEntriesForUser method, see if it address your problem.
Call session.flush() AND session.close() before getLastMonthsBillingEntriesForUser gets called.
Please correct my assumptions if they are not correct...
As far as I can tell, the relationship between entry and user is a many to one.
So why is your query doing a "one to many" type join? You should rather make your query:
select be from BillingEntry as be where be.user=:user and be.timestamp >= :firstOfLastMonth and be.timestamp < :firstOfCurrentMonth
And then pass in the User object, not the user id. This query will be a little lighter in that it will not have to fetch the details for the user. i.e. not have to do a select on user.
Unfortunately this is probably not causing your problem, but it's worth fixing nevertheless.
Move the declaration of BillingEntry entry = new BillingEntry(); to within the for loop. That code looks like it's updating one entry over and over again.
I'm guessing here, but what you've coded goes against what I think I know about java persistence and hibernate.
Are you certain that those entries are being persisted properly? In my mind, what is happening is that a new BillingEntry is being created, it is then persisted. At this point the next iteration of the loop simply changes the values of an entry and calls merge. It doesn't look like you're doing anything to create a new BillingEntry after the first time, thus no new id's are generated which is why you can't retrieve them later.
That being said, I'm not convinced the timing of the flush isn't a culprit here either, so I'll wait with bated breathe for the downvotes.

Categories

Resources