This is a very simple DAO attempt I'd like to share.
My question is if this is a correct way to test a DAO. What I mean is that I'm verifying the SQL query and giving it return a mock. Then tell the mock to return these specific values and assert them?
I have updated the DAO to use prepared statement instead of simple Statement. Thanks.
public class PanelDao implements IO {
private final static Logger LOGGER = Logger.getLogger(PanelDao.class);
private Connection connection;
public PanelDao() throws SQLException {
this(MonetConnector.getConnection());
}
public PanelDao(Connection connection) throws SQLException {
this.connection = connection;
}
#Override
public void save(Panel panel) throws SQLException {
final String query = "INSERT INTO panels VALUES ( ?, ?, ?, ?, ?, ?, ? )";
final PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, panel.getId());
statement.setString(2, panel.getColor());
statement.setDate(3, (new Date(panel.getPurchased().getTime())) );
statement.setDouble(4, panel.getCost());
statement.setDouble(5, panel.getSellingPrice());
statement.setBoolean(6, panel.isOnSale());
statement.setInt(7, panel.getUserId());
LOGGER.info("Executing: "+query);
statement.executeUpdate();
}
#Override
public void update(Panel object) {
throw new UnsupportedOperationException();
}
#Override
public void delete(Panel object) {
throw new UnsupportedOperationException();
}
#Override
public Panel find(String id) throws SQLException {
final String query = "SELECT * FROM panels WHERE id = ? ";
final PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, id);
LOGGER.info("Executing: "+query);
final ResultSet result = statement.executeQuery();
final Panel panel = new Panel();
if (result.next()) {
panel.setId(result.getString("id"));
panel.setColor(result.getString("color"));
}
return panel;
}
}
And the test class
public class PanelDaoTest {
#InjectMocks
private PanelDao panelDao;
#Mock
private Connection connection;
#Mock
private Statement statement;
#Mock
private ResultSet result;
private Panel panel;
#BeforeClass
public static void beforeClass() {
BasicConfigurator.configure();
}
#Before
public void init() throws SQLException {
MockitoAnnotations.initMocks(this);
Mockito.when(connection.createStatement()).thenReturn(statement);
panel = new Panel("AZ489", "Yellow", new Date(), 10.00, 7.50, true, 1);
}
#Test
public void testSave() throws SQLException {
Mockito.when(connection.prepareStatement("INSERT INTO panels VALUES ( ?, ?, ?, ?, ?, ?, ? )")).thenReturn(statement);
panelDao.save(panel);
Mockito.verify(statement).executeUpdate();
}
#Test
public void testFind() throws SQLException {
Mockito.when(connection.prepareStatement("SELECT * FROM panels WHERE id = ? ")).thenReturn(statement);
Mockito.when(statement.executeQuery()).thenReturn(result);
Mockito.when(result.next()).thenReturn(true);
Mockito.when(result.getString("id")).thenReturn("AZ489");
Mockito.when(result.getString("color")).thenReturn("Yellow");
Panel panel = panelDao.find("AZ489");
assertEquals("AZ489",panel.getId());
assertEquals("Yellow",panel.getColor());
Mockito.verify(statement).executeQuery();
}
}
2.0 Testing DAO with HSQLDB
After taking into account your feedback I decided to use HSQLDB for real database testing. Please find this as a resource if tackling similar problems.
public class PanelDao implements IO {
private final static Logger LOGGER = Logger.getLogger(PanelDao.class);
private Connection connection;
/**
* Default constructor is using Monet connector
*/
public PanelDao() throws SQLException {
this(MonetConnector.getConnection());
}
public PanelDao(Connection connection) throws SQLException {
this.connection = connection;
}
#Override
public void save(Panel panel) throws SQLException {
final String query = "INSERT INTO panels VALUES ( ?, ?, ?, ?, ?, ?, ? )";
final PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, panel.getId());
statement.setString(2, panel.getColor());
statement.setDate(3, (new Date(panel.getPurchased().getTime())) );
statement.setDouble(4, panel.getCost());
statement.setDouble(5, panel.getSellingPrice());
statement.setBoolean(6, panel.isOnSale());
statement.setInt(7, panel.getUserId());
LOGGER.info("Executing: "+query);
statement.executeUpdate();
}
#Override
public void update(Panel object) {
throw new UnsupportedOperationException();
}
#Override
public void delete(Panel object) {
throw new UnsupportedOperationException();
}
#Override
public Panel find(String id) throws SQLException {
final String query = "SELECT * FROM panels WHERE id = ? ";
final PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, id);
LOGGER.info("Executing: "+query);
final ResultSet result = statement.executeQuery();
if (result.next()) {
final Panel panel = new Panel();
panel.setId(result.getString("id"));
panel.setColor(result.getString("color"));
panel.setPurchased(new Date(result.getDate("purchased").getTime()));
panel.setCost(result.getDouble("cost"));
panel.setSellingPrice(result.getDouble("selling_price"));
panel.setOnSale(result.getBoolean("on_sale"));
panel.setUserId(result.getInt("user_id"));
return panel;
}
return null;
}
}
and the Test class:
public class PanelDaoTest {
private PanelDao panelDao;
private Panel panel;
/* HSQLDB */
private static Server server;
private static Statement statement;
private static Connection connection;
#BeforeClass
public static void beforeClass() throws SQLException {
BasicConfigurator.configure();
server = new Server();
server.setAddress("127.0.0.1");
server.setDatabaseName(0, "bbtest");
server.setDatabasePath(0, ".");
server.setPort(9000);
server.start();
PanelDaoTest.connection = DriverManager.getConnection("jdbc:hsqldb:hsql://127.0.0.1:9000/bbtest", "SA", "");
PanelDaoTest.statement = PanelDaoTest.connection.createStatement();
}
#Before
public void createDatabase() throws SQLException {
PanelDaoTest.statement.execute(SqlQueries.CREATE_PANEL_TABLE);
panelDao = new PanelDao(PanelDaoTest.connection);
}
#Test
public void testSave() throws SQLException {
panel = new Panel();
panel.setId("A1");
panel.setPurchased(new Date());
panelDao.save(panel);
assertNotNull(panelDao.find("A1"));
}
#Test
public void testFind() throws SQLException {
final String id = "45ZZE6";
panel = Panel.getPanel(id);
Panel received = panelDao.find(id);
assertNull(received);
panelDao.save(panel);
received = panelDao.find(id);
assertNotNull(received);
assertEquals(panel.getId(), received.getId());
assertEquals(panel.getColor(), received.getColor());
assertEquals(panel.getPurchased().getDate(), received.getPurchased().getDate());
assertEquals(panel.getPurchased().getMonth(), received.getPurchased().getMonth());
assertEquals(panel.getPurchased().getYear(), received.getPurchased().getYear());
assertEquals(panel.getCost(), received.getCost(),0.001);
assertEquals(panel.getSellingPrice(), received.getSellingPrice(),0.001);
assertEquals(panel.getUserId(), received.getUserId());
}
#After
public void tearDown() throws SQLException {
statement.executeUpdate(SqlQueries.DROP_PANEL_TABLE);
}
#AfterClass
public static void stopServer() {
server.shutdown();
}
}
First of all, you should not create SQL queries by concatenation, because it's vulnerable to SQL injection. Use PreparedStatements instead.
Actually, it doesn't make much sense to test DAO this way. Your tests only verify that your DAO passes values back and forth correctly, but it doesn't cover the real complexity that lies in correctness of SQL queries issued by your DAO.
In other words, if you want to test your DAOs you need to create integration test that involves real database. This way you can verify that SQL queries issued by your DAO are correct.
I don't really think that method of testing is really buying you anything, and the test code is extremely brittle. I would use something like DBUnit that allows you to "mock" your database. This will actually allow you to test the correctness of your queries.
I would use an in-memory database such as H2, to test that your SQL actually works.
When you test your save method, your test should call save, then select the row from the database and assert that there is actually something there.
When you test your find method, your test should insert some rows in the database directly, then call find and assert that the desired row or rows were actually found.
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'm facing weird issue with Spring jdbc RowMapper:-
Here is my code
public void test(){
String sql=" Query will fetch records where dateColumn<='2021-08-17' Limit 1";
jdbcTemplate.query(sql, new ModelRowMapper());
}
public class ModelRowMapper implements RowMapper<ModelRowMapper> {
#Override
public ModelRowMapper mapRow(ResultSet rs, int rowNum) throws SQLException {
ModelRowMapper model= new ModelRowMapper();
System.out.println(rs.getString("value"));
}
}
Example:-
db records:-
2021-08-21
2021-08-15
2021-08-13
Output I'm expecting is 2021-08-15
In the ModelRowMapper class observed resultSet prints two values(1st is valid:- 2021-08-15) then print the invalid value and in the response also I will be getting invalid value
But above query properly works when I use the ResultSetExtractor
jdbcTemplate.query(sql, new ResultSetExtractor<String>() {
#Override
public String extractData(ResultSet rs) throws SQLException, DataAccessException {
while (rs.next()) {
System.err.println(rs.getString("value"));
}
//prints only one value and returns the same value
return "";
}
});
What would be the issue with rowMapper?....
Any suggestions would be helpful.......
You are somehow misunderstood how rowmapper should be called! use the following syntax, that would give you the desired result.
public void test(){
String sql=" Query will fetch records where dateColumn<='2021-08-17' Limit 1";
jdbcTemplate.query(query, new RowMapper<ModelRowMapper>(){
#Override
public ModelRowMapper mapRow(ResultSet rs, int rowNum) throws
SQLException {
ModelRowMapper model= new ModelRowMapper();
System.out.println(rs.getString("value"));
}
});
}
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'm not sure whether I'm using JMockit incorrectly, or there's something amiss in my setup. I'm using JMockit 1.32 with JUnit 4.12 in Eclipse.
My problem seems to be that interfaces aren't being captured. Specifically in the java.sql package. For example:
public class Dto {
private int id;
public Dto(){}
public Dto(ResultSet rs) {
try {
id = rs.getInt(1);
} catch (SQLException e) { }
}
public int getId() {return id;}
void setId(int id) {this.id = id;}
}
.
public class ClassUnderTest {
public static Dto loadObject(Connection conn, String tablename, int id) {
Dto result = null;
ResultSet rs = null;
PreparedStatement ps = null;
try {
String sql = "select * from " + tablename + " where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
rs = ps.executeQuery();
if (rs.next()) {
result = new Dto(rs);
}
} catch (SQLException e) {
} finally {
try {
if (ps != null) ps.close();
} catch (SQLException e) { }
}
return result;
}
}
.
public class ResultSetTest extends junit.framework.TestCase {
private static final int OBJ_ID = 5;
#Capturing
ResultSet mockResultSet;
#Capturing
PreparedStatement mockStatement;
#Capturing
Connection mockConn;
#Test
public void testGetDtoById() throws SQLException {
new Expectations() {{
mockConn.prepareStatement(anyString); result = mockStatement;
mockStatement.setInt(anyInt, OBJ_ID);
mockResultSet.next(); result = true;
new Dto(mockResultSet); result = new Dto();
mockResultSet.next(); result = true;
}};
Dto dto = ClassUnderTest.loadObject(mockConn, "", OBJ_ID);
assertEquals(dto.getId(), OBJ_ID);
}
}
In this setup, test execution fails with a NPE on the first line in the Expectations(){} block. But from the tutorials, etc. I'm expecting a mocked instance to have been created. (e.g. tutorial)
Trying to move past this, I created explicit mocked classes like so:
public class ResultSetMockup extends MockUp<ResultSet> { }
public class PreparedStatementMockup extends MockUp<PreparedStatement>
{
#Mock ResultSet executeQuery() {return new ResultSetMockup().getMockInstance();}
}
public class ConnectionMockup extends MockUp<Connection>
{
#Mock PreparedStatement prepareStatement(String sql) throws SQLException {
return new PreparedStatementMockup().getMockInstance();
}
}
#Capturing
ResultSet mockResultSet = new ResultSetMockup().getMockInstance();
#Capturing
PreparedStatement mockStatement = new PreparedStatementMockup().getMockInstance();
#Capturing
Connection mockConn = new ConnectionMockup().getMockInstance();
At this point, the Expectations() {} block is happy, but it appears that results is never actually being set. By setting a breakpoint I see that rs.next() always fails. So I presume nothing is actually being captured.
What am I doing wrong? Or is something in my setup preventing JMockit from actually running?
The actual problem in the test is that it's mixing the APIs from JUnit 3 (anything from junit.framework) and JUnit 4+ (org.junit). This should never be done.
JMockit only supports the JUnit 4 API, not the obsolete JUnit 3 API. So, simply remove "extends from junit.framework.TestCase" that it will be ok.
BTW, your Java IDE should have warned against this mistake. IntelliJ, at least, promptly displays "Method 'testGetDtoById()' annotated with '#Test' inside class extending JUnit 3 TestCase".
Also, the test (and the code under test) has several other mistakes...
My problem appears to have been the use of JUnit. I went as far as to try tutorial examples verbatim without luck. But by converting over to TestNG all my problems went away.
It seems as though JMockit's Expectations block wasn't able to hook into the code properly with JUnit. Either calls weren't recognized or the faking wasn't happening. I'm curious now, does anyone have it working with JUnit?
How to create a database trigger that log a row change to another table in H2?
In MySQL, this can be done easily:
CREATE TRIGGER `trigger` BEFORE UPDATE ON `table`
FOR EACH ROW BEGIN
INSERT INTO `log`
(
`field1`
`field2`,
...
)
VALUES
(
NEW.`field1`,
NEW.`field2`,
...
) ;
END;
Declare this trigger:
CREATE TRIGGER my_trigger
BEFORE UPDATE
ON my_table
FOR EACH ROW
CALL "com.example.MyTrigger"
Implementing the trigger with Java/JDBC:
public class MyTrigger implements Trigger {
#Override
public void init(Connection conn, String schemaName,
String triggerName, String tableName, boolean before, int type)
throws SQLException {}
#Override
public void fire(Connection conn, Object[] oldRow, Object[] newRow)
throws SQLException {
try (PreparedStatement stmt = conn.prepareStatement(
"INSERT INTO log (field1, field2, ...) " +
"VALUES (?, ?, ...)")
) {
stmt.setObject(1, newRow[0]);
stmt.setObject(2, newRow[1]);
...
stmt.executeUpdate();
}
}
#Override
public void close() throws SQLException {}
#Override
public void remove() throws SQLException {}
}
Implementing the trigger with jOOQ:
Since you added the jOOQ tag to the question, I suspect this alternative might be relevant, too. You can of course use jOOQ inside of an H2 trigger:
#Override
public void fire(Connection conn, Object[] oldRow, Object[] newRow)
throws SQLException {
DSL.using(conn)
.insertInto(LOG, LOG.FIELD1, LOG.FIELD2, ...)
.values(LOG.FIELD1.getDataType().convert(newRow[0]),
LOG.FIELD2.getDataType().convert(newRow[1]), ...)
.execute();
}
Shorter version of Lukas Eder's answer:
CREATE TRIGGER my_trigger
BEFORE UPDATE
ON my_table
FOR EACH ROW
CALL "com.example.MyTrigger"
public class MyTrigger extends TriggerAdapter {
#Override
public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) throws SQLException {
// mannipulate the rows here by using the methods on the oldRow and newRow objects
}
}