Contents
- Introduction
- Hibernate Configuration
- Defining an Interceptor
- Need for Interceptor
- Using Interceptor
- Creating Hibernate Factory
- Destroying Hibernate Factory
- Model
- Controller
- Database actions using Hibernate
- Querying database using Hibernate
Introduction
This article is same as the JTable Spring Demo (http://www.codeproject.com/Articles/1112115/JTable-Spring-Demo-with-database-integration). In this demo, Hibernate is used instead of Spring JdbcTemplate for database access.
Hibernate Configuration
Hibernate requires a set of properties to be defined in order to be configured. The configuration is done in hibernate.cfg.xml as follows:
xml=encoding=?
<!DOCTYPE hibernate-configuration SYSTEM
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">
org.hibernate.dialect.HSQLDialect
</property>
<property name="hibernate.connection.driver_class">
org.hsqldb.jdbcDriver
</property>
<property name="hibernate.connection.url">
jdbc:hsqldb:file:C:\DB\StudentDB;shutdown=true;hsqldb.default_table_type=cached;sql.enforce_strict_size=false
</property>
<property name="hibernate.connection.username">
sa
</property>
<property name="hibernate.connection.password">
</property>
</session-factory>
</hibernate-configuration>
Refer http://www.tutorialspoint.com/hibernate/hibernate_configuration.htm for more details.
Defining an Interceptor
Spring Interceptor is used to intercept requests and do some processing as desired then hand it over to the Controller for fulfilling the request. In order to do so, an object of SpringRequestInterceptor was created and added to the registry. Interceptor is defined in SpringConfiguration.java as follows:
@Import({SpringSecurityConfig.class})
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"controller"})
public class SpringConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
SpringRequestInterceptor interceptor = new SpringRequestInterceptor();
registry.addInterceptor(interceptor);
}
}
Need for Interceptor
Spring provides HttpServletRequest, HttpServletResponse, HttpSession and ServletContext objects. These objects are required to create Hibernate session. However, in order to use these objects, they need to be passed to a method and then used as per Spring requirement. Since the code has various methods that require these objects, to avoid passing them as parameters successively, the interceptor is used. Before any HTTP request is processed, the interceptor is called which in turn calls the desired method to store the objects and close the session as required.
Using Interceptor
An interceptor can be created by overriding methods of the abstract HandlerInterceptorAdapter or by writing HandlerInterceptor interface. The abstract class provides the basic implementation of the HandlerInterceptor interface. Here two methods (preHandle() and postHandle()) of HandlerInterceptorAdapter are overridden.
When the interceptor is called, SpringRequestInterceptor.java checks if the handler is pointing to BaseController. If it is, then preHandle() and postHandle() methods on the BaseController are called before and after processing any request respectively.
public class SpringRequestInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Object bean = handlerMethod.getBean();
if (bean instanceof BaseController) {
BaseController controller = (BaseController) bean;
return controller.preHandle(request, response, handler);
}
}
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Object bean = handlerMethod.getBean();
if (bean instanceof BaseController) {
BaseController controller = (BaseController) bean;
controller.postHandle(request, response, handler, modelAndView);
}
}
}
For any HTTP request, before it is processed the preHandle() method of the interceptor defined in BaseController is called. The HttpServletRequest, HttpServletResponse, HttpSession and ServletContext objects are passed to it and stored to be used later. postHandle() method is called for closing the Hibernate session after processing the request.
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
this.request = request;
this.response = response;
httpSession = request.getSession(true);
httpContext = request.getServletContext();
session = null;
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
try {
session.close();
session = null;
request = null;
response = null;
httpSession = null;
httpContext = null;
}
catch (Exception ignore) {
}
}
Creating Hibernate Factory
The code snippet below shows how to create Factory. The Factory is time-consuming and heavy to create, creating it for every request is not suggested since it makes the application slow. The Factory is created once and stored in the application and used as required.
In order to create Factory only once, SessionFactory attribute is set after the Factory is created for the first time and stored in ServletContext. Then every other time, SessionFactory attribute is checked. If it exists then we use the already existing one else new Factory is created.
.addAnnotatedClasses is use to map classes to databases.
getFactory() of BaseController.java
public static SessionFactory getFactory(ServletContext httpContext) {
SessionFactory factory = (SessionFactory) httpContext.getAttribute("SessionFactory");
if (factory != null) return factory;
StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
registryBuilder.configure();
StandardServiceRegistry registry = registryBuilder.build();
MetadataSources metadataSources = new MetadataSources(registry);
metadataSources.addAnnotatedClass(City.class);
metadataSources.addAnnotatedClass(Course.class);
metadataSources.addAnnotatedClass(Student.class);
metadataSources.addAnnotatedClass(StudentPhone.class);
metadataSources.addAnnotatedClass(StudentResult.class);
metadataSources.addAnnotatedClass(User.class);
Metadata metadata = metadataSources.buildMetadata();
factory = metadata.buildSessionFactory();
httpContext.setAttribute("SessionFactory", factory);
return factory;
}
Destroying Hibernate Factory
The Factory needs to be destroyed when the application is closed. ServletContext is the common area of the application. The Factory is stored in ServletContext. When the context is destroyed then the factory is also destroyed if it exists. Attribute “SessionFactory” is also removed. Annotation @WebListener makes a class ServletContextListener (contextInitialized and contextDestroyed methods gets called).
contextDestroyed() of SpringContextListener.java
@Override
public void contextDestroyed(ServletContextEvent event) {
ServletContext httpContext = event.getServletContext();
SessionFactory factory = (SessionFactory) httpContext.getAttribute("SessionFactory");
if (factory != null) {
factory.close();
httpContext.removeAttribute("SessionFactory");
}
}
Model
Student.java is created to be mapped to Student table. @Entity and @Table annotation are used to indicate the creation of table “student”. @Id, @GeneratedValue, and @Column annotation are used to indicate self-generating primary key for a primary key column. The code snippet shows only a few fields. Refer Student.java in the source code for the actual code.
Student.java
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
public int id;
@NotNull
@Size(min = 2, max = 30)
public String name;
@NotNull
@Email
public String email;
@NotNull
public String password;
}
Similarly, tables are created for
- City with fields id, name.
- StudentResults with fields id, student_id, course_name, exam_date,degree.
- StudentPhone with fields id, student_id, phone_type, phone_number, record_date.
- Course with fields id, name.
Getter and setter methods were added to all the classes. jTable allows master/child tables (http://jtable.org/Demo/MasterChild). StudentPhone and StudentResults are defined for child tables.
Controller
Each controller has three actions defined: List, Save, Delete.
StudentController.java
List()
@ResponseBody
@RequestMapping(value = "List")
public JTableResult List(JTableRequest jTableRequest) {
JTableResult rslt = new JTableResult();
try {
createSession();
return Student.retrievePage(session, jTableRequest);
} catch (Throwable ex) {
rslt.Result = "Error";
rslt.Message = exceptionToString(ex);
return rslt;
}
}
RetrievePage method will return records based on the paging and sorting parameters.
Save()
@ResponseBody
@RequestMapping(value = "Save")
public JTableResult Save(@Valid Student student, BindingResult bindingResult) {
if (bindingResult.hasErrors()) return toError(bindingResult);
int action = Integer.parseInt(request.getParameter("action"));
if (student.active_flg == null) student.active_flg = "N";
if (action == 1) {
student.record_date = new Date();
return insert(student);
} else {
createSession();
student.record_date = Student.getRecordDateById(session, student.id);
return update(student);
}
}
The getrecorddatebyid() method gets the original record_date for the record that is being updated. Save method calls the insert or the update method of BaseController.java based on the parameter value of action passed.
Delete()
@ResponseBody
@RequestMapping(value = "Delete")
public JTableResult Delete(int id) {
Student student = new Student();
student.id = id;
return delete(student);
}
Delete method of BaseController.java is called.
Database actions using Hibernate
For every database action, a session is created. Once the transaction begins, the desired action is taken ( Ex. save, update or delete) then the action is committed. Once the request is processed, the postHandle method closes the session.
BaseController.java
Inserting a record in the database using Hibernate.
public JTableResult insert(Object obj) {
JTableResult rslt = new JTableResult();
try {
createSession();
session.getTransaction().begin();
session.save(obj);
session.getTransaction().commit();
rslt.Result = "OK";
rslt.Record = obj;
return rslt;
} catch (Throwable ex) {
rslt.Result = "Error";
rslt.Message = exceptionToString(ex);
return rslt;
}
Updating and deleting record in database using Hibernate can done similarly by using .update(obj) and .delete(obj).
Querying database using Hibernate
In order to get total no. of records for each table, a query was executed.
retrievePage() of Student.java gets the total no. of records as follows:
rslt.TotalRecordCount = ((BigInteger) session.createNativeQuery("Select count(*) From student").getSingleResult()).intValue()
.createNativeQuery allows querying the database. The obtained result can be stored according to what is given in “Select” part of the query and no. of records that will be returned.
Source Code
Code is available at: https://github.com/pujagani/JTableSpringHibernateDemo