Writing to a PostgreSQL database with a Java transaction - java

I am new to PostgreSQL and Java and seem to be unable to write to the database with the following transaction. The transaction try block was removed in efforts to test all cases.
'''
public void transaction_return(int cid, String movie_id) throws Exception {
begin_transaction();
int has_movie = helper_who_has_this_movie(movie_id);
(new BufferedReader(new InputStreamReader(System.in))).readLine();
return_mov_stmt.clearParameters();
return_mov_stmt.setInt(1,cid);
return_mov_stmt.setString(2,movie_id);
return_mov_stmt.executeUpdate();
System.out.println("has_movie" + has_movie);
if (has_movie != cid) {
rollback_transaction();
System.out.println("You are not currently renting this movie.");
} else {
commit_transaction();
}
}
'''
I am wondering if this is doing anything to the database.
The following is a seemingly identical transaction (in terms of syntax) that writes to the database. I cannot discern any difference.
'''
public void transaction_choose_plan(int cid, int pid) throws Exception {
try {
begin_transaction();
update_plan_stmt.clearParameters();
update_plan_stmt.setInt(1,pid);
update_plan_stmt.setInt(2,cid);
update_plan_stmt.executeUpdate();
// rollback while > max_rent
int mov_2_rent = mov_2_rent_remaining(cid);
if (mov_2_rent < 0) {
rollback_transaction();
System.out
.println("You can switch to this plan after returning some movies.");
} else {
commit_transaction();
}
} catch (SQLException e) {
try {
rollback_transaction();
} catch (SQLException se) {
}
}
}
'''
The PreparedStatements are as follows:
'''
private String update_plan_sql = "UPDATE mem_cust SET pid = ? WHERE cid = ?";
private PreparedStatement update_plan_stmt;
private String return_mov_sql = "UPDATE his_rent SET status_rent = 'closed' WHERE cid = ? AND movie_id = ?";
private PreparedStatement return_mov_stmt;
'''

Related

Is the MySQL procedure in this Minecraft plugin correct?

To get an idea of what the basic structure looks like, I downloaded a money system including MySQL from Spigot and looked at the code.
public static boolean playerExists(String uuid) {
try {
ResultSet rs = Simplecoinsystem.mysql.query("SELECT * FROM CoinData WHERE UUID= '" + uuid + "'");
if (rs.next())
return (rs.getString("UUID") != null);
return false;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
public static void createPlayer(String uuid) {
if (!playerExists(uuid))
Simplecoinsystem.mysql.update("INSERT INTO CoinData (UUID, COINS) VALUES ('" + uuid +
"', '" + Simplecoinsystem.getInstance().getConfig().getInt("startcoins") + "');");
}
public static Integer getCoins(String uuid) {
Integer i = Integer.valueOf(0);
if (playerExists(uuid)) {
try {
ResultSet rs = Simplecoinsystem.mysql.query("SELECT * FROM CoinData WHERE UUID= '" + uuid + "'");
if (rs.next())
Integer.valueOf(rs.getInt("COINS"));
i = Integer.valueOf(rs.getInt("COINS"));
} catch (SQLException e) {
e.printStackTrace();
}
} else {
createPlayer(uuid);
}
return i;
}
public static void setCoins(String uuid, Integer coins) {
if (playerExists(uuid)) {
Simplecoinsystem.mysql.update("UPDATE CoinData SET COINS= '" + coins + "' WHERE UUID= '" + uuid + "';");
} else {
createPlayer(uuid);
}
}
Am I correct that it is actually impractical to create a new entry with the uuid of the non-existent player after each query of the coins if the player does not exist?
Wouldn't this make it possible to flood the database with thousands of unnecessary entries by issuing, for example, a "/money (player)" command as an evil player/admin?
Couldn't I just ask when entering the server if the uuid is already stored and if not, just enter it? This way there would only be entries from players who have already been on the server before. Whether this needs great server performance, I'm not sure.
This is my first own MySQL class.
public class MySQL {
private String host, database, user, password;
private int port;
private Connection con;
public MySQL(String host, int port, String database, String user, String password) {
this.host = host;
this.port = port;
this.database = database;
this.user = user;
this.password = password;
connect();
}
public void connect() {
try {
con = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database + "?autoReconnect=true", user, password);
System.out.println("&cDie MySQL Verbindung wurde erfolgreich aufgebaut!");
} catch (SQLException e) {
e.printStackTrace();
}
}
public void disconnect() {
try {
if(this.con != null) {
this.con.close();
System.out.println("§cDie MySQL Verbindung wurde erfolgreich beendet!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void update(String query) {
try {
Statement st = con.createStatement();
st.executeUpdate(query);
st.close();
} catch (Exception e) {
e.printStackTrace();
connect();
}
}
public ResultSet qry(String query) {
ResultSet rs = null;
try {
Statement st = con.createStatement();
rs = st.executeQuery(query);
} catch (Exception e) {
e.printStackTrace();
connect();
}
return rs;
}
public Connection getConnection() {
return this.con;
}
}
Except for this part, both MySQL classes are built relatively the same.
This is the part that is in the MySQL class of the Spigot plugin.
Your code have multiple issues.
When the connection will be closed, next time you will have an error. In your Mysql class, I suggest you to do:
public Connection getConnection() {
if(con == null || con.isClosed())
connect();
return con;
}
Then, use it in all method like getConnection().prepareStatement().
You can be attacked with SQL Injection. To fix this, try to do something like:
PreparedStatement st = con.prepareStatement("SELECT * FROM CoinData WHERE UUID = ?");
st.setString(1, uuid.toString()); // Yes it start at 1 !!
st.executeUpdate();
With this, even with all values, you can't be attacked with injections.
You will have an error while getting coins:
if (rs.next()) // go to good line
Integer.valueOf(rs.getInt("COINS")); // useless convertion
i = Integer.valueOf(rs.getInt("COINS")); // error if no line.
You can just do:
if(rs.next())
i = rs.getInt("COINS");
If the column "UUID" is unique, you will not have duplicated lines.
Finally, about performance, it's better to do it one time: at login, instead of all time. You can also create an object stored in an hashmap to easier access to it, without using SQL, like that:
public static HashMap<UUID, Integer> coinsByPlayer = new HashMap<>();
OR:
public static HashMap<UUID, MyObject> coinsByPlayer = new HashMap<>();
public class MyObject {
private int coins = 0;
public MyObject(UUID uuid) {
// make SQL request to get data
}
public int getCoins() {
return coins;
}
public void setCoins(int next){
coins = next;
// here make "UPDATE" sql query
}
}
What do you say? Is it ok with the try/catch function? #Elikill58
public Connection getConnection() {
try {
if(con == null || con.isClosed()) {
connect();
}
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
edit:
public Connection getConnection_one() throws SQLException {
if(con == null || con.isClosed()) {
connect();
return con;
} else {
return con;
}
}

Discord bot unusually repeats

So I'm trying to create a discord bot that has simple access to a database for printing out values, my code currently will print the values to the discord server but it repeats them 5 times.
Bot functionality class:
private MySQLAccess sql = new MySQLAccess();
public static void main(String[] args) throws Exception {
JDABuilder ark = new JDABuilder(AccountType.BOT);
ark.setToken("insert_discord_token_here");
ark.addEventListener(new MessageListener());
ark.buildAsync();
}
#Override
public void onMessageReceived(MessageReceivedEvent e) {
if (e.getAuthor().isBot()) return;
Message msg = e.getMessage();
String str = msg.getContentRaw();
//Ping pong
if (str.equalsIgnoreCase("!ping")) {
e.getChannel().sendMessage("Pong!").queue();
}
//Bal check
if (str.contains("!bal")) {
String user = str.substring(5);
System.out.println(user);
try {
sql.readDataBase(e.getChannel(), user);
} catch (Exception e1) {
}
}
}
Database Access Class:
private Connection connect = null;
private Statement statement = null;
private ResultSet resultSet = null;
private final String user = "pass";
private final String pass = "user";
public void readDataBase(MessageChannel msg, String username) throws Exception {
//Retrieve data and search for username
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connect = DriverManager.getConnection("jdbc:mysql://localhost/serverusers?allowPublicKeyRetrieval=true&useSSL=false", user, pass);
statement = connect.createStatement();
resultSet = statement
.executeQuery("select * from serverusers.userinfo where user=\"" + username + "\"");
writeResultSet(resultSet, msg);
} catch (Exception e) {
throw e;
} finally {
close();
}
}
private void writeResultSet(ResultSet resultSet, MessageChannel msg) throws SQLException {
// Check resultSet and print its contents
if (resultSet.next()) {
String user = resultSet.getString(2);
Double website = resultSet.getDouble(3);
msg.sendMessage("User: " + user).queue();
msg.sendMessage("Bank Amount: " + website).queue();
}
}
private void close() {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (resultSet != null) {
resultSet.close();
}
if (connect != null) {
connect.close();
}
} catch (Exception e) {
}
}
When the program is run it finds the correct data that I'm looking for and the search function is fine, but for some odd reason the program will spit the same username and balance out 5 times.
Screenshot of Discord Bot
The common mistake here is that you run the program multiple times, each instance then responds accordingly with the same thing. You can check if that is the case by opening the task manager and looking for java processes. This often occurs with developers using the Eclipse IDE because of the console hiding other processes behind a drop-down menu on the console.

Lock wait timeout exceeded- insert using a singleton db manager

In my java web app, I'm using a set of web services to query a db and jdbc.
When using a getter web service (select) it works fine, but when I use a post (insert), I am getting this error:
Lock wait timeout exceeded; try restarting transaction
For managing the db I am using this class
...
public final class MysqlConnect {
public Connection conn;
private Statement statement;
public static MysqlConnect db;
private MysqlConnect() {
...
try {
Class.forName(driver).newInstance();
this.conn = (Connection)DriverManager.getConnection(url+dbName,userName,password);
}
catch (Exception sqle) {
sqle.printStackTrace();
}
}
public static synchronized MysqlConnect getDbCon() {
if ( db == null ) {
db = new MysqlConnect();
}
return db;
}
public ResultSet query(String query) throws SQLException{
statement = db.conn.createStatement();
ResultSet res = statement.executeQuery(query);
ResultSetMetaData rsmd = res.getMetaData();
int columnsNumber = rsmd.getColumnCount();
while(res.next()) {
for (int i = 1; i <= columnsNumber; i++) {
if (i > 1) System.out.print(", ");
String columnValue = res.getString(i);
System.out.print(columnValue + " ");
}
System.out.println("");
}
return res;
}
...
public int insert(String insertQuery) throws SQLException {
statement = db.conn.createStatement();
int result = statement.executeUpdate(insertQuery);
return result;
}
}
and here is my WS
#POST
#Path("/postMembership")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON+ ";charset=utf-8")
public Response postMembership(String MembershipRequest) throws JsonParseException, JsonMappingException, IOException, NamingException{
try {
MysqlConnect.getDbCon().insert("INSERT INTO redmine.members (id, user_id, project_id, mail_notification) VALUES (301, 99, 99, 0)");
} catch(Exception ex) {
System.out.println("Exception getMessage: " + ex.getMessage());
}
return Response.status(200).entity("postMembership is called").build();
}
I am using this DB locally so I am the only one in using it,
the same transaction works with mysqlworkbench.
How to get rid of it?
Here are some suggestions:
‘Lock wait timeout’ occurs typically when a transaction is waiting on row(s) of data to update which is already been locked by some other transaction.
Most of the times, the problem lies on the database side. The possible causes may be a inappropriate table design, large amount of data, constraints etc.
Please check out this elaborate answer .

Allowing column name to be specified makes it a potential SQL injection risk

I have these two methods where I was told that "the fact you allow the column name to be specified is (an SQL) injection risk". What does even mean? To be specified by whom? And how can I fix it?
public void tableChanged(TableModelEvent e) {
int row = e.getFirstRow();
int col = e.getColumn();
model = (MyTableModel) e.getSource();
String stulpPav = model.getColumnName(col);
Object data = model.getValueAt(row, col);
Object studId = model.getValueAt(row, 0);
System.out.println("tableChanded works");
try {
new ImportData(stulpPav, data, studId);
bottomLabel.setText(textForLabel());
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
public class ImportData {
public ImportData(String a, Object b, Object c)
throws ClassNotFoundException, SQLException {
PreparedStatement prepStmt = null;
try {
connection = TableWithBottomLine.getConnection();
String stulpPav = a;
String duom = b.toString();
String studId = c.toString();
System.out.println(duom);
String updateString = "update finance.fin " + "set ? = ? " + "where ID = ? "+ ";";
prepStmt = connection.prepareStatement(updateString);
prepStmt.setString(1, stulpPav);
prepStmt.setString(2, duom);
prepStmt.setString(3, studId);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (prepStmt != null)
prepStmt.close();
System.out.println("Data was imported to database");
}
}
}
What does even mean? :)
It means, that if the String was changed, you could put in SQL code to do something different, like updating a password, or garnting access to the systems.
To be specified by whom?
Any code which can access the column name, this is only a problem if the user has access to this field.
And how can I fix it?
Check that there really is no way for the user to specify this column name, and ignore the message

How to retrieve sequences metadata from JDBC?

I am trying to retrieve different kind of metadata of my Oracle DB from Java code (using basic JDBC). For example, if I want to retrieve the list of tables with _FOO suffix, I can do something like:
Connection connection = dataSource.getConnection();
DatabaseMetaData meta = connection.getMetaData();
ResultSet tables = meta.getTables(connection.getCatalog(), null, "%_FOO", new String[] { "TABLE" });
// Iterate on the ResultSet to get information on tables...
Now, I want to retrieve all the sequences from my database (for example all sequence named S_xxx_FOO).
How would I do that, as I don't see anything in DatabaseMetaData related to sequences?
Do I have to run a query like select * from user_sequences ?
Had the same question. It's fairly easy. Just pass in "SEQUENCE" into the getMetaData().getTables() types param.
In your specific case it would be something like:
meta.getTables(connection.getCatalog(), null, "%_FOO", new String[] { "SEQUENCE" });
You can't do this through the JDBC API, because some databases (still) do not support sequences.
The only way to get them is to query the system catalog of your DBMS (I guess it's Oracle in your case as you mention user_sequences)
You can use the hibernate dialect api for retrieving sequence Name. see : http://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/dialect/Dialect.html
From below example, you can see how to use dialect to get sequence names
public static void main(String[] args) {
Connection jdbcConnection = null;
try {
jdbcConnection = DriverManager.getConnection("", "", "");
printAllSequenceName(jdbcConnection);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(jdbcConnection != null) {
try {
jdbcConnection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public static void printAllSequenceName(Connection conn) throws JDBCConnectionException, SQLException {
DialectResolver dialectResolver = new StandardDialectResolver();
Dialect dialect = dialectResolver.resolveDialect(conn.getMetaData());
if ( dialect.supportsSequences() ) {
String sql = dialect.getQuerySequencesString();
if (sql!=null) {
Statement statement = null;
ResultSet rs = null;
try {
statement = conn.createStatement();
rs = statement.executeQuery(sql);
while ( rs.next() ) {
System.out.println("Sequence Name : " + rs.getString(1));
}
}
finally {
if (rs!=null) rs.close();
if (statement!=null) statement.close();
}
}
}
}
If you don't desire to use hibernate, then you have to crate custom sequential specific implementation.
Sample code for custom implementation
interface SequenceQueryGenerator {
String getSelectSequenceNextValString(String sequenceName);
String getCreateSequenceString(String sequenceName, int initialValue, int incrementSize);
String getDropSequenceStrings(String sequenceName);
String getQuerySequencesString();
}
class OracleSequenceQueryGenerator implements SequenceQueryGenerator {
#Override
public String getSelectSequenceNextValString(String sequenceName) {
return "select " + getSelectSequenceNextValString( sequenceName ) + " from dual";
}
#Override
public String getCreateSequenceString(String sequenceName,
int initialValue, int incrementSize) {
return "create sequence " + sequenceName + " start with " + initialValue + " increment by " + incrementSize;
}
#Override
public String getDropSequenceStrings(String sequenceName) {
return "drop sequence " + sequenceName;
}
#Override
public String getQuerySequencesString() {
return "select sequence_name from user_sequences";
}
}
class PostgresSequenceQueryGenerator implements SequenceQueryGenerator {
#Override
public String getSelectSequenceNextValString(String sequenceName) {
return "select " + getSelectSequenceNextValString( sequenceName );
}
#Override
public String getCreateSequenceString(String sequenceName,
int initialValue, int incrementSize) {
return "create sequence " + sequenceName + " start " + initialValue + " increment " + incrementSize;
}
#Override
public String getDropSequenceStrings(String sequenceName) {
return "drop sequence " + sequenceName;
}
#Override
public String getQuerySequencesString() {
return "select relname from pg_class where relkind='S'";
}
}
public void printSequenceName (SequenceQueryGenerator queryGenerator, Connection conn) throws SQLException {
String sql = queryGenerator.getQuerySequencesString();
if (sql!=null) {
Statement statement = null;
ResultSet rs = null;
try {
statement = conn.createStatement();
rs = statement.executeQuery(sql);
while ( rs.next() ) {
System.out.println("Sequence Name : " + rs.getString(1));
}
}
finally {
if (rs!=null) rs.close();
if (statement!=null) statement.close();
}
}
}
public static void main(String[] args) {
Connection jdbcConnection = null;
try {
jdbcConnection = DriverManager.getConnection("", "", "");
printAllSequenceName(new OracleSequenceQueryGenerator(), jdbcConnection);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(jdbcConnection != null) {
try {
jdbcConnection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Given that recent versions of the Oracle JDBC drivers (e.g. 12.1.0.2) don't return sequence information when you call DatabaseMetaData#getTables with types set to ["SEQUENCE"], your best bet is to run the necessary query yourself, e.g.:
SELECT o.owner AS sequence_owner,
o.object_name AS sequence_name
FROM all_objects o
WHERE o.owner LIKE 'someOwnerPattern' ESCAPE '/'
AND o.object_name LIKE 'someNamePattern' ESCAPE '/'
AND o.object_type = 'SEQUENCE'
ORDER BY 1, 2
... where someOwnerPattern and someNamePattern are SQL patterns like the ones you'd use with the LIKE operator (e.g. % matches anything).
This is basically the same as the query run by the driver itself, except that it queries for objects of type SEQUENCE.

Categories