在前几部分里,我们已经讨论过了Java SE的JPA的基本保持元素。在本文里,我们将看一个示例应用程序,并详细讨论如何在你的开发中应用JPA。
首先,让我们来看看示例应用程序的要求,这个程序可以在这里下载。这是一个关于许可证管理的应用程序。在这个例子里,有很多应用程序,每个程序都有多个版本,每个版本都有一个或者多个与之相关的许可证。还有一组用户,他们可能与这许可证中的任何一个相关联。我们想要创建一个能够管理所有这些元素的应用程序。
现在就让我们从实体开始。它们都在自己的程序包里,而没有与应用程序的代码混在一起。这样做是值得的;在大型项目里,你可以将实体作为单独的项目来处理,这样就可以更容易地在其他项目里重复使用它们。我们创建了4个实体:Application、Version、Licence和User,所以让我们具体看看每个实体的作用。
在Application类里,我们与Version类具有一对多的关系。下面是Application方法的一部分;我们跳过了其中的id和name属性,因为它们与我们先前讨论过的内容类似。
@Entity
public class Application {
...
private List<Version> versions=new ArrayList<Version>();
...
@OneToMany(mappedBy="application",cascade=CascadeType.ALL)
public List<Version> getVersions() {
return versions;
}
...
}
上个月,我们讲过了mappedBy参数。本文里的新东西是cascade(层叠)参数。这个cascade参数用来控制persistence引擎进行操作从而影响数据库其他表格的能力。在默认情况下是没有层叠的,所以对集合的更改要求你明确地管理集合的内容。查看一下其他CascadeType的值会发现其中隐含的操作:ALL、PERSIST、MERGE、REMOVE、REFRESH。例如,设置CascadeType.PERSIST将只会层叠保持对象,所以如果一个新的Version实例被加到版本列表里,那么更新Application实例就会进行层叠操作,以便在底层数据里保存新的Version。CascadeType.MERGE会应用相同的规则来进行更新,而CascadeType.REMOVE同样会删除集合里的内容。CascadeType.REFRESH用来从数据库里重新读取实例进行层叠操作;我们将在后面讨论它。
我们现在来看Version类。我们已经有了与Application的多对一(@ManyToOne)关系以及另外一个层叠集合,现在我们就来看看许可证。
@Entity
public class Version {
...
private Application application;
private Set<Licence> licences=new HashSet<Licence>();
...
@ManyToOne
public Application getApplication() {
return application;
}
...
@OneToMany(mappedBy="version",cascade=CascadeType.ALL)
public Set<Licence> getLicences() {
return licences;
}
...
}
顺着程序的思路走下来,我们看到了Licence类。层叠在这里结束。
@Entity
public class Licence {
...
private Version version;
private Set<User> users=new HashSet<User>();
...
我们还有一个集合来表示一组用户。到版本的映射由@ManyToOne批注来处理。
@ManyToOne
public Version getVersion() {
return version;
}
现在我们来到了这个示例中最重要的映射部分;很多许可证可以参考很多用户,所有我们使用@ManyToMany(多对多)批注来表示这种情况。
@ManyToMany
@JoinTable(name="LicenceUsers",
joinColumns=,
inverseJoinColumns=)
public Set<User> getUsers() {
return users;
}
}
@JoinTable标注让我们能够控制@ManyToMany并替代其默认值。Name参数是我们将要创建用来保存很多映射的表格的名称。JoinColumns参数可以让我们设置join。你可能想要知道为什么我们给它的自变量加了括号;这个自变量类型是数组,所以尽管只有一个值,我们仍需要加括号。其中的值是一个@JoinColumn标注,它用来设置数据库里数据列的名称。相同的句法可以用于inverseJoinColumns参数,虽然是多对多的关系。
最后,我们来看看User类。在本文的例子里,我们假设用户通过其唯一的用户名来识别。因此我们可以将其作为标识符,而不用@GeneratedId来生成我们可以用在其他实体类里的唯一ID值。
@Entity
public class User {
private String userName;
private List<Licence> licences=new ArrayList<Licence>();
public User() {}
@Id
public String getUserName() {
return userName;
}
...
现在我们可以看看如何创建一个到许可证的映射,这是一种多对多(ManyToMany)关系,当然也就是@ManyToMany。
@ManyToMany(mappedBy="users")
public List<Licence> getLicences() {
return licences;
}
...
}
这里我们使用mappedBy参数指向Licence类的用户属性。这让persistence引擎能够使用我们在Licence里指定的@JoinTable。
这就是实体;现在我们转到如何操控它们的话题上。我们编写的这个例子基于Java Standard Edition平台,这样我们可以像先前文章里那样创建一个manager类,并从这个类实例化EJB/JPA层。下面是我们要创建的一个单独的LicManStore.java类。
public class LicManStore {
private EntityManagerFactory emf;
private EntityManager em;
private static LicManStore myInstance;
public static LicManStore getStore() {
if(myInstance==null) {
myInstance=new LicManStore();
}
return myInstance;
}
private LicManStore() {
emf=Persistence.createEntityManagerFactory("appman",
new Properties());
em=emf.createEntityManager(PersistenceContextType.EXTENDED);
}
...
这个构造函数与前面的例子有一个明显的不同之处:它使用了PersistenceContextType.EXTENDED。在默认情况下,EntityManagers用TRANSACTION的PersistenceContextType来创建。这样做也就表示,只有当有活动的事务处理在进行时,实体才是可托管的。事务处理一结束,实体就与实体管理程序脱离,这样我们就可以丢弃它。EXTENDED上下文类型表示这种脱离不会发生,即使在事务处理结束后实体仍然是可托管的。这就意味着你不需要担心集合是否被暂缓取回,因为实体管理程序可以用来完成所需要的取回操作。当我们想要保持和更新/合并实体,或者从数据库里删除实体的时候,我们仍然需要获得EntityTransaction,例如我们想要保存一个新的Application实体:
public void saveApplication(Application a) {
EntityTransaction tx=em.getTransaction();
tx.begin();
em.persist(a);
tx.commit();
}
如果你看一下LicManStore的源代码,你会发现Application只有保存、更新和删除操作,User只有保存和删除操作,而Licence和Version没有方法。它们没有被忘记,但是也不需要它们。要记住,我们对Application和Version以及Licence之间的关系设置了层叠。这意味着我们要做的就是更新Application,而Version和Licence所需要的所有操作都已经完成了。如果你观察一下Controller.java的代码,你会看到上面的过程。首先让我们来创建一个Application:
void addApplication(String appname) throws UpdateException {
if(licmanStore.getApplication(appname)!=null)
throw new UpdateException("An application "
+ appname + " already exists");
Application app=new Application();
app.setApplicationname(appname);
licmanStore.saveApplication(app);
populateApplications();
ui.setSelectedApplication(app);
}