Effective Testing around Hibernate/ORM layer
Join the DZone community and get the full member experience.
Join For FreeAs applications that use Hibernate or any ORM frameworks grow complex, lots of performance issues don't get enough attention until it is too late. This article accounts for SLA time as part of the junits and allows developers to think in terms of calculating the number of calls hibernate is going to make in the background. This allows for developers to deal with facts rather than assumptions.
Assuming we are doing TDD, let's build up the TestCase first.
We are going to build up a typical API to create Person with bunch of addresses.
To understand how this works, we will create a buggy Service and include background Audit mechanism, to simulate abstraction to developer.
testAddressCount - Based on the service interface, this test case assume 1 sql is executed to get the count of addresses give a first name
testUpdatePerson - We update the person gender, and assume 3 sqls are executed.
testAddressCount java.lang.AssertionError: Expected: is <1> got: <2>
testUpdatePerson java.lang.AssertionError: Expected: is <3> got: <4>
Let's dive deep into the service and see what is wrong.
As you can see in getAddressCount, instead of writing a custom hql, the developer did an mistake of reading object and doing a size on the list. The correct way of writing this method is to make use of custom query which does a count(*) on address table.
Similarly with Save, the test expected to update Gender, but the service was doing something more than expected, triggering a unexpected audit record on last name.
To demonstrate this article I have made use of datasource-proxy which is very good API to capture sqls that hibernate or any other ORM frameworks execute under the hood.
Also, I feel like it is very good practice to use @Test(timeout) so we know if some one changes a method later on, and hinder the SLA.
Assuming we are doing TDD, let's build up the TestCase first.
We are going to build up a typical API to create Person with bunch of addresses.
To understand how this works, we will create a buggy Service and include background Audit mechanism, to simulate abstraction to developer.
@RunWith(SpringJUnit4ClassRunner.class) public class PersonServiceTest { @Autowired private PersonService personService; @Before public void insertData() { TestUtil.createTestData(personService); } @Before public void cleanup() { QueryCountHolder.clear(); } @Test(timeout = 500) public void testAddressCount() { assertThat(QueryCountHolder.getGrandTotal().getTotalNumOfQuery(), is(0)); assertThat(personService.getAddressCount("John"), is(2)); assertThat(QueryCountHolder.getGrandTotal().getTotalNumOfQuery(), is(1)); } @Test(timeout = 500) public void testUpdatePerson() throws Exception { assertThat(QueryCountHolder.getGrandTotal().getTotalNumOfQuery(), is(0)); Person person = personService.getCustomerByName("John"); assertThat(person.getGender(), CoreMatchers.nullValue()); person.setGender("Male"); QueryCountHolder.clear(); personService.save(person); assertThat(QueryCountHolder.getGrandTotal().getTotalNumOfQuery(), is(3)); person = personService.getCustomerByName("John"); assertThat(person.getGender(), CoreMatchers.is("Male")); } }
testAddressCount - Based on the service interface, this test case assume 1 sql is executed to get the count of addresses give a first name
testUpdatePerson - We update the person gender, and assume 3 sqls are executed.
- To read the person
- Update gender
- Audit event to record gender change
testAddressCount java.lang.AssertionError: Expected: is <1> got: <2>
testUpdatePerson java.lang.AssertionError: Expected: is <3> got: <4>
Let's dive deep into the service and see what is wrong.
@Service public class PersonServiceImpl implements PersonService { @Autowired private PersonRepository repository; @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public int getAddressCount(String firstName) { List<Person> list = repository.findByFirstNameLike(firstName); if (list == null) { return 0; } Person byFirstNameLike = list.get(0); return byFirstNameLike == null ? 0 : byFirstNameLike.getAddresses().size(); } @Transactional(readOnly = false, propagation = Propagation.SUPPORTS) public Person save(Person person) { // un intended operation person.setLastName(person.getLastName() + " 1"); return repository.save(person); } }
As you can see in getAddressCount, instead of writing a custom hql, the developer did an mistake of reading object and doing a size on the list. The correct way of writing this method is to make use of custom query which does a count(*) on address table.
Similarly with Save, the test expected to update Gender, but the service was doing something more than expected, triggering a unexpected audit record on last name.
To demonstrate this article I have made use of datasource-proxy which is very good API to capture sqls that hibernate or any other ORM frameworks execute under the hood.
Also, I feel like it is very good practice to use @Test(timeout) so we know if some one changes a method later on, and hinder the SLA.
Database
Opinions expressed by DZone contributors are their own.
Trending
-
You’ve Got Mail… and It’s a SPAM!
-
Revolutionizing Algorithmic Trading: The Power of Reinforcement Learning
-
The Role of AI and Programming in the Gaming Industry: A Look Beyond the Tables
-
How To Use Pandas and Matplotlib To Perform EDA In Python
Comments