介绍
应用程序在大多数情况下将各种信息存储在关系数据库中是非常普遍的。 尽管它们在处理常规数据类型时表现出色,但在处理二进制数据(例如图像或文档)时效率却不高。 文件系统可以用作替代方案,尽管它们可以提供更好的性能,但是既没有查询信息的查询语言,也没有关系或事务的概念。
在许多情况下,允许第三方访问存储的数据(随着应用程序的增长而出现的典型要求)是一个漫长而复杂的过程,不可能在一夜之间发生。 存储的内部结构可以轻松影响API架构以及信息的检索和遍历方式。
什么是JSR-170?
幸运的是, JSR-170也称为Java Content Repository (JCR),它试图以与实现无关的方式解决这些问题(以及许多其他问题)。 也就是说,无论底层资源(例如数据库,本地或虚拟文件系统)如何,API都相同。 JCR位于数据存储之上,提供内容服务,例如细粒度访问控制,版本控制,内容事件,全文搜索和过滤等。 由Day Software领导的JSR-170背后的专家小组令人印象深刻,包括Vignette,Hummingbird Ltd.,Stellent等内容管理系统(CMS)供应商以及BEA Systems,IBM和Oracle等通常由Java驱动的解决方案提供商,该规范很可能成为事实上的内容管理和文档存储标准。
经过近两年半的工作,该API于2005年6月完成,在javax.jcr包下包含大约50个类(主要是接口和异常),并在2006年初发布了参考实现的初始1.0版本( JackRabbit )。
JSR-170概述
Java Content Repository基于存储库的概念(除了作为存放事物的地方的通常含义之外)还提供了一些处理数据的功能。 信息库将信息存储在由节点和属性组成的“树形结构”中,如右图所示。 圆圈代表节点,而正方形代表属性 。 一个节点可以只有一个父节点,任意数量的子节点(子节点)和任意数量的属性。 一个属性只有一个父级(一个节点),没有子级,并且由一个名称和一个或多个值组成,这些值可以是Boolean,Date,Double,Long,String或Stream。 只有属性可用于信息存储,而节点则可用于在树内创建“路径”。 在某种程度上,这棵树类似于文件系统的结构,其节点为目录,而属性为实际文件。
存储库功能分为几个“合规性”级别,每个级别都提供一组特定的功能:
-
1级
级别1对于所有实现都是必需的,并且提供对存储库的读取访问权限,总之意味着:- 读取对节点和属性的访问权限
- 读取对属性值的访问
- 导出到XML / SAX
- XPATH语法的查询服务
-
2级
级别2提供了写入功能:- 添加和删除节点和属性
- 写入属性值
- 从XML / SAX导入
请注意,规范不需要JCR实现符合2级及更高级别,因此,使用只读存储库是完全有效的。
-
可选的
“可选”级别包含一些高级功能,这些功能在读写存储库中不是必需的,但确实可以为JSR-170增值。 此级别包括(其中包括):- 事务(Transactions) -使存储库有可能被列为JMS或JDBC资源中的一种资源。
- 版本控制 -允许存储库记录其节点的不同状态,以便以后检索。 该规范对此主题做了详尽的描述。 可以使用JSR-170后端构建CVS克隆。
- 事件 -也称为观察,可将存储库内发生的任何活动通知客户端。
- 锁定 -可以冻结树的一部分,有效地将子树呈现为只读的功能
查看API
在处理JSR-170时,建议使用javax.jcr包中的接口,以便可以轻松切换JCR实现,而不必进行任何代码更改。
该API的中心类是Session ,它表示客户端和存储库之间的连接,并由其处于活动状态的工作空间名称和所提供的凭据定义 。 会话包含用于阅读(级别1)和写作(级别2)的方法; 使用基础存储库不支持的功能将引发异常。
该软件包还包含定义组成存储库的单元的接口: Workspace , Credentials , Node , Property , Item ( Item ( Node和Property的超类))和Value 。 查询通过javax.jcr.query包进行处理,节点类型定义通过javax.jcr.nodetype进行处理,而其余包则处理可选级别的功能:即javax.jcr.version , javax.jcr.observation , javax.jcr。锁 。 一个有趣的包是javax.jcr.util ,其中包含ItemVisitor的实现, ItemVisitor是GoF(四人制)的著名“ 设计模式”中的Visitor-pattern模式的接口。
JSR-170实现
Google和SourceForge将列出JSR-170实施项目的页面,但其中大多数处于Alpha阶段,没有发布任何版本。 以下是可以免费下载并已被作者使用的项目的列表:
- 野兔
是JSR-170的参考实现。 它是Apache基础的一部分,并提供1级,2级和可选功能。 在撰写本文时,该项目已通过孵化过程,并已发布官方正式发行版,该版本被认为足以稳定生产使用。 此外,Jackrabbit还被用作JSR-170的领导者Day Software的商业产品的基础。 除了实现JSR-170定义的所有功能之外,JackRabbit还添加了额外的功能(例如SessionListeners或CustomNode注册),并提供了一系列有趣的项目,其中包括JCA连接器,taglib,WebDAV接口和虚拟文件系统以及JDBC后端。 。 JackRabbit在Apache 2.0下获得许可。
- eXo JCR
是eXo平台的一部分,包含规范要求的所有必需功能和一些可选功能。 最新版本(1.0RC7)于2006年6月22日发布,基于规范的最终草案2。 eXo JCR支持与JDBC兼容的数据库(例如MySQL,DB2或HSQL(默认))作为后端存储,并且获得了双重许可(GPL和商业许可)。 该项目的最终发布日期未知。
- 杰切拉
与Jackrabbit和eXo JCR相比,这是一个相对较新的项目。 它在撰写本文时实现了1级和2级的一些要求,并且仅实现了可选级的观察。 不幸的是,该项目处于不完整状态,并且在过去9个月中没有新版本。 但是,它被Magnolia (Jackrabbit旁边的基于Java的流行CMS)用作JCR-170实现。 目前尚不知道计划包含所有级别的最终1.0版本的发布日期。 Jeceira已获得Apache 2.0的许可,并使用HSQL数据库作为其存储引擎。
JCR模块
作为Spring Modules的一部分 ,JCR模块的主要目标是以与主Spring发行版中的ORM包类似的方式简化JSR-170 API的开发。 功能包括:
- JcrTemplate ,它允许执行JcrCallback和异常处理(将检查的JCR异常转换为未检查的Spring DAO异常)。 该模板实现了JCR 会话中的大多数方法,可以很容易地用作替代。 此外,模板还知道可以在多种方法中使用的线程绑定会话,这些功能在使用事务性存储库时非常有用。
- RepositoryFactoryBean ,用于配置,启动和停止存储库实例。 由于JSR-170没有解决应该配置存储库的方式,因此在这方面的实现有所不同。 该支持包含用于Jackrabbit和Jeceira的预定义FactoryBean以及可以轻松支持其他存储库的抽象基类。
- SessionFactory统一了Repository , Credentials和Workspace接口,并允许自动注册侦听器和自定义名称空间。
- 对实现(可选)事务功能的存储库的Spring声明式事务支持。
- OpenSessionInView拦截器和过滤器允许跨不同组件的每个线程使用相同的会话。 与JcrTemplate一起 ,JCR会话的检索,关闭和管理是外部的,并且对调用者完全透明。
对于本文,将使用参考实现(Jackrabbit),因为JCR模块正在使用javax.jcr接口,因此更改实现只是配置问题。 让我们逐步了解如何在Jackrabbit之上使用Java Content Repository以及Spring Modules如何提供帮助。
存储库和SessionFactory配置
<bean id="repository" class="org.springmodules.jcr.jackrabbit.RepositoryFactoryBean">
<!-- normal factory beans params -->
<property name="configuration" value="classpath:jackrabbit-repo.xml"/>
<property name="homeDir" ref="./tmp/repo"/>
</bean>
JCR支持提供了RepositoryFactoryBean类,用于配置Jackrabbit,该类需要一个JackRabbit配置文件和一个主目录。 请注意,如果您正在使用本地文件系统,则RepositoryFactoryBean很有用。 对于可能在JNDI中注册存储库的服务器环境,可以使用JndiObjectFactoryBean帮助器类(Spring发行版的一部分)来检索它:
<bean id="repository" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jcr/myRepository"/>
</bean>
或使用Spring 2.0模式名称空间:
<jndi:lookup id="entityManagerFactory" jndi-name="jcr/myRepository"/>
为了简化使用JCR的工作,该模块添加了SessionFactory接口:
public interface SessionFactory {
public Session getSession() throws RepositoryException;
public SessionHolder getSessionHolder(Session session);
}
SessionFactory将身份验证详细信息隐藏在实现中,以便在配置之后,可以轻松检索具有相同凭据的会话。 为了利用实现功能(规范未涵盖),该接口允许检索SessionHolder (JCR模块特定的类),该类用于事务和会话管理,具有默认的,通用的实现,可用于每个JCR实现,但不支持的可选特征或定制酮(如JackrabbitSessionHolder支持Jackrabbit'stransaction基础设施)。 JCR模块提供了一种简单透明的发现SessionHolder实现的方式(我将在稍后对此进行详细讨论)使插入对其他JSR-170兼容库的支持变得容易。
SessionFactory的默认实现是JcrSessionFactory ,它需要使用一个存储库和凭据。
<!-— SessionFactory -->
<bean id="jcrSessionFactory" class="org.springmodules.jcr.JcrSessionFactory">
<property name="repository" ref="repository"/>
<property name="credentials">
<bean class="javax.jcr.SimpleCredentials">
<constructor-arg index="0" value="bogus"/>
<!-- create the credentials using a bean factory -->
<constructor-arg index="1">
<bean factory-bean="password" factory-method="toCharArray"/>
</constructor-arg>
</bean>
</property>
</bean>
<!-- create the password to return it as a char[] -->
<bean id="password" class="java.lang.String">
<constructor-arg index="0" value="pass"/>
</bean>
Bean声明非常简单。 唯一的“ catch”是提供给SimpleCredential构造函数的密码:它仅接受char数组,并且在变通方法中,使用了Spring工厂声明。
Jcr模板
JcrTemplate是JCR模块的核心类之一,因为它提供了一种方便的方式来处理JCR会话,从而使调用者不必处理会话的打开和关闭,事务回滚(如果它们由基础存储库提供)以及其他功能中的异常处理:
<bean id="jcrTemplate" class="org.springmodules.jcr.JcrTemplate">
<property name="sessionFactory" ref="jcrSessionFactory"/>
<property name="allowCreate" value="true"/>
</bean>
同样,模板定义很简单,类似于Spring框架中的其他模板类,例如HibernateTemplate 。
例
现在已经配置了存储库,让我们“弹跳” Jackrabbit Wiki页面中的示例之一 :
public Node importFile(final Node folderNode, final File file, final String mimeType,
final String encoding) {
return (Node) execute(new JcrCallback() {
/**
* @see org.springmodules.jcr.JcrCallback#doInJcr(javax.jcr.Session)
*/
public Object doInJcr(Session session) throws
RepositoryException, IOException {
JcrConstants jcrConstants = new JcrConstants(session);
//create the file node - see section 6.7.22.6 of the spec
Node fileNode = folderNode.addNode(file.getName(),
jcrConstants.getNT_FILE());
//create the mandatory child node - jcr:content
Node resNode = fileNode.addNode(jcrConstants.getJCR_CONTENT(),
jcrConstants.getNT_RESOURCE());
resNode.setProperty(jcrConstants.getJCR_MIMETYPE(), mimeType);
resNode.setProperty(jcrConstants.getJCR_ENCODING(), encoding);
resNode.setProperty(jcrConstants.getJCR_DATA(), new FileInputStream(file));
Calendar lastModified = Calendar.getInstance();
lastModified.setTimeInMillis (file.lastModified ());
resNode.setProperty(jcrConstants.getJCR_LASTMODIFIED(), lastModified);
session.save();
return resNode;
}
});
}
主要区别在于,代码被包装在JCR模板中,这使我们不必使用try / catch块(由于IO和存储库检查的异常)和处理会话(如果有事务,则进行清理)。 值得一提的是,诸如“ jcr:data”之类的硬编码字符串是通过JcrConstants实用程序类解析的,该类知道名称空间前缀更改,并提供了一种处理JCR常量的干净方法。 如您所见,我只是使示例更加健壮,并且对实际业务代码的影响最小。
交易支持
JCR模块的优势之一是能够通过Java Content Repository使用Spring事务基础结构(以声明方式和编程方式)。 JSR 170将事务支持视为可选功能,并且没有强加公开事务挂钩的标准方法,因此每个实现都可以选择不同的方法。 在撰写本文时,已知只有Jackrabbit支持事务(在其大部分操作中),并且通过为每个JcrSession公开javax.transaction.XAResource来支持事务。 JCR模块提供了一个LocalTransactionManager ,可用于本地事务:
<bean id="jcrTransactionManager" class="org.springmodules.jcr.jackrabbit.LocalTransactionManager">
<property name="sessionFactory" ref="jcrSessionFactory"/>
</bean>
对于声明式事务划分,我使用标准的Spring类以及上面声明的事务管理器bean:
<!-- transaction proxy for Jcr services/facades -->
<bean id="txProxyTemplate" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="proxyTargetClass">
<value>true</value>
</property>
<property name="transactionManager" ref="jcrTransactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED, readOnly</prop>
</props>
</property>
</bean>
<bean id="jcrService" parent="txProxyTemplate">
<property name="target">
<bean class="org.springmodules.examples.jcr.JcrService">
<property name="template" ref="jcrTemplate"/>
</bean>
</property>
</bean>
如果需要JTA管理器,一个简单而优雅的解决方案是使用来自Jackrabbit contrib软件包的jca连接器。 您不一定需要应用程序服务器,因为您可以使用Jencks之类的可插入JCA容器。 配置JCA连接器不在本文讨论范围之内; 但是,您可以在JCR模块样本中找到使用Jencks的示例。
TransactionAware存储库
对于需要纯JCR代码的应用程序,JCR模块允许通过直接使用JCR API的代码透明地使用事务驱动的会话。 可以使用TransactionAwareRepository ,它以JcrSessionFactory作为参数,这样,如果使用Session.login()创建的任何新会话接受JcrSessionFactory上定义的参数,则该会话将返回线程绑定的会话(如果找到)。 请注意,如果使用事务,则JCR会话是事务性的,如果没有,则必须手动将allowNonTxRepository属性设置为true,如下面的配置所示,否则将引发异常:
<bean id="transactionRepository" class="org.springmodules.jcr.TransactionAwareRepository">
<property name="allowNonTxRepository" value="true"/>
<property name="targetFactory" ref="jcrSessionFactory"/>
</bean>
transactionRepository bean可以用作普通的JCR Repository,而无需了解底层机制或线程绑定会话(无论是否具有事务性)(关闭会话将提交事务)。
可选功能支持检测
为了最大程度地重复使用代码,但仍允许可插拔的可选功能(例如对不同JCR实现的事务支持),JCR模块使用SessionHolder接口(已经提到过)以及SessionHolderProvider和SessionHolderProviderManager接口。 通常,用户不需要与他们进行交互,因为它们是框架的内部。 但是它们代表了JCR模块的主要扩展点。
SessionHolder类由各种组件内部使用,主要是事务管理器来处理会话,而SessionHolderProvider和SessionHolderProviderManager分别处理sessionHolder的创建方式和提供程序的使用方式。 默认情况下,使用ServiceSessionHolderProviderManager ,它通过JDK 1.3 Service Provider机制自动发现功能。 管理器将在类路径中搜索META-INF / services / org.springmodules.jcr.SessionHolderProvider条目,其中包含SessionHolderProvider实现的完全限定名称。 Jackrabbit支持以这种方式配置,JCR模块分发jar包含仅一行的META-INF / services文件:
org.springmodules.jcr.jackrabbit.support.JackRabbitSessionHolderProvider
JcrSessionFactory在内部使用默认的SessionHolderProviderManager ,因此在工厂启动时,可以选择任何自定义实现并与适当的存储库一起使用。 但是,通过在JcrSessionFactory上设置SessionHolderProviderManager ,可以轻松地切换到其他发现策略。 默认服务发现的替代方法是ListSessionHolderProviderManager ,它接受提供程序列表,允许轻松使用自定义提供(例如用于测试)。
<bean id="listProviderManager" class="org.springmodules.jcr.support.ListSessionHolderProviderManager">
<property name="providers">
<list>
<bean class="org.mycompany.jcr.CustomHolderProvider"/>
<bean class="org.springmodules.jcr.jackrabbit.support.JackRabbitSessionHolderProvider"/>
<bean class="org.springmodules.jcr.support.GenericHolderProvider"/>
</list>
</property
</bean>
<bean id="jcrSessionFactory" class="org.springmodules.jcr.JcrSessionFactory">
...
<property name="sessionHolderProviderManager" ref="listProviderManager"/>
</bean>
请注意,每个存储库应有一个提供程序–如果列表包含适用于同一存储库的多个提供程序,则顺序将变得很重要,因为将使用第一个匹配的提供程序。
Java内容存储库的未来
尽管JSR-170已于2005年5月完成,但Java Content Repository的工作尚未结束:官方继任者JSR-283将专注于诸如联合,远程处理,客户端/服务器协议映射以及内容建模功能扩展之类的增强功能。仅举几个。 在JSR之外还存在一些想法和倡议:一个绑定/映射框架,可以将Java类转换为JCR树,而反向框架(由Java Content Repository而不是数据库支持的ORM)是建立在JCR之上的WebDAV服务器(参见Jackrabbit贡献套餐)和其他。 JSR-170连接器已经出现在各种产品中,例如Alfresco,BEA Portal Server和IBM Domino等。
至于JCR模块,路线图包括与Acegi的安全集成以实现几种实现,对Spring 2.0名称空间架构的支持(这将减少配置XML)以及与其他JCR实现的集成。 显然,JCR的前景一片光明。