具有AOP事务管理的Spring ORM示例
这是一个非常简单的Spring ORM示例,向您展示了如何使用具有以下功能的Spring配置应用程序:
- 依赖注入(@Autowired注释),
- JPA EntityManager(由Hibernate提供),
- 事务方法(已配置AOP)。
Spring ORM示例与AOP事务
要配置事务方法,我们现在不使用@Transactional注释,因为我们现在使用面向方面的方法(例如:我们可以说服务包中以" get"开头的所有方法都是只读事务方法)。
在本示例中,我们使用内存数据库进行简化,因此无需任何数据库设置。
这是一个独立的应用程序示例。
1. Maven依赖
我们将首先在pom.xml文件中查找所有必需的依赖项及其版本。
在此示例中,我们使用了Spring 4和Hibernate 4。
<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>
我们需要spring-context和spring-orm作为Spring依赖项。
spring-aop还具有spring-context作为其依赖库,因此无需添加它。
Aspectjweaver是spring-aop的依赖项,但由于未为spring-aop明确定义,因此我们必须添加它。
我们使用hibernate-entitymanager将Hibernate用作JPA供应商。
hibernate-entitymanager依赖于hibernate-core,这就是为什么我们不必将hibernate-core显式放入pom.xml的原因。我们还需要一个JDBC驱动程序作为Hibernate使用的数据库访问的依赖项。
在此示例中,我们使用了包含JDBC驱动程序的hsqldb以及该示例中的在内存中工作的数据库。
(如果要使用外部数据库,则必须将其更改为该数据库的JDBC驱动程序,例如PostgreSQL或者mysql-connector-java maven依赖项。
)
2.型号类别
因为Hibernate提供了JPA实现,所以我们可以在模型中使用标准的JPA批注进行映射。
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 + "]"; } }
我们使用@Entity和@Id JPA批注将POJO限定为Entity并定义其主键。
3.DAO类
我们创建一个非常简单的DAO类,该类具有persist和findALL方法。
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是Spring注释,它告诉Spring容器我们可以通过Spring IoC(依赖注入)使用此类。
我们使用JPA的@PersistenceContext批注,该批注指示对EntityManager的依赖项注入。
Spring根据spring.xml配置注入适当的EntityManager实例。
4.服务等级
我们的简单服务类具有2种写入和1种读取方法:add,addAll和listAll。
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(); } }
请注意
方法之前没有@Transactional批注,但我们希望这些方法具有事务性。
原因是我们使用AOP方法来配置哪些方法是事务性的,这些方法将在下面的spring.xml中进行配置。我们使用Spring的@Autowired批注将ProductDao组件注入依赖项。
5. Spring配置XML
现在,我们已经准备好迷你应用程序中所需的每个工作类。
让我们创建Spring的配置文件:
<?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>
首先,我们告诉Spring我们要对Spring组件(服务,DAO等)使用类路径扫描,而不是在此xml中不方便地一个接一个地定义它们,并且还要启用Spring注释检测。
添加数据源,当前为hsqldb(内存数据库)。
我们设置了一个JPA EntityManagerFactory,应用程序将使用它来获取EntityManager。
Spring支持3种不同的方法来执行此操作(有关详细信息,请参见下面的参考),并且我们将LocalContainerEntityManagerFactoryFactoryBean用于完整的JPA功能。
我们将其属性设置为:packagesToScan属性,该属性指向模型的软件包。
(不需要persistence.xml)
数据源(上面定义)
jpaVendorAdapter作为Hibernate(还设置了一些hibernate属性)
我们通过以下方式配置Spring-AOP行为:我们希望服务包中的所有方法都是事务性的,对于以" get *"或者" list *"开头的方法,我们希望它们是只读的事务性的。
这个简单。我们将Spring的PlatformTransactionManager实例创建为JpaTransactionManager。
(此事务管理器适用于使用单个JPA EntityManagerFactory进行事务数据访问的应用程序。
)
6. Spring ORM示例测试类
我们的设置已经准备就绪,所以让我们为应用程序编写一个测试类。
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(); } }
您会看到我们可以简单地从main方法启动Spring容器,并获得第一个依赖项注入入口点,即服务类实例。
在上下文初始化后,将ProductDao
类引用注入到ProductService
类。
在获得ProducService实例之后,我们可以测试其方法,由于Spring的代理机制,所有方法调用都将被事务处理。
在此示例中,我们还将测试回滚。
如果运行,将得到以下日志:
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]]
请注意,第二笔交易已回滚,这就是产品列表未更改的原因。
您可以看到,我们制作事务方法时没有使用@Transactional批注一一指定它们,就像我们在AOP方法中配置它一样。
如果您使用附件中的" log4j.properties"文件,则可以查看引擎盖下发生的情况。