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


回到顶部