The Spring Framework is an application framework and inversion of control container for the Java
platform. The framework’s core features can be used by any Java application, but there are extensions for building web
applications on top of the Java EE platform. Although the framework does not impose any specific programming model, it has
become popular in the Java community as an alternative to, replacement for, or even addition to the Enterprise JavaBeans
(EJB) model.
Introduction
This is the fourth part of a series of posts focused on Hibernate
and JPA
. Throughout this series we have learnt the basics
of JPA , setup pooling with HikariCP
and database migration with Liquibase .
In this post we will refactor the source code to take advantage of Spring’s programming paradigm.
Prerequisites
Project Structure
This is a build up on previous posts and our folder structure will remain relatively the same:
.
|__src/
| |__main/
| | |__java/
| | | |__com/
| | | | |__tutorial/
| | | | | |__Application.java
| | | | | |__entity/
| | | | | | |__Person.java
| | | | | |__repository/
| | | | | | |__PersonRepository.java
| | | | | | |__PersonRepositoryImpl.java
| | |__resources/
| | | |__application.properties
| | | |__dbChangelog.xml
| | | |__log4j2.properties
|__pom.xml
Setting up Dependencies
To setup the project in Spring we need the following dependecies
spring-core
spring-tx
spring-orm
spring-context
Modify the pom.xml
file by adding the following dependencies:
<dependencies>
...
<dependency>
<groupId> org.springframework</groupId>
<artifactId> spring-context</artifactId>
<version> 4.3.7.RELEASE</version>
</dependency>
<dependency>
<groupId> org.springframework</groupId>
<artifactId> spring-orm</artifactId>
<version> 4.3.7.RELEASE</version>
</dependency>
</dependencies>
The above dependencies will transitively pull in all the other spring dependencies.
Setting up Spring
The first thing we need to do is delete the persistence.xml
file. We will configure the persistence unit programmatically using
Spring.
Next is to declare annotations
on the Application
class to set it up as a Spring configuration
class.
file: src/main/java/com/tutorial/Application.java
:
@Configuration
@ComponentScan
public class Application {...}
Define the PersonRepositoryImpl
class as a Spring Component
to be detected by the @ComponentScan
annotation.
file: src/main/java/com/tutorial/repository/PersonRepositoryImpl.java
:
@Component
public class PersonRepositoryImpl implements PersonRepository {...}
With this done, let’s bootstrap two beans leveraging Spring’s JPA support.
file: src/main/java/com/tutorial/Application.java
:
...
@Bean
public LocalContainerEntityManagerFactoryBean entityManager () {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean ();
entityManager . setPackagesToScan ( "com.tutorial.entity" );
entityManager . setJpaVendorAdapter ( new HibernateJpaVendorAdapter ());
entityManager . setJpaProperties ( properties ());
entityManager . setPersistenceUnitName ( "com.juliuskrah.tutorial" );
return entityManager ;
}
@Bean
public PlatformTransactionManager transactionManager ( EntityManagerFactory emf ) {
return new JpaTransactionManager ( emf );
}
private Properties properties () {
Properties props = new Properties ();
props . setProperty ( "javax.persistence.provider" , "org.hibernate.jpa.HibernatePersistenceProvider" );
props . setProperty ( "javax.persistence.schema-generation.database.action" , "none" );
props . setProperty ( "hibernate.hikari.dataSourceClassName" , "org.h2.jdbcx.JdbcDataSource" );
props . setProperty ( "hibernate.hikari.dataSource.url" , "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MVCC=true" );
props . setProperty ( "hibernate.hikari.dataSource.user" , "sa" );
props . setProperty ( "hibernate.hikari.dataSource.password" , "" );
props . setProperty ( "hibernate.hikari.minimumIdle" , "5" );
props . setProperty ( "hibernate.hikari.maximumPoolSize" , "10" );
props . setProperty ( "hibernate.hikari.idleTimeout" , "30000" );
props . setProperty ( "hibernate.connection.handling_mode" , "delayed_acquisition_and_hold" );
props . setProperty ( "hibernate.connection.provider_class" , "org.hibernate.hikaricp.internal.HikariCPConnectionProvider" );
return props ;
}
With a transaction manager bean defined, we can add @EnableTransactionManagement
annotation on the Application
class.
We have defined our EntityManager
bean and will need to replace:
file: src/main/java/com/tutorial/repository/PersonRepositoryImpl.java
:
private EntityManagerFactory emf = Persistence . createEntityManagerFactory ( "com.juliuskrah.tutorial" );
with:
file: src/main/java/com/tutorial/repository/PersonRepositoryImpl.java
:
@PersistenceUnit ( unitName = "com.juliuskrah.tutorial" )
private EntityManagerFactory emf ;
We are almost done with the changes. Add @Transactional
on the PersonRepositoryImpl` class and remove all transactional code.
Transaction management will be handled by the Spring container. The final class will look like this:
file: src/main/java/com/tutorial/repository/PersonRepositoryImpl.java
:
@Component
@Transactional
public class PersonRepositoryImpl implements PersonRepository {
@PersistenceUnit ( unitName = "com.juliuskrah.tutorial" )
private EntityManagerFactory emf ;
private EntityManager em ;
public PersonRepositoryImpl () {}
@PostConstruct
public void initEntityManager () {
em = emf . createEntityManager ();
}
@Override
public Optional < Person > create ( Person person ) {
Objects . requireNonNull ( person , "Person must not be null" );
em . persist ( person );
return Optional . of ( person );
}
@Transactional ( readOnly = true )
@Override
public Optional < Person > read ( Long id ) {
Person person = em . find ( Person . class , id );
return Optional . ofNullable ( person );
}
@Override
public Optional < Person > update ( Person person ) {
Objects . requireNonNull ( person , "Person must not be null" );
person = em . merge ( person );
return Optional . of ( person );
}
@Override
public void delete ( Person person ) {
em . remove ( person );
}
}
Setting up Migration
The last piece is to setup Liquibase
migration using Spring. To achieve this we need to bootstrap two more beans:
file: src/main/java/com/tutorial/Application.java
:
...
@Bean ( destroyMethod = "close" )
public DataSource dataSource () {
HikariConfig config = HikariConfigurationUtil . loadConfiguration ( properties ());
return new HikariDataSource ( config );
}
@Bean
public SpringLiquibase liquibase ( DataSource dataSource ) {
SpringLiquibase liquibase = new SpringLiquibase ();
liquibase . setDataSource ( dataSource );
liquibase . setChangeLog ( "classpath:dbChangelog.xml" );
liquibase . setContexts ( "test" );
liquibase . setDropFirst ( true );
liquibase . setShouldRun ( true );
return liquibase ;
}
The Spring enabled Application
class should now look something like this:
file: src/main/java/com/tutorial/Application.java
:
@Configuration
@ComponentScan
@EnableTransactionManagement
public class Application {
public static void main ( String [] args ) {
ApplicationContext ctx = new AnnotationConfigApplicationContext ( Application . class );
PersonRepository repository = ctx . getBean ( PersonRepository . class );
Person person = new Person ();
person . setFirstName ( "Julius" );
person . setLastName ( "Krah" );
person . setCreatedDate ( LocalDateTime . now ());
person . setDateOfBirth ( LocalDate . of ( 1990 , Month . APRIL , 4 ));
// Create person
repository . create ( person );
// Hibernate generates id of 1
Optional < Person > p = repository . read ( 1L );
p . ifPresent ( consumer -> {
consumer . setModifiedDate ( LocalDateTime . now ());
consumer . setFirstName ( "Abeiku" );
});
// Update person record
repository . update ( p . get ());
p = Optional . empty ();
// Read updated record
p = repository . read ( 1L );
p . ifPresent ( consumer -> {
System . out . format ( "Person from database: %s" , consumer );
});
// Delete person
repository . delete ( p . get ());
p = Optional . empty ();
p = repository . read ( 1L );
// close application context
(( AnnotationConfigApplicationContext ) ctx ). close ();
}
private Properties properties () {
Properties props = new Properties ();
props . setProperty ( "javax.persistence.provider" , "org.hibernate.jpa.HibernatePersistenceProvider" );
props . setProperty ( "javax.persistence.schema-generation.database.action" , "none" );
props . setProperty ( "hibernate.hikari.dataSourceClassName" , "org.h2.jdbcx.JdbcDataSource" );
props . setProperty ( "hibernate.hikari.dataSource.url" , "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MVCC=true" );
props . setProperty ( "hibernate.hikari.dataSource.user" , "sa" );
props . setProperty ( "hibernate.hikari.dataSource.password" , "" );
props . setProperty ( "hibernate.hikari.minimumIdle" , "5" );
props . setProperty ( "hibernate.hikari.maximumPoolSize" , "10" );
props . setProperty ( "hibernate.hikari.idleTimeout" , "30000" );
props . setProperty ( "hibernate.connection.handling_mode" , "delayed_acquisition_and_hold" );
props . setProperty ( "hibernate.connection.provider_class" , "org.hibernate.hikaricp.internal.HikariCPConnectionProvider" );
return props ;
}
@Bean ( destroyMethod = "close" )
public DataSource dataSource () {
HikariConfig config = HikariConfigurationUtil . loadConfiguration ( properties ());
return new HikariDataSource ( config );
}
@Bean
public SpringLiquibase liquibase ( DataSource dataSource ) {
SpringLiquibase liquibase = new SpringLiquibase ();
liquibase . setDataSource ( dataSource );
liquibase . setChangeLog ( "classpath:dbChangelog.xml" );
liquibase . setContexts ( "test" );
liquibase . setDropFirst ( true );
liquibase . setShouldRun ( true );
return liquibase ;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManager () {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean ();
entityManager . setPackagesToScan ( "com.tutorial.entity" );
entityManager . setJpaVendorAdapter ( new HibernateJpaVendorAdapter ());
entityManager . setJpaProperties ( properties ());
entityManager . setPersistenceUnitName ( "com.juliuskrah.tutorial" );
return entityManager ;
}
@Bean
public PlatformTransactionManager transactionManager ( EntityManagerFactory emf ) {
return new JpaTransactionManager ( emf );
}
}
Conclusion
In this post we converted the previous example to a Spring project. We also learned how to switch from Application Managed
(private EntityManagerFactory emf = Persistence.createEntityManagerFactory("com.juliuskrah.tutorial")
) Entity
Manager to Container Managed
(@PersistenceUnit(unitName = "com.juliuskrah.tutorial") private EntityManagerFactory emf
) Entity
Manager.
As usual you can find the full example to this guide in the github repository . Until the next post, keep doing cool things .
If you would like to support my work, consider