1. 程式人生 > >用Hibernate實現領域物件的自定義欄位

用Hibernate實現領域物件的自定義欄位

在開發企業級業務應用(企業規模)時,客戶往往要求在不修改系統原始碼的情況下對應用物件模型的擴充套件性提供支援。利用可擴充套件域模型可以實現新功能的開發,而不需要額外的精力和成本

  1. 應用的使用週期將被延長; 
  2. 外部因素改變時,系統工作流也可以隨之被修改;
  3. 已經被部署的應用可以被“設定”,使其符合企業的特定情況。

完成以上功能需求最簡單、最具成本效益的方法應該是在應用中實現支援自定義欄位的可擴充套件業務實體。

什麼是“自定義欄位”?

什麼是自定義欄位?終端使用者如何從中受益呢?自定義欄位是一種物件屬性,它不是由系統開發人員在開發階段建立的,而是在系統實際使用中由系統使用者在不改變任何原始碼的情況下新增到物件中的。

可能會需要哪些功能呢?

讓我們舉一個CRM(客戶關係管理系統)應用的例子來領會一下。 假設我們有一個客戶“Client”物件。理論上講,這個物件可以有任意多的各種屬性:幾個email地址、若干電話號碼和地址等。某公司的銷售部門可能會使用其中一個屬性,但其它公司卻會完全忽略它。將終端使用者可能會用到的(也可能不會用到的)所有屬性都加入到物件當中,這是很浪費並很不合理的。

既然這樣,允許系統使用者(或者管理員)來建立他們公司的銷售經理們需要的屬性,也許是更好的做法。例如,如果有需要,管理員可以建立“工作電話”或者“家庭地址”等屬性。 此外,這些屬性還可以用到資料過濾和查詢中去。

簡要說明

在實施Enterra CRM專案時,客戶提出了在應用中支援自定義欄位的目標,“系統管理員不需要重啟系統就可以建立或刪除自定義欄位”。

系統後端開發使用了Hibernate 3.0框架,這個因素(技術約束)是考慮實現這個需求的關鍵。

實現

在這一章裡面我們將介紹採用Hibernate框架實現的關鍵環節。

環境

例子的開發環境如下所示: 

  1. JDK 1.5;
  2. Hibernate 3.2.0框架;
  3. MySQL 4.1。

限制

簡單起見,我們不使用Hibernate EntityManager(譯註一)和Hibernate Annotations(譯註二)。持久化物件的對映關係將基於xml對映檔案。此外,值得一提的是,由於演示用例是基於xml對映檔案管理對映,所以使用Hibernate Annotations的話,它將不能正常執行。

功能定義

我們必須實現一種機制——允許實時地建立/刪除自定義欄位而不重啟應用,向其中新增值並保證值能儲存到應用的資料庫中。此外我們還必須保證自定義欄位能用於查詢。

解決方案

域模型

首先,我們需要一個進行試驗的業務實體類。假設是Contact類,它有兩個持久化欄位:id和name。

但是,除了這些持久不變的欄位外,這個類還應該有一些儲存自定義欄位值的資料結構。Map也許是針對於此的理想資料結構。

為所有支援自定義欄位的業務實體建立一個基類——CustomizableEntity,它包含處理自定義欄位的Map型別屬性customProperties:

package com.enterra.customfieldsdemo.domain;

import java.util.Map;
import java.util.HashMap;

public abstract class CustomizableEntity {

private Map customProperties;

public Map getCustomProperties() {
         if (customProperties == null)
             customProperties = new HashMap();
        return customProperties;
}
public void setCustomProperties(Map customProperties) {
        this.customProperties = customProperties;
}

public Object getValueOfCustomField(String name) {
        return getCustomProperties().get(name);
}

public void setValueOfCustomField(String name, Object value) {
        getCustomProperties().put(name, value);
}

} 

清單1-基類CustomizableEntity

Contact類繼承上面的基類:

package com.enterra.customfieldsdemo.domain;

import com.enterra.customfieldsdemo.domain.CustomizableEntity;

public class Contact extends CustomizableEntity {

private int id;
private String name;

public int getId() {
     return id;
}

public void setId(int id) {
     this.id = id;
}

public String getName() {
     return name;
}

public void setName(String name) {
     this.name = name;
}

} 

清單2-繼承自CustomizableEntity的Contact類

別忘了這個類的對映檔案:

<?xml version="1.0" encoding="UTF-8"?>

 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" 
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

 <hibernate-mapping auto-import="true" default-access="property" default-cascade="none" default-lazy="true">

 <class abstract="false" name="com.enterra.customfieldsdemo.domain.Contact" table="tbl_contact">

     <id column="fld_id" name="id">
         <generator class="native"/>
     </id>

     <property name="name" column="fld_name" type="string"/>
     <dynamic-component insert="true" name="customProperties" optimistic-lock="true" unique="false" update="true">
     </dynamic-component>
 </class>
 </hibernate-mapping> 

清單3-Contact類的對映

注意id和name屬性都是當作普通的屬性來處理,但對於customProperties,我們使用了(動態元件)標籤。Hibernate 3.2.0GA文件裡面關於dynamic-component的要點如下:

<dynamic-component>對映的語義與<component>是一樣的。該對映的優點是僅僅通過編輯對映檔案,就能在部署時確定bean的現行屬性。使用DOM解析器,對映檔案的執行時操作也是可行的。甚至,你可以通過Configuration物件,來訪問(和修改)Hibernate的配置時元模型。

基於Hibernate文件中的這段規則,我們來建立前面要求的功能機制。

HibernateUtil和hibernate.cfg.xml

定義了應用中的域模型之後,我們需要建立Hibernate框架運轉的必要條件。為此我們必須建立一個配置檔案hibernate.cfg.xml和一個處理Hibernate核心功能的類。

<?xml version='1.0' encoding='utf-8'?>

 <!DOCTYPE hibernate-configuration

 PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"

 "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 <hibernate-configuration>

 <session-factory>

     <property name="show_sql">true</property>
     <property name="dialect">
org.hibernate.dialect.MySQLDialect</property>
     <property name="cglib.use_reflection_optimizer">true</property>
     <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
     <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/custom_fields_test</property>
     <property name="hibernate.connection.username">root</property>
     <property name="hibernate.connection.password"></property>
     <property name="hibernate.c3p0.max_size">50</property>
     <property name="hibernate.c3p0.min_size">0</property>
     <property name="hibernate.c3p0.timeout">120</property>
     <property name="hibernate.c3p0.max_statements">100</property>
     <property name="hibernate.c3p0.idle_test_period">0</property>
     <property name="hibernate.c3p0.acquire_increment">2</property>
     <property name="hibernate.jdbc.batch_size">20</property>
     <property name="hibernate.hbm2ddl.auto">update</property>
 </session-factory>
 </hibernate-configuration>

清單4-Hibernate配置檔案

hibernate.cfg.xml檔案沒有什麼需要特別關注的,除了下面這句:

<property name="hibernate.hbm2ddl.auto">update</property>

清單5-使用auto-update(自動更新)

後面我們將詳細解釋其目的,並更多地講解沒有它我們怎樣實現。HibernateUtil類有好幾種實現方式。由於Hibernate配置檔案內容的不同,我們的實現與已知的那些將有一點兒不同。

package com.enterra.customfieldsdemo;

import org.hibernate.*;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.hibernate.cfg.Configuration;
import com.enterra.customfieldsdemo.domain.Contact;

public class HibernateUtil {

private static HibernateUtil instance;
private Configuration configuration;
private SessionFactory sessionFactory;
private Session session;

public synchronized static HibernateUtil getInstance() {
     if (instance == null) {
         instance = new HibernateUtil();
     }
     return instance;
}

private synchronized SessionFactory getSessionFactory() {
     if (sessionFactory == null) {
         sessionFactory = getConfiguration().buildSessionFactory();
     }
     return sessionFactory;
}

public synchronized Session getCurrentSession() {
     if (session == null) {
         session = getSessionFactory().openSession();
         session.setFlushMode(FlushMode.COMMIT);
         System.out.println("session opened.");
     }
     return session;
}

private synchronized Configuration getConfiguration() {
     if (configuration == null) {
         System.out.print("configuring Hibernate ... ");
         try {
             configuration = new Configuration().configure();
             configuration.addClass(Contact.class);
             System.out.println("ok");
         } catch (HibernateException e) {
             System.out.println("failure");
             e.printStackTrace();
         }
     }
     return configuration;
}
public void reset() {
     Session session = getCurrentSession();
     if (session != null) {
         session.flush();
         if (session.isOpen()) {
             System.out.print("closing session ... ");
             session.close();
             System.out.println("ok");
         }
     }
     SessionFactory sf = getSessionFactory();
     if (sf != null) {
         System.out.print("closing session factory ... ");
         sf.close();
         System.out.println("ok");
     }
     this.configuration = null;
     this.sessionFactory = null;
     this.session = null;
}

public PersistentClass getClassMapping(Class entityClass){
     return getConfiguration().getClassMapping(entityClass.getName());
}
} 

清單6-HibernateUtils類

除了平常的getCurrentSession()和getConfiguration()方法(這些方法對基於Hibernate的應用的常規操作是很必要的)之外,我們還需要實現像reset()和getClassMapping(Class entityClass)這樣的方法。在getConfiguration()方法中,我們配置Hibernate、並將類Contact新增到配置中去。

reset()方法關閉所有Hibernate使用的資源、清除所有的設定:

public void reset() {
     Session session = getCurrentSession();
     if (session != null) {
         session.flush();
         if (session.isOpen()) {
            System.out.print("closing session ... ");
            session.close();
            System.out.println("ok");
         }
     }
     SessionFactory sf = getSessionFactory();
     if (sf != null) {
         System.out.print("closing session factory ... ");
         sf.close();
         System.out.println("ok");
     }
     this.configuration = null;
     this.sessionFactory = null;
     this.session = null;
} 

清單7-reset()方法

getClassMapping(Class entityClass)方法返回PersistentClass物件,該物件包含相關實體對映的全部資訊。特別地,對PersistentClass物件的處理允許在執行時修改實體類的屬性設定。

public PersistentClass getClassMapping(Class entityClass){
     return getConfiguration().getClassMapping(entityClass.getName());
} 

清單8-getClassMapping(Class entityClass)方法

處理對映

一旦我們有了可用的業務實體類(Contact)和與Hibernate互動的主類,我們就能開始工作了。我們能建立、儲存Contact類的例項。甚至可以在Map物件customProperties裡面放置一些資料,但是需要注意的是儲存在Map物件customProperties裡面的資料並不會被儲存到資料庫裡。

為了儲存資料,我們需要讓這個機制能在類裡面建立自定義欄位,並且要讓Hibernate知道該如何處理它們。

為了實現對類對映的處理,我們需要建立一些介面。叫它CustomizableEntityManager吧。名字應該表現出該介面管理業務實體及其內容、屬性的意圖:

package com.enterra.customfieldsdemo;

import org.hibernate.mapping.Component;

public interface CustomizableEntityManager {
     public static String CUSTOM_COMPONENT_NAME = "customProperties";

     void addCustomField(String name);

     void removeCustomField(String name);

     Component getCustomProperties();

     Class getEntityClass();
} 

清單9-CustomizableEntityManager介面

介面中重要的方法是void addCustomField(String name)和void removeCustomField(String name)。它們將分別在相應類的對映裡建立、刪除我們的自定義欄位。

下面是實現該介面的情況:

package com.enterra.customfieldsdemo;

import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.*;
import java.util.Iterator;

public class CustomizableEntityManagerImpl implements CustomizableEntityManager {
     private Component customProperties;
     private Class entityClass;

     public CustomizableEntityManagerImpl(Class entityClass) {
         this.entityClass = entityClass;
     }

     public Class getEntityClass() {
         return entityClass;
     }

     public Component getCustomProperties() {
         if (customProperties == null) {
             Property property = getPersistentClass().getProperty(CUSTOM_COMPONENT_NAME);
             customProperties = (Component) property.getValue();
         }
         return customProperties;
     }

     public void addCustomField(String name) {
         SimpleValue simpleValue = new SimpleValue();
         simpleValue.addColumn(new Column("fld_" + name));
         simpleValue.setTypeName(String.class.getName());

         PersistentClass persistentClass = getPersistentClass();
         simpleValue.setTable(persistentClass.getTable());

         Property property = new Property();
         property.setName(name);
         property.setValue(simpleValue);
         getCustomProperties().addProperty(property);

         updateMapping();
     }

     public void removeCustomField(String name) {
         Iterator propertyIterator = customProperties.getPropertyIterator();

         while (propertyIterator.hasNext()) {
             Property property = (Property) propertyIterator.next();
             if (property.getName().equals(name)) {
                 propertyIterator.remove();
                 updateMapping();
                 return;
             }
         }
     }

     private synchronized void updateMapping() {
         MappingManager.updateClassMapping(this);
         HibernateUtil.getInstance().reset();
         //        updateDBSchema();
     }

     private PersistentClass getPersistentClass() {
         return HibernateUtil.getInstance().getClassMapping(this.entityClass);
     }
} 

清單10-介面CustomizableEntityManager的實現

首先需要指出的是,在構造CustomizableEntityManager時,我們要指定管理器操作的業務實體類。該業務實體類作為引數傳遞給CustomizableEntityManager的建構函式:

private Class entityClass;

public CustomizableEntityManagerImpl(Class entityClass) {
     this.entityClass = entityClass;
}

public Class getEntityClass() {
     return entityClass;
} 

清單11-CustomizableEntityManagerImpl建構函式

現在我們應該對void addCustomField(String name)方法的實現更感興趣:

public void addCustomField(String name) {
     SimpleValue simpleValue = new SimpleValue();
     simpleValue.addColumn(new Column("fld_" + name));
     simpleValue.setTypeName(String.class.getName());

     PersistentClass persistentClass = getPersistentClass();
     simpleValue.setTable(persistentClass.getTable());

     Property property = new Property();
     property.setName(name);
     property.setValue(simpleValue);
     getCustomProperties().addProperty(property);

     updateMapping();
} 

清單12-建立自定義欄位

正如我們從實現中看到的一樣,Hibernate在處理持久化物件的屬性及其在資料庫中的表示方面提供了更多的選擇。下面分步講解該方法的要素:

1)建立一個SimpleValue類物件,它指明瞭自定義欄位的值如何被儲存到欄位和表所在的資料庫中:

SimpleValue simpleValue = new SimpleValue();
simpleValue.addColumn(new Column("fld_" + name));
simpleValue.setTypeName(String.class.getName());

PersistentClass persistentClass = getPersistentClass();
simpleValue.setTable(persistentClass.getTable()); 

清單13-表建立新列

2)給持久化物件建立一個屬性(property),並將動態元件新增進去,注意,這是我們為了這個目的已經計劃好的:

Property property = new Property()
property.setName(name)
property.setValue(simpleValue)
getCustomProperties().addProperty(property) 

清單14-建立物件屬性

3)最後應該讓應用修改xml檔案,並更新Hibernate配置。這個可以由updateMapping()方法來完成;

闡明上面程式碼中另外兩個get方法的用途還是很有必要的。第一個方法是getCustomProperties():

public Component getCustomProperties() {
     if (customProperties == null) {
         Property property = getPersistentClass().getProperty(CUSTOM_COMPONENT_NAME);
         customProperties = (Component) property.getValue();
     }
     return customProperties;
} 

清單15-獲取元件CustomProperties

該方法找到並返回與業務實體對映中標籤相對應的元件(Component)物件。

第二個方法是updateMapping():

private synchronized void updateMapping() {
     MappingManager.updateClassMapping(this);
     HibernateUtil.getInstance().reset();
     //        updateDBSchema();
} 

清單16-updateMapping()方法

該方法負責儲存更新後的持久化類對映,並且更新Hibernate的配置狀態,以進一步使改變生效。

順便,我們回過頭來看看Hibernate配置中的語句: 

<property name="hibernate.hbm2ddl.auto">update</property>

如果缺少該配置,我們就必須使用Hibernate工具類來執行資料庫schema的更新。然而使用該設定讓我們避免了那麼做。

儲存對映

執行時對對映的修改不會將自身儲存到相應的xml對映檔案中,為了使變化在應用下次的執行中活化,我們需要手動將變化儲存到對應的對映檔案中去。

我們使用MappingManager類來完成這件工作,該類的主要目的是將指定的業務實體的對映儲存到其xml對映檔案中去:

package com.enterra.customfieldsdemo;

import com.enterra.customfieldsdemo.domain.CustomizableEntity;
import org.hibernate.Session;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Property;
import org.hibernate.type.Type;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Iterator;

public class MappingManager {
     public static void updateClassMapping(CustomizableEntityManager entityManager) {
         try {
             Session session = HibernateUtil.getInstance().getCurrentSession();
             Class entityClass = entityManager.getEntityClass();
             String file = entityClass.getResource(entityClass.getSimpleName() + ".hbm.xml").getPath();

             Document document = XMLUtil.loadDocument(file);
             NodeList componentTags = document.getElementsByTagName("dynamic-component");
             Node node = componentTags.item(0);
             XMLUtil.removeChildren(node);

             Iterator propertyIterator = entityManager.getCustomProperties().getPropertyIterator();
             while (propertyIterator.hasNext()) {
                 Property property = (Property) propertyIterator.next();
                 Element element = createPropertyElement(document, property);
                 node.appendChild(element);
             }

             XMLUtil.saveDocument(document, file);
         } catch (Exception e) {
             e.printStackTrace();
         }
    }

    private static Element createPropertyElement(Document document, Property property) {
         Element element = document.createElement("property");
         Type type = property.getType();

         element.setAttribute("name", property.getName());
         element.setAttribute("column", ((Column) property.getColumnIterator().next()).getName());
         element.setAttribute("type", type.getReturnedClass().getName());
         element.setAttribute("not-null", String.valueOf(false));

         return element;
     }
} 

清單17-更新持久化類對映的工具類

該類一一執行了下面的操作:

  1. 對於指定的業務實體,定義其xml對映的位置,並載入到DOM Document物件中,以供進一步操作;
  2. 查詢到Document物件中的元素。我們將在這裡儲存自定義欄位和我們所做的內容變化;
  3. 將該元素內巢狀的所有元素都刪除;
  4. 對於負責自定義欄位儲存的元件所包含的任意持久化屬性,我們都建立一個特定的document元素,並根據相應的屬性為元素定義屬性;
  5. 儲存這個新建的對映檔案。

雖然我們這裡用了XMLUtil類(正如從程式碼中看到的一樣)來處理XML,但是一般而言,可以換成任何一種方式來實現,不過XMLUtil已經足以載入並儲存xml檔案。

我們的實現如下面的清單所示:

package com.enterra.customfieldsdemo;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.dom.DOMSource;
import java.io.IOException;
import java.io.FileOutputStream;

public class XMLUtil {
     public static void removeChildren(Node node) {
         NodeList childNodes = node.getChildNodes();
         int length = childNodes.getLength();
         for (int i = length - 1; i > -1; i--)
             node.removeChild(childNodes.item(i));
         }

     public static Document loadDocument(String file)
         throws ParserConfigurationException, SAXException, IOException {

         DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();
         DocumentBuilder builder = factory.newDocumentBuilder();
         return builder.parse(file);
     }

     public static void saveDocument(Document dom, String file)
         throws TransformerException, IOException {

         TransformerFactory tf = TransformerFactory.newInstance();
         Transformer transformer = tf.newTransformer();

         transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, dom.getDoctype().getPublicId());
         transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dom.getDoctype().getSystemId());

         DOMSource source = new DOMSource(dom);
         StreamResult result = new StreamResult();

         FileOutputStream outputStream = new FileOutputStream(file);
         result.setOutputStream(outputStream);
         transformer.transform(source, result);

         outputStream.flush();
         outputStream.close();
     }
} 

清單18-XML處理工具類

測試

我們有了所有必需的執行程式碼, 現在可以編寫測試程式碼來看看一切到底是怎樣工作的。第一個測試建立自定義欄位“email”,建立並儲存Contact類的例項,並給它定義“email”屬性。

首先讓我們看一下資料庫表tbl_contact,它包括兩個欄位:fld_id和fld_name。程式碼如下:

package com.enterra.customfieldsdemo.test;

import com.enterra.customfieldsdemo.HibernateUtil;
import com.enterra.customfieldsdemo.CustomizableEntityManager;
import com.enterra.customfieldsdemo.CustomizableEntityManagerImpl;
import com.enterra.customfieldsdemo.domain.Contact;
import org.hibernate.Session;
import org.hibernate.Transaction;
import java.io.Serializable;

public class TestCustomEntities {
     private static final String TEST_FIELD_NAME = "email";
     private static final String TEST_VALUE = "[email protected]";

     public static void main(String[] args) {
         HibernateUtil.getInstance().getCurrentSession();

         CustomizableEntityManager contactEntityManager = new
      CustomizableEntityManagerImpl(Contact.class);

         contactEntityManager.addCustomField(TEST_FIELD_NAME);

         Session session = HibernateUtil.getInstance().getCurrentSession();

         Transaction tx = session.beginTransaction();
         try {

             Contact contact = new Contact();
             contact.setName("Contact Name 1");
             contact.setValueOfCustomField(TEST_FIELD_NAME, TEST_VALUE);
             Serializable id = session.save(contact);
             tx.commit();

             contact = (Contact) session.get(Contact.class, id);
             Object value = contact.getValueOfCustomField(TEST_FIELD_NAME);
             System.out.println("value = " + value);

         } catch (Exception e) {
            tx.rollback();
            System.out.println("e = " + e);
         }
     }
} 

清單19-測試建立自定義欄位

這個類的main方法負責執行下面的工作:

  1. 建立Contact類的CustomizableEntityManager;
  2. 建立名為“email”的自定義欄位;
  3. 在事務中,我們建立一個新的Contact物件,並設定自定義欄位的值為“[email protected]”;
  4. 儲存Contact;
  5. 獲取自定義欄位“email”的值。

我們可以看到執行的結果如下:

configuring Hibernate ... ok
session opened.
closing session ... ok
closing session factory ... ok
configuring Hibernate ... ok
session opened.
Hibernate: insert into tbl_contact (fld_name, fld_email) values (?, ?)
value = [email protected] 

清單20-測試結果

在資料庫裡,可以看到如下所示的記錄:

+--------+---------------------+----------------------+
| fld_id    | fld_name                 | fld_email                   |
+--------+---------------------+----------------------+
|     1     | Contact Name 1        | [email protected]          |
+--------+---------------------+----------------------+ 

清單21-DB結果

正如看到的那樣,新的欄位在執行時被建立,其值也被成功儲存。

第二個測試使用新建立的欄位來查詢資料庫:

package com.enterra.customfieldsdemo.test;

import com.enterra.customfieldsdemo.HibernateUtil;
import com.enterra.customfieldsdemo.CustomizableEntityManager;
import com.enterra.customfieldsdemo.domain.Contact;
import org.hibernate.Session;
import org.hibernate.Criteria;
import org.hibernate.criterion.Restrictions;
import java.util.List;

public class TestQueryCustomFields {
     public static void main(String[] args) {
         Session session = HibernateUtil.getInstance().getCurrentSession();
         Criteria criteria = session.createCriteria(Contact.class);
         criteria.add(Restrictions.eq(CustomizableEntityManager.CUSTOM_COMPONENT_NAME + ".email", "[email protected]"));
         List list = criteria.list();
         System.out.println("list.size() = " + list.size());
     }
} 

清單22-測試自定義欄位查詢

Execution result:
configuring Hibernate ... ok
session opened.
Hibernate: select this_.fld_id as fld1_0_0_, this_.fld_name as fld2_0_0_,
this_.fld_email as fld3_0_0_ from tbl_contact this_ where this_.fld_email=?
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元資料。

相關推薦

Hibernate實現領域物件定義

在開發企業級業務應用(企業規模)時,客戶往往要求在不修改系統原始碼的情況下對應用物件模型的擴充套件性提供支援。利用可擴充套件域模型可以實現新功能的開發,而不需要額外的精力和成本 應用的使用週期將被延長; 外部因素改變時,系統工作流也可以隨之被修改;已經被部署的應用可以被“設

微信小程式定義實現選項的動態新增和刪除

問題描述: 在自定義選項中,點選新增選項按鈕,會出現一個選項的輸入框,輸入的選項可以通過點選左側的減號刪除 效果如圖: 解決過程: 1.首先寫出大體的框架。在pages下,建立了一個selfdefine的資料夾,在wxml中寫出靜態結構 selfdefine.wxml 說明

在thinkcmf5中實現為各個分類下的文章新增定義的想法

前言 這裡只是自己的一個想法。此想法只完成了裡面的一部分。記於此,希望有同樣需求的同學們共同討論和學習。 我也是剛學習使用這個框架。在學習使用的過程中有這樣的一個需求。我的某一個文章分類下面的文章需要新增兩個欄位(原價、現價),我想很多同學都有這樣的需求吧。

織夢dede:arclist按照定義的條件呼叫相關文章

dedecms織夢dede:arclist按照自定義欄位的條件呼叫相關文章,這對於想要在首頁呼叫某個自定義欄位的文章的同學來講,非常不錯 開啟 /include/taglib/arclist.lib.php 找到 //關鍵字條件 在它的上面加入 //自定義欄位關鍵字條件 if($ctag->G

織夢likearticle呼叫附加定義

在dedecms文章頁中我們經常會顯示相關文章之類的文章列表,就需要使用{dede:likearticle}標籤,但是預設的likearticle是不能顯示自定義的附加欄位的。 解決辦法 開啟 include/taglib/likearticle.lib.php 找到 if($keyword != ''

織夢獲取定義附件型別檔案的格式型別檔案大小上傳日期

內容頁顯示效果 實現步驟 1、\templets\system\channel_addon.htm 裡面要清空,只留~link~   (注意:前後不能留有一個空格) 2、/include/extend.func.php  最下面加入方法 function GetFileInfo

織夢新增超過兩百個定義後在使用addfields呼叫定義出錯的解決方法

dedecsm 自定義模型  新增自定義欄位(個數一百多個),使用addfields  方法呼叫,出現呼叫不出來的情況【addfields  裡面就能新增145個欄位,多了直接亂碼或者無法顯示】 解決方法 分別開啟 include/dedehtml2.class.

spring data jpa 查詢定義,轉換為定義實體

目標:查詢資料庫中的欄位,然後轉換成 JSON 格式的資料,返回前臺。 環境:idea 2016.3.4, jdk 1.8, mysql 5.6, spring-boot 1.5.2 背景:首先建立 entity 對映資料庫(非專業 java 不知道這怎麼說) @Entity @Tab

dede文章增加HTML定義字元被過濾問題

  在dedecms後臺頻道模型增加自定義欄位,一般HTML文字編輯器能解決使用者編輯問題,當然還包括純單行或多行文字編輯。但發現dedecms會自動過濾掉某些敏感的字元,比如style樣式,百度地圖js呼叫問題。下面主要圍繞著兩個問題分享一下新聞發言人開放時候的一些經驗。

織夢likearticle標籤呼叫和顯示附加(定義)辦法

在Dedecms文章頁中我們經常會顯示相關文章之類的文章列表,就需要使用{dede:likearticle}標籤,但是預設的 likearticle是不能顯示自定義的附加欄位的。解決辦法: 修改include/taglib/likearticle.lib.php. 大概在178行 $

PHPCMS推薦呼叫定義

  第1步:成功登入到phpcms後臺。   第2步:開啟內容>>內容相關設定>>模型管理>>找到自己相對應的模型。   第3步:開啟模型找到並開啟“欄位管理”,在自己需要顯示的欄位上點選“修改”。   第4步:在最後倒數第三個”在推薦位標籤中呼叫“上選擇是並確定儲存

log4net配置定義存入資料庫

  前言 以bs專案中引入log4net為例。log4net存入資料庫提供了基本的(時間、執行緒、等級、message)欄位。 但是實際日誌場景中可能需要統計IP、使用者ID或者其他業務相關的資訊記入日誌。 需要重寫log4net的部分方法來實現。本文展示使用log4net從0開始到記錄自定

destoon-定義新增到供應列表模糊搜尋中

/module/sell/sell.class.php $keyword = $item['title'].','.$TYPE[$item['typeid']].','.strip_tags(cat_pos(get_cat($item['catid']), ','));

Android配置build.gradle的buildTypes動態自動維護debug和release包定義變數值

Android配置build.gradle的buildTypes動態自動維護debug和release包自定義欄位變數值 在Android編譯出包階段,debug包和正式的release有時候需要作出有所區別的調整。 比如最常見的是App應用程式中Log日誌輸出開關的控制。開發者希望在deb

dede首頁新增定義詳細介紹

 注:要獲取附加表內容,必須符合兩個條件 1、指定 channelid 屬性 2、指定要獲得的欄位 addfields='欄位1,欄位'    每個欄位用英文半形逗號分開 如: {dede:arclist addfields='goumai,dianpu

Dede定義搜尋結果頁顯示定義

1。修改puls/advancedsearch.php檔案,找到   $query = "select arctype.* FROM $addontable addon   left join dede2_arctype arctype on arctype.id=a

destoon後臺定義複選框多選框增加全選全不選按鈕

修改include/fields.func.php 中的函式function fields_show() 在177行 case 'checkbox': if(

dedecms教程:在高階搜尋頁面advancedsearch.php呼叫定義

做了advancedsearch搜尋頁面,卻發現自定義的模型裡面的自定義欄位不顯示? 如下: 如果通過“內容模型管理 > 自定義搜尋”搜尋的結果頁如果要顯示自定義欄位,可使用以下方法: 1。修改puls/advancedsearch.php檔案,找到程式碼

DEDE首頁定義、多條件搜尋功能

<select name="ca" id="ca" style="width:100px; height:26px; font-size:14px;"> <option value="0">招聘狀態</option&

jeecg定義datagrid封裝列表分頁資料顯示定義

/** * easyui AJAX請求資料 * @param clockin * @param request * @param response * @param dataGrid */ @RequestMapping(params = "dat