我们有一个要求,每天大约要发送25个CSV文件,并将数据库以等效的表结构存储。
将来,任何CSV文件列结构都可以通过添加新的/删除列来更改,并且基础数据库表将与新格式对齐,而无需更改代码或重新部署。
这是技术的选择。
如果使用Hibernate,如何根据传入的CSV实现表的动态列管理?
据我所知,Hibernate将具有与Table等效的Java Entity类,该类将用于持久化数据。任何表更改也需要更改实体类。
可能的解决方案是
这是Hibernate可以实现的吗? 或任何其他更适合此类任务的产品。
答案 0 :(得分:0)
任务定义 我们将必须实现一种机制,该机制允许实时创建/删除自定义字段,以避免应用程序重新启动,在其中添加一个值并确保该值存在于应用程序数据库中。此外,我们必须确保可以在查询中使用自定义字段。
解决方案 领域模型 我们首先需要一个将要试验的业务实体类。让我们成为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继承的类联系人。
我们不应忘记此类的映射文件:
<?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,我们使用标记。有关Hibernate 3.2.0GA的文档指出,动态组件的要点是:
“映射的语义与相同。这种映射的优点是能够在部署时通过编辑映射文档来确定Bean的实际属性。对映射文档的运行时操作也更好的是,您可以通过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.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(Class entityClass)。
映射操作 一旦有了可用的业务实体类(联系人),并且可以与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,该类允许我们表示该自定义字段的值将如何存储在DB的哪个字段和表中:
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配置的。如果缺少此字符串,则必须使用休眠实用程序启动对数据库模式的执行更新。但是,使用该设置可以避免这种情况。
保存映射 在运行时对映射所做的修改不会自己保存到相应的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-用于更新持久类映射的实用程序。
该类按字面意义执行以下操作:
定义一个位置,并将指定业务实体的xml映射加载到DOM Document对象中,以便对其进行进一步操作; 查找此文档的元素。特别是在这里,我们存储自定义字段及其更改的内容; 从该元素中删除(!)所有嵌入式元素; 对于负责自定义字段存储的组件中包含的任何持久属性,我们创建一个特定的document元素,并从相应的属性中定义该元素的属性; 保存此新创建的映射文件。 在处理XML时,我们使用(如从代码中可以看到的)类XMLUtil,尽管可以正确地加载和保存xml文件,但通常可以以任何方式实现。
我们的实现在以下步骤中给出:
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();
}
}