The Spring Data JPA, a sub project of Spring Data is aimed at simplifying repository implementations. Repositories typically provide Create, Read, Update and Delete (CRUD) operations for domain objects. In this blog post, I will show how Spring Data JPA drastically reduces the effort needed to create repositories.
We will be using HSQLDB as the database and Hibernate as the JPA implementation provider. Here is a complete list project’s dependencies from Maven’s pom.xml file:
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.1.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.1_3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.2a</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.1.2.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.8</version>
</dependency>
</dependencies>
Before we go about implementing repositories using Spring Data JPA, let’s create two domain objects Person and Address:
package com.inflinx.blog.springdata.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="PERSON")
public class Person {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="PERSON_ID")
private Long id;
@Column(name="FIRST_NAME")
private String firstName;
@Column(name="LAST_NAME")
private String lastName;
@Column(name="GENDER_CODE")
private String genderCode;
// Getters and Setters
}
package com.inflinx.blog.springdata.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name="ADDRESS")
public class Address {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="ADDRESS_ID")
private int id;
@Column(name="STREET")
private String street;
@Column(name="CITY")
private String city;
@Column(name="STATE")
private String state;
@Column(name="ZIPCODE")
private int zipCode;
@ManyToOne
@JoinColumn(name="PERSON_ID")
private Person person;
// Getters and Setters
}
Notice that the two domain classes have JPA annotations such as @Table and @Column that map these classes to relational tables. Also notice the Many To One relationship between Address and Person classes.
Simple JPA Repository
We now start by creating a Spring Data JPA repository for the Person class. This involves creating a repository interface as shown below. The PersonRepository interface extends the Spring Data JPA’s Repository marker interface and has one finder method. The findOne method follows Spring Data JPA’s naming convention for retrieving an entity using its ID.
package com.inflinx.blog.springdata.repository;
import org.springframework.data.repository.Repository;
import com.inflinx.blog.springdata.domain.Person;
public interface PersonRepository extends Repository < Person, Long > {
public Person findOne(Long id);
}
Traditionally, we would also create an implementation class for this repository. However Spring Data JPA alleviates this need and provides an implementation class (proxy) automatically at runtime. For this to happen, we add the jpa:repositories bean declaration to our context file. Here is the complete context 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"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.1.xsd
http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:schema.sql"/>
<jdbc:script location="classpath:test-data.sql"/>
</jdbc:embedded-database>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:data-source-ref="dataSource" p:persistence-xml-location="classpath:test-persistence.xml" p:jpaDialect-ref="jpaDialect">
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entity-manager-factory-ref="entityManagerFactory">
</bean>
<jpa:repositories base-package="com.inflinx.blog.springdata.repository" />
</beans>
The jdbc:embedded-database tag in the context file starts an in-memory database using HSQLDB and creates a datasource for it. The schema.sql file shown below has the DDL commands for creating the Person and Address table:
CREATE TABLE PERSON (PERSON_ID INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
FIRST_NAME VARCHAR(256),
LAST_NAME VARCHAR(256),
GENDER_CODE VARCHAR(1));
CREATE TABLE ADDRESS(ADDRESS_ID INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
STREET VARCHAR(256),
CITY VARCHAR(100),
STATE VARCHAR(10),
ZIPCODE INT,
PERSON_ID INT,
FOREIGN KEY (PERSON_ID) REFERENCES PERSON(PERSON_ID));
The test-data.sql file has the test data we will be using to test our repositories:
INSERT INTO PERSON (FIRST_NAME, LAST_NAME, GENDER_CODE) VALUES ('John', 'Doe', 'M');
INSERT INTO PERSON (FIRST_NAME, LAST_NAME, GENDER_CODE) VALUES ('Jane', 'Doe', 'F');
INSERT INTO PERSON (FIRST_NAME, LAST_NAME, GENDER_CODE) VALUES ('Joe', 'Bloggs', 'M');
The entitiyManagerFactory bean uses test-persistence.xml file that has the standard JPA configuration.
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
<persistence-unit name="springdata" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.inflinx.blog.springdata.domain.Person</class>
<class>com.inflinx.blog.springdata.domain.Address</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
This completes all the setup needed for creating a Spring Data JPA repository. Now let’s create a JUnit test class to test our implementation.
package com.inflinx.blog.springdata.repository;
import org.junit.Assert;
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.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import com.inflinx.blog.springdata.domain.Person;
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
@Transactional
@ContextConfiguration(locations = {"classpath:test-context.xml"})
public class PersonRepositoryTest {
@Autowired
private PersonRepository personRepository;
@Test
public void testPerson() {
Person person = personRepository.findOne(1L);
Assert.assertNotNull(person);
System.out.println("First Name: " + person.getFirstName());
}
}
Notice that we are simply injecting the PersonRepository implementation into the test. When you run the class, you should see that test has passed and “First Name: Jane” in your console.
CRUD JPA Repository
The above PersonRepository has only one finder method and might not be ideal for most real world cases. To address this, Spring Data JPA provides an aptly named CrudRepository interface that extends Repository interface and adds persistence, finder and delete methods. The table below lists some of the available CrudRepository methods:
save(S entity) |
Saves a given entity |
save(Iterable entities) |
Saves all the given entities |
findOne(ID id) |
Retrieves an entity by its id |
exists(ID id) |
Checks if a given entity exists |
findAll |
Returns all the entities |
count |
Returns the number of all the entities |
delete(ID id) |
Deletes an entity with the given id |
delete(T entity) |
Delets a given entity |
A new person repository interface that uses CrudRepository is given below.
package com.inflinx.blog.springdata.repository;
import org.springframework.data.repository.CrudRepository;
import com.inflinx.blog.springdata.domain.Person;
public interface PersonCrudRepository extends CrudRepository < Person, Long > {
}
Here is a JUnit test that tests the above repository:
package com.inflinx.blog.springdata.repository;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
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;
import org.springframework.transaction.annotation.Transactional;
import com.inflinx.blog.springdata.domain.Person;
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})
@Transactional
@ContextConfiguration(locations = {"classpath:test-context.xml"})
public class PersonCrudRepositoryTest {
@Autowired
private PersonCrudRepository personRepository;
@Test
@Rollback(false)
public void testCreate() {
Person p = new Person();
p.setFirstName("Test");
p.setLastName("Data");
p.setGenderCode("M");
p = personRepository.save(p);
Assert.assertNotNull(p.getId());
System.out.println("Created Id: " + p.getId());
}
}
Custom Finder Methods
Spring Data JPA makes it very easy to add new finder methods to repositories. For example, let’s say we want to retrieve all persons with a given last name. Here is the modified PersonCrudRepository repository with the new finder method:
package com.inflinx.blog.springdata.repository;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import com.inflinx.blog.springdata.domain.Person;
public interface PersonCrudRepository extends CrudRepository < Person, Long > {
public List< Person > findByLastName(String lastName);
}
The convention for these finder methods is simply findByMemberName. It is also possible to combine multiple members into one finder method. So, findByLastNameAndFirstName(String lastName, String firstName) will find all persons with the given first name and last name.
Here is the JUnit test class to test the findByLastName method:
@Test
public void testFindByLastName() {
List< Person > lastNameList = personRepository.findByLastName("Doe");
Assert.assertEquals(2, lastNameList.size());
}
Named Queries
The out of the box CRUD methods are sufficient for most cases. However, there are times where we might want to execute custom SQL Queries to retrieve data. For example, let’s say we want to count all the persons that have a given last name. To address such situations, Spring Data JPA provides a @Query annotation. Here is the new person repository with the @Query annotated method:
package com.inflinx.blog.springdata.repository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import com.inflinx.blog.springdata.domain.Person;
public interface PersonCrudRepository extends CrudRepository < Person, Long > {
@Query("select count(p) from Person p where p.lastName = ?1")
public Long getLastNameCount(String lastName);
}
Here is a test method for the above Query method:
@Test
public void testGetLastNameCount() {
Long count = personRepository.getLastNameCount("Doe");
Assert.assertEquals(2L, count.longValue());
}
Custom Repository Implementation
For cases, where the out of the box repository implementations are not enough, Spring Data JPA makes it easy to provide custom implementations that would still integrate with the generic repository abstraction. To see this in action, let’s implement a custom Address repository. We start with a custom interface that declares the additional functionality.
package com.inflinx.blog.springdata.repository;
public interface AddressRepositoryCustom {
public Long someRandomMethod();
}
The next step is to create a class that implements the custom functionality.
package com.inflinx.blog.springdata.repository.jpa;
import com.inflinx.blog.springdata.repository.AddressRepositoryCustom;
public class AddressRepositoryImpl implements AddressRepositoryCustom {
public Long someRandomMethod() {
return 143L;
}
}
Finally, we create the AddressRepository interface that extends the custom interface as shown below:
public interface AddressRepository extends CrudRepository < Address, Long >, AddressRepositoryCustom {
}
Here is a test class that tests the custom method implementation:
package com.inflinx.blog.springdata.repository;
import org.junit.Assert;
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.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class})
@Transactional
@ContextConfiguration(locations = {"classpath:test-context.xml"})
public class AddressRepositoryCustomTest {
@Autowired
private AddressRepository personRepository;
@Test
public void testSomeMethod() {
Assert.assertEquals(143L, personRepository.someRandomMethod().longValue());
}
}
Spring Data JPA requires that you follow strict naming conventions when creating custom repository implementation (unless you provide additional configuration). For example, if the repository interface is AddressRepository, then the name of the custom interface should be AddressRepositoryCustom. If you don’t follow this convention, you will end up with errors similar to this:
Caused by: java.lang.IllegalArgumentException: Could not create query metamodel for method public abstract java.lang.Long com.inflinx.blog.springdata.repository.AddressRepositoryCustom.someRandomMethod()!
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:92)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:162)