Enterprise Java

Spring Hibernate Tutorial

1. Introduction

In this post, we shall demonstrate how to leverage the power of one of the most popular ORM (object-relational mapping) tool, Hibernate which facilitates the conversion of an object-oriented domain model to a traditional relational database. Hibernate is one of the most popular Java frameworks out there. For this reason we have provided an abundance of tutorials here at Java Code Geeks, most of which can be found here.

In this lesson, we will create a simple Spring Boot based application which will leverage the power of Hibernate configuration along with Spring Data JPA. We will make use of H2 in-memory database. The choice for the database should not affect the Spring Data definitions we will construct as this is the main advantage Hibernate & Spring Data JPA offers. It enables us to completely separate the Database queries from the application logic.

2. Making the Spring Boot Project

We will be using one of the most popular web tool to make a sample project in this lesson and won’t do it from the command line, we will make use of Spring Initializr. Just open the link in your browser and explore around. To setup our project, we used the following configuration:

Spring Initializr config
Spring Initializr config

We added three dependencies in this tool:

  • Web: This is a basic Spring dependency which collects configuration related and basic annotations into the project.
  • H2: As we make use of an in-memory database, this dependency is required.
  • Data JPA: We will make use of Spring Data JPA for our Data Access layer.

Next, unzip the downloaded zip project and import it into your favourite IDE.

3. Maven Dependencies

To start with, we need to see what Maven dependencies were added to our project by the tool and what others are needed. We will have the following dependency to our pom.xml file:

pom.xml

<dependencies>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-search-orm</artifactId>
    <version>5.6.1.Final</version>
</dependency>

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

</dependencies>

Find the latest Spring related dependencies here. We have added dependencies which are need to perform Hibernate search too.

Note that we have also added the H2 database dependency here as well with its scope as runtime as the H2 data is washed away as soon as the application has stopped. In this lesson, we will not focus on how H2 actually works but will restrict ourself to Hibernate configuration. You may also see how we can Configure Embedded H2 Console With a Spring Application.

Finally, to understand all the JARs which are added to the project when we added this dependency, we can run a simple Maven command which allows us to see a complete Dependency Tree for a project when we add some dependencies to it. Here is a command which we can use:

Check Dependency Tree

mvn dependency:tree

When we run this command, it will show us the following Dependency Tree:

Dependency Tree

[INFO] --------< com.javacodegeeks.example:JCG-BootHibernate-Example >---------
[INFO] Building JCG-BootHibernate-Example 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ JCG-BootHibernate-Example ---
[INFO] com.javacodegeeks.example:JCG-BootHibernate-Example:jar:1.0-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:1.5.6.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:1.5.6.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:1.5.6.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:1.5.6.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:1.5.6.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.1.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.1.11:compile
[INFO] |  |  |  +- org.slf4j:jul-to-slf4j:jar:1.7.25:compile
[INFO] |  |  |  \- org.slf4j:log4j-over-slf4j:jar:1.7.25:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.17:runtime
[INFO] |  +- org.springframework.boot:spring-boot-starter-aop:jar:1.5.6.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-aop:jar:4.3.10.RELEASE:compile
[INFO] |  |  \- org.aspectj:aspectjweaver:jar:1.8.10:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:1.5.6.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat:tomcat-jdbc:jar:8.5.16:compile
[INFO] |  |  |  \- org.apache.tomcat:tomcat-juli:jar:8.5.16:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:4.3.10.RELEASE:compile
[INFO] |  +- org.hibernate:hibernate-core:jar:5.0.12.Final:compile
[INFO] |  |  +- antlr:antlr:jar:2.7.7:compile
[INFO] |  |  \- org.jboss:jandex:jar:2.0.0.Final:compile
[INFO] |  +- javax.transaction:javax.transaction-api:jar:1.2:compile
[INFO] |  +- org.springframework.data:spring-data-jpa:jar:1.11.6.RELEASE:compile
[INFO] |  |  +- org.springframework.data:spring-data-commons:jar:1.13.6.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-orm:jar:4.3.10.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-context:jar:4.3.10.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-tx:jar:4.3.10.RELEASE:compile
[INFO] |  |  +- org.springframework:spring-beans:jar:4.3.10.RELEASE:compile
[INFO] |  |  +- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] |  |  \- org.slf4j:jcl-over-slf4j:jar:1.7.25:compile
[INFO] |  \- org.springframework:spring-aspects:jar:4.3.10.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:1.5.6.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-tomcat:jar:1.5.6.RELEASE:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.5.16:compile
[INFO] |  |  +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.5.16:compile
[INFO] |  |  \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.5.16:compile
[INFO] |  +- org.hibernate:hibernate-validator:jar:5.3.5.Final:compile
[INFO] |  |  +- javax.validation:validation-api:jar:1.1.0.Final:compile
[INFO] |  |  \- com.fasterxml:classmate:jar:1.3.3:compile
[INFO] |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.8.9:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile
[INFO] |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.8.9:compile
[INFO] |  +- org.springframework:spring-web:jar:4.3.10.RELEASE:compile
[INFO] |  \- org.springframework:spring-webmvc:jar:4.3.10.RELEASE:compile
[INFO] |     \- org.springframework:spring-expression:jar:4.3.10.RELEASE:compile
[INFO] +- org.hibernate:hibernate-search-orm:jar:5.6.1.Final:compile
[INFO] |  \- org.hibernate:hibernate-search-engine:jar:5.6.1.Final:compile
[INFO] |     +- org.apache.lucene:lucene-core:jar:5.5.4:compile
[INFO] |     +- org.apache.lucene:lucene-misc:jar:5.5.4:compile
[INFO] |     +- org.apache.lucene:lucene-analyzers-common:jar:5.5.4:compile
[INFO] |     \- org.apache.lucene:lucene-facet:jar:5.5.4:compile
[INFO] |        \- org.apache.lucene:lucene-queries:jar:5.5.4:compile
[INFO] +- org.hibernate:hibernate-entitymanager:jar:5.0.12.Final:compile
[INFO] |  +- org.jboss.logging:jboss-logging:jar:3.3.1.Final:compile
[INFO] |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  +- org.hibernate.common:hibernate-commons-annotations:jar:5.0.1.Final:compile
[INFO] |  +- org.hibernate.javax.persistence:hibernate-jpa-2.1-api:jar:1.0.0.Final:compile
[INFO] |  +- org.javassist:javassist:jar:3.21.0-GA:compile
[INFO] |  \- org.apache.geronimo.specs:geronimo-jta_1.1_spec:jar:1.1.1:compile
[INFO] +- com.h2database:h2:jar:1.4.196:runtime
[INFO] \- org.springframework.boot:spring-boot-starter-test:jar:1.5.6.RELEASE:test
[INFO]    +- org.springframework.boot:spring-boot-test:jar:1.5.6.RELEASE:test
[INFO]    +- org.springframework.boot:spring-boot-test-autoconfigure:jar:1.5.6.RELEASE:test
[INFO]    +- com.jayway.jsonpath:json-path:jar:2.2.0:test
[INFO]    |  \- net.minidev:json-smart:jar:2.2.1:test
[INFO]    |     \- net.minidev:accessors-smart:jar:1.1:test
[INFO]    |        \- org.ow2.asm:asm:jar:5.0.3:test
[INFO]    +- junit:junit:jar:4.12:test
[INFO]    +- org.assertj:assertj-core:jar:2.6.0:test
[INFO]    +- org.mockito:mockito-core:jar:1.10.19:test
[INFO]    |  \- org.objenesis:objenesis:jar:2.1:test
[INFO]    +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO]    +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO]    +- org.skyscreamer:jsonassert:jar:1.4.0:test
[INFO]    |  \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO]    +- org.springframework:spring-core:jar:4.3.10.RELEASE:compile
[INFO]    \- org.springframework:spring-test:jar:4.3.10.RELEASE:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Noticed something? So many dependencies were added by just adding four dependencies to the project. Spring Boot collects all related dependencies itself and leaves nothing for us in that matter. The biggest advantage is that all these dependencies are guaranteed to be compatible with each other.

4. Project Structure

Before we move on and start working on the code for the project, let’s present here the project structure we will have once we’re finished adding all code to the project:

Hibernate Project Structure
Hibernate Project Structure

We have divided the project into multiple packages so that the principle of separation of concern is followed and code remains modular.

Note that the indexpath directory was created by Hibernate for storing indexes (discussed later in the lesson) and it will not exist when you import the project in your IDE.

5. Defining Hibernate Dialects

In the application.properties file, we define two properties which are used by Spring Data JPA which is present on the project classpath. Spring Boot uses Hibernate as the default JPA implementation.

application.properties

## Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

# Hibernate DDL auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update

The spring.jpa.hibernate.ddl-auto property is important here. Due to this, Spring Data will automatically make Database tables on the basis of entities we define in our project and columns will be made from the entity’s fields. As we have set the property to update, whenever we update a field in our Entity class, it will automatically be updated in the DB as soon as we relaunch the project.

6. Defining an Entity

We will start by adding a very simple model in our project, a Person. Its definition will be very standard, like:

Person.java

@Entity
@Indexed
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    @Field(termVector = TermVector.YES)
    private String name;

    @Field
    private int age;

    // standard getters and setters

    @Override
    public String toString() {
        return String.format("Person{id=%d, name='%s', age=%d}", id, name, age);
    }
}

We omitted standard getters and setters for brevity but they are necessary to be made as Jackson uses them during Serialization and Deserialization of an Object.

The @Entity annotation marks this POJO as an object which will be managed by the Spring Data APIs and its fields will be treated as table columns (unless marked transient) and @Field annotation marks that this field should be indexed by Hibernate so that we can run the full-text search query on these fields as well.

Finally, we added a custom implementation for the toString() method so that we can print related data when we test our application.

7. Making a Service Interface

In this section, we will define a service interface which will act as a contract for the implementation and represent all the actions our Service must support. These actions will be related to making a new user and getting information related to the objects in the database.

Here is the contract definition we will be using:

PersonService.java

public interface PersonService {
    Person createPerson(Person person);
    Person getPerson(Long id);
    Person editPerson(Person person);
    void deletePerson(Person person);
    void deletePerson(Long id);
    List<Person> getAllPersons(int pageNumber, int pageSize);
    List<Person> getAllPersons();
    long countPersons();
    List<Person> fuzzySearchPerson(String term);
    List<Person> wildCardSearchPerson(String term);
}

Notice that the contract also includes two methods at the end to provide support for Hibernate search as well.


 

8. Implementing the Service

We will use the above interface definition to provide its implementation so that we can perform CRUD operations related to the Person Entity we defined earlier. We will do it here:

PersonServiceImpl.java

@Service
public class PersonServiceImpl implements PersonService {

    private final PersonRepository personRepository;
    private final PersonDAL personDAL;

    @Autowired
    public PersonServiceImpl(PersonRepository personRepository, PersonDAL personDAL) {
        this.personRepository = personRepository;
        this.personDAL = personDAL;
    }

    @Override
    public Person createPerson(Person person) {
        return personRepository.save(person);
    }

    @Override
    public Person getPerson(Long id) {
        return personRepository.findOne(id);
    }

    @Override
    public Person editPerson(Person person) {
        return personRepository.save(person);
    }

    @Override
    public void deletePerson(Person person) {
        personRepository.delete(person);
    }

    @Override
    public void deletePerson(Long id) {
        personRepository.delete(id);
    }

    @Override
    public List<Person> getAllPersons(int pageNumber, int pageSize) {
        return personRepository.findAll(new PageRequest(pageNumber, pageSize)).getContent();
    }

    @Override
    public List<Person> getAllPersons() {
        return personRepository.findAll();
    }

    @Override
    public long countPersons() {
        return personRepository.count();
    }

    @Override
    @Transactional(readOnly = true)
    public List<Person> fuzzySearchPerson(String term) {
        return personDAL.fuzzySearchPerson(term);
    }

    @Override
    @Transactional(readOnly = true)
    public List<Person> wildCardSearchPerson(String term) {
        return personDAL.wildCardSearchPerson(term);
    }
}

We just used the DAL bean to access the methods we defined above. We also made use of the @Transactional(readOnly = true) annotation so that we don’t have to open a Hibernate session which is needed when you do some write operations, but as we only need to perform the search, we can safely mention readOnly property to true.

9. Defining JPA Repository

As most of the operations are done by JPA Repository itself, let’s define it here:

PersonRepository.java

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {
}

Although above interface definition is empty, we still have some points which we need to understand:

  • @Repository annotation marks this interface as a Spring Bean which is initialised on application startup. With this annotation, Spring takes care of managing exception database interaction throws gracefully
  • We used Person as a parameter to signify that this JPA interface will manage the Person Entity
  • Finally, we also passed the data type Long as a parameter. This signifies that the Person Entity contains a unique identifier which is of the type Long

10. Defining Data Access Layer (DAL) Interface

Although we have defined the JPA Repository which performs all the CRUD operations, we still will be making a DAL layer which defines the queries for Hibernate free-text search. Let us look at the contract we define:

PersonDAL.java

public interface PersonDAL {
    List<Person> fuzzySearchPerson(String term);
    List<Person> wildCardSearchPerson(String term);
}

11. Implementing DAL Interface

In the DAL Interface we defined, we will be implementing two different type of free-text search:

  • Fuzzy Search: Fuzzy search is applicable when we want to find terms which are at a distance from the search term. To understand the gap, let us consider an example. The term Hibernate and Hibernat has a gap of 1 due to missing e in later word, the terms Hibernate and Hibernawe also has a gap of 1 as a single character in later String i.e. w can be replaced to form the former String.
  • Wildcard Search: These re just like SQL statements which have a matching phrase. Like matching phrases for Hibernate can be Hiber, bernate etc.

Let us implement both of these in our DAL layer.

11.1 Defining Fuzzy Query

We will be starting with Fuzzy search implementation. This is a very intelligent and complex search as this needs Tokenization of each term saved in the database indexes. Read more about how Lucene do this here.

Let us implement this search query here:

Fuzzy Query

@PersistenceContext
private EntityManager entityManager;

@Override
public List<Person> fuzzySearchPerson(String term) {

    FullTextEntityManager fullTextEntityManager =
            org.hibernate.search.jpa.Search.
                    getFullTextEntityManager(entityManager);

    QueryBuilder queryBuilder =
            fullTextEntityManager.getSearchFactory()
                    .buildQueryBuilder().forEntity(Person.class).get();

    Query fuzzyQuery = queryBuilder
            .keyword()
            .fuzzy()
            .withEditDistanceUpTo(2)
            .withPrefixLength(0)
            .onField("name")
            .matching(term)
            .createQuery();

    FullTextQuery jpaQuery =
            fullTextEntityManager.createFullTextQuery(fuzzyQuery, Person.class);

    return jpaQuery.getResultList();
}

We just used the edit distance of 2. This is the maximum gap which is supported by Hibernate and Lucene engine.

11.2 Defining Wildcard Query

Wildcard query is easy to understand and implement. This works just like SQL LIKE statements:

Wildcard Query

    @Override
    public List<Person> wildCardSearchPerson(String term) {
        FullTextEntityManager fullTextEntityManager =
                org.hibernate.search.jpa.Search.
                        getFullTextEntityManager(entityManager);

        QueryBuilder queryBuilder =
                fullTextEntityManager.getSearchFactory()
                        .buildQueryBuilder().forEntity(Person.class).get();

        Query wildcardQuery = queryBuilder
                .keyword()
                .wildcard()
                .onField("name")
                .matching("*" + term + "*")
                .createQuery();


        FullTextQuery jpaQuery =
                fullTextEntityManager.createFullTextQuery(wildcardQuery, Person.class);

        return jpaQuery.getResultList();
    }

We applied * to front and back of the term so that LIKE can work in both the directions.

12. Building search Index in Hibernate

Before Hibernate can start storing the index data, we need to make sure that the search index actually exists. This can be done by constructing it as soon as the application starts:

BuildSearchIndex.java

@Component
public class BuildSearchIndex implements ApplicationListener<ApplicationReadyEvent> {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        try {
            FullTextEntityManager fullTextEntityManager =
                    Search.getFullTextEntityManager(entityManager);
            fullTextEntityManager.createIndexer().startAndWait();
        }
        catch (InterruptedException e) {
            System.out.println(
                    "An error occurred trying to build the serach index: " +
                            e.toString());
        }
        return;
    }
}

Although we have constructed a search index in Hibernate but where will the indexed data be stored. We will configure this next.

13. Storing Index data

As Hibernate needs to store the Index data so that it doesn’t have to rebuild it every time an operation is performed, we will provide Hibernate with filesystem directory where it can store this data:

application.properties

# Specify the Lucene Directory
spring.jpa.properties.hibernate.search.default.directory_provider = filesystem

# Using the filesystem DirectoryProvider you also have to specify the default
# base directory for all indexes
spring.jpa.properties.hibernate.search.default.indexBase = indexpath

The directory_provider just provides what type of system will store the data as we can even store indexing data to the cloud.

14. Creating a Command-Line Runner

Now, we’re ready to run our project. To insert sample data into

DataJpaApp.java

@SpringBootApplication
public class DataJpaApp implements CommandLineRunner {

	private static final Logger LOG = LoggerFactory.getLogger("JCG");

	@Autowired
	private PersonService service;

	public static void main(String[] args) {
		SpringApplication.run(DataJpaApp.class, args);
	}

	@Override
	public void run(String... strings) {

		LOG.info("Current objects in DB: {}", service.countPersons());

		Person person = service.createPerson(new Person("Shubham", 23));
		LOG.info("Person created in DB : {}", person);

		LOG.info("Current objects in DB: {}", service.countPersons());

		List<Person> fuzzySearchedPersons = service.fuzzySearchPerson("Shubha");
		LOG.info("Founds objects in fuzzy search: {}", fuzzySearchedPersons.get(0));

		List<Person> wildSearchedPersons = service.wildCardSearchPerson("hub");
		LOG.info("Founds objects in wildcard search: {}", wildSearchedPersons.get(0));

		person.setName("Programmer");
		Person editedPerson = service.editPerson(person);
		LOG.info("Person edited in DB  : {}", person);

		service.deletePerson(person);
		LOG.info("After deletion, count: {}", service.countPersons());
	}
}

15. Running the Project with Maven

Running the application is easy with maven, just use the following command:

Running the Application

mvn spring-boot:run

Once we run the project, we will be seeing the following output:

Run Hibernate Project
Run Hibernate Project

As expected, we first created some sample data and confirmed it by calling the count() method call. Finally, called the search methods to obtain expected results.

16. Conclusion

In this lesson, we studied how we can use Hibernate to configure Spring Data APIs and how it can help us to automatically construct Tables in our database just by defining POJO classes for our entities. Even when we update our entities, we need not to worry about making changes in the database!

We also ran examples with Hibernate search which our powered by Lucene engine itself for indexing and Hibernate provides us useful wrapper over Lucene functionalities.

17. Download the Source Code

This was an example of with Spring Boot and Hibernate ORM Framework.

Download
You can download the full source code of this example here: JCG-BootHibernate-Example

Shubham Aggarwal

Shubham is a Java EE Engineer with about 3 years of experience in building quality products with Spring Boot, Spring Data, AWS, Kafka, PrestoDB.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

11 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Divya
6 years ago

Hello,
This is very helpful blog you have created for Spring and hibernate,
Now We understand, how we can use Hibernate to configure Spring Data APIs and how it can help us to automatically construct Tables in our database just by defining POJO classes for our entities

Rahul
4 years ago

thanks for the information

vasundhara
4 years ago

Great information. Thank you for this helpful blog.

IIHTSURAT
3 years ago

Such nice Information for sharing thank you so much…..

Riya Dzone
3 years ago
Reply to  IIHTSURAT

Join Dzone of
training in jaipur

unicvv
3 years ago

Great information. Thank you for this helpful blog

Programming
3 years ago

Great Post , I would like to read your post about spring hibernate .

muskan
muskan
2 years ago

thank you for sharing

Filehippo
2 years ago

Thanks for sharing with us .This amazing post .

Ultima Farm
2 years ago

In hibernate framework, we provide all the database information hibernate.cfg.xml file.
Thanks for sharing with us .

Nilani
4 months ago

Informative, Thanks for sharing!

Back to top button