导言
在开发企业级业务应用(企业规模)时,客户往往要求在不修改系统源代码的情况下对应用对象模型的扩展性提供支持。利用可扩展域模型可以实现新功能的开发,而不需要额外的精力和成本
- 应用的使用周期将被延长;
- 外部因素改变时,系统工作流也可以随之被修改;
- 已经被部署的应用可以被“设定”,使其符合企业的特定情况。
完成以上功能需求最简单、最具成本效益的方法应该是在应用中实现支持自定义字段的可扩展业务实体。
什么是“自定义字段”?
什么是自定义字段?最终用户如何从中受益呢?自定义字段是一种对象属性,它不是由系统开发人员在开发阶段创建的,而是在系统实际使用中由系统用户在不改变任何源代码的情况下添加到对象中的。
可能会需要哪些功能呢?
让我们举一个 CRM(客户关系管理系统)应用的例子来领会一下。 假设我们有一个客户“Client”对象。理论上讲,这个对象可以有任意多的各种属性:几个 email 地址、若干电话号码和地址等。某公司的销售部门可能会使用其中一个属性,但其它公司却会完全忽略它。将最终用户可能会用到的(也可能不会用到的)所有属性都加入到对象当中,这是很浪费并很不合理的。
既然这样,允许系统用户(或者管理员)来创建他们公司的销售经理们需要的属性,也许是更好的做法。例如,如果有需要,管理员可以创建“工作电话”或者“家庭地址”等属性。 此外,这些属性还可以用到数据过滤和查询中去。
简要说明
在实施 Enterra CRM 项目时,客户提出了在应用中支持自定义字段的目标,“系统管理员不需要重启系统就可以创建或删除自定义字段”。
系统后端开发使用了 Hibernate 3.0 框架,这个因素(技术约束)是考虑实现这个需求的关键。
实现
在这一章里面我们将介绍采用 Hibernate 框架实现的关键环节。
环境
例子的开发环境如下所示:
- JDK 1.5;
- Hibernate 3.2.0 框架;
- MySQL 4.1。
限制
简单起见,我们不使用 Hibernate EntityManager(译注一)和 Hibernate Annotations(译注二)。持久化对象的映射关系将基于 xml 映射文件。此外,值得一提的是,由于演示用例是基于 xml 映射文件管理映射,所以使用 Hibernate Annotations 的话,它将不能正常运行。
功能定义
我们必须实现一种机制——允许实时地创建 / 删除自定义字段而不重启应用,向其中添加值并保证值能保存到应用的数据库中。此外我们还必须保证自定义字段能用于查询。 ## 解决方案
域模型
首先,我们需要一个进行试验的业务实体类。假设是 Contact 类,它有两个持久化字段:id 和 name。
但是,除了这些持久不变的字段外,这个类还应该有一些存储自定义字段值的数据结构。Map 也许是针对于此的理想数据结构。
为所有支持自定义字段的业务实体创建一个基类——CustomizableEntity,它包含处理自定义字段的 Map 类型属性 customProperties:
package com.enterra.customfieldsdemo.domain;<p>import java.util.Map;</p><br></br>import java.util.HashMap;<p>public abstract class CustomizableEntity {</p><p>private Map customProperties;</p><p>public Map getCustomProperties() {</p><br></br> if (customProperties == null)<br></br> customProperties = new HashMap();<br></br> return customProperties;<br></br>}<br></br>public void setCustomProperties(Map customProperties) {<br></br> this.customProperties = customProperties;<br></br>}<p>public Object getValueOfCustomField(String name) {</p><br></br> return getCustomProperties().get(name);<br></br>}<p>public void setValueOfCustomField(String name, Object value) {</p><br></br> getCustomProperties().put(name, value);<br></br>}<p>} </p>
清单 1- 基类 CustomizableEntity
Contact 类继承上面的基类:
package com.enterra.customfieldsdemo.domain;<p>import com.enterra.customfieldsdemo.domain.CustomizableEntity;</p><p>public class Contact extends CustomizableEntity {</p><p>private int id;</p><br></br>private String name;<p>public int getId() {</p><br></br> return id;<br></br>}<p>public void setId(int id) {</p><br></br> this.id = id;<br></br>}<p>public String getName() {</p><br></br> return name;<br></br>}<p>public void setName(String name) {</p><br></br> this.name = name;<br></br>}<p>} </p>
清单 2- 继承自 CustomizableEntity 的 Contact 类
别忘了这个类的映射文件:
<?xml version="1.0" encoding="UTF-8"?><p> <br></br>"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><p> <hibernate-mapping auto-import="true" default-access="property" default-cascade="none" default-lazy="true"></p><p> <class abstract="false" name="com.enterra.customfieldsdemo.domain.Contact" table="tbl_contact"></p><p> <id column="fld_id" name="id"></p><br></br> <generator class="native"/><br></br> </id><p> <property name="name" column="fld_name" type="string"/></p><br></br> <dynamic-component insert="true" name="customProperties" optimistic-lock="true" unique="false" update="true"><br></br> </dynamic-component><br></br> </class><br></br> </hibernate-mapping>
清单 3-Contact 类的映射
注意 id 和 name 属性都是当作普通的属性来处理,但对于 customProperties,我们使用了(动态组件)标签。Hibernate 3.2.0GA 文档里面关于 dynamic-component 的要点如下:
映射的语义与 是一样的。该映射的优点是仅仅通过编辑映射文件,就能在部署时确定 bean 的现行属性。使用 DOM 解析器,映射文件的运行时操作也是可行的。甚至,你可以通过 Configuration 对象,来访问(和修改)Hibernate 的配置时元模型。
基于 Hibernate 文档中的这段规则,我们来建立前面要求的功能机制。
HibernateUtil 和 hibernate.cfg.xml
定义了应用中的域模型之后,我们需要创建 Hibernate 框架运转的必要条件。为此我们必须创建一个配置文件 hibernate.cfg.xml 和一个处理 Hibernate 核心功能的类。
<?xml version='1.0' encoding='utf-8'?><p> <p> PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"</p><p> "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"></p><p> <hibernate-configuration></p><p> <session-factory></p><p> <property name="show_sql">true</property></p><br></br> <property name="dialect"><br></br>org.hibernate.dialect.MySQLDialect</property><br></br> <property name="cglib.use_reflection_optimizer">true</property><br></br> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property><br></br> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/custom_fields_test</property><br></br> <property name="hibernate.connection.username">root</property><br></br> <property name="hibernate.connection.password"></property><br></br> <property name="hibernate.c3p0.max_size">50</property><br></br> <property name="hibernate.c3p0.min_size">0</property><br></br> <property name="hibernate.c3p0.timeout">120</property><br></br> <property name="hibernate.c3p0.max_statements">100</property><br></br> <property name="hibernate.c3p0.idle_test_period">0</property><br></br> <property name="hibernate.c3p0.acquire_increment">2</property><br></br> <property name="hibernate.jdbc.batch_size">20</property><br></br> <property name="hibernate.hbm2ddl.auto">update</property><br></br> </session-factory><br></br> </hibernate-configuration>
清单 4-Hibernate 配置文件
hibernate.cfg.xml 文件没有什么需要特别关注的,除了下面这句:
<property name="hibernate.hbm2ddl.auto">update</property>
清单 5- 使用 auto-update(自动更新)
后面我们将详细解释其目的,并更多地讲解没有它我们怎样实现。HibernateUtil 类有好几种实现方式。由于 Hibernate 配置文件内容的不同,我们的实现与已知的那些将有一点儿不同。
package com.enterra.customfieldsdemo;<p>import org.hibernate.*;</p><br></br>import org.hibernate.mapping.PersistentClass;<br></br>import org.hibernate.tool.hbm2ddl.SchemaUpdate;<br></br>import org.hibernate.cfg.Configuration;<br></br>import com.enterra.customfieldsdemo.domain.Contact;<p>public class HibernateUtil {</p><p>private static HibernateUtil instance;</p><br></br>private Configuration configuration;<br></br>private SessionFactory sessionFactory;<br></br>private Session session;<p>public synchronized static HibernateUtil getInstance() {</p><br></br> if (instance == null) {<br></br> instance = new HibernateUtil();<br></br> }<br></br> return instance;<br></br>}<p>private synchronized SessionFactory getSessionFactory() {</p><br></br> if (sessionFactory == null) {<br></br> sessionFactory = getConfiguration().buildSessionFactory();<br></br> }<br></br> return sessionFactory;<br></br>}<p>public synchronized Session getCurrentSession() {</p><br></br> if (session == null) {<br></br> session = getSessionFactory().openSession();<br></br> session.setFlushMode(FlushMode.COMMIT);<br></br> System.out.println("session opened.");<br></br> }<br></br> return session;<br></br>}<p>private synchronized Configuration getConfiguration() {</p><br></br> if (configuration == null) {<br></br> System.out.print("configuring Hibernate ... ");<br></br> try {<br></br> configuration = new Configuration().configure();<br></br> configuration.addClass(Contact.class);<br></br> System.out.println("ok");<br></br> } catch (HibernateException e) {<br></br> System.out.println("failure");<br></br> e.printStackTrace();<br></br> }<br></br> }<br></br> return configuration;<br></br>}<br></br>public void reset() {<br></br> Session session = getCurrentSession();<br></br> if (session != null) {<br></br> session.flush();<br></br> if (session.isOpen()) {<br></br> System.out.print("closing session ... ");<br></br> session.close();<br></br> System.out.println("ok");<br></br> }<br></br> }<br></br> SessionFactory sf = getSessionFactory();<br></br> if (sf != null) {<br></br> System.out.print("closing session factory ... ");<br></br> sf.close();<br></br> System.out.println("ok");<br></br> }<br></br> this.configuration = null;<br></br> this.sessionFactory = null;<br></br> this.session = null;<br></br>}<p>public PersistentClass getClassMapping(Class entityClass){</p><br></br> return getConfiguration().getClassMapping(entityClass.getName());<br></br>}<br></br>}
清单 6-HibernateUtils 类
除了平常的 getCurrentSession() 和 getConfiguration() 方法(这些方法对基于 Hibernate 的应用的常规操作是很必要的)之外,我们还需要实现像 reset() 和 getClassMapping(Class entityClass) 这样的方法。在 getConfiguration() 方法中,我们配置 Hibernate、并将类 Contact 添加到配置中去。
reset() 方法关闭所有 Hibernate 使用的资源、清除所有的设置:
public void reset() {<br></br> Session session = getCurrentSession();<br></br> if (session != null) {<br></br> session.flush();<br></br> if (session.isOpen()) {<br></br> System.out.print("closing session ... ");<br></br> session.close();<br></br> System.out.println("ok");<br></br> }<br></br> }<br></br> SessionFactory sf = getSessionFactory();<br></br> if (sf != null) {<br></br> System.out.print("closing session factory ... ");<br></br> sf.close();<br></br> System.out.println("ok");<br></br> }<br></br> this.configuration = null;<br></br> this.sessionFactory = null;<br></br> this.session = null;<br></br>}
清单 7-reset() 方法
getClassMapping(Class entityClass) 方法返回 PersistentClass 对象,该对象包含相关实体映射的全部信息。特别地,对 PersistentClass 对象的处理允许在运行时修改实体类的属性设置。
public PersistentClass getClassMapping(Class entityClass){<br></br> return getConfiguration().getClassMapping(entityClass.getName());<br></br>}
清单 8-getClassMapping(Class entityClass) 方法
处理映射
一旦我们有了可用的业务实体类(Contact)和与 Hibernate 交互的主类,我们就能开始工作了。我们能创建、保存 Contact 类的实例。甚至可以在 Map 对象 customProperties 里面放置一些数据,但是需要注意的是存储在 Map 对象 customProperties 里面的数据并不会被保存到数据库里。
为了保存数据,我们需要让这个机制能在类里面创建自定义字段,并且要让 Hibernate 知道该如何处理它们。
为了实现对类映射的处理,我们需要创建一些接口。叫它 CustomizableEntityManager 吧。名字应该表现出该接口管理业务实体及其内容、属性的意图:
package com.enterra.customfieldsdemo;<p>import org.hibernate.mapping.Component;</p><p>public interface CustomizableEntityManager {</p><br></br> public static String CUSTOM_COMPONENT_NAME = "customProperties";<p> void addCustomField(String name);</p><p> void removeCustomField(String name);</p><p> Component getCustomProperties();</p><p> Class getEntityClass();</p><br></br>}
清单 9-CustomizableEntityManager 接口
接口中重要的方法是 void addCustomField(String name) 和 void removeCustomField(String name)。它们将分别在相应类的映射里创建、删除我们的自定义字段。
下面是实现该接口的情况:
package com.enterra.customfieldsdemo;<p>import org.hibernate.cfg.Configuration;</p><br></br>import org.hibernate.mapping.*;<br></br>import java.util.Iterator;<p>public class CustomizableEntityManagerImpl implements CustomizableEntityManager {</p><br></br> private Component customProperties;<br></br> private Class entityClass;<p> public CustomizableEntityManagerImpl(Class entityClass) {</p><br></br> this.entityClass = entityClass;<br></br> }<p> public Class getEntityClass() {</p><br></br> return entityClass;<br></br> }<p> public Component getCustomProperties() {</p><br></br> if (customProperties == null) {<br></br> Property property = getPersistentClass().getProperty(CUSTOM_COMPONENT_NAME);<br></br> customProperties = (Component) property.getValue();<br></br> }<br></br> return customProperties;<br></br> }<p> public void addCustomField(String name) {</p><br></br> SimpleValue simpleValue = new SimpleValue();<br></br> simpleValue.addColumn(new Column("fld_" + name));<br></br> simpleValue.setTypeName(String.class.getName());<p> PersistentClass persistentClass = getPersistentClass();</p><br></br> simpleValue.setTable(persistentClass.getTable());<p> Property property = new Property();</p><br></br> property.setName(name);<br></br> property.setValue(simpleValue);<br></br> getCustomProperties().addProperty(property);<p> updateMapping();</p><br></br> }<p> public void removeCustomField(String name) {</p><br></br> Iterator propertyIterator = customProperties.getPropertyIterator();<p> while (propertyIterator.hasNext()) {</p><br></br> Property property = (Property) propertyIterator.next();<br></br> if (property.getName().equals(name)) {<br></br> propertyIterator.remove();<br></br> updateMapping();<br></br> return;<br></br> }<br></br> }<br></br> }<p> private synchronized void updateMapping() {</p><br></br> MappingManager.updateClassMapping(this);<br></br> HibernateUtil.getInstance().reset();<br></br> // updateDBSchema();<br></br> }<p> private PersistentClass getPersistentClass() {</p><br></br> return HibernateUtil.getInstance().getClassMapping(this.entityClass);<br></br> }<br></br>}
清单 10- 接口 CustomizableEntityManager 的实现
首先需要指出的是,在构造 CustomizableEntityManager 时,我们要指定管理器操作的业务实体类。该业务实体类作为参数传递给 CustomizableEntityManager 的构造函数:
private Class entityClass;<p>public CustomizableEntityManagerImpl(Class entityClass) {</p><br></br> this.entityClass = entityClass;<br></br>}<p>public Class getEntityClass() {</p><br></br> return entityClass;<br></br>}
清单 11-CustomizableEntityManagerImpl 构造函数
现在我们应该对 void addCustomField(String name) 方法的实现更感兴趣:
public void addCustomField(String name) {<br></br> SimpleValue simpleValue = new SimpleValue();<br></br> simpleValue.addColumn(new Column("fld_" + name));<br></br> simpleValue.setTypeName(String.class.getName());<p> PersistentClass persistentClass = getPersistentClass();</p><br></br> simpleValue.setTable(persistentClass.getTable());<p> Property property = new Property();</p><br></br> property.setName(name);<br></br> property.setValue(simpleValue);<br></br> getCustomProperties().addProperty(property);<p> updateMapping();</p><br></br>}
清单 12- 创建自定义字段
正如我们从实现中看到的一样,Hibernate 在处理持久化对象的属性及其在数据库中的表示方面提供了更多的选择。下面分步讲解该方法的要素:
1)创建一个 SimpleValue 类对象,它指明了自定义字段的值如何被存储到字段和表所在的数据库中:
SimpleValue simpleValue = new SimpleValue();<br></br>simpleValue.addColumn(new Column("fld_" + name));<br></br>simpleValue.setTypeName(String.class.getName());<p>PersistentClass persistentClass = getPersistentClass();</p><br></br>simpleValue.setTable(persistentClass.getTable());
清单 13- 表创建新列
2)给持久化对象创建一个属性(property),并将动态组件添加进去,注意,这是我们为了这个目的已经计划好的:
Property property = new Property()<br></br>property.setName(name)<br></br>property.setValue(simpleValue)<br></br>getCustomProperties().addProperty(property)
清单 14- 创建对象属性
3)最后应该让应用修改 xml 文件,并更新 Hibernate 配置。这个可以由 updateMapping() 方法来完成;
阐明上面代码中另外两个 get 方法的用途还是很有必要的。第一个方法是 getCustomProperties():
public Component getCustomProperties() {<br></br> if (customProperties == null) {<br></br> Property property = getPersistentClass().getProperty(CUSTOM_COMPONENT_NAME);<br></br> customProperties = (Component) property.getValue();<br></br> }<br></br> return customProperties;<br></br>}
清单 15- 获取组件 CustomProperties
该方法找到并返回与业务实体映射中标签相对应的组件(Component)对象。
第二个方法是 updateMapping():
private synchronized void updateMapping() {<br></br> MappingManager.updateClassMapping(this);<br></br> HibernateUtil.getInstance().reset();<br></br> // updateDBSchema();<br></br>}
清单 16-updateMapping() 方法
该方法负责存储更新后的持久化类映射,并且更新 Hibernate 的配置状态,以进一步使改变生效。
顺便,我们回过头来看看 Hibernate 配置中的语句:
<property name="hibernate.hbm2ddl.auto">update</property>
如果缺少该配置,我们就必须使用 Hibernate 工具类来执行数据库 schema 的更新。然而使用该设置让我们避免了那么做。
保存映射
运行时对映射的修改不会将自身保存到相应的 xml 映射文件中,为了使变化在应用下次的执行中活化,我们需要手动将变化保存到对应的映射文件中去。
我们使用 MappingManager 类来完成这件工作,该类的主要目的是将指定的业务实体的映射保存到其 xml 映射文件中去:
package com.enterra.customfieldsdemo;<p>import com.enterra.customfieldsdemo.domain.CustomizableEntity;</p><br></br>import org.hibernate.Session;<br></br>import org.hibernate.mapping.Column;<br></br>import org.hibernate.mapping.Property;<br></br>import org.hibernate.type.Type;<br></br>import org.w3c.dom.Document;<br></br>import org.w3c.dom.Element;<br></br>import org.w3c.dom.Node;<br></br>import org.w3c.dom.NodeList;<br></br>import java.util.Iterator;<p>public class MappingManager {</p><br></br> public static void updateClassMapping(CustomizableEntityManager entityManager) {<br></br> try {<br></br> Session session = HibernateUtil.getInstance().getCurrentSession();<br></br> Class entityClass = entityManager.getEntityClass();<br></br> String file = entityClass.getResource(entityClass.getSimpleName() + ".hbm.xml").getPath();<p> Document document = XMLUtil.loadDocument(file);</p><br></br> NodeList componentTags = document.getElementsByTagName("dynamic-component");<br></br> Node node = componentTags.item(0);<br></br> XMLUtil.removeChildren(node);<p> Iterator propertyIterator = entityManager.getCustomProperties().getPropertyIterator();</p><br></br> while (propertyIterator.hasNext()) {<br></br> Property property = (Property) propertyIterator.next();<br></br> Element element = createPropertyElement(document, property);<br></br> node.appendChild(element);<br></br> }<p> XMLUtil.saveDocument(document, file);</p><br></br> } catch (Exception e) {<br></br> e.printStackTrace();<br></br> }<br></br> }<p> private static Element createPropertyElement(Document document, Property property) {</p><br></br> Element element = document.createElement("property");<br></br> Type type = property.getType();<p> element.setAttribute("name", property.getName());</p><br></br> element.setAttribute("column", ((Column) property.getColumnIterator().next()).getName());<br></br> element.setAttribute("type", type.getReturnedClass().getName());<br></br> element.setAttribute("not-null", String.valueOf(false));<p> return element;</p><br></br> }<br></br>}
清单 17- 更新持久化类映射的工具类
该类一一执行了下面的操作:
- 对于指定的业务实体,定义其 xml 映射的位置,并加载到 DOM Document 对象中,以供进一步操作;
- 查找到 Document 对象中的元素。我们将在这里存储自定义字段和我们所做的内容变化;
- 将该元素内嵌套的所有元素都删除;
- 对于负责自定义字段存储的组件所包含的任意持久化属性,我们都创建一个特定的 document 元素,并根据相应的属性为元素定义属性;
- 保存这个新建的映射文件。
虽然我们这里用了 XMLUtil 类(正如从代码中看到的一样)来处理 XML,但是一般而言,可以换成任何一种方式来实现,不过 XMLUtil 已经足以加载并保存 xml 文件。
我们的实现如下面的清单所示:
package com.enterra.customfieldsdemo;<p>import org.w3c.dom.Node;</p><br></br>import org.w3c.dom.NodeList;<br></br>import org.w3c.dom.Document;<br></br>import org.xml.sax.SAXException;<br></br>import javax.xml.parsers.ParserConfigurationException;<br></br>import javax.xml.parsers.DocumentBuilderFactory;<br></br>import javax.xml.parsers.DocumentBuilder;<br></br>import javax.xml.transform.TransformerException;<br></br>import javax.xml.transform.TransformerFactory;<br></br>import javax.xml.transform.Transformer;<br></br>import javax.xml.transform.OutputKeys;<br></br>import javax.xml.transform.stream.StreamResult;<br></br>import javax.xml.transform.dom.DOMSource;<br></br>import java.io.IOException;<br></br>import java.io.FileOutputStream;<p>public class XMLUtil {</p><br></br> public static void removeChildren(Node node) {<br></br> NodeList childNodes = node.getChildNodes();<br></br> int length = childNodes.getLength();<br></br> for (int i = length - 1; i > -1; i--)<br></br> node.removeChild(childNodes.item(i));<br></br> }<p> public static Document loadDocument(String file)</p><br></br> throws ParserConfigurationException, SAXException, IOException {<p> DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();</p><br></br> DocumentBuilder builder = factory.newDocumentBuilder();<br></br> return builder.parse(file);<br></br> }<p> public static void saveDocument(Document dom, String file)</p><br></br> throws TransformerException, IOException {<p> TransformerFactory tf = TransformerFactory.newInstance();</p><br></br> Transformer transformer = tf.newTransformer();<p> transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, dom.getDoctype().getPublicId());</p><br></br> transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dom.getDoctype().getSystemId());<p> DOMSource source = new DOMSource(dom);</p><br></br> StreamResult result = new StreamResult();<p> FileOutputStream outputStream = new FileOutputStream(file);</p><br></br> result.setOutputStream(outputStream);<br></br> transformer.transform(source, result);<p> outputStream.flush();</p><br></br> outputStream.close();<br></br> }<br></br>}
清单 18-XML 处理工具类
测试
我们有了所有必需的运行代码, 现在可以编写测试代码来看看一切到底是怎样工作的。第一个测试创建自定义字段“email”,创建并保存 Contact 类的实例,并给它定义“email”属性。
首先让我们看一下数据库表 tbl_contact,它包括两个字段:fld_id 和 fld_name。代码如下:
package com.enterra.customfieldsdemo.test;<p>import com.enterra.customfieldsdemo.HibernateUtil;</p><br></br>import com.enterra.customfieldsdemo.CustomizableEntityManager;<br></br>import com.enterra.customfieldsdemo.CustomizableEntityManagerImpl;<br></br>import com.enterra.customfieldsdemo.domain.Contact;<br></br>import org.hibernate.Session;<br></br>import org.hibernate.Transaction;<br></br>import java.io.Serializable;<p>public class TestCustomEntities {</p><br></br> private static final String TEST_FIELD_NAME = "email";<br></br> private static final String TEST_VALUE = "test@test.com";<p> public static void main(String[] args) {</p><br></br> HibernateUtil.getInstance().getCurrentSession();<p> CustomizableEntityManager contactEntityManager = new</p><br></br> CustomizableEntityManagerImpl(Contact.class);<p> contactEntityManager.addCustomField(TEST_FIELD_NAME);</p><p> Session session = HibernateUtil.getInstance().getCurrentSession();</p><p> Transaction tx = session.beginTransaction();</p><br></br> try {<p> Contact contact = new Contact();</p><br></br> contact.setName("Contact Name 1");<br></br> contact.setValueOfCustomField(TEST_FIELD_NAME, TEST_VALUE);<br></br> Serializable id = session.save(contact);<br></br> tx.commit();<p> contact = (Contact) session.get(Contact.class, id);</p><br></br> Object value = contact.getValueOfCustomField(TEST_FIELD_NAME);<br></br> System.out.println("value = " + value);<p> } catch (Exception e) {</p><br></br> tx.rollback();<br></br> System.out.println("e = " + e);<br></br> }<br></br> }<br></br>}
清单 19- 测试创建自定义字段
这个类的 main 方法负责执行下面的工作:
- 创建 Contact 类的 CustomizableEntityManager;
- 创建名为“email”的自定义字段;
- 在事务中,我们创建一个新的 Contact 对象,并设置自定义字段的值为“test@test.com”;
- 保存 Contact;
- 获取自定义字段“email”的值。
我们可以看到执行的结果如下:
configuring Hibernate ... ok<br></br>session opened.<br></br>closing session ... ok<br></br>closing session factory ... ok<br></br>configuring Hibernate ... ok<br></br>session opened.<br></br>Hibernate: insert into tbl_contact (fld_name, fld_email) values (?, ?)<br></br>value = test@test.com
清单 20- 测试结果
在数据库里,可以看到如下所示的记录:
+--------+---------------------+----------------------+<br></br>| fld_id | fld_name | fld_email |<br></br>+--------+---------------------+----------------------+<br></br>| 1 | Contact Name 1 | test@test.com |<br></br>+--------+---------------------+----------------------+
清单 21-DB 结果
正如看到的那样,新的字段在运行时被创建,其值也被成功保存。
第二个测试使用新创建的字段来查询数据库:
package com.enterra.customfieldsdemo.test;<p>import com.enterra.customfieldsdemo.HibernateUtil;</p><br></br>import com.enterra.customfieldsdemo.CustomizableEntityManager;<br></br>import com.enterra.customfieldsdemo.domain.Contact;<br></br>import org.hibernate.Session;<br></br>import org.hibernate.Criteria;<br></br>import org.hibernate.criterion.Restrictions;<br></br>import java.util.List;<p>public class TestQueryCustomFields {</p><br></br> public static void main(String[] args) {<br></br> Session session = HibernateUtil.getInstance().getCurrentSession();<br></br> Criteria criteria = session.createCriteria(Contact.class);<br></br> criteria.add(Restrictions.eq(CustomizableEntityManager.CUSTOM_COMPONENT_NAME + ".email", "test@test.com"));<br></br> List list = criteria.list();<br></br> System.out.println("list.size() = " + list.size());<br></br> }<br></br>}
清单 22- 测试自定义字段查询
Execution result:<br></br>configuring Hibernate ... ok<br></br>session opened.<br></br>Hibernate: select this_.fld_id as fld1_0_0_, this_.fld_name as fld2_0_0_,<br></br>this_.fld_email as fld3_0_0_ from tbl_contact this_ where this_.fld_email=?<br></br>list.size() = 1
清单 23- 查询结果
正如看到的,使用我们的方法创建的自定义字段能够很容易地参与到数据库查询中。
进一步改善
很显然,我们上面提到的实现相当简单。它并没有反映出该功能在实际实现中会遇到的各种情况。但是它还是说明了在建议的技术平台上解决方案的大体工作机制。
另外明显的是,该需求还可以使用其它办法(比如代码生成)来实现,这些办法也许会在其它文章中介绍。
这个实现仅支持 String 类型的自定义字段,但是,基于该方法的实际应用(Enterra CRM)中, 已经实现了对所有原始类型、对象类型(链接到业务对象)以及集合字段的完全支持。
为了在用户界面支持自定义字段,已经实现了针对自定义字段的元描述符系统,该系统使用了用户界面生成系统。但是生成器的机制是另外一篇文章的主题。
结论
最后,Enterra CRM 团队创建、验证并在实践中应用了基于 ORM 平台 Hibernate 的开放对象模型架构,它满足了客户在运行时不需要对应用源代码做任何改动、就可以按照最终用户的实际需求设置应用的需求。
译注一:Hibernate EntityManager 实现了 JPA、对象生命周期法则、以及 JSR 220 (EJB 3.0) 定义的查询选项。
译注二:Hibernate Annotations 提供了 JDK 5.0 代码标注的功能,从而替代 XML 元数据。
源码包: customfieldsdemo.zip
查看英文原文: Using Hibernate to Support Custom Domain Object Fields - - - - - -
译者简介: 王丽娟(Ivy Wang),一个快乐的程序员,持续从事 Java EE 中间件和 Java EE 企业应用的开发,关注软件架构技术。职业目标是成长为一名优秀的架构师。
评论