I'm trying to develop an statistics / achievement system in my minecraft server.
I've done some research, but still couldn't make decision well, so decided to post my very first question in stack overflow.
There are multiple types of achievements, such as block broken, crop harvested, animal killed.. and so on. Table initializer looks like this. ( I intentionally set those values as double )
public static void init() {
String query = "CREATE TABLE IF NOT EXISTS statistic ("
+ " uuid VARCHAR(255) PRIMARY KEY,"
+ " block_break double, crop_break double, ore_break double,"
+ " wood_break double, animal_kill double, monster_kill double, boss_kill double,"
+ " fish_natural double, fish_auto double "
+ ")";
try {
Connection conn = HikariPoolManager.getInstance().getConnection();
PreparedStatement ps = conn.prepareStatement(query);
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
}
and I save it back with this
public static void save(String uuidString, StatisticsType stat, double val) {
String query= "INSERT INTO statistics (uuid, {stat}) "
+" VALUE (?,?) ON DUPLICATE KEY UPDATE "
+" uuid=VALUES( uuid ), {stat}=VALUES( {stat} )"
.replace("{stat}", stat.name());
try (Connection conn = HikariPoolManager.getInstance().getConnection();
PreparedStatement ps = conn.prepareStatement(query)
){
ps.setString(1, uuidString);
ps.setDouble(2, val);
ps.execute();
} catch (SQLException e) {
e.printStackTrace();
}
}
PlayerCache.java
public class PlayerCache {
#Getter
private static final Map<UUID, PlayerCache> cacheMap = new HashMap<>();
private final UUID uuid;
#Getter
private HashMap<StatisticsType, Double> statistics;
#Getter
private HashSet<StatisticsType> changed;
public PlayerCache(UUID uuid) {
this.uuid= uuid;
}
public void init(HashMap<StatisticsType,Double> achievements) {
this.statistics = new HashMap<>();
this.changed = new HashSet<>();
this.statistics.putAll(achievements);
}
public void addValue(StatisticsType type, double addition) {
statistics.put(type, statistics.get(type) + addition);
changed.add(type);
}
public double getStatistic(StatisticsType type) {
return statistics.getOrDefault(type, 0.0D);
}
public void save() {
for (StatisticsType statisticsType : changed) {
StatisticDAO.save(uuid.toString(),statisticsType, getStatistic(statisticsType));
}
changed.clear();
}
public static PlayerCache get(final UUID uuid) {
PlayerCache playerCache = cacheMap.get(uuid);
if (playerCache == null) {
playerCache = new PlayerCache(uuid);
cacheMap.put(uuid, playerCache);
}
return playerCache;
}
}
I have question on general design of programming , not fixing code itself.
For now, this is how things go.
For simplicity, let me pick two actions of statistics - break stone , and kill monster.
player joins game, and read data , make a cache of player and put statistics information into the cache.
player breaks stone, and it increments statistics in player cache.
If player broke stone, it toggles a boolean flag to show he has broken stone, thus this information need to be flushed to database at some point.
Server loops all players , and check if player has done anything. If player has done something, it calls sql save method, and toggle boolean flag back.
However, there are few problems I encountered this time.
player can break stone, and kill monster in duration of writing to the database. or even more different actions. That will result multiple save functions to be called from a player. Is there better way to deal with this?
Is my general apparoach to read and write data correct? I'm pretty much using same way to do database stuff to other functionalities, but unsure if this is the good way.
There are very few situations where this is a single correct way to do anything; but you did mention a Data Access Object. DAO's follow a pattern, but that pattern is a general one. Depending on your needs the actual objects might contain more (or less) data, or be structured in to back to one (or many tables).
the DAO pattern is a class that performs all direct operations on the database. Minimally it includes add(Object), remove(Object), get(id), and maybe getAll() but it can also include getOldest(), remove(id), and so on.
What it should generally NOT do is expose the underlying table structure directly. So in a sense, your approach (by exposing UUID and the stat to be updated independently) is not following the pattern.
public class PlayerStats {
// contains a UUID field, as well as other stat fields
}
public class PlayerStatsDAO {
public PlayerStatsDAO(DatabaseConnection connection) {
// store the connection and check the connection
}
public void update(PlayerStats value) {
}
public void add(PlayerStats value) {
}
public void addOrUpdate(PlayerStats value) {
}
public PlayerStats newEmptyStats() {
}
public void remove(PlayerStats value) {
}
// as well as searching methods
public PlayerStats statsForUUID(UUID uuid) {
}
public PlayerStats statsForPlayerName(String name) {
}
public PlayerStats mostBockBreaks() {
}
... etc ...
}
The advantage of a DAO is that if you later decide to change the underlying table (or set of joined tables), you have one location to bind the existing "Data Object" to the new table structures.
That will result multiple save functions to be called from a player. Is there better way to deal with this?
I think you are exacerbating the severity of running multiple SQL insert statements for a player. On a Minecraft server, there won't be very much load on the database at all and the fact you are using Hikari ensures that the performance impact of having these extra few queries is negligible.
However, if you are very sure that the environment you are working in is incredibly performance sensitive (which for a Minecraft plugin it probably isn't) then consider running batch SQL statements or combining updates for the same player manually into a single statement and sending that to the SQL database.
For me, you should use an object that will keep all informations, and save them only when you wants. For example: StatsPlayer.
You have a static map : HashMap<UUID, StatsPlayer> that contains all instance of players.
Each instance contains all informations like that :
private HashMap<StatisticsType, Double> stats;
Or:
private double blockBreak;
When creating new instance, don't forget to get informations from DB, for example :
public StatsPlayer(Player p) {
try {
Connection conn = HikariPoolManager.getInstance().getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT * FROM statistics WHERE uuid = ?");
ps.setString(1, p.getUniqueId().toString());
ResultSet rs = ps.executeQuery();
if(rs.next()) {
// get informations from ResultSet instance
} else {
// Insert line into database
}
} catch(Exception e) {
e.printStackTrace();
}
}
Now, you have to do a static getter like that :
public static StatsPlayer getPlayer(Player p) {
synchronized(PLAYERS) {
return PLAYERS.computeIfAbsent(p, StatsPlayer::new);
}
}
In your StatsPlayer object, you should add method to update values, and one to save everything :
public void save() {
try {
Connection conn = HikariPoolManager.getInstance().getConnection();
PreparedStatement ps = conn.prepareStatement("UPDATE statistics SET block_break = ? WHERE uuid = ?");
ps.setDouble(1, getBlockBreak());
ps.setString(2, p.getUniqueId().toString());
ps.executeUpdate(); // make the update
} catch(Exception e) {
e.printStackTrace();
}
}
Finally, you should save the object sometimes, for example only when they left server or when server stop
Related
I am building a Java application that uses a database and I'm using a DAO design pattern: in my code, all objects classes have an associated DAO class that implements an interface with get, save and update methods.
For instance, for a User object, I will have the following class (ConnectionDB implements the connection to the database):
public class UserDAO implements Dao<User, String> {
private final static String TABLE_NAME = "users";
private final static UserDAO instance = new UserDAO();
public static UserDAO getInstance() {
return instance;
}
private UserDAO() {
}
#Override
public User get(String username) throws SQLException {
String query = "SELECT * FROM " + TABLE_NAME + " WHERE username = ?";
PreparedStatement stmt = ConnectionDB.getInstance().prepareStatement(query);
stmt.setString(1, username);
ResultSet result = stmt.executeQuery();
if (!result.next())
return null;
User user = new User(
result.getInt("id"),
username,
);
stmt.close();
result.close();
return user;
}
/* same thing for save and update */
}
Here is the Dao interface for reference:
public interface Dao<T, S> {
T get(S id) throws SQLException;
ArrayList<T> getAll() throws SQLException;
void save(T t) throws SQLException;
void update(T t) throws SQLException;
}
This way works pretty fine but as I have more and more classes in my application, and a DAO class for each one of them, I have a lot of repetitive code. For instance, the only difference between the get implementation on different objects is the name and type of the primary key and the call to the constructor.
In order to make the code more generic, I tried to implement a fetchItem method in the ConnectionDB class that would be able to query an item from the database:
public <T> HashMap<String, Object> fetchItem(String table_name, String pk, T id) throws SQLException {
String query = "SELECT * FROM " + table_name + " WHERE " + pk + " = ?";
PreparedStatement stmt = prepareStatement(query);
stmt.setObject(1, id);
ResultSet result = stmt.executeQuery();
if (!result.next())
return null;
HashMap<String, Object> values = buildObject(result);
stmt.close();
result.close();
return values;
}
public HashMap<String, Object> buildObject(ResultSet result) throws SQLException {
ResultSetMetaData metadata = result.getMetaData();
int columnCount = metadata.getColumnCount();
HashMap<String, Object> values = new HashMap<>();
for (int i = 1; i <= columnCount; i++) {
values.put(metadata.getColumnName(i), result.getObject(i));
}
return values;
}
With this implementation, I can now replace my first get method in the UserDAO class by the following simplified code:
public User get(String username) throws SQLException {
HashMap<String, Object> values = ConnectionDB.getInstance()
.fetchItem(TABLE_NAME, "username", username);
if (values == null || values.isEmpty())
return null;
return new User(
id,
(String) values.get("String")
);
}
While this new implementation is simpler and allows the get methods to only do what they're supposed to do (here, create a User object with the right parameters from the DB), I find it a bit dangerous as I'll have to make a lot of casts; as I have a lot of Object variables in my code I'm not sure whether it'll be easy to debug the code if something fails in any of these function calls.
So here's my question: which implementation is better, easier to maintain and safer?
Connection DB is a very bad place to define such implementation. It is just a link with a specific database thats all. You violate single responsibility rule. Better to implement base generic class for all DAO's and place common logic there.
Also if you will use Hibernate framework, you will not need to work with query strings and Object variables casts.
I have a class that uses multiple get methods the returns
public int getCurrNum(String Name) {
// query clearances table to return an int that represents the clearance level
String sql = "SELECT number FROM clearances WHERE '" + Name + "' = name;";
//String.format("SELECT number FROM clearances WHERE '%s' = name;",clearanceName);
return jdbcTemplate.queryForObject(sql, Integer.class);
}
which I understand I can use Mockito with a statement like Mockito.when(jdbcTemplate.queryForObject(Mockito.anyString(), Mockito.eq(Integer.class))).thenReturn(1);
But that is not really testing values that are being passed in because I am just telling it to return a value I want. I want to make sure all these methods return what the parameters are specially passing in. We have a user token with user details being sent in. We also have more JdbcTemplates such as
public List<String> getCurr(String currCountry) {
// query alliances table to return a list of alliance tags that countain the
// user's country tag
String sql = "SELECT * FROM user WHERE '" + currCountry + "' = ANY(access_tags) ;";
return jdbcTemplate.query(sql, new RowMapper<String>() {
#Override
public String mapRow(ResultSet rs, int rownumber) throws SQLException {
return rs.getString(1);
}
});
Then the last methods combines everything and has this return statement that uses all these setters from another class.
// return list of appropriately filtered missions
return jdbcTemplate.query(sql, new RowMapper<Mission>() {
#Override
public OtherClass mapRow(ResultSet rs, int rownumber) throws SQLException {
OtherClass m = new OtherClass();
m.setNumber(rs.getInt(1));
m.setName(rs.getString(2));
m.setLastCheckinDate(rs.getString(3));
m.setLocation(rs.getString(4));
m.setCurrLocation(rs.getString(5));
m.setFinalLocation(rs.getString(6));
m.setTags(rs.getString(7));
return m;
}
});
}
But I am not sure how to test these statements. Without using the Mockito.when commands, they always turn out null. We have this information in a database .xml file for the column and rows so it knows what information to grab to fill in the lists. Do I need to mock a mock db or something to test this?
Started a new job and need to learn Java (been a .NET developer for over a decade). A mandate from higher up dictates all new stuff is to be done in Java / Oracle.
So I am running through the PluralSight training and right now I am trying to learn the intricacies of JDBC.
I have the following table in a Oracle Database (also a new tech for me).
CREATE TABLE "TEST"."ACCOUNT"
(
"ACCOUNT_ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY VALUE,
"ACCOUNT_NAME" VARCHAR2(20)
"ACCOUNT_NUMBER" VARCHAR2(20)
"ACCOUNT_TYPE" VARCHAR2(20)
)
I set up some code using the Repository pattern. But it is fairly simple:
public static void main(String[] args) throws IOException {
IAccountMapper accountMapper = new AccountMapper();
IConfiguration configuration = new OracleConfiguration();
try (
IDataAccess<ResultSet> dataAccess = new OracleDataAccess(configuration);
IAccountRepository accountRepo = new AccountRepository(accountMapper, dataAccess);
){
//nothing here
List<Account> accounts = accountRepo.query(new AllAccountsSpecification());
} catch (Exception e){
System.err.println(e.getMessage());
}
}
The IDataAccess interface:
public interface IDataAccess<T> {
T get(String query) throws SQLException;
}
All of my implementation is in the base classes and the subclasses are referenced - ie:
public interface IOracleDataAccess extends IDataAccess<ResultSet>{
}
public class OracleDataAccess extends DataAccessBase<ResultSet> implements IOracleDataAccess {
public OracleDataAccess(IConfiguration configuration) throws SQLException {
super(configuration);
}
The implementation of the IDataAccess (a base class where T is the entity used by the repo):
public abstract class DataAccessBase<T> implements AutoCloseable {
protected final IConfiguration configuration;
protected Connection connection;
protected PreparedStatement statement;
protected ResultSet resultSet;
public DataAccessBase(IConfiguration configuration) {
this.configuration = configuration;
this.connection = DriverManager.getConnection(configuration.getConnectionString(),
configuration.getUsername(), configuration.getPassword());
}
public T get(String query) throws SQLException {
statement = connection.prepareStatement(query);
return (T)statement.executeQuery();
}
}
This DataAccess class is injected into my Repository class and is used to return the results:
public abstract class ReadOnlyRepositoryBase<T> implements IReadOnlyRepository<T> {
protected final IMapper<ResultSet, T> mapper;
protected final IDataAccess<ResultSet> database;
public ReadOnlyRepositoryBase(IMapper<ResultSet, T> mapper, IDataAccess<ResultSet> database) {
this.mapper = mapper;
this.database = database;
}
public List<T> query(ISpecification specification){
List<T> entities = new ArrayList<>;
try {
System.out.println(specification.toSqlQuery());
//This is what is output:
//SELECT * FROM ACCOUNT
//this returns 1 record when: "select tablespace_name, table_name from user_tables"
//returns no records when "select * from account"
ResultSet rs = database.get(specification.toSqlQuery());
//loop is never entered for "select * from account"
//runs once for "select tablespace_name, table_name from user_tables"
while(rs.next()){
entities.add(mapper.map(rs));
}
} catch (SQLException sqlEx){
sqlEx.printStackTrace();
}
return entities;
}
}
The problem is nothing is coming back (the ResultSet next() method returns false) This SQL:
SELECT * FROM ACCOUNT
Returns 2 records from the Oracle SQL Developer IDE - but nothing in code.
If I run this query from code:
"SELECT TABLESPACE_NAME, TABLE_NAME FROM USER_TABLES"
I get 1 record back (TEST, ACCOUNT)
Do I need to further qualify the tables in the SQL to get records back?
I am logged into the database with the same credentials I am connecting with - so I do not think it is a permission thing.
Feel like an idiot - but I imagine I am not the first here. #AlexPoole was spot on. I had inserted the records through the IDE - but apparently
I did not commit them. Coming from SQL Server - I do not need to commit after a
Insert into [Table] values (stuff)
I guess that is different with Oracle.
I have had some trouble with using a general type in a static method.
All comments on the source code are welcome, especially ones that significantly improve the code. I am also currently not planning on using any external framework, apart from JDBC, to keep it still simple, please do not put too much emphasis on that.
My view on not using external frameworks is also supported by the fact that the operations I will be using on the database are very minimal:
Inserting data
Updating data
Retrieving all fields. (And simply by putting in a different SQL Query you could already select what fields to retrieve
I do not plan on making a full framework, so I know that it will not be supporting everything. The speed of retrieving all fields is neither a real issue, as this will be pretty much only done on server bootup, and if used at any other time it will be done in a background task for which I do not really care when it is finished.
Entity.java:
abstract public class Entity<KeyType, DataType> {
protected KeyType key;
protected List<Object> data;
public Entity() {
data = new ArrayList<>();
}
//abstract public static Map<KeyType, DataType> getAll();
protected List<Object> createData(final DataAction dataAction) {
List<Object> list = new ArrayList<>();
if (dataAction == DataAction.INSERT) {
list.add(key);
}
list.addAll(data);
if (dataAction == DataAction.UPDATE) {
list.add(key);
}
return list;
}
abstract public void insert();
abstract public void update();
protected static <KeyType, DataType> Map<KeyType, DataType> getData(final Class<DataType> dataTypeClass, final String query) {
Map<KeyType, DataType> map = new HashMap<>();
try {
PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(query);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
KeyType key = (KeyType)resultSet.getObject(1);
int index = 2;
List<Object> dataList = new ArrayList<>();
while (resultSet.getObject(index) != null) {
dataList.add(resultSet.getObject(index));
index++;
}
DataType dataObject = null;
try {
dataObject = dataTypeClass.getConstructor(List.class).newInstance(dataList);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
}
map.put(key, dataObject);
}
} catch (SQLException ex) {
Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
}
return map;
}
protected void executeQuery(final String query, final List<Object> data) {
try {
PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(query);
int dataIndex = 0;
for (Object dataObject : data) {
preparedStatement.setObject(dataIndex, dataObject);
dataIndex++;
}
preparedStatement.execute();
preparedStatement.close();
} catch (SQLException ex) {
Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
A concrete implementation, Account.java:
public class Account extends Entity<String, Account> {
private final static String SELECT_ALL_QUERY = "SELECT * FROM accounts";
private final static String INSERT_QUERY = "INSERT INTO accounts (username, password) VALUES(?, ?)";
private final static String UPDATE_QUERY = "UPDATE accounts SET password=? WHERE username=?";
private String username;
private String password;
public Account(final String username, final String password) {
this.username = username;
this.password = password;
key = username;
data.add(password);
}
public Account(final List<Object> data) {
this((String)data.get(0), (String)data.get(1));
}
public String getUsername() {
return username;
}
public void setUsername(final String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(final String password) {
this.password = password;
}
public static Map<String, Account> selectAll() {
return getData(Account.class, SELECT_ALL_QUERY);
}
#Override
public void insert() {
executeQuery(INSERT_QUERY, createData(DataAction.INSERT));
}
#Override
public void update() {
executeQuery(UPDATE_QUERY, createData(DataAction.UPDATE));
}
}
I am generally happy about the concrete implementation, it seems like I have managed to bring it down to a bare minimum, except public Account(final List<Object> data) does not seem that nice, but I can live with it.
However, as guessed, the getData() from Entity is definately not nice, and I would like to improve it if possible.
What I would like to use is something like DataType dataObject = new DataType(dataList), but it seems like Generic Type Arguments cannot be instantiated.
So are there any ways of optimizing my current code in my current view? And is it possible to decouple the concrete classes and abstract classes even more?
EDIT:
Added a relevant question (I don't think I should make a fully new question for this thing, right?):
Is there a way to move the static Strings (SQL Queries) and the insert() and update() out of the Account class, into the Entity class?
To avoid the use of reflection in your getData method you should accept a factory that given a ResultSet creates instances of the specific type. Your selectAll method would then be something like:
public static Map<String, Account> selectAll()
{
return getData(
new EntityFactory<Account>()
{
public Account newInstance(ResultSet resultSet) throws SQLException
{
return new Account(resultSet.getString(0), resultSet.getString(1));
}
},
SELECT_ALL_QUERY
);
}
The getData method then ends up something like:
protected static <K, T extends Entity<K>> Map<K, T> getData(EntityFactory<T> entityFactory, String query)
{
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try
{
connection = dataSource.getConnection();
preparedStatement = connection.prepareStatement(query);
resultSet = preparedStatement.executeQuery();
Map<K, T> entities = new HashMap<>();
while (resultSet.next())
{
Entity<K> entity = entityFactory.newInstance(resultSet);
entities.put(entity.getKey(), entity);
}
return entities;
}
finally
{
closeQuietly(resultSet);
closeQuietly(prepareStatement);
closeQuietly(connection);
}
}
And assumes the Entity looks like:
public interface Entity<K>
{
public K getKey();
}
This allows you to remove the reflection and keeps the code that understands the database structure in one place. You should also use a similar template pattern to map from the domain object to the prepared statement when doing inserts and updates.
Now you've asked for comments on the code in general.
First off, code like this violates the Single Responsibility Principal and Seperation Of Concerns. A domain class should be a domain class and not contain persistance logic. Look at patterns like the Data Access Object for how this should be done.
Second, while I'm all for keeping it simple, Hibernate solved this problem a long time ago and JPA standardized it - you need a very good reason not to use one or both of these APIs.
Finally, your use of database resources - if you are going to use JDBC directly you have to clean up properly. Database connections are expensive resources and should be handled as such, the basic template for any JDBC call should be:
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try
{
connection = //get connection from pool or single instance.
preparedStatement = connection.prepareStatement("SELECT * FROM table WHERE column = ?");
preparedStatement.setString(1, "some string");
resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
//logic goes here.
}
}
catch (SQLException e)
{
//Handle exceptions.
}
finally
{
closeQuietly(resultSet);
closeQuietly(prepareStatement);
closeQuietly(connection);
}
The closeQuietly method has to be overloaded but should take the general form:
try
{
if (resultSet != null)
{
resultSet.close();
}
}
catch (SQLException e)
{
//Log exceptions but don't re-throw.
}
Well, as Darwind and Nick Holt told you, in a normal situation, you should use JPA, which is the Java standard specification for object-relational mapping. You can use Hibernate, EclipseLink or any other framework behind. Their design is can manage connections, transactions. In addition, using standards rather than exotic frameworks means that you can get help more easily for the community.
Another option is using Spring JDBC, which is quite light and facilitates many things.
Anyway, I suppose you did this for learning purpose so let's try to go further.
First, I think you should separate the classes in charge or retrieving the data (call it manager or Data Access Object -DAO-) and the entites representing the data themselves.
For me, using the class to get all the data as you did isn't a problem in itself. The problem is the position of the key is hardcoded. This should not be determined directly a generic (I mean the same for all the Entity implementation). This makes queries subjects to bugs when the first field is not the key (are you sure a select * from... will ALWAYS return the key in the first position? ) or with a composite key.
I think a better solution is to crate a Mapper interface and to implement it for each entity.
public interface RecordMapper<KeyType, DataType extends Entity> {
public void appendToMap(ResultSet resultSet, Map<KeyType, DataType>) throws SQLException;
}
The implementation of the mapper should be in charge of instanciating your entity, retrieving the key from the resultset, populating your entity and putting it in the map you expect.
public class AccountMapper implement RecordMapper<String, Account>{
public void appendToMap(ResultSet resultSet, Map<String, Account> accounts) throws SQLException {
String user= resultSet.getString("userName");
String pwd= resultSet.getString("passWord");
Account account = new Account(user, pwd);
accounts.put(user, account);
}
}
As I told you should move your data access methods in a DAO:
public class DAO{
public <KeyType, DataType> Map<KeyType, DataType> getData(final RecordMapper<KeyType, DataType> mapper, final String query) {
Map<KeyType, DataType> map = new HashMap<>();
try {
PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(query);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
mapper.appendToMap(resultSet, map);
}
} catch (SQLException ex) {
Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if(resultSet != null){
try{resultSet.close();} catch (Exception e){}
}
if(preparedStatement!= null){
try{preparedStatement.close();} catch (Exception e){}
}
}
return map;
}
public void executeQuery(final String query, final List<Object> data) {
try {
PreparedStatement preparedStatement = DatabaseConnection.getConnection().prepareStatement(query);
int dataIndex = 0;
for (Object dataObject : data) {
preparedStatement.setObject(dataIndex, dataObject);
dataIndex++;
}
preparedStatement.execute();
} catch (SQLException ex) {
Logger.getLogger(Entity.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if(resultSet != null){
try{resultSet.close();} catch (Exception e){}
}
if(preparedStatement!= null){
try{preparedStatement.close();} catch (Exception e){}
}
}
}
}
To answer your second quenstion, I think that putting your request string in the abstract parent instead of is certainly not a good idea. Each time you create new entity, you have to create a new query in the parent. Weird...unless I haven't understood properly your question.
Personnaly I think that the queries should be build dynamically and you should use reflection and annotations but the answer should be a bit long. Once again, you can get a look at JPA to see how creating an entity should look like. By the way, it should be even better if the entities didn't have to extend a parent Entity class.
I have a Java class with instance fields (and matching setter methods) that match the column names of a SQL database table. I would like to elegantly fetch a row from the table (into a ResultSet) and map it to an instance of this class.
For example:
I have a "Student" class with instance fields "FNAME", "LNAME", "GRADE" and appropriate getter and setter methods for each.
I also have a SQL table with three columns of the same name.
Right now I am doing something like this:
rs = statement.executeQuery(query);
Student student = new Student();
student.setFNAME(rs.getString("FNAME"));
student.setLNAME(rs.getString("LNAME"));
student.setGRADE(rs.getString("GRADE"));
There has to be a less verbose way of doing this, right? As I add columns this might get really annoying and messy.
I recommend using Spring JDBC. You don't need to use the rest of Spring to use their JDBC library. It will manage connections for you (no more closing Connection, Statement, or ResultSet) and has many conveniences, including row mapping.
We've retrofitted legacy code with Spring JDBC with little trouble.
Here is a presentation (PDF) of an overview of Spring JDBC. It's a few years old but it still works essentially the same, even without letting Spring inject the dependencies.
Spring JDBC Presentation PDF
You can do it generically by doing the following simple methods:
Interface to use as a method pointer:
public interface I_DBtoJavaObjectConvertable<T>
{
public T createFromDB(ResultSet i_rs) throws SQLException;
}
Generic class to handle every mapping from SQL to java Object:
public class DBManager
{
static volatile Connection conn;
//set here a static c'tor to handle the connection to the database
//The General generic method:
public static <T> List<T> GetObjectsFromDB(String i_Query, I_DBtoJavaObjectConvertable i_Converter)
{
List<T> ResList = new ArrayList<>();
try
{
Statement st = conn.createStatement();
for (ResultSet rs = st.executeQuery(i_Query); rs.next();)
{
ResList.add((T) i_Converter.createFromDB(rs));
}
}
catch (SQLException ex)
{
_LOG_ERROR(ex.getMessage());
}
return ResList;
}
}
Now By using Lanbda expression use can easlly convert an sql row to object, by given your convertion method, for example:
public static User FetchUserFromDB(ResultSet i_rs)
{
User userToCreate = null;
try
{
String FirstName = i_rs.getString("FirstName");
String LastName = i_rs.getString("LastName");
String Password = i_rs.getString("Password");
userToCreate = new User(FirstName, LastName, Password);
}
catch (SQLException ex)
{
_LOG_ERROR("Error in fetching user from DB: \n" + ex.getMessage());
}
return userToCreate;
}
And now you can use this this method to bring any Users you want:
public static List<User> GetAllUsersFromDB() throws SQLException
{
String Query = "select * "
+ "from UsersTable";
return DBManager.GetObjectsFromDB(Query, rs -> FetchUserFromDB(rs));
}
Or:
public static List<String> GetAllNamesFromDB() throws SQLException
{
String Query = "select FirstName "
+ "from UsersTable";
return DBManager.GetObjectsFromDB(Query, rs -> rs.getString("FirstName"));
}
You could use an ORM like one of the JPA providers e.g. Hibernate. This lets you set up mappings between your objects and your tables.
If you use JDBC that is how it works. If you want to avoid adding columns like this in Java, you may consider using some ORM frameworks.
A slightly less verbose way would be to give Student a constructor that accepts 3 strings. Then you could do this:
Student student = new Student(rs.getString("FNAME"), rs.getString("LNAME"), rs.getString("GRADE"));
The other way to do it is to use an ORM like Hibernate but Hibernate only becomes worth the massive setup effort for really big projects dealing with lots of tables.
There are many ORM libraries that simplify or eliminate the JDBC drudgery. See Source Forge ORM for some examples. I like my library, sormula, since it can be used with minimal configuration.
If you do not want to use any other framework, you can create standard mapping method and use it after every Result.
public class CurrencyDAO(){
public Currency findById(int id) {
String sql = "SELECT * FROM CCR.CURRENCY WHERE id = ?";
Currency currency = null;
Connection c = null;
try {
c = DBConnection.getConnection();
PreparedStatement ps = c.prepareStatement(sql);
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
currency = processRow(rs);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
DBConnection.close(c);
}
return currency;
}
protected Currency processRow(ResultSet rs) throws SQLException {
Currency currency = new Currency();
currency.setId(rs.getInt("id"));
currency.setEUR(rs.getString("EUR"));
currency.setUSD(rs.getString("USD"));
currency.setRate(rs.getString("rate"));
return currency;
}
}
Give q2o a try. It is a JPA based object mapper which helps you with many of the tedious SQL and JDBC ResultSet related tasks, but without all the complexity an ORM framework comes with.
Bind the Student class to its corresponding table:
#Table(name = "STUDENTS")
public class Student (
private String FNAME;
private String LNAME;
private String GRADE;
...
)
Select some students by their grade:
List<Student> students = Q2ObjList.fromClause(Student.class, "GRADE = ?", grade);
Change a student's grade and persist the change to the database:
student.setGRADE(grade);
Q2obj.update(student);
q2o is helpful even when you depend on Spring JDBC:
jdbcTemplate.queryForObject("...", new RowMapper<Student>() {
#Override
public Student mapRow(final ResultSet rs, final int rowNum) throws SQLException {
return Q2Obj.fromResultSet(rs, Student.class);
}
});
It is pretty easy, isn't it? Find more about q2o here.
When you execute a query you can get metadata from the ResultSet. You have access to the columns from this. Here's an example:
#RestController
public class MyController {
#GetMapping("/characters")
public List<Payload> characters() {
List<Payload> results = new ArrayList<>();
try (Connection conn = new Connection()) {
conn.makeConnection();
Statement stmt = conn.createStatement();
ResultSet result = stmt.executeQuery("SELECT * FROM public.hello;");
ResultSetMetaData resultMetaData = result.getMetaData();
Set<String> columns = new HashSet<>();
for (int i = 1; i <= resultMetaData.getColumnCount(); i++) {
columns.add(resultMetaData.getColumnName(i));
}
while (result.next()) {
results.add(new Data(result, columns));
}
} catch (Exception e) {
results.add(new Fail("404", e.getMessage()));
}
return results;
}
}
public class Data implements Payload {
private final Map<String, Object> data = new HashMap<>();
public Data(ResultSet result, Set<String> columns) throws SQLException {
for (String column : columns) {
data.put(column, result.getString(column));
}
}
public Map<String, Object> getData() {
return data;
}
}
Now you can have one class object that parses out the columns and data for any table. You never really care what columns there are. The down side is that all of your info is now stored in a data field. So the payload would look something like:
[
{"data":{"id":"1","name":"Rick Sanchez"}},
{"data":{"id":"2","name":"Morty Smith"}},
{"data":{"id":"3","message":"Summer Smith"}}
]