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"));
}
});
}
Related
I'm developing this application to fetch data from a single table from an existing Oracle database.
Here we've got the entity:
public class OrdemDeServicoCount {
private Long ordensInternas;
private Long ordensAtrasadas;
// assume getters and setters
}
The mapper:
public class OrdemMapper implements RowMapper<OrdemDeServicoCount> {
#Override
public OrdemDeServicoCount mapRow(ResultSet rs, int rowNum) throws SQLException {
OrdemDeServicoCount ordens = new OrdemDeServicoCount();
ordens.setOrdensInternas(rs.getLong("ordensInternas"));
// ordens.setOrdensAtrasadas(rs.getLong("ordensAtrasadas"));
return ordens;
}
}
And finally, the DAO:
public class OrdemDAO {
private JdbcTemplate jdbcTemplate;
public OrdemDAO(JdbcTemplate jdbcTemplate) {
super();
this.jdbcTemplate = jdbcTemplate;
}
public List<OrdemDeServicoCount> countOrdensInternasSemEncerrar() {
String sql = "SELECT COUNT(a.nr_sequencia) AS ordensInternas FROM MAN_ORDEM_SERVICO a "
+ "WHERE a.IE_STATUS_ORDEM IN (1,2) AND a.NR_GRUPO_PLANEJ IN (21)";
List<OrdemDeServicoCount> ordens = jdbcTemplate.query(sql, new OrdemMapper());
return ordens;
}
By the way, you all must know that if I declare uncomment the line ordens.setOrdensInternas(rs.getLong("ordensInternas")); in the mapper, I would get an error, because in my DAO, I'm not using that field.
But what if I need to create another method that uses just the ordensInternas field? Then again, I'd get an error...
So, my doubt here is: if I need to use the ordensAtrasadas field from the entity, will I have to create another class just to implement another mapper? Or is there a way that I can do any conditional in my current OrdemMapper class?
Just put your assignments in individual try-catch statements.
public class OrdemMapper implements RowMapper<OrdemDeServicoCount> {
#Override
public OrdemDeServicoCount mapRow(ResultSet rs, int rowNum) throws SQLException {
OrdemDeServicoCount ordens = new OrdemDeServicoCount();
try {
ordens.setOrdensInternas(rs.getLong("ordensInternas"));
} catch (SQLException ex) {
// This will happen if the columnIndex is invalid among other things
}
try {
ordens.setOrdensAtrasadas(rs.getLong("ordensAtrasadas"));
} catch (SQLException ex) {
// This will happen if the columnIndex is invalid among other things
}
return ordens;
}
}
What I am tring to do is to select the username, password, and role from studentstable using queryForObject.
In my JdbcTemplate syntax is
public static Object queryForObject(String sql,RowMAPPER mapper,Object ...args)
The problem is here in my JdbcStudentDAO
public class JdbcStudentDAO implements StudentDAO{
public String getLogin(StudentTO sto) {
String sql="select username,password,role from studentstable";
System.out.println(sql);
Here I don't know what is wrong below
Object obj=JdbcTemplate.queryForObject(sql,new StudentRowMapper(),sto.getUsername(),sto.getPassword(),sto.getRole());
StudentTO sto1=(StudentTO)obj;
System.out.println(sto1);
return sto1.toString();
}
}
This is my RowMapper where I'm getting all rows of my database, as shown below
public class StudentRowMapper implements RowMapper{
public Object mapRow(ResultSet rs) throws SQLException {
StudentTO sto=new StudentTO();
sto.setSid(rs.getInt(1));
sto.setName(rs.getString(2));
sto.setUsername(rs.getString(3));
sto.setPassword(rs.getString(4));
sto.setEmail(rs.getString(5));
sto.setPhone(rs.getLong(6));
sto.setRole(rs.getString(7));
return sto;
}
}
This is an abstract method in StudentDAO
public interface StudentDAO {
public String getLogin(StudentTO sto);
}
This is your query
select username,password,role from studentstable
You only have 3 columns, and they are not in the order defined by your extraction...
sto.setSid(rs.getInt(1));
sto.setName(rs.getString(2));
sto.setUsername(rs.getString(3)); // This is actually index 1
sto.setPassword(rs.getString(4)); // This is actually index 2
sto.setEmail(rs.getString(5));
sto.setPhone(rs.getLong(6));
sto.setRole(rs.getString(7)); // This is actually index 3
Therefore, you would need this
sto.setUsername(rs.getString(1));
sto.setPassword(rs.getString(2));
sto.setRole(rs.getString(3));
And the other properties are the defaults
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
}
}
I would like to know how I would get the count of a specific column within my database. Each distinct value in my column I would like to store the returned column value and its count within a Java List. This is what I have so far:
public List<Sighting> getCountPest() {
return jdbc.query("select pest_name, count(pest_name) from sighting group by pest_name", new RowMapper<Sighting>() {
public Sighting mapRow(ResultSet rs, int rowNum) throws SQLException {
Sighting sighting = new Sighting();
sighting.setCount(rs.getInt("count")); // will not work as no column name in table
sighting.setPest_name(rs.getString("pest_name"));
return sighting;
}
});
}
Essentially I would like to use pest_name and returned count value for a chart.
This is my sightingRowMapper if it helps:
public Sighting mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setUsername(rs.getString("username")); // setting the username if logged in
Sighting sighting = new Sighting();
sighting.setId(rs.getInt("id"));
sighting.setTotal_pests(rs.getInt("total_pests"));
sighting.setDate(rs.getString("date"));
sighting.setInformation(rs.getString("information"));
sighting.setPest_name(rs.getString("pest_name"));
sighting.setPark(rs.getString("park"));
sighting.setLocation(rs.getString("location"));
sighting.set_Username(rs.getString("username"));
sighting.setUser(user);
return sighting;
}
Sighting I guess you would need another result mapper than, which will deal only with pest_name and count column. At least it would be cleaner.
If you don't like creating new class for that, you could create anonymous class :
jdbc.query(
"select pest_name, count(pest_name) from sighting group by pest_name",
new RowMapper<Sighting>() {
public Sighting mapRow(ResultSet rs, int rowNum) throws SQLException {
return sighting;
}
});
Or use existing, but changing the query a bit:
select pest_name, count(pest_name) as total_pests
but it's a hack :)
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.