Skip to content

Conversation

jacsimas
Copy link
Owner

@jacsimas jacsimas commented Jun 13, 2025

Summary

Only the following commits matter:

  • Transaction Audit feature added
    It collects each transfer information (sender and receiver id, nickname, amount sent, date and time) that has been done by any of the customers, is inserted into a database table.

and
Finished working on friendships and friends transfers feature
Three separate postgreSQL tables created, one for friendships between customers, second for transfers audit only between customers who are friends.
One extra table that initially supposed to be only for friends, but now is for everyone - transfers totals table, it covers everyone's money totals sent and received separately to and from the different customer, by creating two rows for each transaction. for example:

sender_id | receiver_id | sent_cents | received_cents
-------------+-----------+------------+----------------
8 | 2 | 4000 | 0
2 | 8 | 0 | 4000

Testing
In order to test it there has to be a PostgreSQL database connected and 5 tables made.

@jacsimas jacsimas requested a review from ganiulis June 13, 2025 14:17
Copy link
Collaborator

@ganiulis ganiulis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lacks description of feature.

@jacsimas jacsimas marked this pull request as draft June 16, 2025 12:34
@jacsimas jacsimas closed this Jun 16, 2025
@jacsimas jacsimas reopened this Jun 16, 2025
@jacsimas jacsimas added invalid This doesn't seem right enhancement New feature or request and removed enhancement New feature or request invalid This doesn't seem right labels Jun 16, 2025
@jacsimas jacsimas requested a review from ganiulis June 16, 2025 13:53
ps.setString(5, getterName);

int rows = ps.executeUpdate(); // returns 1
System.out.println("Rows inserted: " + rows);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename the variable to rowsInserted just so it is a little clearer. rows implies an entire row data structure with columns. rowsInserted implies just a number.

Suggested change
System.out.println("Rows inserted: " + rows);
System.out.println("Rows inserted: " + rowsInserted);

String time = null;

String sql = "SELECT id, sender_id, sender_nickname, amount_sent, receiver_id, receiver_nickname FROM transactions_audit WHERE id = ?";
// + customerId + " (id, nickname, money, password) " + "VALUES (?, ?, ?)";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid these kinds of comments. Generally speaking comments are a hot topic and I've seen both a lot of good and bad comments. The best kind of comments are no comments where the code is self-documenting. But if you believe you really need to add a comment read https://alexkondov.com/comments-the-good-the-bad-the-ugly and make your own conclusion. There is no hard rule here.

Comment on lines +163 to +167
try (
PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setLong(1, id);

try (ResultSet rs = ps.executeQuery()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't nest try blocks. Let one trycatch exist and handle the cases there.

Comment on lines +179 to +181
} catch (SQLException e) {
System.out.println(e.getMessage());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful for debugging, but I would advise not put these when writing production-ready code. You don't want your end-users see the bug message.

receiver_id = rs.getInt("receiver_id");
receiver_nickname = rs.getString("receiver_nickname");
time = rs.getString("time");
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can return this data immediately here. This would remove the need to instantiate these variables at the beginning of this function:

Suggested change
}
return new TransactionAudit(id, sender_id, sender_nickname, amount_sent, receiver_id, receiver_nickname, time);
}

And you then can just throw an error at the end if something went wrong and there is nothing to return.

Comment on lines +189 to +190
ps.setLong(1, senderId); // customer_id
ps.setLong(2, receiverId); // friend_id
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either remove the comments or make the variables more meaningful:

Suggested change
ps.setLong(1, senderId); // customer_id
ps.setLong(2, receiverId); // friend_id
ps.setLong(1, customerId);
ps.setLong(2, friendId);

Comment on lines +217 to +220
addTx.setLong(1, senderId);
addTx.setLong(2, receiverId);
addTx.setLong(3, amount);
addTx.executeUpdate();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fluent setter here if possible.

Comment on lines +223 to +234
public void insertIntoTransferTotals1(int senderId, int receiverId, int amount) throws SQLException {
PreparedStatement upsert1 = connection.prepareStatement(
"""
INSERT INTO transfer_totals (customer_id, friend_id, sent_cents)
VALUES (?, ?, ?)
ON CONFLICT (customer_id, friend_id)
DO UPDATE SET sent_cents = transfer_totals.sent_cents + EXCLUDED.sent_cents
""");
upsert1.setLong(1, senderId);
upsert1.setLong(2, receiverId);
upsert1.setLong(3, amount);
upsert1.executeUpdate();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird variable naming. Why the 1 here?

Comment on lines +237 to +248
public void insertIntoTransferTotals2(int senderId, int receiverId, int amount) throws SQLException {
PreparedStatement upsert2 = connection.prepareStatement(
"""
INSERT INTO transfer_totals (customer_id, friend_id, received_cents)
VALUES (?, ?, ?)
ON CONFLICT (customer_id, friend_id)
DO UPDATE SET received_cents = transfer_totals.received_cents + EXCLUDED.received_cents
""");
upsert2.setLong(1, receiverId);
upsert2.setLong(2, senderId);
upsert2.setLong(3, amount);
upsert2.executeUpdate();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird variable naming. Would prefer to change it to something simpler. Why the 2?

Comment on lines +251 to +267
public void createTable() throws SQLException {
String sql = """
CREATE TABLE IF NOT EXISTS test (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
friend_name TEXT NOT NULL,
friend_status TEXT NOT NULL,
total_money_sent BIGINT NOT NULL,
total_money_received BIGINT NOT NULL,
since_when TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
""";

Statement st = connection.createStatement();
st.executeUpdate(sql);

}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is OK for this project, but this is also a good pivot for learning about database migrations.

See https://medium.com/@AlexanderObregon/best-practices-for-database-migrations-in-java-for-beginners-cbe42e50cc76.

You don't need to add it to this project, but you should learn about it once you got time. Same concept as here, only with ordered migrations + a reliable package does table schema changes for you.

Comment on lines +16 to +22
// DBController dbcontrol = new DBController();
//
// ThreadTest R1 = new ThreadTest( "Thread-1");
// R1.start();
//
// ThreadTest R2 = new ThreadTest( "Thread-2");
// R2.start();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be careful with comments like these. This is dead code which may stay dead forever. Either commit it uncommented or remove it, or leave a TODO at the top regarding what to do with it.

Comment on lines 6 to +10
private String user;
private int moneyInCents;
private String password;
private String realName;
private String email;
private String realName;
private String email;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a perfect class to replace with a record. See https://www.baeldung.com/java-record-keyword.

You set the data once when it instantiates, and it becomes permanently immutable.

private String realName;
private String email;

public Customer(int i, String ss, int i1, String password) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should consider changing these parameters to be identical to their field names.

Comment on lines +1 to +6
package org.example.Other;

public class DataAutomation {


}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be careful with leaving potentially unused classes.

Comment on lines +32 to +33
int senderreturnedId = dbcontroller.findCustomerByUsername(nameSenderCustomer);
int receiverreturnedId = dbcontroller.findCustomerByUsername(nameReceiverCustomer);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These variables should be camelCase

Comment on lines +35 to +36
boolean sendmoney = sendMoneyToSomeone(senderreturnedId, amountToSend);
if (sendmoney){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's a bool I assume it checks whether the transaction was successful... so I suggest changing the variable to reflect that:

Suggested change
boolean sendmoney = sendMoneyToSomeone(senderreturnedId, amountToSend);
if (sendmoney){
boolean isTransactionSuccessful = sendMoneyToSomeone(senderreturnedId, amountToSend);
if (isTransactionSuccessful){

As it is the variable sendmoney is hard to understand just by reading it in isolation.

}
}

// what if funds insufficient? add error handlers
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small trick you may not know:

Suggested change
// what if funds insufficient? add error handlers
// TODO: what if funds insufficient? add error handlers

A TODO: as a comment prefix alerts your IDE to treat it as a TODO comment, which this one is. It is perfectly fine to leave these in unfinished parts of code. Don't overdo it, of course, but they are fine to use anyway.

return true;
}
return false;
} // add isMoneyReceived method, splitting responsibilities and securing the transfers
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} // add isMoneyReceived method, splitting responsibilities and securing the transfers
} // TODO: add isMoneyReceived method, splitting responsibilities and securing the transfers

Comment on lines +70 to +71
dbcontroller.insertIntoTransferTotals1(senderreturnedId, receiverreturnedId, amountToSend);
dbcontroller.insertIntoTransferTotals2(senderreturnedId, receiverreturnedId, amountToSend);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hard to understand what the difference between insertIntoTransferTotals1 and insertIntoTransferTotals2 is. I suggest renaming the functions to reflect their differences better. Generally speaking cases like these should not be used.


public class CustomerServices {

DBController dbcontroller = new DBController();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A service should not use a controller. A controller should use a service.

This is the MVC pattern. See https://www.tutorialspoint.com/design_pattern/mvc_pattern.htm.

Services aren't part of MVC, but they are used to encapsulate business logic. A controller calls different services to do things. A service does not know anything about controllers.

In your case since you don't have a UI you won't have a View class anywhere, but that is fine, as you still follow MVC just with your views essentially being the terminal.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are they called controllers anyway? They "control" the flow of code. They call each class one after the other, controlling the flow of data. It doesn't make sense that a handler or service would "control" when a controller is called in this case.

@@ -0,0 +1,34 @@
package org.example.Services;

public class ThreadTest implements Runnable {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this class. Is this a test? Or is it a class that you used while sandboxing multithreading?

import java.sql.SQLException;

public class TransferHandler {
// CAN BE AN OBJECT RECEIVED FROM THE FRONTEND
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, this class isn't used anywhere anyway so all good

public class TransferHandler {
// CAN BE AN OBJECT RECEIVED FROM THE FRONTEND

DBController dbcontroller;
Copy link
Collaborator

@ganiulis ganiulis Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A controller should not be instantiated inside a handler. A handler should be instantiated inside a controller.

BTW if you're interested in naming things read this article written by a fellow Lithuanian: https://nikas.praninskas.com/javascript/2016/11/28/naming-things-handlers. It concerns itself with the overused naming of classes as etcHandler and etcHandler.handleSomething(...) in the industry. It's more as food for thought rather than an actionable request.

Comment on lines +14 to +16
String nameSenderCustomer = null;
String nameReceiverCustomer = null;
int amountToSend = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're mixing data transfer objects with service classes.

See https://martinfowler.com/bliki/AnemicDomainModel.html. This one may be a bit hard to understand at first. See https://dev.to/wallacefreitas/understanding-god-objects-in-object-oriented-programming-5636 to get an example of what a God Object anti-pattern looks like.

Comment on lines +73 to +76
int senderId = 5;
int receiverId = 3;

dbcontroller.insertIntoFriendships(senderId, receiverId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not asserting anything here.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used it as frontend to insert info and test if data got updated in the database. Seemed simple at that time

Comment on lines +81 to +83
boolean isfriend = dbcontroller.checkIfHasFriend(7, 5);
// fix this, now it is only one way check
assertThat(isfriend).isTrue();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is fine. You can add a second test to check if it's false, but generally speaking in terms of code coverage it should cover both cases already.

// UserModel expectedUser = new UserModel(55, "bb", 100);

@Test
public void returnAuditRecordTest() throws SQLException {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're not asserting anything here.

Copy link
Collaborator

@ganiulis ganiulis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job. But there's some things that should be changed.

Also, target directory has your compiled binaries which shouldn't be committed. Generally speaking anything that's binary or compiled (in some languages also called "minified") shouldn't be version-controlled as these files may contain sensitive environmental-dependent details as well as variables such as secret keys and passwords. They also take up a lot of space and can crash GitHub when viewing them from the browser, whereas actual .java files do not.

I suggest you learn to remove it and put target into .gitignore so git knows to ignore whenever you compile them. See https://docs.github.com/en/get-started/git-basics/ignoring-files and https://www.w3schools.com/git/git_ignore.asp on how to do that.

jacsimas and others added 2 commits June 20, 2025 12:41
Co-authored-by: Ganiulis <91011496+ganiulis@users.noreply.github.com>
Co-authored-by: Ganiulis <91011496+ganiulis@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants