在本部分的第三部分,我们将连接 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库(唯一的依赖项)