Showing posts with label spring-example. Show all posts
Showing posts with label spring-example. Show all posts

Thursday, 14 April 2011

Spring – How to pass a Date into bean property (CustomDateEditor )


Simple method may not work
Generally, Spring developer are not allow to pass a date format parameter into bean property via DI.

For example,

public class CustomerService
{
Date date;

public Date getDate() {
return date;
}

public void setDate(Date date) {
this.date = date;
}

}

Bean configuration file
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="customerService" class="com.services.CustomerService">
<property name="date" value="2010-01-31" />
</bean>

</beans>

Run it

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.mkyong.customer.services.CustomerService;

public class App
{
public static void main( String[] args )
{
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"Spring-Customer.xml"});

CustomerService cust = (CustomerService)context.getBean("customerService");
System.out.println(cust.getDate());
}
}

Error message prompt.

Caused by: org.springframework.beans.TypeMismatchException:
Failed to convert property value of type [java.lang.String] to
required type [java.util.Date] for property 'date';

nested exception is java.lang.IllegalArgumentException:
Cannot convert value of type [java.lang.String] to
required type [java.util.Date] for property 'date':
no matching editors or conversion strategy found

Solution

There are two solutions available.

1. Factory bean

Declare a dateFormat bean, and reference it as a factory bean from the date property. The factory method will call the SimpleDateFormat.parse() menthod to convert the String into Date object automatically.

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="dateFormat" class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd" />
</bean>

<bean id="customerService" class="com.mkyong.customer.services.CustomerService">
<property name="date">
<bean factory-bean="dateFormat" factory-method="parse">
<constructor-arg value="2010-01-31" />
</bean>
</property>
</bean>

</beans>

2. Property editors (CustomEditorConfigurer + CustomDateEditor)

Declare a CustomDateEditor class to convert the String into java.util.Date properties.

<bean id="dateEditor"
class="org.springframework.beans.propertyeditors.CustomDateEditor">

<constructor-arg>
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd" />
</bean>
</constructor-arg>
<constructor-arg value="true" />

</bean>

Register the CustomDateEditor in CustomEditorConfigurer, so that the Spring will convert the properties whose type is java.util.Date.


<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date">
<ref local="dateEditor" />
</entry>
</map>
</property>
</bean>

Bean configuration file.

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

<bean id="dateEditor"
class="org.springframework.beans.propertyeditors.CustomDateEditor">

<constructor-arg>
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyy-MM-dd" />
</bean>
</constructor-arg>
<constructor-arg value="true" />

</bean>

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date">
<ref local="dateEditor" />
</entry>
</map>
</property>
</bean>

<bean id="customerService" class="com.mkyong.customer.services.CustomerService">
<property name="date" value="2010-02-31" />
</bean>

</beans>




USING SPRING’S STOREDPROCEDURE

One very useful portion of the Spring Framework is the StoredProcedure wrapper’s and the RowMapper objects. Together these allow you to call a stored procedure and then parse the result set back into a collection of objects with very little pain. Below is an example of how to do just this for a simple query like a user query.

public class MyStoredProcedure extends StoredProcedure {

public MyStoredProcedure (DataSource ds, String spname,
Map map, String sqlOutKey, Integer returnType,
RowMapper rowmapper) {
super();
setDataSource(ds);

/* resultset has to be declared first over other declare parameters */
if (rowmapper != null) {
declareParameter(new SqlReturnResultSet(sqlOutKey, rowmapper));
}

if (map != null) {
Iterator itr = map.keySet().iterator();
while (itr.hasNext()) {
String key = (String) itr.next();
Integer value = (Integer) map.get(key);
declareParameter(new SqlParameter(key, value.intValue()));
}
}

/*
         * sql out paramter has to be declared based on the order in stored
         * procedures, In all our stored procedures we have it after input
         * parameters
         */
if (returnType != null) {
declareParameter(new SqlOutParameter(sqlOutKey, returnType
.intValue()));
}

setSql(spname);
compile();
}
}

Next, we have the Mapper class:

public class UserMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setUserId(rs.getString(Constants.USER_ID));
user.setFirstName(rs.getString(Constants.FIRST_NAME));
user.setLastName(rs.getString(Constants.LAST_NAME));
user.setOrganizationName(rs.getString(Constants.ORGANIZATION_NAME));
return user;
}
}

Next, we have to query the actual stored procedure from a DAO. Here’s a sample method that would do just such a thing:

public Collection searchUsers(User user) throws Exception {

Map lhm = new LinkedHashMap(4);
lhm.put(Constants.USER_ID, new Integer(Types.VARCHAR));
lhm.put(Constants.FIRST_NAME,new Integer(Types.VARCHAR));
lhm.put(Constants.LAST_NAME,new Integer(Types.VARCHAR));
lhm.put(Constants.ORGANIZATION_NAME,new Integer(Types.VARCHAR));

UserMapper mapper = new UserMapper();

// Call Stored Procedure
EntitlementsStoredProcedure proc = new EntitlementsStoredProcedure(
ds, StoredProcedureConstants.USER_SEL, lhm,
Constants.RESULTSET, null, mapper);

// Collect the criteria for the search
Map map = new LinkedHashMap(4);
map.put(Constants.USER_ID, user.getUserId());
map.put(Constants.FIRST_NAME, user.getFirstName());
map.put(Constants.LAST_NAME, user.getLastName());
map.put(Constants.ORGANIZATION_NAME, user.getOrganizationName());

Map results = proc.execute(map);
List resultList = (LinkedList)results.get(Constants.RESULTSET);

//iterate of results list and print
for (Iterator it=resultList.iterator(); it.hasNext(); ) {
User user1 = (User)it.next();
System.out.println(user1);
}

return resultList;
}

That’s all there is to it! This shows just how simple it is to do queries in an object oriented way, and have generic row mappers. There are full object relational mapping solutions, such as Hibernate, that do a great job of solving the working with relational data in an OO way paradigm, but they take a LOT of configuration and can be daunting if you’re not accustomed to working with them. This solution, however, I feel works very well in simpler scenarios. It also allows someone who is used to looking at code to quickly read through and get an idea of how to use this.

One point to note: this gets even simpler when using generics that are introduced in Java 1.5.

Mapping object to row in spring using jdbc template

See this tutorial for getting the feel of spring jdbc template.
The example below uses database and corresponding from this post.
Now we can have our object from result set. Either it can be 1 value returned from select query, or we can have multiple rows. So thats where we have to map the object. Spring provides various ways to map result sets to these objects,eg. Flight.


Querying for Single Row


1. Custom RowMapper


class FlightMapper implements RowMapper<Flight> {
@Override
public Flight mapRow(ResultSet rs, int index) throws SQLException {
Flight flight = new Flight();
flight.setFlightNo(rs.getString(1));
flight.setCarrier(rs.getString(2));
flight.setFrom(rs.getString(3));
flight.setTo(rs.getString(4));
return flight;
}
Now this should be passed to queryForObject function.
Pass it to queryForObject() method, the returned result will call your custom mapRow() method to match the value into the properly.

public Flight getFlightInfo(String flightNo) {
String sql = "select * from flights_test where flightno=?"
Flight flight = (Flight) jdbcTemplate.queryForObject(sql,new Object[]
{flightNo},
new FlightMapper());  
        return flight;             
}


2. BeanPropertyRowMapper

In Spring 2.5, comes with a handy RowMapper implementation called ‘BeanPropertyRowMapper’, which can maps a row’s column value to a property by matching their names. Just make sure both the property and column has the same name, e.g property ‘flightNo’ will match to column name ‘FLIGHTNO’ or with underscores ‘FLIGHT_NO’.
Again queryForObject method can be used.

//Similar to above but using BeanPropertyRowMapper
public Flight getFlightInfo(String flightNo) {
String sql = "select * from flights_test where flightno=?"
Flight flight = (Flight) jdbcTemplate.queryForObject(sql,new Object[]
{flightNo},
new BeanPropertyRowMapper(Flight.Class)
()
);  
        return flight;             
}

Querying for Multiple Rows

Here’s two examples to show how to query or extract multiple rows from database, and convert it into a List.

1. Map it manually

In mutiple return rows, RowMapper is not supported in query() method, you need to map it manually. 
public List<Flight> getAvailableFlights(String carrier) {
return jdbcTemplate.query("select * from flights_test where carrier = ?", new FlightMapper(), carrier);
}

2. BeanPropertyRowMapper

The simplest solution is using the BeanPropertyRowMapper class.
    public List<Flight> getAvailableFlights(String carrier) {
return jdbcTemplate.query("select * from flights_test where
carrier = ?"
, new BeanPropertyRowMapper(Flight.Class), carrier);
}





Creating a DAO interface and implementation using JdbcTemplate

Prerequisites

  1. Spring installation
  2. To create a dao in spring we first need a database and corresponding class to represent entities in database. For this see - Creating a Database and corresponding pojo class to represent that entity
Now for data access we should use Dao pattern, so we create interface and implementation for this.

Creating a Dao Interface
package repository;

import java.util.List;
import java.util.Map;

import entity.Flight;

public interface FlightRepository {

public int getTotalFlights();

public int getTotalFlights(String carrier);

public Map getFlightInfo(String flightNo);

public List getFlights(String carrier);

public int getTotalFlights(String from, String to);

public List<Flight> getAvailableFlights(String
carrier);

public void newFlight(Flight flight);

}
Creating corresponding implementation class:
  • Creating a jdbc template:
    JdbcTemplate class requires a datasource to be supplied to successful creation. Provides full JDBC APIs.

    jdbcTemplate = new JdbcTemplate(dataSource);

    Following operations are supported i.e. CRUD(Create retrieve update and Delete) and some other operations:

    • Querying (SELECT operations).

    • Updating (INSERT, UPDATE, and DELETE operations).

    • Other SQL operations (all other SQL operations).
    SimpleJdbcTemplate class supports java5 features like generics and varargs. Provides method only for common CRUD operations:
    SimpleJdbcTemplate jdbcTemplate = new 
    SimpleJdbcTemplate(dataSource);
    Use NamedParameterJdbcTemplate class which allows the usage of named parameters ':name' rather than traditional '?'.
    NamedJdbcTemplate namedTemplate = new
    NamedParameterJdbcTemplate(dataSource);
    So we take both jdbc-template type as our fields in Repository implementation class and set their datasource:
    private SimpleJdbcTemplate jdbcTemplate;
    private NamedParameterJdbcTemplate namedTemplate;

    public void setDataSource(DataSource dataSource) {
    jdbcTemplate = new SimpleJdbcTemplate
    (dataSource);
    namedTemplate = new NamedParameterJdbcTemplate
    (dataSource);
    }
    Now we can perform various database operations using these templates.
    The JdbcTemplate query methods are used to send SELECT queries to the database. A variety of different query methods are supported, depending on how complicated the return values are.
  • Quering for integer:
    Sometimes we simply require integer like count of something, or sometimes fields are integer as well eg. age . So queryOfInt method can be used.
    Now this can be done using 2 ways:
    Using select query :
    public int getTotalFlights() {
    return jdbcTemplate.queryForInt("select count(*)
    from flights_test"
    );
    }

    Using bind parameters:
    public int getTotalFlights(String carrier) { 
    //takes 2 parameters 
    return jdbcTemplate.queryForInt("select count(*) 
    from flights_test where carrier = ?"
    ,
    new Object[]{carrier});
    }

    Also note that SimpleJdbcTemplate class supports varargs. So Above bind parameters can be used with more than 1 binding parameters.
    Similarly there is queryForLong to fetch longs.

    Query For String
    What if you want String as 1 item in result-set. So in that case queryForObject can be used, and string can be returned. Example:

    public String getCarrierNameForFlightNo(String flightNo)  {
    String myString=(String) 
    simpleJdbcTemplateTarget.queryForObject
    ("select carrier from flights_test
    where flight_no= ?",
    String.class,new Object[]{flightNo});

    return myString;
    }
    So here we are querying for object, but returning string. We can use queryForObject for more purposes as well.
  • Query for single row:
    In this case we can use queryForMap to get this:
    public Map getFlightInfo(String flightNo) {
    return jdbcTemplate.queryForMap("select * from
    flights_test where flightno=?"
    , flightNo);
    }

  • Using name JdbcParameterTemplate
    Eg, We want a total no. of flights from 1 destination to other. So to have that we can use named parameter jdbc template:
    public int getTotalFlights(String from, String to) {
    Map
    <String, String> params = new HashMap<String, String>();
    params
    .put("from", from);
    params
    .put("to", to);
    String sql = "select count(*) from
    flights_test where kahase=:from and
    kahatak=:to"
    ;
    return namedTemplate.queryForInt(sql, params);
    }


  • Querying for list:
    We ca return a list where each element of the List contains a Map object holding column name, column value pair data using queryForList();
    public List getFlights(String carrier) {
    return jdbcTemplate.queryForList("select *
    from flights_test where carrier = ?"
    , carrier);
    }

  • Query for Domain objects
    Though in above case we returned list, but we have to still map manually. Therefore spring provides us with RowMapper interface:
    public List<Flight> getAvailableFlights(String carrier) {
    class FlightMapper implements RowMapper<Flight> {
    @Override
    public Flight mapRow(ResultSet rs, int index)  
                                     throws SQLException {
    Flight flight = new Flight();
    flight.setFlightNo(rs.getString(1));
    flight.setCarrier(rs.getString(2));
    flight.setFrom(rs.getString(3));
    flight.setTo(rs.getString(4));
    return flight;
    }
    }
    return jdbcTemplate.query("select * from flights_test
                   where carrier = ?", new FlightMapper()
                   , carrier);
    }


    So a flight object is created from every resultset and corresponding mapper object is passed to query() method, which finally returns a list. For more on mapping please refer to this post.
  • For all other DML (data manipulation language) operations
    For this, update() function is used
    public void newFlight(Flight flight) {
    jdbcTemplate
    .update("insert into
    flights_test values(?, ?, ?, ?)"
    ,
    flight
    .getFlightNo(), flight.getCarrier(),
    flight.getFrom(), flight.getTo());
    }

Full code listing
package repository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;

import entity.Flight;

public class JdbcFlightRepository implements FlightRepository {

private SimpleJdbcTemplate jdbcTemplate;
private NamedParameterJdbcTemplate namedTemplate;

public void setDataSource(DataSource dataSource) {
jdbcTemplate = new SimpleJdbcTemplate(dataSource);
namedTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int getTotalFlights() {
return jdbcTemplate.queryForInt("select count(*)
from flights_test"
);
}

public int getTotalFlights(String carrier) {
return jdbcTemplate.queryForInt("select count(*)
from flights_test where carrier = ?"
,
new Object[]{carrier});
}

public Map getFlightInfo(String flightNo) {
return jdbcTemplate.queryForMap("select *
from flights_test where flightno=?"
,
flightNo);
}

public List getFlights(String carrier) {
return jdbcTemplate.queryForList("select *
from flights_test where carrier = ?"
, carrier);
}

public int getTotalFlights(String from, String to) {
Map<String, String> params = new HashMap<String, String>();
params.put("from", from);
params.put("to", to);
String sql = "select count(*) from flights_test where kahase=:from and kahatak=:to";
return namedTemplate.queryForInt(sql, params);
}

public List<Flight> getAvailableFlights(String carrier) {
class FlightMapper implements RowMapper<Flight> {
@Override
public Flight mapRow(ResultSet rs, int index) throws SQLException {
Flight flight = new Flight();
flight.setFlightNo(rs.getString(1));
flight.setCarrier(rs.getString(2));
flight.setFrom(rs.getString(3));
flight.setTo(rs.getString(4));
return flight;
}
}
return jdbcTemplate.query("select * from
flights_test where carrier = ?"
, new FlightMapper(), carrier);
}

public void newFlight(Flight flight) {
jdbcTemplate.update("insert into
flights_test values(?, ?, ?, ?)"
,
flight.getFlightNo(), flight.getCarrier(),
flight.getFrom(), flight.getTo());
}
}
Config files to set up the beans: Managing Datasource:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
>

<bean id="ds"
class="org.springframework.jdbc.datasource.
DriverManagerDataSource"
>
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>

</beans>
Managing the bean config file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

<import resource="db-config.xml" />

<bean id="flightRepository" class="repository.JdbcFlightRepository">
<property name="dataSource" ref="ds" />
</bean>

</beans>
Tester of the program:
package test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import repository.FlightRepository;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:ex-config.xml")
public class FlightRepositoryTest {

@Autowired private FlightRepository flightRepository;

@Test
public void testDifferentMethods() {
System.out.println(flightRepository.getTotalFlights());
//System.out.println(flightRepository.getTotalFlights("KINGFISHER"));
//System.out.println(flightRepository.getFlightInfo("JL-120"));
//System.out.println(flightRepository.getFlights("KINGFISHER"));
//System.out.println(flightRepository.getTotalFlights("MUMBAI", "JAIPUR"));
//System.out.println(flightRepository.getAvailableFlights("KINGFISHER"));
}
}