使用Hibernate支持自定义域对象字段

介绍

在开发公司级业务应用程序(企业级)时,客户通常需要实现对应用程序对象模型的可扩展性的支持,而不修改系统源代码。 使用可扩展域模型可以开发新功能,而无需付出额外的努力和开销:

  1. 该应用程序将使用更长的时间
  2. 更改外部因素时,系统工作流可以随时间进行修改
  3. “设置”应用程序以适合其已部署企业的具体情况。

实现所需功能的最简单,最具成本效益的方法是在应用程序中通过自定义字段的支持来实现可扩展业务实体。

什么是“自定义字段”?

什么是自定义字段,最终用户如何从中受益? 定制字段是对象的属性,该对象尚未由系统开发人员在开发阶段创建,但已由系统用户在实际使用系统时添加到该对象中,而没有对应用程序的源代码进行任何更改。

可能需要哪些功能?

让我们尝试基于CRM应用程序的示例来弄清楚这一点。 假设我们有一个对象“ Client”。 从理论上讲,该对象可以具有任意数量的各种属性:几个电子邮件地址,许多电话号码,地址等。其中一个可以由一个公司的销售部门使用,但是另一组织将完全忽略它们。 将所有可能的属性输入到最终用户可能会(可能不会)使用的对象中是浪费和不合理的。

在这种情况下,最好允许系统的用户(或管理员)创建特定组织中的销售经理所需的属性。 例如,如果实际上需要此字段或“家庭地址”等,则管理员可以创建属性“工作电话”或“家庭电话”。此外,这些字段可以在应用程序中使用,例如用于过滤和搜索数据。

简要描述;简介

在实施Enterra CRM项目时,客户将任务设置为支持应用程序中的“自定义文件”:“系统管理员应该能够创建/删除自定义字段,而无需重新启动系统”。

Hibernate 3.0框架用于开发后端。 该因素(技术约束)是实施该要求所考虑的关键。

实作

在本章中,我们将考虑使用Hibernate作为框架的细节,提供实现的关键时刻。

环境

下面的实现演示变体是使用以下工具开发的:

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

局限性

为了简化起见,我们将不使用Hibernate EntityManager,Hibernate Annotations。 持久对象的映射将建立在xml文件映射的基础上。 此外,值得一提的是,当使用Hibernate Annotations时,示例实现示例将无法运行,因为它基于xml文件映射进行管理。

任务定义

我们将必须实现一种机制,该机制允许实时创建/删除自定义字段,从而避免应用程序重启,在其中添加一个值并确保该值存在于应用程序数据库中。 此外,我们必须确保可以在查询中使用自定义字段。

领域模型

我们首先需要一个将要试验的业务实体类。 让我们成为Contact类。 将有两个持久字段:id和name。

但是,除了这些永久和不可更改的字段之外,该类还应该是某种类型的结构,用于存储自定义字段的值。 地图将是一个理想的构造。

让我们为所有支持自定义字段的业务实体创建一个基类-CustomizableEntity,其中包含要与自定义字段一起使用的Map CustomProperty:

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继承的Class 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-映射类联系人。

请注意,属性id和name与所有普通属性一样完成,但是对于customProperties,我们使用标记<dynamic-component>。 有关Hibernate 3.2.0GA的文档指出,动态组件的要点是:

“ <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-使用自动更新。

稍后,我们将详细解释其目的,并告诉我们如果没有它,我们将如何发展。 有几种方法可以实现类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类。

除了基于Hibernate的应用程序的常规工作所必需的常规方法(如getCurrentSession(),getConfiguration())外,我们还实现了以下方法:reset()和getClassMapping(ClassentityClass)。 在方法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(ClassEntityClass)返回对象PersistentClass,其中包含有关映射相关实体的完整信息。 特别是,使用对象PersistentClass进行的操作允许在运行时修改实体类的属性集。

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

清单8-方法getClassMapping(ClassEntityClass)。

映射操作

一旦有了可用的业务实体类(联系人),并且可以与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 = 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的配置状态以进行进一步的更改,这些更改在更改生效后才生效。

顺便说一句,我们应该回到字符串:

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

Hibernate配置。 如果缺少此字符串,则必须使用Hibernate实用程序启动对数据库模式的执行更新。 但是,使用设置可以避免这种情况。

保存映射

在运行时对映射所做的修改不会自己保存到相应的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<? extends CustomizableEntity> 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. 查找此文档的元素<dynamic-component>。 特别是在这里,我们存储自定义字段及其更改的内容;
  3. 从该元素中删除(!)所有嵌入式元素;
  4. 对于负责自定义字段存储的组件中包含的任何持久属性,我们创建一个特定的document元素,并从相应属性定义该元素的属性;
  5. 保存此新创建的映射文件。

在处理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 = "test@test.com";

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-测试创建自定义字段。

此方法负责以下事项:

  1. 为类Contact创建CustomizableEntityManager;
  2. 创建名为“电子邮件”的新自定义字段;
  3. 在交易中,我们创建一个新的联系人,并给自定义字段值“ test@test.com”;
  4. 保存联系人;
  5. 获取cusom字段“ 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 = test@test.com

清单20-测试结果。

在数据库中,可以看到以下记录:

+--------+---------------------+----------------------+
| fld_id | fld_name | fld_email
|
+--------+---------------------+----------------------+
| 1 | Contact Name 1 | test@test.com |
+--------+---------------------+----------------------+

清单21-数据库结果

如我们所见,新字段已在运行时创建,其值已成功保存。

第二个测试使用新创建的字段向数据库创建查询:

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", "test@test.com"));
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的开放对象模型的体系结构,该模型允许在运行过程中根据最终用户的实际需求满足客户对应用程序设置的需求。 -time无需更改应用程序的源代码。

翻译自: https://www.infoq.com/articles/hibernate-custom-fields/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值