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:
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:
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 thePerson
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
andHibernat
has a gap of 1 due to missinge
in later word, the termsHibernate
andHibernawe
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 beHiber
,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:
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.
You can download the full source code of this example here: JCG-BootHibernate-Example
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
thanks for the information
Great information. Thank you for this helpful blog.
Such nice Information for sharing thank you so much…..
Join Dzone of
training in jaipur
Great information. Thank you for this helpful blog
Great Post , I would like to read your post about spring hibernate .
thank you for sharing
Thanks for sharing with us .This amazing post .
In hibernate framework, we provide all the database information hibernate.cfg.xml file.
Thanks for sharing with us .
Informative, Thanks for sharing!