在本部分的第三部分,我们将连接 JPA 2 与静态元模型生成器、Bean Validation 和 Envers
JPA
让我们变得持久。当我们谈论持久性时,我们需要一个 persistence.xml 文件,所以让我们在 src/main/resources 下的 META-INF 文件夹中创建一个
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <persistence-unit name="Greetings"> <jta-data-source>java:/DefaultDS</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" /> <property name="hibernate.hbm2ddl.auto" value="create-drop" /> <property name="hibernate.show-sql" value="true" /> </properties> </persistence-unit> </persistence>
我们使用的是 JBoss AS 6 附带的默认 HSQL DefaultDS。如果您想真的潮,可以在网上搜索 @DataSourceDefinition,这是 EE 6 中的新孩子(尚未尝试 AS 6 是否支持它)
接下来,让我们将我们的模型从字符串扩展到 Greeting 实体。创建一个
package com.acme.greetings; import static javax.persistence.TemporalType.TIMESTAMP; import java.util.Date; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Temporal; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @Entity public class Greeting { @Id @GeneratedValue int id; String text; @Temporal(TIMESTAMP) Date created = new Date(); public int getId() { return id; } public void setId(int id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
将 GreetingBean 更改为
package com.acme.greetings; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Event; import javax.inject.Inject; import javax.inject.Named; import org.icefaces.application.PushRenderer; @ApplicationScoped @Named public class GreetingBean implements Serializable { Greeting greeting = new Greeting(); List<Greeting> greetings = new ArrayList<Greeting>(); @Inject @Added Event<Greeting> greetingAddedEvent; @Inject GreetingArchiver greetingArchiver; @PostConstruct public void init() { greetings = greetingArchiver.loadGreetings(); } public void addGreeting() { greetings.add(greeting); greetingAddedEvent.fire(greeting); greeting = new Greeting(); PushRenderer.render("greetings"); } public Greeting getGreeting() { return greeting; } public void setGreeting(Greeting greeting) { this.greeting = greeting; } public List<Greeting> getGreetings() { PushRenderer.addCurrentSession("greetings"); return greetings; } public void setGreetings(List<Greeting> greetings) { this.greetings = greetings; } }
我们还注入了一个事件,当添加评论时会被触发
@Inject @Added Event<Greeting> greetingAddedEvent;
因此需要一个名为 Added 的限定符
package com.acme.greetings; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RUNTIME) @Target( { METHOD, FIELD, PARAMETER, TYPE }) public @interface Added { }
并将 greetings.xhtml 更改为
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:ice="http://www.icesoft.com/icefaces/component" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:head> <title> Greetings </title> </h:head> <h:body> <h:form> <ice:inputText value="#{greetingBean.greeting.text}" effect="#{greetingBean.appear}"/> <h:commandButton value="Add" action="#{greetingBean.addGreeting}" /> <h:dataTable value="#{greetingBean.greetings}" var="greeting"> <h:column> <h:outputText value="#{greeting.text}"/> </h:column> </h:dataTable> </h:form> </h:body> </html>
(我们更改了表格和输入字段的顺序,因为当我们添加评论时字段和按钮会向下移动,这很烦人)
当然,由于我们在触发事件,所以如果有人真的在监听会更好。让我们创建一个 GreetingArchvier
package com.acme.greetings; import java.util.List; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @ApplicationScoped public class GreetingArchiver { @PersistenceContext EntityManager db; @Inject UserTransaction userTransaction; public void saveGreeting(@Observes @Added Greeting greeting) { try { userTransaction.begin(); db.joinTransaction(); db.persist(greeting); db.flush(); userTransaction.commit(); } catch (Exception e) { e.printStackTrace(); // The recommended way of dealing with exceptions, right? } } public List<Greeting> loadGreetings() { return db.createQuery("from Greeting").getResultList(); } }
它观察 @Added Greetings 并将它们存储到数据库中。注意 GreetingBean 在其 @PostConstruct 中调用的 loadGreetings() 方法,它会用旧评论填充自身。嗯,由于我们的 persistence.xml 中有 create-drop,所以没有多少可以加载的,但我们稍后再解决这个问题。
JPA 2
这些都很好,但我们正试图走在前沿,所以让我们引入 JPA 和类型安全查询,并且有了这些,我们最好有一些静态元模型生成器,否则属性会很快变成负担。有 Eclipse 集成(网上搜索),但如果您正在使用基于 Maven 的自动化构建,您无论如何都需要这个。由于 Eclipse 和 Maven 都参与了构建,因此当添加新实体时,请准备好在 Eclipse 中进行一些鸡生蛋项目清理和刷新。无论如何,打开 pom.xml 并添加一些插件仓库
<pluginRepositories> <pluginRepository> <id>jfrog</id> <url>http://repo.jfrog.org/artifactory/plugins-releases/</url> </pluginRepository> <pluginRepository> <id>maven plugins</id> <url>http://maven-annotation-plugin.googlecode.com/svn/trunk/mavenrepo/</url> </pluginRepository> </pluginRepositories>
当我们把jpamodelgen添加到类路径后,maven-compiler-plugin将需要一个参数来阻止自动处理注解。
<configuration> <source>1.6</source> <target>1.6</target> <compilerArgument>-proc:none</compilerArgument> </configuration>
然后,这个任务将由我们新的构建插件接管。
<plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>1.3.5</version> <executions> <execution> <id>process</id> <goals> <goal>process</goal> </goals> <phase>generate-sources</phase> <configuration> <outputDirectory>target/metamodel</outputDirectory> </configuration> </execution> </executions> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>1.0.0.Final</version> </dependency> </dependencies> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>1.3</version> <executions> <execution> <id>add-source</id> <phase>generate-sources</phase> <goals> <goal>add-source</goal> </goals> <configuration> <sources> <source>target/metamodel</source> </sources> </configuration> </execution> </executions> </plugin>
Dan Allen认为这为这个任务配置了很多,我必须记住问他是否找到了将工件放置在可用位置的那种简单、优雅的解决方案。
运行mvn eclipse:eclipse将目标/metamodel添加到Eclipse中,并刷新项目级别的Maven项目配置。
运行Maven构建,你应该会在target/metamodel和WAR结构中看到Greeting_类。现在让我们将其投入使用。
首先,我们添加EntityManger/EntityManagerFactory生成器(推荐使用CDI方式包装它们)
package com.acme.greetings; import javax.enterprise.inject.Produces; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceUnit; public class DBFactory { @Produces @GreetingDB @PersistenceContext EntityManager entityManager; @Produces @GreetingDB @PersistenceUnit EntityManagerFactory entityManagerFactory; }
我们还需要一个限定符
package com.acme.greetings; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; @Qualifier @Retention(RUNTIME) @Target( { METHOD, FIELD, PARAMETER, TYPE }) public @interface GreetingDB { }
最后,让我们修改GreetingArchiver
package com.acme.greetings; import java.util.Date; import java.util.List; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Root; import javax.transaction.UserTransaction; @ApplicationScoped public class GreetingArchiver { @Inject @GreetingDB EntityManager db; @Inject UserTransaction userTransaction; CriteriaQuery<Greeting> loadQuery; ParameterExpression<Date> timestampParam; @Inject public void initQuery(@GreetingDB EntityManagerFactory emf) { CriteriaBuilder cb = emf.getCriteriaBuilder(); timestampParam = cb.parameter(Date.class); loadQuery = cb.createQuery(Greeting.class); Root<Greeting> greeting = loadQuery.from(Greeting.class); loadQuery.select(greeting); loadQuery.where(cb.greaterThan(greeting.get(Greeting_.created), timestampParam)); } public void saveGreeting(@Observes @Added Greeting greeting) { try { userTransaction.begin(); db.joinTransaction(); db.persist(greeting); db.flush(); userTransaction.commit(); } catch (Exception e) { e.printStackTrace(); // The recommended way of dealing with exceptions, right? } } public List<Greeting> loadGreetings() { Date tenMinutesAgo = new Date(); tenMinutesAgo.setTime(tenMinutesAgo.getTime() - 10 * 60 * 1000); return db.createQuery(loadQuery).setParameter(timestampParam, tenMinutesAgo).getResultList(); } }
Bean Validation
添加Bean Validation非常简单,只需在Greeting中的实体字段上添加注解
@Size(min = 1, max = 50) String text;
并在greetings.xhtml的输入字段中附加消息
<ice:inputText id="feedback" value="#{greetingBean.greeting.text}" effect="#{greetingBean.appear}"/> <h:message for="feedback" />
我尝试在text上放置@NotNull,但提交时它仍然失败,因为输入的值是空字符串(这可能是设计行为),所以我使用了min = 1。
Envers
如果您想在实体上启用审计,您需要在pom.xml中添加Envers依赖项。
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> <version>3.5.1-Final</version> <scope>provided</scope> </dependency>
我稍微作弊了一下,将其标记为provided
,因为它引入了很多依赖项。我下载了envers.jar并将其放入服务器公共库,与它的其他hibernate朋友jar:s一起。之后,我们可以在实体上放置一个注解。
@Entity @Audited public class Greeting
最后但同样重要的是,为了享受自动数据审计,我们需要在persistence.xml中添加Envers监听器。
<property name="hibernate.ejb.event.post-insert" value="org.hibernate.ejb.event.EJB3PostInsertEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-update" value="org.hibernate.ejb.event.EJB3PostUpdateEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-delete" value="org.hibernate.ejb.event.EJB3PostDeleteEventListener,org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.pre-collection-update" value="org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.pre-collection-remove" value="org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-collection-recreate" value="org.hibernate.envers.event.AuditEventListener" />
这完成了第二部分,下次我们将更详细地探讨EJB:s和MDB:s。也许我们现在可以暂时放弃WAR-only乌托邦。我想我必须学习关于maven EJB插件的。欢迎提供好的教程提示。
PS. 你检查过使用所有这些技术后WAR文件的大小了吗?大约320k。其中大约85%是ICEfaces库(唯一的依赖项)