I have following java class
package com.picvik.model;
import java.util.Date;
public class ViewAlbum {
private Integer albumid;
private String albumname;
private String description;
private String location;
private Date date;
private Integer uid;
public Integer getAlbumid() {
return albumid;
}
public void setAlbumid(Integer albumid) {
this.albumid = albumid;
}
public String getAlbumname() {
return albumname;
}
public void setAlbumname(String albumname) {
this.albumname = albumname;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
}
I am retrieving data from db and adding it to my array list like this
public ArrayList getAllAlbums(Integer uid) {
ViewAlbum album = new ViewAlbum();
ArrayList<ViewAlbum>allAlbums = new ArrayList<ViewAlbum>();
try {
String qstring = "SELECT albumid, albumname, description, location," +
" date, uid FROM picvik_picture_album WHERE " +
"uid = '" + uid + "';";
System.out.println(qstring);
connection = com.picvik.util.MySqlConnection.getInstance().getConnection();
ptmt = connection.prepareStatement(qstring);
resultSet = ptmt.executeQuery();
while(resultSet.next()) {
//System.out.println(resultSet.getString("albumname"));
album.setAlbumid(resultSet.getInt("albumid"));
album.setAlbumname(resultSet.getString("albumname"));
album.setDescription(resultSet.getString("description"));
album.setLocation(resultSet.getString("location"));
album.setDate(resultSet.getDate("date"));
album.setUid(resultSet.getInt("uid"));
allAlbums.add(album);
}
resultSet.close();
ptmt.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
return allAlbums;
}
But when I am trying to print the values stored in array list. Its always giving me the last inserted record.
<div class="row">
<div class="span10">
<s:iterator value="allAlbums">
<s:property value="albumname"/>
</s:iterator>
</div>
</div>
Here,
ViewAlbum album = new ViewAlbum();
// ...
while (resultSet.next()) {
album.setAlbumid(resultSet.getInt("albumid"));
// ...
allAlbums.add(album);
}
you're reusing the very same album instance for all records. The instance's data get overridden everytime in the loop. The list does not contain copies of the instance, but it contains copies of the reference to the single instance. You know, Java is Object Oriented.
You should be creating a new album instance per record. Move the instantiation to inside the loop.
// ...
while (resultSet.next()) {
ViewAlbum album = new ViewAlbum();
album.setAlbumid(resultSet.getInt("albumid"));
// ...
allAlbums.add(album);
}
See also:
How can I pass an Integer class correctly by reference?
Unrelated to the concrete problem, you should be closing JDBC resources in the finally block, or be opening them in the try() try-with-resources statement, otherwise they will still leak away in case of an exception during executing the query or processing the result set. You should also move the declarations of JDBC resources to inside the method block, otherwise you'll run into threadsafety issues as well. Last but not least, you should use the setter methods of PreparedStatement to set user-controlled variables in a SQL string. If they were strings, you'd have a SQL injection attack hole.
See also:
Java Iterator backed by a ResultSet
JDBC MySql connection pooling practices to avoid exhausted connection pool
You have only one instance of ViewAlbum and you are playing(setting the values) only with that single instance throughout the loop. So after completition of loop you have only one object inserted into ArrayList for N(Size of Resultset) no of times.
Related
My information is read in via CSV for "banking information", A person CSV is
5
1,Tony,Stark,C,tonys,naslvj34-t934,stark#hmmm.org;tony#gmail.com;tostark55#yaho.com
2,Patrick,Hart,C,phart2,sdlwgl5034i52,hart#hmmm.org;hart1#who.org
3,Tom,Baker,E,bake95,kweojtnefq567,
4,Kevin,Black,C,keb765,prjhohier99,black#mail.org
5,Alex,Codd,E,alcodd,andlqjr78,codd#DBMS.org;alex#gmail.com
my Account CSV is,
5
1,313001,S,35881.12
2,313002,G,772400.34
3,313003,C,250002.15
4,313004,P,96310.66
5,313005,P,15624.15
1,313006,C,39950.99
In the Account CSV the 2nd token is My "Account type", so S = "Savings" G = "Gold Savings" C = "Checkings" and so on. When reading these in my CSV parser class, an object was made of the types, so A Checking class was made and when the CSV was tokenized when token[2] was equal to "C".
My Persons class is set up as so, excluding my getters and setters
private int personId;
private String firstName;
private String lastName;
private String type;
private String usrname;
private String password;
private ArrayList<String> emails;
public Person(int personId, String firstName, String lastName, String type, String usrname, String password,
ArrayList<String> emails) {
super();
And my Account class is set up as so.
private Person accountHolder;
private int accountNumber;
private double currentBalance;
public Account() {}
public Account(Person accountHolder, int accountNumber, double currentBalance) {
this.accountHolder = accountHolder;
this.accountNumber = accountNumber;
this.currentBalance = currentBalance;
}
This is one of my example account types (there are multiple)
public class CheckingAccount extends Account {
public CheckingAccount(Person p, int i, double d) {
super(p,i, d);
}
This is my database adder
public static void DbAddAccount(Person personId, Account accountHolder){
Connection conn = null;
try {
conn = DriverManager.getConnection(DatabaseInfo.URL, DatabaseInfo.USERNAME, DatabaseInfo.PASSWORD);
} catch (SQLException e) {
System.out.println("Connection Failed");
throw new RuntimeException(e);
}
String accountQuery = "insert into Account (accountId,accountNumber,currentBalance,type,personId) values (?,?,?,?,?);";
PreparedStatement accountPs = null;
ResultSet accountKeys = null;
int accountId = 0;
try {
//These are my prepare Statements for my queries
accountPs = conn.prepareStatement(accountQuery);
accountPs.setInt(1, accountId+ 1 );
accountPs.setInt(2, accountHolder.getAccountNumber());
accountPs.setDouble(3, accountHolder.getCurrentBalance());
accountPs.setString(4, ??????????????????????????????????);
accountPs.setInt(5, personId.getPersonId());
accountPs.executeUpdate();
accountPs.close();
conn.close();
}
catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
JDBC is not adding anything when running.
I think your code has following issues:
Account Object:
Object is missing field to store "Account Type"
Setting incorrect account type in DbAddAccount method
At line accountPs.setString(4, ??????????????????????????????????);
you are number of question marks (without enclosing in quote to make string) as account type which appears it is not correct.
series of ??? wont get converted to string without enclosed in quotes.
Even if you enclose in quotes, I think it wont be intended account number;
Solution
for Issue 1; declare account_type in Account Object.
for issue 2; instead of using accountPs.setString(4, ??????????????????????????????????);
use:
accountPs.setString(4, account.getAccountType());
I'm creating an app using the mvvm pattern with android room, but I've ran into some trouble validating user input. When a user wants to add an ingredient to the app, they are required to enter a name for this ingredient. I want the app to notify the user if the name is already in use. I have tried some stuff using the Transformations.Map() functions but without any success.
I'm fairly new to the mvvm pattern and LiveData, and I've been stuck on this for quite a while now so any advice would be appreciated.
This is the ingredient entity:
#Entity(tableName = "ingredient")
public class BaseIngredient {
#PrimaryKey(autoGenerate = true)
private int id;
private String name;
private String category;
#ColumnInfo(name = "cooking_time")
private int cookingTime;
#Ignore
public BaseIngredient() {
}
public BaseIngredient(int id, #NonNull String name, #NonNull String category, int cookingTime)
throws InvalidValueException {
this.id = id;
setName(name);
setCookingTime(cookingTime);
setCategory(category);
}
public void setName(String name) throws InvalidNameException {
if (name == null || name.isEmpty())
throw new InvalidNameException("Name is empty");
if (!name.matches("[A-z0-9]+( [A-z0-9]+)*"))
throw new InvalidNameException("Name contains invalid tokens");
this.name = name;
}
public void setCategory(String category) throws InvalidCategoryException {
if (category == null || category.isEmpty())
throw new InvalidCategoryException("Category is empty");
if (!category.matches("[A-z0-9]+"))
throw new InvalidCategoryException("Category contains invalid tokens");
this.category = category;
}
public void setCookingTime(int cookingTime) throws InvalidCookingTimeException {
if (cookingTime < 1)
throw new InvalidCookingTimeException("Time must be positive");
this.cookingTime = cookingTime;
}
/* getters */
public boolean isValid() {
return name != null && category != null && cookingTime != 0;
}
This is the IngredientRepository I'm using:
private IngredientDao ingredientDao;
private LiveData<List<BaseIngredient>> ingredients;
public IngredientRepository(Application application) {
LmcfyDatabase database = LmcfyDatabase.getDatabase(application.getApplicationContext());
ingredientDao = database.ingredientDao();
ingredients = ingredientDao.getAllIngredients();
}
public LiveData<List<BaseIngredient>> getAllIngredients() {
return ingredients;
}
public LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query) {
return ingredientDao.getIngredientsWithQuery("%" + query + "%");
}
public void insert(BaseIngredient ingredient) {
LmcfyDatabase.databaseWriteExecutor.execute(() -> {
ingredientDao.insert(ingredient);
});
}
public LiveData<Integer> getIngredientsWithNameCount(String name) {
return ingredientDao.getIngredientsWithNameCount(name);
}
The IngredientDao:
#Insert(onConflict = OnConflictStrategy.IGNORE, entity = BaseIngredient.class)
long insert(BaseIngredient ingredient);
#Delete(entity = BaseIngredient.class)
void delete(BaseIngredient ingredient);
#Query("SELECT * FROM ingredient")
LiveData<List<BaseIngredient>> getAllIngredients();
#Query("SELECT * FROM ingredient WHERE name LIKE :query")
LiveData<List<BaseIngredient>> getIngredientsWithQuery(String query);
#Query("SELECT COUNT(id) FROM ingredient WHERE name LIKE :name")
LiveData<Integer> getIngredientsWithNameCount(String name);
And finally the ViewModel that is used to create an Ingredient
private final IngredientRepository repository;
private final BaseIngredient ingredient;
private final MutableLiveData<String> nameError;
private final MutableLiveData<String> categoryError;
private final MutableLiveData<String> cookingTimeError;
private final MutableLiveData<Boolean> ingredientValidStatus;
public AddIngredientViewModel(#NonNull Application application) {
super(application);
repository = new IngredientRepository(application);
ingredient = new BaseIngredient();
nameError = new MutableLiveData<>();
categoryError = new MutableLiveData<>();
cookingTimeError = new MutableLiveData<>();
ingredientValidStatus = new MutableLiveData<>();
}
public void onNameEntered(String name) {
try {
ingredient.setName(name);
nameError.setValue(null);
} catch (InvalidNameException e) {
nameError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCategoryEntered(String category) {
try {
ingredient.setCategory(category);
categoryError.setValue(null);
} catch (InvalidCategoryException e) {
categoryError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
public void onCookingTimeEntered(int cookingTime) {
try {
ingredient.setCookingTime(cookingTime);
cookingTimeError.setValue(null);
} catch (InvalidCookingTimeException e) {
cookingTimeError.setValue(e.getMessage());
} finally {
updateIngredientValid();
}
}
private void updateIngredientValid() {
ingredientValidStatus.setValue(ingredient.isValid());
}
public boolean saveIngredient() {
if (ingredient.isValid()) {
Log.d(getClass().getName(), "saveIngredient: Ingredient is valid");
repository.insert(ingredient);
return true;
} else {
Log.d(getClass().getName(), "saveIngredient: Ingredient is invalid");
return false;
}
}
The onXXEntered() functions in the viewmodel are linked to the textViews in the fragment, and the saveIngredient() function is called when a save button is pressed. The XXError LiveData's are used to display errors to the user.
The real problem lies in the fact that LiveData is async, and the user can change their input and click the save button before the LiveData contains the result from the database. If I want the check the input upon saving it, the 'add activity' will have already finished before the check is done.
Again, any help would be very much appreciated.
I had to do something similar in one of my recent projects. What I did was:
Room cannot create columns with SQLite Unique constraint (if it is not the PrimaryKey - which is your case). So don't initialize the database in your app code using Room. Instead, create a database file outside your application. Add a Unique constraint on the 'name' column. Then add the database file in your project under assets folder. (for example, create a subfolder inside assets - 'db_files' - and copy your pre-created database file under this folder)
I guess you use Singleton pattern for your #DataBase class. Replace your 'getInstance()' method with following:
public static MyDB getInstance(final Context context) {
if(INSTANCE == null) {
synchronized (AVListDB.class) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
MyDB.class,"myDB.db")
.createFromAsset( "db/myDB.db")
.build();
}
}
return INSTANCE;
}
This creates a copy of your pre-packaged database file under applications database files path.
With unique constraint in place, your #Insert and #Update annotated methods will respect the constraint condition, and will throw a SQLiteConstraintException if you try to insert a previously used name. You can catch this exception, and pass it to your View or ViewModel as you wish (I implemented a simple centralized event publisher component).
I hope that helps.
Yesterday I posted a question regarding retrieving data from a Db and iterating over it. Someone helpfully pointed my to JDBI and away from raw data types.
Caveats: I am a tester forst and foremost and have just started to explore JDBI for some automated tests.
So, I think I've improved the previous solution but I am just struggling with implementing the best way to iterate over my dataset now it is retrieved.
Here is my method to return the dataset:
public List<FlightDataBean> lastFlightBookedResults(String supplierCode, String channel) {
String sqlQuery = getData(supplierCode, channel);
List<FlightDataBean> dataSet = jdbi.withHandle(handle ->
handle.createQuery(sqlQuery)
.mapToBean(FlightDataBean.class)
.list());
return dataSet;
}
Here is my Bean class:
public class FlightDataBean {
private String startDate;
private String origin;
private String destination;
public FlightDataBean(){
}
public FlightDataBean(String startDate, String origin, String destination) {
this.startDate = startDate;
this.origin = origin;
this.destination = destination;
}
public String getStartDate() {
return startDate;
}
public void setStartDate(String startDate) {
this.startDate = startDate;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
}
Here is an example of the returned dataset, 30 rows of 3 columns:
Clearly I can retrieve individual results by doing suchlike:
List<FlightDataBean> resultSet;
resultSet = getFlightData(syndicatorName);
String startDate = (resultSet.get(0).getStartDate());
String origin = String.valueOf((resultSet.get(1)).getOrigin());
String destination = String.valueOf(resultSet.get(2).getDestination());
I just need a pointer as to the best/most efficient/safest way of iterating over all 30 because I am using the results as search test data and need to potentially use each one in turn in a further method which uses the dataset until it gets results back on a website.
I'm continuing to learn JDBI but in the meantime any help would be great
I answered your last question already with a similar answer...you can just adept the code to fit your current case:
for (FlightDataBean i : resultSet){
String startDate = i.getStartDate();
String origin = i.getOrigin();
String destination = i.getDestination();
//further code, go on from here.
}
The for-loop says nothing else, than for every Bean in your resultSet, extract the 3 values (and do the code you added afterwards).
I am using Apache Commons DBUtils according to QueryRunner#insert method in its documentation,insert return the generic type of ResultSetHandler. I have a BR_Author object at my project.
BR_Author.java
import org.springframework.stereotype.Repository;
#Repository
public class BR_Author {
private int id;
private String authorName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAuthorName() {
return authorName;
}
public void setAuthorName(String authorName) {
this.authorName = authorName;
}
}
I write a simple insert statement at my service class like
AuthorService#createAuthor
public BR_Author createAuthor(String authorName) throws ApiException {
String sql = "Insert into BR_AUTHOR(authorName) VALUES(?)";
ResultSetHandler<BR_Author> rs = new BeanHandler<BR_Author>(
BR_Author.class);
QueryRunner qr = new QueryRunner(dataSource);
Object[] params = { authorName };
try {
BR_Author author = qr.insert(sql, rs, params);
System.out.println("Br_Author:" + author);
return author;
} catch (SQLException e) {
throw new ApiException(ErrorCode.ERR20001, e.getMessage());
}
}
I am trying to return the added value at createAuthor method, but if i configure id field as auto increment object return as
Br_Author:Br_Author [id=0, authorName=null]
when i check the db i see that it adds the values successfully.
If i disable auto increment and set id from code, author object is null. So i want to learn that am i misunderstand QueryRunner#insert method or it has a bug. I already check below links.
QueryRunner_insert_add_javadoc.patch
Generated key handling for updates
BTW: Select queries working fine for BR_Author class so it means there shouldn't be any mapping issue.
I didn't find any official solution from DBUtils API so i just return generated ID and then query it after insert the record with this id at Service layer. This way method return Object at service layer, but i do one extra query.
I'm making a mysql database connector with java to show all the data.
When I run the code, I get an NullPointerException in my getData() function.
here is my code.
public String[][] getData() {
String values[][];
try {
rs = st.executeQuery("SELECT * FROM adresses");
int i = 0;
while(rs.next()) {
String id = rs.getString("id");
String name = rs.getString("name");
String adress = rs.getString("email_adress");
String catagory = rs.getString("catarogy");
values[i][0] = id;
values[i][1] = name;
values[i][2] = adress;
values[i][3] = catagory;
i++;
}
return values;
} catch (SQLException e) {
e.printStackTrace();
return values;
}
}
When the value of the String values is nothing I get The error. But if I give the String allready a value it says nothing .
public String[][] getData() {
String values[][] = {{"","","",""},
{"","","",""},
{"","","",""},};
try {
rs = st.executeQuery("SELECT * FROM adresses");
int i = 0;
while(rs.next()) {
String id = rs.getString("id");
String name = rs.getString("name");
String adress = rs.getString("email_adress");
String catagory = rs.getString("catarogy");
values[i][0] = id;
values[i][1] = name;
values[i][2] = adress;
values[i][3] = catagory;
i++;
}
return values;
} catch (SQLException e) {
e.printStackTrace();
return values;
}
}
I want more data than that in my data String. how can I let it automatically do that??
Tnx.
PS.
The function is called in my class FrameGUI and has to change to Object
public class FrameGUI extends JFrame {
public JTable dataHolder;
Mysql mysql = new Mysql();
public String[] columnNames = {
"ID", "Name", "Adress", "Catagory"
};
-> public Object[][] data = mysql.getData();
public FrameGUI() {
init();
mysql.getData();
}
}
You do not initialize String values[][] so it is null. You either need to initialize it first or use a more appropriate datastructure like a List.
You should define a class and use a List (e.g. the ArrayList) instead.
e.g. if you want to call it User -
public class User {
private String id;
private String name;
//...
}
and a list
List<User> users = new ArrayList<User>();
and then instantiate the User class for each row and add the new instance to the list -
User currUser = new User();
users.add(currUser);
//set values from result set
The list can grow automatically when needed and the code is much more readable than using the array.
You get an index out of bounds in the first example because a String[][] (or String Matrix) gets initialized as a zero-length array.
In the second instance, you initialized the array to a size of 3x4 - that works so long as you only get 3 results back.
What you really need is a data structure with a dynamic size. Arrays aren't automatically sized dynamically. Try using a collection implementation like ArrayList or LinkedList or Vector.
Also, instead of saving your values to a String[], try creating a bean class that can hold your result. Create a new instance of it for each result that you get back instead of initializing a new array.
Because you didn't initialized your array, that is why you get NPE. Actually I suggest you to use List for your purposes:
public ArrayList<ArrayList<String>> getData() {
ArrayList<ArrayList<String>> values = new ArrayList<>();
try {
rs = st.executeQuery("SELECT * FROM adresses");
while(rs.next()) {
String id = rs.getString("id");
String name = rs.getString("name");
String adress = rs.getString("email_adress");
String catagory = rs.getString("catarogy");
ArrayList<String> list = new ArrayList<>();
list.add(id);
list.add(name);
list.add(adress);
list.add(catagory);
values.add(list);
}
return values;
} catch (SQLException e) {
e.printStackTrace();
return null;
}
}
The main problem in you code is you are using arrays to save variable number of data. Arrays is fixed sized after they are created so you can't add (or remove) elements to them dynamically.
Instead of using arrays you should use an ArrayList object which have methods to add more elements. Also instead of creating a multidimensional array it looks like a better idea to create a class for the data you get from you database.
So lets first create a Address class:
public class Address {
public String id, name, adress, catagory;
public Address(String id, String name, String adress, String catagory) {
this.id = id;
this.name = name;
this.adress = adress;
this.catagory = catagory;
}
}
Now you can write you code as:
public List<Address> getData() {
List<Address> values = new ArrayList<Address>();
try {
rs = st.executeQuery("SELECT * FROM adresses");
int i = 0;
while(rs.next()) {
String id = rs.getString("id");
String name = rs.getString("name");
String adress = rs.getString("email_adress");
String catagory = rs.getString("catarogy");
values.add(new Address(id, name, adress, catagory));
}
return values;
} catch (SQLException e) {
e.printStackTrace();
return values;
}
}
The returned list will contain a list of Address objects which have the values from you database. Also, the size of the list is always the same as the content you put into it.