Simple Spring Data JPA Example

With this simple example you can quick start with spring-data-jpa. This project has minimalistic design and focused to make easy to understand.

What is spring-data-jpa for actually?

  • You use @Repository interfaces instead of DAO Implementations,
  • Less boilerplate coding of DAO classes for frequent operations like persisting, finding, listing, etc…
  • You can define JPA queries by annotations like this:
    @Query("select p from Product p where p.name = :name")
  • Or you can define a query by only keywords inside method names:
    findByNameContainingIgnoreCase(String searchString);

I used in-memory database for this example to simplify, so no need for any database setup. This is a standalone application to minimize all dependencies for this example.

 

The project structure:

projectStructure - Tutorial-SpringORMwithDataJPA

We go through these files one by one and explain them:

1. Maven dependencies

We will first look into our pom.xml file for all the required dependencies and their versions. We have used Spring 4, and Hibernate 4 in this example.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>hu.daniel.hari.learn.spring</groupId>
    <artifactId>Tutorial-SpringORMwithDataJPA</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>

    <properties>
        <!-- Generic properties -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.7</java.version>

        <!-- SPRING & HIBERNATE / JPA -->
        <spring.version>4.0.0.RELEASE</spring.version>
        <spring.data.jpa.version>1.7.1.RELEASE</spring.data.jpa.version>
        <hibernate.version>4.1.9.Final</hibernate.version>
    </properties>

    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- Spring-Data-JPA -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>${spring.data.jpa.version}</version>
        </dependency>

        <!-- JPA Implementation (Hibernate)-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- IN MEMORY Database and JDBC Driver -->
        <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
        </dependency>

        <!-- LOG -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <!-- This plugin is needed to define the java version in maven project. -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
  • We need spring-context and spring-orm for Spring dependencies.
  • We use hibernate-entitymanager for using Hibernate as a JPA Vendor. hibernate-entitymanager is dependent to hibernate-core this why we don’t have to put hibernate-core in pom.xml explicitly.
  • We have spring-data-jpa, that has different versioning from spring. This module gives us the possibility of using @Repository-s on a clever way.
  • We need also a JDBC driver as a dependency for DB access that Hibernate uses. In this example we used hsqldb that contains the JDBC driver, and a working in memory db also for this example. (If you want to use external database, you have to change this to that db’s JDBC driver, fe: PostgreSQL, or mysql-connector-java maven dependency.)

2. Model Class

We can use standard JPA annotations for mapping in our model because Hibernate provides JPA implementation.

Product.java

package hu.daniel.hari.learn.spring.orm.model;

import javax.persistence.Entity;
import javax.persistence.Id;

/**
 *  This is a data structure, so
 *  fields can be public. (Clean-Code)
 */
@Entity
public class Product {

    @Id
    public Integer id;
    public String name;

    public Product() {
        //Default constructor needed for JPA.
    }
    public Product(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Product [id=" + id + ", name=" + name + "]";
    }

}

We use @Entity and @Id JPA annotations to qualify our POJO as an Entity and defining it’s primary key.

3. Repository interface (the focus of this tutorial)

Repository is the data access layer in this project instead of DAO Classes. It is an interface, so no any implementation needed, only method names and annotations.  We inherit JpaRepository that has many CRUD and other JPA operations defined already, that we can use without defining them, but we can define more specific operations like below.

  • We haven’t need to define findAll(), because that is inherited.
  • We defined a query by fluent keywords method name : findByNameContainingIgnoreCase(). spring-data-jpa translates this to a query automatically.
  • We defined a query by @Query annotation.

(For supported keywords check this link)

ProductRepository.java


package hu.daniel.hari.learn.spring.orm.repository; import hu.daniel.hari.learn.spring.orm.model.Product; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; /** * This is the Data Access layer. Simple huh? * PLease note that no need for any dao implementation. This is an interface! */ @Repository public interface ProductRepository extends JpaRepository<Product, Long> { /** No need to define findAll() here, because * inherited from JpaRepository with many other basic JPA operations.**/ //public List<Product> findAll(); /** spring-jpa-data understands this method name, * because it supports the resolution of specific keywords inside method names. **/ public List<Product> findByNameContainingIgnoreCase(String searchString); /** You can define a JPA query.**/ @Query("select p from Product p where p.name = :name") public List<Product> findByNameIs(@Param("name") String name); }
  • @Repository annotation is for register this class as a spring repository. This includes also validating @Query contents at bootstrap.
  • In the @Query annotation, we can define a standard JPQL query.

4. Service Class

ProductService.java

package hu.daniel.hari.learn.spring.orm.service;

import hu.daniel.hari.learn.spring.orm.model.Product;
import hu.daniel.hari.learn.spring.orm.repository.ProductRepository;

import java.util.Collection;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service layer.
 * Specify transactional behavior and mainly
 * delegate calls to Repository.
 */
@Component
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Transactional
    public void add(Product product) {
        productRepository.save(product);
    }

    @Transactional(readOnly=true)
    public List<Product> findAll() {
        return productRepository.findAll();
    }

    @Transactional
    public void addAll(Collection<Product> products) {
        for (Product product : products) {
            productRepository.save(product);
        }
    }

    @Transactional(readOnly=true)
    public List<Product> findByNameIs(String name) {
        return productRepository.findByNameIs(name);
    }

    @Transactional(readOnly=true)
    public List<Product> findByNameContainingIgnoreCase(String searchString) {
        return productRepository.findByNameContainingIgnoreCase(searchString);
    }
}

We call our injected ProductRepository for our service calls.
In the ProductRepository class we also have inherited CRUD operations ready for use:Untitled

 

5. Spring configuration XML

Now we have ready with every working classes needed in our mini application. Let’s create Spring’s configuration file:

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/data/jpa
        http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
        ">

    <!-- Scans for components that will be auto-registered as Spring beans -->
    <context:component-scan base-package="hu.daniel.hari.learn.spring" />
    <!-- Scans for repositories that will be auto-registered -->
    <jpa:repositories base-package="hu.daniel.hari.learn.spring.orm.repository" />
    <!-- Activates various annotations to be detected in bean classes e.g: @Autowired -->
    <context:annotation-config />

    <!-- JPA -->

    <!-- Datasource, that is currently hsqldb (in-memory database). -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver" />
        <property name="url" value="jdbc:hsqldb:mem://productDb" />
        <property name="username" value="sa" />
        <property name="password" value="" />
    </bean>

    <!-- EntityManagerFactory -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
            p:packagesToScan="hu.daniel.hari.learn.spring.orm.model"
            p:dataSource-ref="dataSource"
            >
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="true" />
                <property name="showSql" value="false" />
            </bean>
        </property>
    </bean>

    <!-- Transactions -->
    <tx:annotation-driven transaction-manager="transactionManager" />
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

</beans>
  • For Spring-DATA-JPA we set the base package of the repositories.

Other settings for spring and jpa, hibernate:

  • Datasource is currently hsqldb (in-memory database).
  • We set up a JPA EntityManagerFactory that will used by the application to obtain an EntityManager. Spring supports 3 different ways to do this (see the reference below for details), and we use LocalContainerEntityManagerFactoryBean for full JPA capabilities. We set it’s attributes as:
    • packagesToScan attribute that points to our models’ package. (the entities)
    • datasource (defined above)
    • jpaVendorAdapter as Hibernate (also setting some hibernate property)
  • We create Spring’s PlatformTransactionManager instance as a JpaTransactionManager. (This transaction manager is appropriate for applications that use a single JPA EntityManagerFactory for transactional data access.)
  • We enable the configuration of transactional behavior based on annotations, and we set the transactionManager we created.

6. Test Class

Our setup is ready, so let’s write a testing class for our application.

SpringDataJPAMain.java

package hu.daniel.hari.learn.spring.orm.main;

import hu.daniel.hari.learn.spring.orm.model.Product;
import hu.daniel.hari.learn.spring.orm.service.ProductService;

import java.util.Arrays;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Simple tester for Spring-Data-JPA.
 **/
public class SpringDataJPAMain {
    public static void main(String[] args) {

        //Create Spring application context
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/spring.xml");

        //Get service from context.
        ProductService productService = ctx.getBean(ProductService.class);

        //Add some items
        productService.add(new Product(1, "Television"));
        productService.add(new Product(2, "Phone"));
        productService.addAll(Arrays.asList(
                new Product(3, "Peach"),
                new Product(4, "Strawberry"),
                new Product(5, "Melone"),
                new Product(6, "Onion")
                ));

        //Test entity listing
        System.out.println("findAll=" + productService.findAll());

        //Test specified find methods
        System.out.println("findByName is 'Peach': " + productService.findByNameIs("Peach"));
        System.out.println("findByNameContainingIgnoreCase 'on': " + productService.findByNameContainingIgnoreCase("on"));

        ctx.close();
    }
}

You can see how simple we can start the Spring container from a main method, and getting our first dependency injected entry point, the service class instance. ProductDao class reference injected to the ProductService class after the context initialized.

After we got ProducService instance, we can test it’s methods, all method call will be transactioned due to Spring’s proxy mechanism. We also test rollback in this example.

If you run, you get below log:

findAll=[Product [id=1, name=Television], Product [id=2, name=Phone], Product [id=3, name=Peach], Product [id=4, name=Strawberry], Product [id=5, name=Melone], Product [id=6, name=Onion]]
findByName is 'Peach': [Product [id=3, name=Peach]]
findByNameContainingIgnoreCase 'on': [Product [id=1, name=Television], Product [id=2, name=Phone], Product [id=5, name=Melone], Product [id=6, name=Onion]]

If you use log4j.properties file from attached source, you can see what’s going on under the hood.

References: https://spring.io/guides/gs/accessing-data-jpa/

You can download the final maven project from below link:

Written by Dániel Hári

Dániel Hári is the founder of log4jtester.com, cleancodejava.com. Who is an enthusiastic Java developer who enhances his knowledge by continously searching for best practices and modern technologies. Engaged to clean coding, and honors the engineering profession as releasing quality work.