I have this bean/table "Userinfo" with columns id, username, and twitchChannel.
For most userinfo the twitchChannel column will be null. I'm going through every userinfo entity in the table and search the column twitchChannel, if the column is not null I put the twitchChannel in an array.
this is what my request looks like:
"SELECT ui FROM Userinfo ui WHERE ui.iduserinfo=:id"
It is very inefficient because I'm going through every single entity even those which have a null twitchChannel and I'm not interested in those.
This is java but I commented every line so it's easy to understand for those who don't know it.
while (true) { // I'm going through the table in an infinite loop
int id = 0; //id that is incremented for searches
Userinfo ui; // this will be an object that will hold the result of my query
do {
ui = ups.getUserInfo(id); // this execute the query I posted above
id++; //incrementing id for next search
if (ui.getTwitch() != null) { // if the search didn't return null
twitchChannels.add(ui.getTwitch()); // put my twitch in an array
}
} while (ui != null);
}
So at the moment I'm going through every entity in my table even those with a null twitch. To my understanding it's possible to speed up the process with indexes.
CREATE INDEX twitchChannel
ON Userinfo (twitchChannel)
So something like that would have a table with not null twitchChannel. How I loop through this table like above ?
Will it work the same way with java persistence?
Change the query to:
SELECT ui
FROM Userinfo ui
WHERE twitchChannel IS NOT NULL
This will benefit from an index on Userinfo(twitchChannel) (assuming there really are very few values that are filled in). At the very least, this reduces the amount of data passed from the database to the application, even if an index is not used.
If I've understood your question correctly. You have a table containing numerical id's. And you are searching the space of real numbers to see if any of those correspond to an id in your table ('twitch' id ?)
Assuming you have less than infinity users, I would have thought you can reverse this logic.
Change your query to :
SELECT iduserinfo FROM Userinfo ORDER BY iduserinfo
Then your java code will be something along the lines of :
uiResult = ups.getUserInfo(id); // this executes the new query
while (uiResult.next()) {
twitchChannels.add(uiResult.getTwitch()); // put my twitch in an array
}
(Apologies, its been a long time since I've used jdbc).
Sorry If I've misunderstood the question.
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 table that maintains a sequence number that is used as an identifier for multiple tables (multiple invoice tables all the tables are using single sequence).
Whenever i want to insert a new record in invoice table I read the current sequence number from the table and update it with +1.
The problem is when there are multiple requests for new invoice numbers the sequence number returns duplicate numbers.I tried synchronized block but still it returning duplicate values when multiple requests are hitting at same time.
Here is the method to retrieve the sequence number
synchronized public int getSequence(){
Sequence sequence = getCurrentSession().get(Sequence.class,1); //here 1 is the id of the row
int number = sequence.getSequenceNumber();
sequence.setSequenceNumber(number+1);
getCurrentSession().saveOrUpdate(sequence);
return number;
}
Is there something I am missing?
First of all I won't recommend you to use table implementation of the sequence. Explanation why
But if you have to - hibernate knows how to manage it. Take a look
And one more thing. I strongly recommend you to implement synchronization on the data base side. Imagine you have 2 instances of your application connected to the same database instance and working simultaneously.
Using transactions also not worked for me. I tried all the isolations in mysql but nothing helps me. I solved it with below solution.
synchronized public int getSequence(){
Sequence sequence = getCurrentSession().get(Sequence.class,1); //here 1 is the id of the row
int prevNumber = sequence.getSequenceNumber();
Query<Sequence> query = getCurrentSession().createQuery("UPDATE Sequence SET sequenceNumber = :number WHERE sequenceNumber = :prevNumber",Sequence.class);
query.setParameter("number",prevNumber+1);
query.setParameter("prevNumber",prevNumber);
int affectedRows = query.executeUpdate();
if(accectedRows > 0)
return number;
else
throw new Exception();
}
So whenever a conflict happens it will throw an exception.
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;
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)
I have a strange problem to update a table in my database...forgive me if I can not explain well but I'm a bit confused...
The problem is this:
I created a table with values, I read this values in my listview..everything works for now..insert and delete values works without problem..now created a loop in a service why do I need to make a comparison between a value and a string of my database and when this comparison is true, I need to change a value in my table..
The real problem is this: my db.update works only if not use ... never, the command db.delete... if use it, the db.update not work anymore ..and to make it work again, i need to make a new AVD.
how is it possible?
my db.delete and id is this:
item.getMenuInfo();
id = getListAdapter().getItemId(info.position);
public void deleteReg(SQLiteDatabase db ,long id)
{
db.delete(TabRegistry.TABLE_NAME, TabRegistry._ID + "=" + id, null);
}
on activity:
databaseHelper.deleteReg(db, id);
my db.update is this: (positions is a value of getPositions(),for locate a positions with a cursor(always works, even when fails db.update))
public void updateReg(SQLiteDatabase db,int positions, String stat)
{
ContentValues v = new ContentValues();
v.put(TabRegistry.STATUS, stat);
db.update(TabRegistry.TABLE_NAME, v, TabRegistry._ID + " = " + positions, null);
}
on service:
databaseHelper.updateReg(db, positions, "SUCCESS");
if you need more code, tell me what I add now..thanks in advance
The SQLite api you are using is based off of CRUD operations (you should read this).
You are DELETE-ing the record from the database, therefore there is nothing to UPDATE when you attempt to update it. If you want to create a new record, or recreate the one you deleted then you would perform an INSERT instead of an UPDATE.
EDIT:
It also appears you are passing in position number to the update and delete. I assume that you are also using this value to place the record in your table? Is it possible that when you delete the record from the table and the database, that the other records now have an invalid position because they haven't been updated also? It's just a shot in the dark, figured I might as well ask.