#Data persistence overview#
Since Spring 1.x, in Spring core framework, Spring had provided several approaches to support data operations for RDBMS system, including the popular Jdbc, Jpa, Hibernate, iBatis API support.
But thing was changed nowdays, we are entering a big data era. Spring Data project try to provide a more generic abstraction for different datastore, including RDBMS support, such as Jdbc, JPA etc, and NoSQL support, such as MongoDB, Neo4j etc.
##Legacy DaoSupport API##
In the Spring core framework(provided in the spring-jdbc and spring-orm maven dependency), it has provided a series of standard DaoSupport APIs and Template pattern to help user to retrieve data from database and save the state back into database.
###Jdbc support
There is a JdbcDaoSupport class and JdbcTempalte class provided for Jdbc operations.
Using these APIs in your project is very simple and stupid.
- Firstly register a DataSource bean, and also declare a Jdbc transaction manager(we will discuss Spring transaction in future posts).
<pre> <jdbc:embedded-database id="dataSource" type="H2"> <jdbc:script location="classpath:/com/hantsylabs/example/spring/config/schema.sql" /> </jdbc:embedded-database> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> </pre>
In this codes fragment, we are using a H2 embedded database, and execute a predefined sql scripts to create the tables and insert the initial test data when the database starts up.
- Declare your Dao interface and provide your implementation to subclass JdbcDaoSupprt.
<pre> public interface ConferenceDao { public abstract Conference findById(Long id); public abstract Long save(Conference conference); public abstract void delete(Long id); public abstract void delete(Conference obj); public abstract void update(Conference conference); public abstract void deleteAll(); public abstract Conference findBySlug(String string); } </pre>
In your implementation class, inject the DataSouce bean. If you explore the JdbcDaoSupport class in your IDE, you will find it had already provided a JdbcTemplate bean for you, you can use getJdbcTemplate() method to access it.
<pre> public class JdbcConferenceDaoImpl extends JdbcDaoSupport implements ConferenceDao { @Autowired DataSource dataSource; @Autowired public JdbcConferenceDaoImpl(DataSource dataSource) { super(); setDataSource(dataSource); } @Override public Conference findById(Long id) { return jdbcTemplate.queryForObject( "select conference where id =?", new Object[] { id }, new ConferenceMapper()); } } </pre>
Alternatively, you can declare a JdbcTemplate bean(and inject the DataSource bean) yourself, and use JdbcTemplate in your implementation class directly. The benefit is you are free from subclassing the JdbcDaoSupport class.
<pre> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> </pre>
Now you can use the injected JdbcTemplate directly.
<pre> public class JdbcConferenceDaoImpl implements ConferenceDao { @Autowired JdbcTemplate jdbcTemplate; @Override public Conference findById(Long id) { return jdbcTemplate.queryForObject( "select conference where id =?", new Object[] { id }, new ConferenceMapper()); } } </pre>
- A glance at the JdbcTemplate API.
JdbcTemaplate provides a series of methods for sql execution, the generic method is execute, there are several variants for accept different parameters. Some specific-purpose methods are also provided, such as:
query is designated for execute general select * from table statement.
<pre> public Conference findBySlug(String slug) { List<Conference> confs = getJdbcTemplate().query( "select * from conference where slug=?", new Object[] { slug }, new ConferenceMapper()); if (!confs.isEmpty()) { return confs.get(0); } return null; } </pre>
Generally, you can implement a RowMapper, RowCallbackHandler or ResultSetExtacter to extract the data from Jdbc ResultSet and wrap them into your own object.
<pre> private class ConferenceMapper implements ParameterizedRowMapper<Conference> { @Override public Conference mapRow(ResultSet rs, int rowNum) throws SQLException { Conference conference = new Conference(); conference.setName(rs.getString("name")); conference.setDescription(rs.getString("description")); conference.setSlug(rs.getString("slug")); conference.setStartedDate(rs.getDate("started_date")); conference.setEndedDate(rs.getDate("ended_date")); conference.setId(rs.getLong("id")); return conference; } } </pre>
There are some simplified queryForXXX methods, which are useful for some specific query, for example, in order to get the count of the table rows, you can use queryForInt to get the result directly.
And update is more easy when executing a insert, update, delete sql statement.
<pre> public void delete(final Long id) { getJdbcTemplate().update("delete from conference where id=?", id); } </pre>
The update method can accept a KeyHolder parameter to get the generated identity key from a insert sql.
<pre> public Long save(final Conference conference) { final String INSERT_SQL = "insert into conference (id, name, slug, description, started_date, ended_date, version) values (default, ?, ?, ?, ?, ?, 1) "; KeyHolder generatedKeyHolder = new GeneratedKeyHolder(); getJdbcTemplate().update(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement( Connection connection) throws SQLException { PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" }); ps.setString(1, conference.getName()); ps.setString(2, conference.getSlug()); ps.setString(3, conference.getDescription()); ps.setTimestamp(4, new java.sql.Timestamp( conference.getStartedDate().getTime())); ps.setTimestamp(5, new java.sql.Timestamp( conference.getEndedDate().getTime())); return ps; } }, generatedKeyHolder); return (Long) generatedKeyHolder.getKey(); } </pre>
batchUpdate is use for batch operations.
Let us have a look at the parameter classes of these methods.
PreparedStatementCreator is use for create a PreparedStatement object from the Jdbc Connection object.
PreparedStatementSetter is use for fill values into the PreparedStatement placeholder.
PreparedStatementCallback is use for gather the result of the execution.
There are also some method and classes are ready for Stored Procedure operations which are not covered here.
- The improved NamedParameterJdbcTemplate.
As the name indicates, it is a improved version of JdbcTemplate and allow you use named parameters in sql statement instead of the position based placeholder.
<pre> <bean id="namedParamJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTempl ate"> <constructor-arg index="0" ref="dataSource"> </constructor-arg> </bean> </pre>
The NamedParameterJdbcTemplate can accept a DataSource bean or classic JdbcTemplate as constructor arguments.
<pre> @Autowired NamedParameterJdbcTemplate namedParamJdbcTemplate; @Override public Conference findBySlug(String slug) { List<Conference> confs = namedParamJdbcTemplate.query( "select * from conference where slug=:slug", new MapSqlParameterSource().addValue("slug", slug), new ConferenceMapper()); if (!confs.isEmpty()) { return confs.get(0); } return null; } </pre>
Now the query method can accept a SqlParameterSource or a Map as arguments. Another improved JdbcDaoSupport version named NamedParameterJdbcDaoSupport is also provided.
As you see, these APIs are more friendly for developers.
###Hibernate support
Here we will use Hibernate 3 as example to demonstrate the HibernateDaoSupport API and HibernateTemplate.
NOTE: In Spring 3.1, Spring provides another ~.hibernate4 package to support the Hibernate 4 API, it does not include DaoSupport and Template at all, Spring recommend you use the native Hibernate API in new projects, I will demo it in the next post.
The HibernateDaoSupport and HibernateTemplate are very similar with the Jdbc ones.
- Register a DataSource bean, a Hibernate specific SessionFactory and declare a HibernateTransactionManager.
<pre> <jdbc:embedded-database id="dataSource"> </jdbc:embedded-database> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan"> <list> <value>com.hantsylabs.example.spring.model</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.format_sql=true hibernate.show_sql=true hibernate.hbm2ddl.auto=create hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider </value> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </pre>
- Write your implementation class which is extended HibernateDaoSupport class, and inject the SessionFactory bean.
<pre> public class Hibernate3ConferenceDaoImpl extends HibernateDaoSupport implements ConferenceDao { @Autowired public Hibernate3ConferenceDaoImpl( SessionFactory sessionFactory) { super(); setSessionFactory(sessionFactory); } } </pre>
Now you can use HibernateTemplate freely in your implementation class via getHibernateTemplate() method from HibernateDaoSupport.
Alternatively, you can declare a HiberanteTemplate bean and inject it into your implementation class, it is very similar with the scenario in the Jdbc section.
<pre> <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </pre>
Now you are free from subclassing HibernateDaoSupport, only need to implement your own interface.
<pre> public class HibernateTemplateConferenceDaoImpl implements ConferenceDao { @Autowired private HibernateTemplate hibernateTemplate; } </pre>
- HibernateTemplate API
In fact, the methods in HibernateTemplate class are very similar with the ones in Hibernate , but it provides more features, such as it support Spring exception translator.
You can use Hibernate Session API in a custom HibernateCallback inner class.
<pre> public Conference findBySlug(final String slug) { List<Conference> result = getHibernateTemplate().executeFind( new HibernateCallback<List<Conference>>() { @Override public List<Conference> doInHibernate(Session session) throws HibernateException, SQLException { return session .createQuery("from Conference where slug=:slug") .setParameter("slug", slug).list(); } }); if (!result.isEmpty()) { return result.get(0); } return null; } </pre>
###Jpa Support
Hibernate was approved as a big success in the latest years in the java communities, and finally most of the Hibernate APIs were standardized as JPA specification.
Some Hiberante concept can be mapped to JPA directly, for example, Session is equivalent to the EntityManager and SessionFactory acts as the same role of EntityManagerFactory.
Spring embrace Jpa at the first time.
It also provides JpaDaoSupport and JpaTemplate classes.
- Configure EntityManagerFactory and JpaTransactionManager beans.
<pre> <jdbc:embedded-database id="dataSource"> </jdbc:embedded-database> <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory"> <property name="persistenceUnitName" value="persistenceUnit" /> <property name="dataSource" ref="dataSource" /> <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence"></property> <property name="packagesToScan"> <array> <value>com.hantsylabs.example.spring.model</value> </array> </property> <property name="jpaProperties"> <value> hibernate.format_sql=true hibernate.show_sql=true hibernate.hbm2ddl.auto=create </value> </property> </bean> <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> </pre>
- Use JpaTemplate in your implementation class.
<pre> public class JpaConferenceDaoImpl extends JpaDaoSupport implements ConferenceDao { @Autowired public JpaConferenceDaoImpl(EntityManagerFactory emf) { super(); setEntityManagerFactory(emf); } @Override public Conference findById(Long id) { return (Conference) getJpaTemplate().find(Conference.class, id); } } </pre>
JpaTemplate almost copies the methods from EntityManager.
NOTE: In Spring 3.1, the JpaDaoSupport is marked as @Deprecated and not recommended in new pojects. In the future post, I will demonstrate how to use the native JPA API.
##DAO is dead?
If you search this topic by Google, you will get much disscussion and debate about this topic.
DAO(Date Access Object) is a classic J2EE pattern introduced in the famous Core J2EE Patterns book.
In the wikipedia page, DAO was described as:
In computer software, a data access object (DAO) is an object that provides an abstract interface to some type of database or other persistence mechanism.
In the next sections in the wiki page, JPA, JDO specifications and products, such as Hibernate, iBatis, Doctrine are considered as DAO implementation.
The world was changed when Java EE 5 was born, and EJB 3 is simplified dramatically, most of the classic J2EE patterns are proved they are anti-patterns, we have got POJO programming capability in the newest Java EE5/6. That means we do not needs the J2EE patterns.
wrote much blog entries about the newest Java EE patterns, and wrote down two books to refresh the Java EE patterns, including the alternative of DAO.
Obviously, we still needs DAO when you use Jdbc and other raw APIs in projects. But when we use JPA, the basic CURD operations is included in EntityManager, and fields are mapping to the table columns automatically, we do not need extra effort on them. The left is custom queries, we have to write them ourselves.
Adam Bien introduced a to resovle this issue, thus we can avoid to repeat the common operations.
In Spring ecosystem, the umbrella projects fill the blank table. I will introduce it later.
##SQLException Translator
In the Spring jdbc support, the most attractive feature is the encapsulation of SQLException.
Before Spring, if you coded against Jdbc API, you would get a SQLException with stupid error codes and descriptions which are very dependent on the Jdbc provider.
Spring redesigns the SQLExcdption processing and categorizes the exceptions and translates the SQLException into Spring friendly DataAccessException(and its subclasses).
Please explore the DataAccessException class and its subclasses for details.
##Summary Spring DaoSupport and Template had been widely used in projects for years, but in new projects, personally, I think the DaoSupport API should be avoided. The JpaDaoSupport is already marked as @Deprecated, and the HibernateDaoSupport also should be deprecated in future, there is no update for years.
You can switch to native API when you are using Hibernate and JPA. Spring Data is another alternative of data operations.