首页 > 解决方案 > 使用 Hibernate 将 CSV 文件动态映射到数据库表

问题描述

我们有一个要求,每天大约有 25 个 CSV 文件,并将数据库存储在等效的表结构中。

任何 CSV 文件列结构将来都可以通过添加新的 /remove 列来更改,并且基础数据库表将与新格式对齐,而无需更改代码或重新部署。

这里是技术的选择。

如果使用 Hibernate,如何根据传入的 CSV 实现表的动态列管理?

据我所知,Hibernate 将具有与 Table 等效的 Java Entity 类,它将用于持久化数据。任何表更改也需要实体类更改。

可能的解决方案可能是

这可以通过 Hibernate 实现吗?或任何其他更适合此类任务的产品。

标签: javahibernatecsv

解决方案


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

解决方案领域模型 我们首先需要一个我们将试验的业务实体类。让我们成为联系人类。将有两个持久字段:id 和 name。

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

让我们为支持自定义字段的所有业务实体创建一个基类 - CustomizableEntity,其中包含映射自定义属性以使用自定义字段:

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 步 - 类 Contact 继承自 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,我们使用 tag 。Hibernate 3.2.0GA 上的文档说动态组件的要点是:

“映射的语义与 相同。这种映射的优点是能够在部署时确定 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.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 类。

除了 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 ,它允许我们表示这个自定义字段的值将如何存储在 DB 中的哪个字段和 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-method 的目的。第一种方法是 getCustomProperties():

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

第 15 步 - 将 CustomProperties 作为组件。

该方法在我们的业务实体的映射中查找并返回标签对应的对象组件。

第二种方法是 updateMapping():

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

第 16 步 - 方法 updateMapping()。

该方法负责存储持久化类的更新映射,并更新 Hibernate 的配置状态以进行进一步的更改,这些更改在更改生效时生效。

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

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

休眠配置。如果缺少此字符串,我们将不得不使用休眠实用程序启动数据库模式的执行更新。但是,使用该设置可以避免这种情况。

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

来源:更多详情请参考这篇文章


推荐阅读