- 浏览: 23764 次
- 性别:
- 来自: 江苏
最新评论
-
anybyb:
到后面就有点晕了!不大看得懂了
用Hibernate实现领域对象的自定义字段 -
ootabc:
session opened.
java.io.FileNot ...
用Hibernate实现领域对象的自定义字段 -
ootabc:
你好! 我是一名hibernate 初学者,也是一名应届生. ...
用Hibernate实现领域对象的自定义字段 -
jasonjar:
可以
判断单选是否选中
testing......
导言
在开发企业级业务应用(企业规模)时,客户往往要求在不修改系统源代码的情况下对应用对象模型的扩展性提供支持。利用可扩展域模型可以实现新功能的开发,而不需要额外的精力和成本
应用的使用周期将被延长;
外部因素改变时,系统工作流也可以随之被修改;
已经被部署的应用可以被“设定”,使其符合企业的特定情况。
完成以上功能需求最简单、最具成本效益的方法应该是在应用中实现支持自定义字段的可扩展业务实体。
什么是“自定义字段”?
什么是自定义字段?最终用户如何从中受益呢?自定义字段是一种对象属性,它不是由系统开发人员在开发阶段创建的,而是在系统实际使用中由系统用户在不改变任何源代码的情况下添加到对象中的。
可能会需要哪些功能呢?
让我们举一个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;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-更新持久化类映射的工具类
该类一一执行了下面的操作:
对于指定的业务实体,定义其xml映射的位置,并加载到DOM Document对象中,以供进一步操作;
查找到Document对象中的元素。我们将在这里存储自定义字段和我们所做的内容变化;
将该元素内嵌套的所有元素都删除;
对于负责自定义字段存储的组件所包含的任意持久化属性,我们都创建一个特定的document元素,并根据相应的属性为元素定义属性;
保存这个新建的映射文件。
虽然我们这里用了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 = "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-测试创建自定义字段
这个类的main方法负责执行下面的工作:
创建Contact类的CustomizableEntityManager;
创建名为“email”的自定义字段;
在事务中,我们创建一个新的Contact对象,并设置自定义字段的值为“test@test.com”;
保存Contact;
获取自定义字段“email”的值。
我们可以看到执行的结果如下:
configuring Hibernate ... oksession opened.closing session ... okclosing session factory ... okconfiguring Hibernate ... oksession 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-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", "test@test.com")); List list = criteria.list(); System.out.println("list.size() = " + list.size()); }}
清单22-测试自定义字段查询
Execution result:configuring Hibernate ... oksession 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)中, 已经实现了对所有原始类型、对象类型(链接到业务对象)以及集合字段的完全支持。
为了在用户界面支持自定义字段,已经实现了针对自定义字段的元描述符系统,该系统使用了用户界面生成系统。但是生成器的机制是另外一篇文章的主题。
导言
在开发企业级业务应用(企业规模)时,客户往往要求在不修改系统源代码的情况下对应用对象模型的扩展性提供支持。利用可扩展域模型可以实现新功能的开发,而不需要额外的精力和成本
应用的使用周期将被延长;
外部因素改变时,系统工作流也可以随之被修改;
已经被部署的应用可以被“设定”,使其符合企业的特定情况。
完成以上功能需求最简单、最具成本效益的方法应该是在应用中实现支持自定义字段的可扩展业务实体。
什么是“自定义字段”?
什么是自定义字段?最终用户如何从中受益呢?自定义字段是一种对象属性,它不是由系统开发人员在开发阶段创建的,而是在系统实际使用中由系统用户在不改变任何源代码的情况下添加到对象中的。
可能会需要哪些功能呢?
让我们举一个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;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-更新持久化类映射的工具类
该类一一执行了下面的操作:
对于指定的业务实体,定义其xml映射的位置,并加载到DOM Document对象中,以供进一步操作;
查找到Document对象中的元素。我们将在这里存储自定义字段和我们所做的内容变化;
将该元素内嵌套的所有元素都删除;
对于负责自定义字段存储的组件所包含的任意持久化属性,我们都创建一个特定的document元素,并根据相应的属性为元素定义属性;
保存这个新建的映射文件。
虽然我们这里用了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 = "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-测试创建自定义字段
这个类的main方法负责执行下面的工作:
创建Contact类的CustomizableEntityManager;
创建名为“email”的自定义字段;
在事务中,我们创建一个新的Contact对象,并设置自定义字段的值为“test@test.com”;
保存Contact;
获取自定义字段“email”的值。
我们可以看到执行的结果如下:
configuring Hibernate ... oksession opened.closing session ... okclosing session factory ... okconfiguring Hibernate ... oksession 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-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", "test@test.com")); List list = criteria.list(); System.out.println("list.size() = " + list.size()); }}
清单22-测试自定义字段查询
Execution result:configuring Hibernate ... oksession 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)中, 已经实现了对所有原始类型、对象类型(链接到业务对象)以及集合字段的完全支持。
为了在用户界面支持自定义字段,已经实现了针对自定义字段的元描述符系统,该系统使用了用户界面生成系统。但是生成器的机制是另外一篇文章的主题。
评论
3 楼
anybyb
2012-08-01
到后面就有点晕了!不大看得懂了
2 楼
ootabc
2009-05-24
session opened.
java.io.FileNotFoundException: E:\eclips%20%e4%bd%9c%e5%93%81\MyEclipse%207.0M1%20workplace\work\WebRoot\WEB-INF\classes\com\enterra\customfieldsdemo\domain\Contact.hbm.xml (系统找不到指定的路径。)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(Unknown Source)
at java.io.FileOutputStream.<init>(Unknown Source)
at com.enterra.customfieldsdemo.XMLUtil.saveDocument(XMLUtil.java:35)
at com.enterra.customfieldsdemo.MappingManager.updateClassMapping(MappingManager.java:28)
at com.enterra.customfieldsdemo.CustomizableEntityManagerImpl.updateMapping(CustomizableEntityManagerImpl.java:43)
at com.enterra.customfieldsdemo.CustomizableEntityManagerImpl.addCustomField(CustomizableEntityManagerImpl.java:29)closing session ... ok
closing session factory ... ok
configuring Hibernate ...
at com.enterra.customfieldsdemo.test.TestCustomEntities.main(TestCustomEntities.java:15)
ok
session opened.
Exception in thread "main" org.hibernate.exception.GenericJDBCException: Cannot open connection
at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:29)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:426)
at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:144)
at org.hibernate.jdbc.JDBCContext.connection(JDBCContext.java:119)
at org.hibernate.transaction.JDBCTransaction.begin(JDBCTransaction.java:57)
at org.hibernate.impl.SessionImpl.beginTransaction(SessionImpl.java:1326)
at com.enterra.customfieldsdemo.test.TestCustomEntities.main(TestCustomEntities.java:17)
Caused by: java.sql.SQLException: Connections could not be acquired from the underlying database!
at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:104)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:264)
at com.mchange.v2.c3p0.PoolBackedDataSource.getConnection(PoolBackedDataSource.java:94)
at org.hibernate.connection.C3P0ConnectionProvider.getConnection(C3P0ConnectionProvider.java:56)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:423)
... 5 more
Caused by: com.mchange.v2.resourcepool.CannotAcquireResourceException: A ResourcePool could not acquire a resource from its primary factory or source.
at com.mchange.v2.resourcepool.BasicResourcePool.awaitAcquire(BasicResourcePool.java:972)
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:208)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:260)
... 8 more
还有上面这么异常
java.io.FileNotFoundException: E:\eclips%20%e4%bd%9c%e5%93%81\MyEclipse%207.0M1%20workplace\work\WebRoot\WEB-INF\classes\com\enterra\customfieldsdemo\domain\Contact.hbm.xml (系统找不到指定的路径。)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(Unknown Source)
at java.io.FileOutputStream.<init>(Unknown Source)
at com.enterra.customfieldsdemo.XMLUtil.saveDocument(XMLUtil.java:35)
at com.enterra.customfieldsdemo.MappingManager.updateClassMapping(MappingManager.java:28)
at com.enterra.customfieldsdemo.CustomizableEntityManagerImpl.updateMapping(CustomizableEntityManagerImpl.java:43)
at com.enterra.customfieldsdemo.CustomizableEntityManagerImpl.addCustomField(CustomizableEntityManagerImpl.java:29)closing session ... ok
closing session factory ... ok
configuring Hibernate ...
at com.enterra.customfieldsdemo.test.TestCustomEntities.main(TestCustomEntities.java:15)
ok
session opened.
Exception in thread "main" org.hibernate.exception.GenericJDBCException: Cannot open connection
at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:29)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:426)
at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:144)
at org.hibernate.jdbc.JDBCContext.connection(JDBCContext.java:119)
at org.hibernate.transaction.JDBCTransaction.begin(JDBCTransaction.java:57)
at org.hibernate.impl.SessionImpl.beginTransaction(SessionImpl.java:1326)
at com.enterra.customfieldsdemo.test.TestCustomEntities.main(TestCustomEntities.java:17)
Caused by: java.sql.SQLException: Connections could not be acquired from the underlying database!
at com.mchange.v2.sql.SqlUtils.toSQLException(SqlUtils.java:104)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:264)
at com.mchange.v2.c3p0.PoolBackedDataSource.getConnection(PoolBackedDataSource.java:94)
at org.hibernate.connection.C3P0ConnectionProvider.getConnection(C3P0ConnectionProvider.java:56)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:423)
... 5 more
Caused by: com.mchange.v2.resourcepool.CannotAcquireResourceException: A ResourcePool could not acquire a resource from its primary factory or source.
at com.mchange.v2.resourcepool.BasicResourcePool.awaitAcquire(BasicResourcePool.java:972)
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:208)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:260)
... 8 more
还有上面这么异常
1 楼
ootabc
2009-05-24
你好! 我是一名hibernate 初学者,也是一名应届生.前些天去面试公司给我出了上面那一样题要我完成. 我测试你发布的代码
测试创建自定义字段 时出现在了如下问题: 到那就不动了 .. 找了许久还是不知道是什么原因... 能帮我解决一下? 我的邮箱是ootabc@126.com qq:306460920 谢谢了
configuring Hibernate ... log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
ok
测试创建自定义字段 时出现在了如下问题: 到那就不动了 .. 找了许久还是不知道是什么原因... 能帮我解决一下? 我的邮箱是ootabc@126.com qq:306460920 谢谢了
configuring Hibernate ... log4j:WARN No appenders could be found for logger (org.hibernate.cfg.Environment).
log4j:WARN Please initialize the log4j system properly.
ok
相关推荐
本案例聚焦于使用Groovy脚本语言来实现一个特定的功能:从明细表中提取字段值,并将其更新到主表对应的字段中。这样的操作对于数据同步、报表生成以及业务流程自动化等场景非常常见。 Groovy是一种动态、灵活的Java...
在Hibernate中,这可能通过自定义的Hibernate类型或者在保存前使用Java的`PasswordEncoder`接口进行实现。同时,系统应该有防止SQL注入的措施,例如使用预编译的SQL语句或使用Hibernate的参数化查询。 在实际运行时...
在Hibernate的生态系统中,"Hibernate校验"是一个不可或缺的组件,它实现了JSR-303(Java Bean Validation)标准,为Java对象提供了强大的数据验证功能。本文将深入探讨Hibernate校验的原理、使用方法及其在实际开发...
Hibernate作为Java领域中流行的ORM(对象关系映射)框架,提供了丰富的注解来简化实体类与数据库表之间的映射。本文将深入解析一系列关键注解,帮助理解Hibernate如何实现这一过程。 #### 1. `@Entity`与`@Table` ...
在Hibernate映射文件中,`fileContent`字段的类型设置为`org.springframework.orm.hibernate3.support.BlobByteArrayType`,这是Spring提供的用户自定义类型,用于处理Blob字段。这样,我们就可以直接操作`byte[]`,...
2. **数据持久层**:在Hibernate中,Blob字段在Java领域对象中声明为`byte[]`类型,而不是`java.sql.Blob`。在映射文件中,Blob字段的type设置为`org.springframework.orm.hibernate3.support.BlobByteArrayType`,...
实体管理是Hibernate中的重要部分,书中会详细介绍如何定义实体类,使用注解来声明属性与数据库字段的映射,以及如何实现对象的生命周期管理,包括瞬时态、持久态、托管态和游离态。此外,还会讲解实体间的一对一、...
6. **hibernate-validator**:这个模块是Hibernate提供的验证框架,它遵循JSR-303/JSR-349规范,用于校验实体对象的字段,提供了丰富的校验规则和自定义校验注解。 7. **hibernate-search**:提供了全文搜索引擎...
9. **事件监听器**:Hibernate允许自定义事件监听器,实现对对象生命周期各个阶段的监听,如保存、更新、加载、删除等,可以进行一些额外的操作。 10. **类型转换**:Hibernate支持自定义类型转换,允许开发者将...
Hibernate,作为Java领域中的一款著名对象关系映射(ORM)框架,极大地简化了数据库操作。然而,为了满足更复杂的业务需求,Hibernate还提供了丰富的扩展功能,这就是我们今天要讨论的Hibernate-Extensions。这个...
Spring AOP是Java领域中广泛使用的AOP框架,它可以方便地实现方法拦截,从而在合适的时候记录操作信息。 为了实现审计日志,我们需要创建一个审计实体类,它包含如操作用户、操作时间、操作类型(增、删、改)、...
Hibernate作为Java领域中的一款主流对象关系映射(ORM)工具,极大地简化了数据库操作,使得开发者能够更加专注于业务逻辑,而非底层的SQL语言。在本实例程序中,我们将深入探讨Hibernate5的主要特性和用法。 1. **...
Hibernate是Java领域中一款著名的对象关系映射(ORM)框架,它允许开发者使用面向对象的方式来操作数据库,将数据库中的数据与Java对象进行映射,从而简化了数据访问的复杂性。这里的"hibernate3.2.5.jar"是...
1. 对象关系映射(ORM):Hibernate通过映射Java类到数据库表,实现了对象与关系数据之间的桥梁,使得开发者可以使用面向对象的方式处理数据库操作。 2. Session:Session是Hibernate的主要工作单元,负责管理对象...
在Java领域,Hibernate是一个非常流行且强大的ORM框架,它简化了数据库操作,使开发者可以使用Java对象来处理数据库交互,而无需直接编写SQL语句。 标题提到的"ORM Hibernate .jar包"指的是Hibernate框架的可执行库...
- Domain Object(领域对象):代表业务逻辑中的实体,应包含默认构造方法和主键标识符。 - hbm.xml映射文件:定义对象与数据库表的映射关系,包括字段映射、主键生成策略等。 - CRUD(Create、Read、Update、...
2.4. Hibernate独有的注解扩展:除了标准的EJB3注解,Hibernate还提供了一些自定义注解,如`@GeneratedValue`用于设置主键生成策略,`@Formula`用于在属性中使用SQL表达式,`@Cache`用于配置缓存,`@Filter`用于动态...
Hibernate,作为Java领域中的一款强大且广泛应用的对象关系映射(ORM)框架,极大地简化了数据库操作。深入理解Hibernate的源码,对于提升开发效率、优化性能以及解决实际问题具有重大意义。本文将从多个角度探讨...
Hibernate是Java领域中一款著名的对象关系映射(ORM)框架,它允许开发人员使用面向对象的方式来操作数据库,而无需直接编写SQL语句。版本3.2.5是Hibernate的一个稳定版本,发布于2007年,包含了多个关键的改进和...