This is a very simple Spring ORM example that shows you how to configure an application with Spring that uses
- Dependency Injection (@Autowired annotation),
- JPA EntityManager (provided by Hibernate),
- Transactional methods (AOP configured).
Spring ORM Example with AOP Transactions
To configure the transactional methods we do not use @Transactional annotations now because we use aspect oriented approach now (eg.: we can say all methods in the service package starting with “get” is a read-only transactional method). We use in-memory database for this example to simplify, so no need for any database setup. This is a standalone application example.
(For @Transactional annotated approach please check this tutorial:
https://www.journaldev.com/7655/spring-orm-example-jpa-hibernate-transaction)
The project structure:
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>hu.daniel.hari.learn.spring</groupId> <artifactId>Tutorial-SpringORMwithTX-AOP</artifactId> <version>0.0.1-SNAPSHOT</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> <hibernate.version>4.1.9.Final</hibernate.version> </properties> <dependencies> <!-- LOG --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- 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> <!-- AspectJ (required spring-aop dependency) --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.5</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> </dependencies> <build> <plugins> <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 as Spring dependencies.
- spring-aop also comes with spring-context as it’s dependent library, so no need to add it.
- aspectjweaver is spring-aop’s dependency, but we have to add it since it is not defined explicitly for spring-aop.
- 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 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package hu.daniel.hari.learn.spring.orm.model; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Product { @Id private Integer id; private String name; public Product() { //Default constructor needed for JPA. } public Product(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { 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. Dao Class
We create a very simple DAO class, that have persist and findALL methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package hu.daniel.hari.learn.spring.orm.dao; import hu.daniel.hari.learn.spring.orm.model.Product; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.stereotype.Component; @Component public class ProductDao { @PersistenceContext private EntityManager em; public void persist(Product product) { System.out.println("persist:"+product); em.persist(product); } public List<Product> findAll() { return em.createQuery("SELECT p FROM Product p") .getResultList(); } } |
- @Component is Spring annotation that tell the Spring container that we can use this class through Spring IoC (Dependency Injection).
- We use JPA’s @PersistenceContext annotation that indicate dependency injection to an EntityManager. Spring injects a proper instance of EntityManager according to the spring.xml configuration.
4. Service Class
Our simple service class has 2 write and 1 read methods: add, addAll, and listAll.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package hu.daniel.hari.learn.spring.orm.service; import hu.daniel.hari.learn.spring.orm.dao.ProductDao; import hu.daniel.hari.learn.spring.orm.model.Product; import java.util.Collection; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class ProductService { @Autowired private ProductDao productDao; public void add(Product product) { productDao.persist(product); } public void addAll(Collection<Product> products) { for (Product product : products) { productDao.persist(product); } } public List<Product> listAll() { return productDao.findAll(); } } |
Please note that
- there are no @Transactional annotations before the methods, but we want these methods to be transactional. The reason is that we use AOP approach to configure which methods are transactional that will be configured in spring.xml below.
- We use Spring’s @Autowired annotation to have ProductDao component be dependency injected.
5. Spring configuration XML
Now we have ready with every working classes needed in our mini application. Let’s create Spring’s configuration file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="https://www.springframework.org/schema/beans" xmlns:p="https://www.springframework.org/schema/p" xmlns:context="https://www.springframework.org/schema/context" xmlns:tx="https://www.springframework.org/schema/tx" xmlns:aop="https://www.springframework.org/schema/aop" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.0.xsd https://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd https://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd "> <!-- Scans the classpath for annotated components that will be auto-registered as Spring beans --> <context:component-scan base-package="hu.daniel.hari.learn.spring" /> <!-- 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="true" /> </bean> </property> </bean> <!-- AOP Configuration for selecting transactional methods --> <!-- the transactional advice (what 'happens'; see the <aop:advisor/> ) --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- all methods starting with 'list' or 'get' are read-only --> <tx:method name="list*" read-only="true"/> <tx:method name="get*" read-only="true"/> <!-- for other methods use the default transaction settings --> <tx:method name="*" /> </tx:attributes> </tx:advice> <!-- ensure that the above transactional advice runs for any execution of a method in the service package --> <aop:config> <aop:pointcut id="serviceMethods" expression="execution(* hu.daniel.hari.learn.spring.orm.service.*.*(..))" /> <aop:advisor pointcut-ref="serviceMethods" advice-ref="txAdvice" /> </aop:config> <!-- TransactionManager --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> </beans> |
- First we tell spring that we want to use classpath scanning for Spring components (Services, DAOs, etc…) rather than unconviniently defining them one by one in this xml, and also we enable Spring annotation detection.
- Adding the datasource, that 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. (no need for persistence.xml)
- datasource (defined above)
- jpaVendorAdapter as Hibernate (also setting some hibernate property)
- We configure the Spring-AOP behavior the following way: We want all methods in the service package transactional, and for methods starting with “get*” or “list*”, we want them to be read-only transactional. This simple.
- 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.)
6. Spring ORM Example Test Class
Our setup is ready, so let’s write a testing class for our application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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; import org.springframework.dao.DataAccessException; /** * Simple tester for a Spring application that uses JPA * with AOP based Transactions. **/ public class SpringOrmMain { public static void main(String[] args) { //Create Spring application context ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/spring.xml"); //Get service from context. (service's dependency (ProductDAO) is autowired in ProductService) ProductService productService = ctx.getBean(ProductService.class); //Do some data operation productService.add(new Product(1, "Television")); productService.add(new Product(2, "Phone")); System.out.println("listAll: " + productService.listAll()); //Test transaction rollback (for duplicated key) try { productService.addAll(Arrays.asList( new Product(3, "Peach"), new Product(4, "Strawberry"), new Product(1, "Melone"))); } catch (DataAccessException dataAccessException) { //Do nothing here, we just test rollback } //Test element list after rollback (same two element, 3 more hasn't been added.) System.out.println("listAll: " + productService.listAll()); 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:
1 2 3 4 5 6 7 8 9 10 11 |
Hibernate: insert into Product (name, id) values (?, ?) Hibernate: insert into Product (name, id) values (?, ?) Hibernate: select product0_.id as id0_, product0_.name as name0_ from Product product0_ listAll: [Product [id=1, name=Bulb], Product [id=2, name=Dijone mustard]] Hibernate: insert into Product (name, id) values (?, ?) Hibernate: insert into Product (name, id) values (?, ?) Hibernate: insert into Product (name, id) values (?, ?) Hibernate: select product0_.id as id0_, product0_.name as name0_ from Product product0_ listAll: [Product [id=1, name=Bulb], Product [id=2, name=Dijone mustard]] |
Note that the second transaction rolled back, this why product list didn’t changed.
You can see that we made transactional methods without specifying them one by one with @Transactional annotations, as we configured it in AOP approach.
If you use log4j.properties
file from attached source, you can see what’s going on under the hood.
References:
Object Relational Mapping (ORM) Data Access
Aspect Oriented Programming with Spring
(For @Transactional annotated approach please check this tutorial:
https://www.journaldev.com/7655/spring-orm-example-jpa-hibernate-transaction)
You can download the final maven project source from below link and play around with it to learn more.