Hibernate Interview Questions and Answers
Hibernate Interview Questions and Answers
If you ask me to list two frameworks that are very prevalently used in Java projects and
very popular in job interviews, it would be (1) Spring framework (2)
Hibernate framework.
Mapping files (*.hbm.xml): These files are used to map persistent objects to a relational
database. It is the best practice to store each object in an individual mapping file (i.e
mapping file per class) because storing large number of persistent classes into one
mapping file can be difficult to manage and maintain. The naming convention is to use
the same name as the persistent (POJO) class name. For example Account.class will have
a mapping file named Account.hbm.xml. Alternatively, hibernate annotations can be used
as part of your persistent class code instead of the *.hbm.xml files.
view plainprint?
1. public class HibernateUtil {
2.
3. public static final ThreadLocal local = new ThreadLocal();
4.
5. public static Session currentSession() throws HibernateException {
6. Session session = (Session) local.get();
7. //open a new session if this thread has no session
8. if(session == null) {
9. session = sessionFactory.openSession();
10. local.set(session);
11. }
12. return session;
13. }
14. }
It is also vital that you close your session after your unit of work completes.
Note: Keep your Hibernate Session API handy. Quite often, hibernate is used with Spring
framework, using the Template design pattern.
Q. Explain hibernate object states? Explain hibernate objects life cycle?
A.
Pros:
When long transactions are required due to user think-time, it is the best practice
to break the long transaction up into two or more transactions. You can use
detached objects from the first transaction to carry data all the way up to the
presentation layer. These detached objects get modified outside a transaction and
later on re-attached to a new transaction via another session.
Cons:
In general, working with detached objects is quite cumbersome, and it is better
not to clutter up the session with them if possible. It is better to discard them and
re-fetch them on subsequent requests. This approach is not only more portable but
also more efficient because - the objects hang around in Hibernate's cache
anyway.
Also from pure rich domain driven design perspective, it is recommended to use
DTOs (DataTransferObjects) and DOs (DomainObjects) to maintain the
separation between Service and UI tiers.
view plainprint?
1. Session session1 = sessionFactory.openSession();
2. Car myCar = session1.get(Car.class, carId);//”myCar” is a persistent object at this
stage.
3. session1.close(); //once the session is closed “myCar” becomes a detached o
bject
you can now pass the “myCar” object all the way upto the presentation tier. It can be
modified without any effect to your database table.
view plainprint?
1. myCar.setColor(“Red”); //no effect on the database
When you are ready to persist this change to the database, it can be reattached to another
session as shown below:
view plainprint?
1. Session session2 = sessionFactory.openSession();
2. Transaction tx = session2.beginTransaction();
3. session2.update(myCar); //detached object ”myCar” gets re-attached
4. tx.commit(); //change is synchronized with the database.
5. session2.close()
Q. How does Hibernate distinguish between transient (i.e. newly instantiated) and
detached objects?
A.
Hibernate uses the "version" property, if there is one.
If not uses the identifier value. No identifier value means a new object. This does
work only for Hibernate managed surrogate keys. Does not work for natural keys
and assigned (i.e. not managed by Hibernate) surrogate keys.
Write your own strategy with Interceptor.isUnsaved( ).
Note: When you reattach detached objects, you need to make sure that the dependent
objects are reattached as well.
?
<property name="hibernate.cache.use_second_level_cache">true</property>
1 <property
2 name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
?
1 <cache name="com.myapp.Order"
maxElementsInMemory="300"
2
eternal="true"
3 overflowToDisk="false"
4 timeToIdleSeconds="300"
5 timeToLiveSeconds="300"
6 diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
7
memoryStoreEvictionPolicy="LRU"
8
9 />
10
second-level cache reduces the database traffic by caching loaded objects at the
SessionFactory level between transactions. These objects are available to the whole
application, not just to the user running the query. The 'second-level' cache exists as long
as the session factory is alive. The second-level cache holds on to the 'data' for all
properties and associations (and collections if requested) for individual entities that are
marked to be cached. It is imperative to implement proper cache expiring strategies as
caches are never aware of changes made to the persistent store by another application. he
following are the list of possible cache strategies.
Read-only: This is useful for data that is read frequently, but never updated. This
is the most simplest and best-performing cache strategy.
Nonstrict read/write: This is most appropriate for data that is read often but only
occasionally modified.This strategy does not guarantee that two transactions
won't simultaneously modify the same data.
Transactional: This is a fully transactional cache that may be used only in a JTA
environment.
?
1 <class name="com.myapp.Order">
2
3 <cache usage="read-write"/>
4 ....
</class>
5
?
1 public final class Hibernate extends Object {
2 ....
3 public static void initialize(Object proxy) throws HibernateException
4 ....
}
5
As a consequence of using the Hibernate second-level cache, you have to be aware of the
fact that each call of a data access method can either result in a cache hit or miss. So,
configure your log4j.xml to log your hits and misses.
?
1 <logger name="org.hibernate.cache">
2 <level value="DEBUG" />
3 </logger>
Alternatively, you can use Spring AOP to log the cache access on your DAO methods.
The second level cache is a powerful mechanism for improving performance and
scalability of your database driven application. Read-only caches are easy to handle,
while read-write caches are more subtle in their behavior. Especially, the interaction with
the Hibernate session can lead to unwanted behavior.
?
1 Query query = session.createQuery("from Order as o where o.status=?");
2 query.setInt(0, "Active");
3 query.setCacheable(true); // the query is cacheable
4 List l = query.list();
You also have to change the hibernate configuration to enable the query cache. This is
done by adding the following line to the Hibernate configuration.
<property name="hibernate.cache.use_query_cache">true</property>
1. Set entity’s keys as query parameters, rather than setting the entire entity object.
Critreia representations should also use identifiers as parameters. Write HQL queries to
use identifiers in any substitutable parameters such as WHERE clause, IN clause etc.
In the example below, the entire customer and everything he/she references would be
held in cache until either the query cache exceeds its configured limits and it is evicted,
or the table is modified and the results become dirty.
?
1 final Customer customer = ... ;
2 final String hql = "FROM Order as order WHERE order.custOrder = ?"
3 final Query q = session.createQuery(hql);
4 q.setParameter(0, customer);
q.setCacheable(true);
5
Instead of setting the whole customer object as shown above, just set the id.
?
1 final Order customer = ... ;
2 final String hql = "from Order as order where order.cusomer.id = ?"
3 final Query q = session.createQuery(hql);
4 q.setParameter(0, customer.getId());
q.setCacheable(true);
5
1. Session.load will always try to use the cache. Session.find does not use the cache for
the primary object, but cause the cache to be populated. Session.iterate always uses the
cache for the primary object and any associated objects.
2. While developing, enable the show SQL and monitor the generated SQL.
<property name="show_sql">true</property>
Also enable the "org.hibernate.cache" logger in your log4j.xml to monitor cache hits
and misses.
?
1
2 <!-- hibernate properties-->
3 <bean id="myappHibernateProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
4 <property name="properties">
5 <props>
6 <prop key="hibernate.dialect">org.hibernate.dialect.SybaseDialect</pr
7 <prop key="hibernate.show_sql">false</prop>
<prop
8
key="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvide
9 </props>
10 </property>
11 </bean>
?
1
2
3 <!-- datasource configured via JNDI-->
4 <bean id="myappDataSource"
5 class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
6 <value>java:comp/env/jdbc/dataSource/myapp-ds</value>
7 </property>
8 </bean>
STEP 3: The hibernate mapping files. The mapping file tells Hibernate what table in the
database it has to access, and what columns in that table it should use.
?
1
2
3 <bean name="myappHibernateMappingFiles"
4 class="org.springframework.beans.factory.config.ListFactoryBean">
5 <property name="sourceList">
6 <<span class="IL_AD" id="IL_AD6">list</span>>
<value>hbm/Order.hbm.xml</value>
7 <value>hbm/Trade.hbm.xml</value>
8 <value>hbm/Customer.hbm.xml</value>
9 </list>
10 </property>
</bean>
11
?
1
2 <!-- An interceptor that does nothing. May be used as a base class
3 for application-defined custom interceptors. -->
4 <bean id="myappEntityInterceptor"
class="org.hibernate.EmptyInterceptor" />
?
1
2
3 <!-- Define the session factory -->
4 <bean name="myappSessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"
5 autowire="byName">
6 <property name="hibernateProperties">
7 <ref bean="myappHibernateProperties"/>
8 </property>
<property name="dataSource">
9 <ref bean="myappDataSource"/>
10 </property>
11 <property name="mappingResources">
12 <ref bean="myappHibernateMappingFiles"/>
13 </property>
<property name="entityInterceptor">
14 <ref bean="myappEntityInterceptor"/>
15
16 </property>
17 </bean>
STEP 6: The hibernate template. Helper class that simplifies Hibernate data access code.
?
1
2
3 <!-- Hibernate Template -->
4 <bean name="myappHibernateTemplate"
5 class="org.springframework.orm.hibernate3.HibernateTemplate"
6 autowire="no" scope="prototype">
<property name="sessionFactory">
7
<ref bean="myappSessionFactory"/>
8 </property>
9 <property name="allowCreate">
10 <value>false</value>
11 </property>
<property name="maxResults">
12 <value>50000</value>
13 </property>
14 <property name="flushMode">
15 <value>3</value>
16 </property>
</bean>
17
1
2 <!-- Constructor Inject the Template into your Repository classes
3 (Data acess layer) -->
<bean id="myappOrderRepository"
4 class="com.HibernateOrderRepository">
5 <constructor-arg><ref bean="myappHibernateTemplate"
6 /></constructor-arg>
</bean>
?
1
2
3 <!-- Service Layer: myappOrderRepository is injected via setter
4 injection -->
<bean id="myappOrderService" class="com.OrderServiceImpl">
5 <property><ref bean="myappOrderRepository" /></property>
6 </bean>
7
STEP 9: The repository interface and class that makes use if the hibernate template.
?
1
2 package com;
3
4 import com.ObjectNotFoundException;
5
6 public interface OrderRepository {
7 public Order load(Long identity) throws ObjectNotFoundException;
public void save(Order order);
8
}
9
The template.load, template.save, etc are database operations via the HibernateTemplate.
?
1
2 package com;
3
4 import org.apache.log4j.Logger;
import
5 org.springframework.orm.hibernate3.HibernateObjectRetrievalFailureException;
6 import org.springframework.orm.hibernate3.HibernateTemplate;
7
8 import com.ObjectNotFoundException;
9 import com.Order;
10 import com.OrderRepository;
11
12 public class HibernateOrderRepository implements OrderRepository {
13
14 private static final Logger LOG =
15 Logger.getLogger(HibernateOrderRepository.class);
16
17 private final HibernateTemplate template;
18
19
20 public HibernateOrderRepository(HibernateTemplate template) {
this.template = template;
21 }
22
23 @Override
24 /**
25 * Read Order from database
26
27
28 */
29 public Order load(Long identity) throws ObjectNotFoundException {
30 if (LOG.isDebugEnabled()) {
LOG.debug("Loading " + identity);
31
}
32 try {
33 return template.load(Order.class,identity);
34 } catch (HibernateObjectRetrievalFailureException e) {
35 throw new ObjectNotFoundException(identity + " not found", e);
}
36 }
37
38 /**
39 * Save the record to database
40 * @param order */
41 public void save(Order order) {
if (LOG.isDebugEnabled()) {
42 LOG.debug("Saving " + order);
43 }
44 template.save(order);
45 }
46 }
47
48
?
1 <!-- Transaction Manger -->
<bean name="myappTransactionManager"
2 class="org.springframework.orm.hibernate3.HibernateTransactionManager">
3 <property name="sessionFactory" ref="myappSessionFactory" />
4 </bean>
STEP 2: Create a transaction interceptor that uses the above transaction manager.
?
1 <bean id="myappHibernateInterceptor"
2 class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="myappTransactionManager" />
3 <property name="transactionAttributes">
4 <props>
5 <prop key="*">PROPAGATION_REQUIRED,-Exception</prop>
6 </props>
7 </property>
8 </bean>
STEP 3: Create other optional interceptors for logging, deadlock retry, etc.
?
<!-- SERVICE INTERCEPTORS -->
1
2 <bean id="myappLoggingInterceptor"
3 class="com.MyAppLoggingInterceptor" />
4 <bean id="myappDeadlockRetryInterceptor"
class="com.OracleDeadlockRetryInterceptor" />
?
1 <!-- SERVICES -->
2
3 <bean id="myappAbstractService" abstract="true"
4 class="org.springframework.aop.framework.ProxyFactoryBean">
5 <property name="interceptorNames">
<list>
6 <value>myappLoggingInterceptor</value>
7 <value>myappDeadlockRetryInterceptor</value>
8 <value>myappHibernateInterceptor</value>
9 </list>
10 </property>
</bean>
11
STEP 5: The concrete service that uses the myappOrderRepository along with the
interceptors. Especially the "myappHibernateInterceptor" that performs transaction
demarcation.
?
1 <bean id="myappOrderService" parent="myappAbstractService">
2 <property name="proxyInterfaces">
3 <value>com.mgl.mts.oms.model.service.OrderService</value>
4 </property>
<property name="target">
5 <bean class="com.OrderServiceImpl">
6 <constructor-arg><ref bean="myappOrderRepository"
7 /></constructor-arg>
8 </bean>
9 </property>
</bean>
10
STEP 6 The OrderServiceImpl class looks like
?
1 package com;
2
3 import ....
4
5 public class OrderServiceImpl implements OrderService {
6
7 private final Logger LOG =
Logger.getLogger(OrderServiceImpl.class);
8
9 private final OrderRepository orderRepository;
10
11 public OrderServiceImpl(OrderRepository orderRepository) {
12 this.orderRepository = orderRepository;
13 }
14
15 //......
}
16
Note: The OrderServiceImpl will have the interceptors turned using AOP to manage
transaction, logging, and deadlock retry. The diagram below gives a big picture of
interceptors, service, and repository.
Note: The deadlock retry filter is an interesting one. When an exception is thrown, it is
inspected using a pattern matching (i.e. regular expression) to see if it is due to deadlock.
If it is due to deadlock the invocation is repeated. This makes the call again to the target,
which is the OrderServiceimpl via the TransactionInterceptor, which starts a new
transaction.
?
1 package com.test;
2
3 import org.aopalliance.intercept.MethodInterceptor;
4 import org.aopalliance.intercept.MethodInvocation;
5 import org.apache.log4j.Logger;
6
7
8 public class MyAppLoggingInterceptor implements MethodInterceptor {
9
private static final Logger LOG =
10 Logger.getLogger(MyAppLoggingInterceptor.class);
11
12 @Override
13 public Object invoke(MethodInvocation invocation) throws Throwable
14 {
long begin = System.currentTimeMillis();
15
16 //proceed to the next interceptor on the chain
17 Object result = invocation.proceed();
18
19 long end = System.currentTimeMillis();;
20
21 LOG.info("Time elapsed " + (end - begin) + " ms");
22
23 return result;
}
24
25
}
26
?
1 package com.test;
2
3 import java.sql.SQLException;
4
import org.aopalliance.intercept.MethodInterceptor;
5 import org.aopalliance.intercept.MethodInvocation;
6 import org.apache.log4j.Logger;
7
8
9 public class OracleDeadlockRetryInterceptor implements
10 MethodInterceptor {
11
12 private static final Logger LOG =
13 Logger.getLogger(OracleDeadlockRetryInterceptor.class);
14
private int attempts = 3;
15
16 @Override
17 public Object invoke(MethodInvocation invocation) throws Throwable
18 {
19 return doInvoke(invocation, 1);
}
20
21
private Object doInvoke(MethodInvocation invocation, int count)
22 throws Throwable {
23 try {
24 //proceed to next interceptor
25 return invocation.proceed();
} catch (Exception exception) {
26 if (!isDeadlockException(exception)) {
27 throw exception;
28 }
29 LOG.warn("A Database deadlock occured. Will try again.",
30 exception);
if (count < attempts) {
31 count++;
32 return doInvoke(invocation, count);
33 }
34 throw new SQLException("Service Invocation failed " +
attempts
35
+ " times with a SQLException.", exception);
36 }
37 }
38
39 }
Firstly, define a parent domain object class for any common method implementations.
?
1 package com.myapp.domain.model;
2
3 public class MyAppDomainObject {
4
//for example
5 protected boolean isPropertyEqual(Object comparee, Object
6 compareToo) {
7 if (comparee == null) {
8 if (compareToo != null) {
return false;
9 }
10 } else if (!comparee.equals(compareToo)) {
11 return false;
12 }
13 return true;
}
14
15 }
16
17
?
1 package com.myapp.domain.model;
2
3 @Entity
@org.hibernate.annotations.Entity(selectBeforeUpdate = true)
4 @Table(name = "tbl_employee")
5 @TypeDefs(value = { @TypeDef(name = "dec", typeClass =
6 DecimalUserType.class)}) // custom data type conversion
7
8 @NamedNativeQueries({
@NamedNativeQuery(name = "HighSalary", query = "select * from
9 tbl_employee where salary > :median_salary " , resultClass =
10 Employee.class),
11 @NamedNativeQuery(name = "LowSalary", query = "select * from
12 tbl_employee where salary < :median_salary " , resultClass =
13 Employee.class)
})
14
15 public class Employee extends MyAppDomainObject implements Serializable
16 {
17
18 @Id
19 @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "employee_id")
20
private Long id;
21
22 @Column(name = "emp_code")
23 private String accountCode;
24
25 @Column(name = "manager_code")
26 private String adviserCode;
27
28
29
30 @Column(name = "type")
@Enumerated(EnumType.STRING)
31 private EmployeeType type = EmployeeType.PERMANENT;
32
33
34 @Type(type = "dec")
35 @Column(name = "base_salary")
36 private Decimal salary = Decimal.ZERO;
37
38 @Transient
private Decimal salaryWithBonus; //not persisted to database
39
40
@Formula("base_salary*2")
private Decimal doubleSalary; //derived or calculated read only
property
41
42 @Formula("(select base_salary where type = 'Permanent' )")
43 private Decimal permanantLeaveLoading; //derived or calculated
44 read only property
45
46
47 @OneToOne(cascade = { CascadeType.REFRESH })
@JoinColumn(name = "emp_code", insertable = false, updatable =
48
false)
49 private EmployeeExtrInfo extraInfo;
50
51 @ManyToOne(cascade = { CascadeType.REFRESH })
52 @JoinColumn(name = "manager_code", insertable = false, updatable =
53 false)
private Manager manager;
54
55 @OneToMany(cascade = { ALL, MERGE, PERSIST, REFRESH }, fetch =
56 FetchType.LAZY)
57 @JoinColumn(name = "emp_code", nullable = false)
58 @Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE,
59 org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private <span class="IL_AD" id="IL_AD6">List</span><PaymentDetail>
60 paymentDetails = new ArrayList<PaymentDetail>();
61
62 //getters and setters omitted for brevity
}
The dependency classes like EmployeeExtrInfo, Manager, and PaymentDetail will be
mapped in a similar manner as the Employee class. The EmployeeType enum class is
shown below. Also note the verys usefull annotations like @NamedNativeQueries,
@TypeDefs, and @Formula. The @Formula marks a property as derived, or calculated,
read-only property, where its value is calculated at fetch time using SQL expressions.
?
1 package com.myapp.domain.model;
2
3
4 public enum EmployeeType {
5
PERMANENT("Permanent"),
6 CONTRACTOR("Contractor"),
7 CASUAL("Casual");
8
9 private String type;
10
11 private EmployeeType (String type) {
this.type = type;
12
}
13
14
15
16 public String getType() {
return this.type;
17 }
18 }
19
The "dec" is a custom data type, you need to define the custom data type class. The
"salary" attribute will be making use of this special data type. This is ust a trivial
example, but more powerful custom type conversion classes can be created.
?
1 package com.myapp.domain.model;
2
3 import java.io.Serializable;
import java.math.BigDecimal;
4 import java.sql.PreparedStatement;
5 import java.sql.ResultSet;
6 import java.sql.SQLException;
7 import java.sql.Types;
import java.util.Properties;
8
9 import org.hibernate.usertype.ParameterizedType;
10 import org.hibernate.usertype.UserType;
11
12 public class DecimalUserType implements UserType, ParameterizedType {
13 public static final int PRECISION = 28;
14 public static final int SCALE = 15;
15
public int[] sqlTypes() {
16 return new int[]{Types.DECIMAL};
17 }
18
19 public Class<Decimal> returnedClass() {
20 return BigDecimal.class;
}
21
22 public boolean equals(Object x, Object y) {
23 if (x == y) {
24 return true;
25 }
26 if (x == null || y == null) {
return false;
27 }
28 return x.equals(y);
29 }
30
31 public int hashCode(Object x) {
32 return 0;
}
33
34 public Object nullSafeGet(ResultSet rs, String[] names, Object
35 owner) throws SQLException {
36 BigDecimal forReading = rs.getBigDecimal(names[0]);
37
38 if (forReading == null) {
return null;
39
}
40
41 return forReading.setScale(2, RoundingMode.HALF_EVEN);
42 //round to 2 decimal places
43 }
44
45
46 public void nullSafeSet(PreparedStatement st, Object value, int
index) throws SQLException {
47 if (value == null) {
48 st.setNull(index, Types.NUMERIC);
49 return;
50 }
51
52
53 BigDecimal forSaving = (BigDecimal) value;
st.setBigDecimal(index, forSaving.setScale(2,
54 RoundingMode.HALF_EVEN));
55 }
56
57 public Object deepCopy(Object value) {
58 return value;
59 }
60
public boolean isMutable() {
61 return false;
62 }
63
64 public Serializable disassemble(Object value) {
65 return null;
}
66
67 public Object assemble(Serializable cached, Object owner) {
68 return null;
69 }
70
71 public Object replace(Object original, Object target, Object
72 owner) {
return original;
73 }
74
75 public void setParameterValues(Properties parameters) {
76 }
77 }
78
79
80
81
82
83
84
The named queries are also shown above with the @NamedNativeQueries and
@NamedNativeQuery annotations. The parametrized values like :median_salary needs to
be supplied via the Hibernate repository class that makes use of the Employee domain
object. Firstly define the interface.
?
1 package com.myapp.domain.repo;
2
import java.util.List;
3
4
5 public interface EmployeeTableRepository {
6
7 Employee saveEmployee(Employee employee) throws RepositoryException ;
8 Employee loadEmployee(Long employeeId) throws RepositoryException ;
9 List<Employee> findAllEmployeesWithHighSalary(BigDecimal
10 medianSalary) throws RepositoryException;
List<Employee> findAllEmployeesWithLowSalary(BigDecimal medianSalary)
11 throws RepositoryException
12
13 }
Q. How will you wire up the code snippet discussed above using Spring?
A. The following 3 Spring configuration files are used for wiring up the classes defined
above.
The daoContext.xml file to define the hibernate session factory, jndi data source,
hibernate properties, and the user defined domain class and the repository.
The transactionContext.xml file to define the transaction manager.
The servicesContext.xml to define the custom services class
?
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spr
2.5.xsd">
<bean id="hibernateProperties"
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="properties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SybaseDialect</prop>
<prop key="hibernate.generate_statistics">false</prop>
<prop key="hibernate.hbm2ddl.auto">verify</prop>
<prop key="hibernate.jdbc.batch_size">50</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop
key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFact
</props>
</property>
<property name="location">
<value>classpath:/hibernate.properties</value>
</property>
</bean>
<bean id="hibernateAnnotatedClasses"
class="org.springframework.beans.factory.config.ListFactoryBean">
<property name="sourceList">
<list>
<value>com.myapp.domain.model.Employee</value>
</list>
</property>
</bean>
<bean id="hibernateSessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSourceShadow" />
<property name="hibernateProperties">
<ref local="hibernateProperties" />
</property>
<property name="entityInterceptor">
</property>
<property name="annotatedClasses">
<ref local="hibernateAnnotatedClasses" />
</property>
<property name="annotatedPackages">
<list></list>
</property>
</bean>
</beans>
The transactionContext.xml
?
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="advisorAutoProxy"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
<bean id="transactionAttrSource"
class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref="transactionInterceptor" />
</bean>
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributeSource">
<bean
class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"
/>
</property>
</bean>
</beans>
?
1 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
2
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:context="http://www.springframework.org/schema/context"
4 xmlns:aop="http://www.springframework.org/schema/aop"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
7 http://www.springframework.org/schema/context/spring-context-
8 2.5.xsd
9 http://www.springframework.org/schema/aop
10 http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
11
12 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
13 <!-- CONFIGURE SERVICE BEANS -->
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
14 <bean id="employeeService" class="com.myapp.service.EmployeeService">
15 <constructor-arg ref="employeeTableRepository" />
16 <constructor-arg ref="transactionManager" />
17 </bean>
18
19 </beans>
Q. How will you go about writing an integration or unit test for the EmployeeService
described above?
A. Since the dataSource is looked up via JNDI, you need to emulate the JNDI lookup.
This can be achieved with the Spring helper classes SimpleNamingContextBuilder and
DriverManagerDataSource.
Define a bootsrapper class that emulates JNDI lookup using Spring helper classes
like SimpleNamingContextBuilder and DriverManagerDataSource. For example,
SybaseDevBootstrapper.java file.
Wire-up this via a Spring config file named sybaseDevBootstrapContext.xml.
Finally, write the JUnit test class EmployeeServicesSybTest.java.
Define the TestExecutionListeners if required.
?
package com.myapp.test.db;
import javax.naming.NamingException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
/**
* helper class to bootstrap the sybase database datasources
*/
public class SybaseDevBootstrapper {
} catch (NamingException e) {
throw new BeanCreationException(e.getExplanation());
}
}
?
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-
2.5.xsd"
default-autowire="byName">
<bean id="sybaseDevBootstrapper"
class="com.myapp.test.db.SybaseDevBootstrapper" init-method="start"
destroy-method="stop"/>
</beans>
import javax.annotation.Resource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import
org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
//...other imports
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
"classpath:/sybaseDevBootstrapContext.xml",
"classpath:/transactionContext.xml",
"classpath:/daoContext.xml",
"classpath:/servicesContext.xml",
})
@TestExecutionListeners(value = {
DependencyInjectionTestExecutionListener.class,
SessionBindingHibernateListener.class
})
@Resource
EmployeeService employeeService;
@Test
public void testSaveEmployee() throws RepositoryException {
Assert.assertTrue(employeeService != null);
employeeService.seaveEmployee(employee);
The JUnit's way of setting up cross cutting concerns like security, locale, currency,
timezone, and any other pre-initilization rules for the test cases function correctly is via
annoattions like @Before and @After. The Spring's TestContext framework uses the
anootation @TestExecutionListeners to acheive setting up of these cross cutting
concerns. In the above example, we are using the
DependencyInjectionTestExecutionListener.class from the Spring framework to provide
support for dependncy injection and the custom SessionBindingHibernateListener.class to
bind the session to the current thread. The custom implementation shown below extends
the AbstractTestExecutionListener, which is the abstract implementation of
TestExecutionListener class from the Spring framework.
?
/**
* Helper class for binding sessions to the current thread
*
*/
public class SessionBindingHibernateListener extends
SessionBindingListener {
public SessionBindingHibernateListener() {
super(BEAN_NAME);
}
}
?
1 import org.hibernate.FlushMode;
2 import org.hibernate.Session;
import org.hibernate.SessionFactory;
3 import org.springframework.context.ApplicationContext;
4 import org.springframework.orm.hibernate3.SessionFactoryUtils;
5 import org.springframework.orm.hibernate3.SessionHolder;
6 import org.springframework.test.context.TestContext;
import
7 org.springframework.test.context.support.AbstractTestExecutionListener;
8 import
9 org.springframework.transaction.support.TransactionSynchronizationManager;
10
11
12 /**
13 * Helper class for binding sessions to the current thread
*
14 */
15 public class SessionBindingListener extends AbstractTestExecutionListener {
16
17 private final String beanName;
18
19 public SessionBindingListener(String beanName) {
20 this.beanName = beanName;
}
21
22 @Override
23 public void prepareTestInstance(TestContext testContext) throws Exception
24 {
25 ApplicationContext context = testContext.getApplicationContext();
SessionFactory sessionFactory = (SessionFactory)
26 context.getBean(beanName);
27
28 Session session = SessionFactoryUtils.getSession(sessionFactory,
29 true);
30 session.setFlushMode(FlushMode.MANUAL);
31 if (!
TransactionSynchronizationManager.hasResource(sessionFactory)) {
32 TransactionSynchronizationManager.bindResource(sessionFactory,
33 new SessionHolder(session));
34 }
35 }
}
?
1
2 package com.myapp.deadlock;
3
4 import java.lang.annotation.ElementType;
5 import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
6 import java.lang.annotation.RetentionPolicy;
7 import java.lang.annotation.Target;
8
9 @Retention(RetentionPolicy.RUNTIME)
10 @Target(ElementType.METHOD)
@Inherited
11 public @interface DeadlockRetry {
12
13 int maxTries() default 21;
14 int tryIntervalMillis() default 100;
15
16 }
17
?
1 package com.myapp.deadlock;
2
3 import java.lang.reflect.Method;
4
import org.aopalliance.intercept.MethodInterceptor;
5 import org.aopalliance.intercept.MethodInvocation;
6 import org.slf4j.Logger;
7 import org.slf4j.LoggerFactory;
8 import org.hibernate.exception.LockAcquisitionException;
import org.springframework.dao.DeadlockLoserDataAccessException;
9
10
11 public class DeadlockRetryMethodInterceptor implements
12 MethodInterceptor {
13
14 private static final Logger LOGGER =
15 LoggerFactory.getLogger(DeadlockRetryMethodInterceptor.class);
16
17 @Override
public Object invoke(MethodInvocation invocation) throws Throwable
{
18 Object obj = invocation.getThis();
19 Method method = invocation.getMethod();
20 Method targetMethod =
obj.getClass().getMethod(method.getName(),
21 method.getParameterTypes());
22 DeadlockRetry dlRetryAnnotation =
23 targetMethod.getAnnotation(DeadlockRetry.class);
24 int maxTries = dlRetryAnnotation.maxTries();
25 int tryIntervalMillis = dlRetryAnnotation.tryIntervalMillis();
for (int i = 0; i < maxTries; i++) {
26 try {
27 LOGGER.info("Attempting to invoke " +
28 invocation.getMethod().getName());
29 Object result = invocation.proceed(); // retry
LOGGER.info("Completed invocation of " +
30
invocation.getMethod().getName());
31 return result;
32 } catch (Throwable e) {
33 Throwable cause = e;
34
35 //... put the logic to identify DeadlockLoserDataAccessException
or LockAcquisitionException
36 //...in the cause. If the execption is not due to deadlock,
37 throw an exception
38
39 if (tryIntervalMillis > 0) {
40 try {
41 Thread.sleep(tryIntervalMillis);
} catch (InterruptedException ie) {
42 LOGGER.warn("Deadlock retry thread interrupted", ie);
43 }
44 }
45
46 }
47
48 //gets here only when all attempts have failed
throw new RuntimeException
49 ("DeadlockRetryMethodInterceptor failed to successfully
50 execute target "
51 + " due to deadlock in all retry attempts",
52 new DeadlockLoserDataAccessException("Created by
53 DeadlockRetryMethodInterceptor", null));
}
}
Wire up the annotation and the interceptor via Spring config transactionContext.xml.
Only the snippet is shown.
?
1 <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!-- Deadlock Retry AOP -->
2
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
3 <bean id="deadlockRetryPointcut"
4 class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut">
5
6 <constructor-arg><null/></constructor-arg>
7 <constructor-arg value="com.myapp.deadlock.DeadlockRetry" />
8 </bean>
9
10 <bean id="deadlockRetryMethodInterceptor"
class="com.myapp.deadlock.DeadlockRetryMethodInterceptor" />
11
12 <bean id="deadlockRetryPointcutAdvisor"
13 class="org.springframework.aop.support.DefaultPointcutAdvisor">
14 <constructor-arg ref="deadlockRetryPointcut" />
15 <constructor-arg ref="deadlockRetryMethodInterceptor" />
</bean>
16
17
Finally, annotate the method that needs to be retried in the event of dead lock issues.
?
1 package com.myapp.service;
2
3
4 import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
5 import org.springframework.transaction.TransactionStatus;
6 import
7 org.springframework.transaction.support.DefaultTransactionDefinition;
8 //....other imports
9
public class EmployeeServiceImpl implements EmployeeService {
10
11 private final EmployeeTableRepository employeeRepository;
12 private PlatformTransactionManager transactionManager;
13
14 public EmployeeServiceImpl(EmployeeTableRepository
15 employeeRepository, PlatformTransactionManager transactionManager) {
16 this.employeeRepository = employeeRepository;
this.transactionManager = transactionManager;
17 }
18
19
20 @DeadlockRetry
21 public Employee saveEmployee(Employeee employee) throws
22 RepositoryException {
23 TransactionStatus transactionStatus =
transactionManager.getTransaction(new
24 DefaultTransactionDefinition(
25 TransactionDefinition.PROPAGATION_REQUIRED));
26 try {
27 employee = this.employeeRepository.saveEmployee(employee);
} catch (Exception e) {
28 transactionManager.rollback(transactionStatus);
29 throw new RepositoryException(e);
30 } finally {
31 if (!transactionStatus.isCompleted()) {
32
33 transactionManager.commit(transactionStatus);
34 }
}
35 return employee;
36 }
37
38
39 //....other methods omitted for brevity
40
41 }
42
The above example gives a real life example using a custom annotation and AOP (i.e.
Aspect Oriented Programming).
Note: Also refer to Spring Interview Questions and Answers for explanation on
interceptors.
Q. What are the pros and cons between Spring AOP and AspectJ AOP?
A. The Spring AOP is simpler as it is achieved via a runtime dynamic proxy class. Unlike
AspectJ, it does not have to go through load time weaving or a compiler. The Spring AOP
uses the proxy and decorator design patterns.
Since Spring AOP uses proxy based AOP, it can only offer method execution pointcut,
whereas the AspectJ AOP supports all pointcuts.
In some cases, the compile time weaving (e.g. AspectJ AOP) offers better performance.
Before this, please refer to Hibernate Interview Questions and Answers: with annotations
and Spring framework , as the examples below are extension of this blog.
Q. How would you go about integration testing your DAO (i.e. Data Access Objects)
layer or your Hibernate Repository classes?
A. It is a bit tricky to write integration tests because any changes to the underlying data
can make your tests to fail. For example, addition of new records, modification to
existing data , etc. The key is to keep the data as static as possible. There are two possible
strategies.
1. Use a separate in memory database like HSQL (i.e. Hyper Structured Query
Language) Database. The data can be stored in flat text files -- say in pipe delimited
format and loaded into the in memory database during test set up phase, and deleted
during the test tear down phase.
2. The second alternative is to use a framework like DBUnit to extract the relevant data
from a given database and convert it into XML based data sets that can be inserted into
your test database during the test setUp phase and deleted in the test tear-down phase.
The DBUnit takes care of the data extraction and data load.
Both the above approaches maintain static data in either xml or text based flat files and
load the data during the test setup.
Q. How would you go about using an in memory database like HSQL DB?
A. It involves the following steps.
?
1 #employee_id, emp_code, manager_code, type, base_salary
2 A342|XSMITH|A456|IM|Permanent|43,500.00
3 A342|YSMITH|A678|IM|Contract|57,700.00
Define the parser that loads the data by reading from the above flat file. For e.g.
EmployeeParser.java. This class can be further improved by moving out the methods
that will be shared by other parsers to a parent class or a helper class.
?
1 package com.myapp.database;
2
3 import java.io.BufferedReader;
import java.io.IOException;
4 import java.io.InputStreamReader;
5
6 import org.hibernate.HibernateException;
7 import org.hibernate.Query;
8 import org.hibernate.Session;
9
public class EmployeeParser {
10
11 private static final String DROP_SQL = "drop table tbl_employee";
12 private static final String CREATE_SQL =
13 "create table tbl_employee(employee_id varchar(12), emp_code
14 varchar(12), "+
15 "manager_code varchar(12), type varchar(12), base_salary
decimal";
16
17
18 private static final String INSERT_SQL =
19 "insert into tbl_employee (employee_id, emp_code, manager_code,
20 type, base_salary) values (";
21
22
23 public void parseEmployee(Session session) throws ParserException,
IOException {
24
createDatabaseTable(session);
25 BufferedReader file = findFile(getFileName());
26 String[] data = readLine(file);
27 while (data != null) {
28 Query query =
session.createSQLQuery(INSERT_SQL + "'" + data[0] + "','"
29 + data[1] + "','"
30 + data[2] + "'," + data[3] + "','" + data[4] +
31 ")");
32 query.executeUpdate();
33 data = readLine(file); // read next line from the file
}
34 }
35
36
37 protected String[] readLine(BufferedReader file) throws IOException {
38 String[] data = null;
39
40
41 String line = file.readLine();
42 while (line != null && line.startsWith("#")) {
43 line = file.readLine();
44 }
if (line != null) {
45
data = line.split("\\|"); //split by "|"
46 }
47 return data;
48 }
49
50
51 private void createDatabaseTable(Session session) {
Query query = session.createSQLQuery(DROP_SQL);
52 try {
53 query.executeUpdate();
54 } catch (HibernateException e) {
55 }
56 query = session.createSQLQuery(CREATE_SQL);
query.executeUpdate();
57 }
58
59 protected BufferedReader findFile(String fileName) {
60 final InputStreamReader file =
61 new
InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName));
62
BufferedReader stream = new BufferedReader(file);
63 return stream;
64 }
65
66
67
68 public String getFileName() {
69 return "employeeSearch.txt";
}
70
71 }
72
73
74
Define an override spring context file to override the actual datasource properies. For
example, the actual database could be Sybase or Oracle. The override-daoContext.xml
is shown below.
?
1 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
2
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:context="http://www.springframework.org/schema/context"
4
5 xsi:schemaLocation="http://www.springframework.org/schema/beans
6 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
7 http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd"
8 default-autowire="byName">
9
10 <bean id="hibernateProperties"
11 class="org.springframework.beans.factory.config.PropertiesFactoryBean">
12 <property name="properties">
13 <props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop
14 <prop key="hibernate.generate_statistics">true</prop>
15 <prop key="hibernate.hbm2ddl.auto">create-drop</prop>
16 <prop key="hibernate.jdbc.batch_size">1000</prop>
17 <prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.use_sql_comments">true</prop>
18
<prop key="hibernate.cache.use_query_cache">false</prop>
19 <prop key="hibernate.cache.use_second_level_cache">false</prop>
20 <prop
21 key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorF
22 </props>
</property>
23 <property name="location">
24 <value>classpath:/hibernate.properties</value>
25 </property>
26 </bean>
27
28
29 </beans>
?
1 package com.myapp.test.db;
2
3 import javax.naming.NamingException;
4
import org.springframework.beans.factory.BeanCreationException;
5 import org.springframework.jdbc.datasource.DriverManagerDataSource;
6 import org.springframework.mock.jndi.SimpleNamingContextBuilder;
7
8 /**
9 * helper class to bootstrap the sybase database datasources
10 */
public class HsqlDevBootstrapper {
11
12 public static final String JNDI_BINDING_DB =
13 "java:comp/env/jdbc/dataSource/mydb";
14 public static final String DRIVER_CLASS = "org.hsqldb.jdbcDriver";
15
16
17 private SimpleNamingContextBuilder builder; //Spring JNDI mock class
18
19 /**
20 **/setup HSQL DB, and bind it to jndi tree
21 public void start() {
22 try {
23 builder =
24 SimpleNamingContextBuilder.emptyActivatedContextBuilder();
25
DriverManagerDataSource ds = new
26 DriverManagerDataSource();
27 ds.setDriverClassName(DRIVER_CLASS);
28 ds.setUrl("jdbc:hsqldb:mem:my_db"); //in memory HSQL DB
29 URL
30 ds.setUsername("user");
ds.setPassword("pwd");
31 builder.bind(JNDI_BINDING_DB, ds);
32
33
34 } catch (NamingException e) {
35 throw new BeanCreationException(e.getExplanation());
36 }
37 }
38
public void stop() {
39 builder.deactivate();
40 builder.clear();
41 }
42
43 }
44
Wire up the JNDI bootstrap class via Spring the config file hsqlBootstrapContext.xml.
?
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:context="http://www.springframework.org/schema/context"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans
5 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
6 http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-
7 2.5.xsd"
8 default-autowire="byName">
9
10 <bean id="hsqlBootstrapper"
11 class="com.myapp.test.db.HsqlBootstrapper" init-method="start" destroy-
method="stop"/>
</beans>
Define a DatabasePopulator.java class that populates all the relevant (i.e. associated)
database tables incling tbl_employee.
?
1 package com.myapp.database;
2
3 import java.io.BufferedReader;
import java.io.IOException;
4 import java.io.InputStreamReader;
5 import java.sql.SQLException;
6 import java.text.ParseException;
7 import java.util.HashMap;
import java.util.List;
8 import java.util.Map;
9
10 import org.hibernate.HibernateException;
11 import org.hibernate.Query;
12 import org.hibernate.Session;
13 import org.hibernate.Transaction;
import org.springframework.orm.hibernate3.HibernateTemplate;
14
15
16 public class DatabasePopulator {
17
18 private final HibernateTemplate daoTemplate;
19
20
21
22 public DatabasePopulator(HibernateTemplate daoTemplate) throws
23 Exception {
this.daoTemplate = daoTemplate;
24
try {
25 createDB();
26 } catch (Exception e) {
27 throw e;
28 }
}
29
30 /**
31 * This is where all the loading happens
32 */
33 public void createDB() throws HibernateException, SQLException,
34 IOException {
Session session =
35 daoTemplate.getSessionFactory().openSession();
36
37 Transaction tran = session.beginTransaction();
38
39 //make use of the parser to read the data from a file and load it
40 (i.e. ETL operation - Extract, Transaform, and Load)
41
42 EmployeeParser empParser = new EmployeeParser();
43 empParser.parseEmployee(session);
44
45 //load other relevant data
46
47 session.flush();
48 tran.commit();
}
49
50 }
51
52
?
<?xml version="1.0" encoding="UTF-8"?>
1 <beans xmlns="http://www.springframework.org/schema/beans"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
4 http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
5 http://www.springframework.org/schema/context
6 http://www.springframework.org/schema/context/spring-context-
7 2.5.xsd"
default-autowire="byName">
8
9 <bean name="databasePopulator"
10 class="com.myapp.database.DatabasePopulator">
11 <constructor-arg ref="daoTemplate" />
12 </bean>
13
</beans>
Finally the test class. Some of the missing Spring context files and classes were defined
in a different blog entry mentioned at the beginning of this blog. The context files need to
be loaded in the right order.
?
package com.myapp.repository;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Resource;
import org.joda.time.DateTime;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import
org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={
"classpath:hsqlBootstrapContext.xml",
"classpath:transactionContext.xml",
"classpath:daoContext.xml",
"classpath:override-daoContext.xml",
"classpath:databasePopulatorContext.xml"
})
@TestExecutionListeners(value = {
DependencyInjectionTestExecutionListener.class,
SessionBindingHibernateListener.class
})
@Resource
EmployeeTableRepository tableRepository;
@Test
public void testLoadEmployee() {
Assert.assertTrue(tableRepository != null);
Emplyee employee = null;
try {
employee = tableRepository.loadEmployee("A342");
} catch (RepositoryException e) {
fail("Load employee threw an exception " + e);
}
assertTrue(employee != null);
assertEquals(employee.getType(), "Permanent");
//...more assertions
?
public class Employee {
}
In the above example, if you use lazy loading then the "superior" and "subordinates" will
be proxied (i.e. not the actual object, but the stub object that knows how to load the actual
object) when the main "Employee" object is loaded. So, if you need to get the
"subordinates" or "superior" object, you invoke the getter method on the employee like
employee.getSuperior( ) and the actual object will be loaded.
Pitfall 1: As explained before, the proxy objects are dynamically created by sub-
classing your object at runtime. The subclass will have all the methods of the parent, but
the fields (e.g. name, etc) in the proxy object will remain null, and when any of the
methods are accessed via getter/setter method, the proxy loads up the real object from the
database.
?
1
2 @Override
3 public boolean equals(Object obj) {
4 if (obj == null) {
return false;
5 }
6 if (obj == this) {
7 return true;
8 }
if (!(obj instanceof Employee)) { //Line X: compare object
9
type
10 return false;
11 }
12 return name.equals((Employee)obj).name); //Line Y: compare names
13 }
14
As discussed before, the supplied object is a proxy object and the supplied name will be
null. This can be fixed by using the getter method in Line Y instead of using the field
directly. Using the getter method will tell the proxy object to load the actual object as
shown below.
?
1
2 @Override
3 public boolean equals(Object obj) {
4 if (obj == null) {
return false;
5 }
6 if (obj == this) {
7 return true;
8 }
if (!(obj instanceof Employee)) {
9
return false;
10 }
11 return name.equals((Employee)obj).getName()); //Line Y: compare
12 names
13 }
14
Pitfall 2: We saw earlier that the the proxy objects are dynamically created by sub-
classing your object. In a simple scenario where you only have the "Employee" object
the typecasting in Line Y and "instanceof" operator in Line X will work. But, what will
happen if you have a type hierarchy for The class Employee as shown below
?
1 public class PermanentEmployee extends Employee {
.......
2
}
3
4 public class CasualEmployee extends Employee {
5 .......
6}
7
8
When you have a type hierarchy as shown above, the type casts and instanceof operators
will not work with the proxy objects.
A proxy class is a subclass of a field type that is required. Hibernate creartes a dynamic
subclass by looking at the type of field. This means that if the field type is not the actual
implementation (e.g. CasualEmployee) type, but an interface or superclass (e.g.
Employee), the type of the proxy will be different than the type of the actual object. So,
as shown in the diagram below, If the field type is the superclass (e.g. Employee) of the
actual implementation (i.e. CasualEmployee), the proxy type (i.e. proxy) and the
implementation-type (e.g. CasualEmployee) will be siblings in the type hierarchy, both
extending the superclass. This means the proxy object will not be an instance of the
implementing type (i.e. CasualEmployee), and the application code depending on this
check will fail.
Approach 1: Switch off proxying on the top level class by setting lazy=”false”, which
will turn proxying off for the hierachy.
Approach 2: Use the "Gang of Four" (i.e. GoF) visitor design pattern that allows you to
adapt a single object or manipulate a collection of polymorphic objects without all the
messy typecasts and instanceof operations.
Pitfall 3: As per the above example, if you have an “Employee” class, that contains a
“name” property, when you invoke do “employee.getName()”, the proxies will get the
"name" from Hibernate caches (either 1st or 2nd levels) or the database when requested.
But if this call happens in the presentation layer like in the Struts action class, you will
get the org.hibernate.LazyInitializationException because the Hibernate Session is
closed and this lazy attribute does not have the session attached, hence can’t load
their lazy references.
The solution is to de-proxy the employee class as shown below:
?
1 public class HibernateUtil {
2
3 public static <T> T unproxy(T entity) {
4 if (entity == null) {
5 return null;
6 }
7
if (entity instanceof HibernateProxy) {
8 Hibernate.initialize(entity);
9 entity = (T) ((HibernateProxy)
10 entity).getHibernateLazyInitializer().getImplementation();
11 }
12
13 return entity;
}
14 }
15
?
1 public Employee getSuperior() {
2 superior = HibernateUtils.unproxy(employee);
3 return superior;
}
4
These types of issues are hard to debug, and being aware of these pitfalls can save you
lots of time in debugging and fixing the issues.