使用Hibernate将CSV文件动态映射到数据库表

时间:2019-03-14 13:09:18

标签: java hibernate csv

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

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

这是技术的选择。

  • SpringBoot作为运行时
  • 休眠为JPA / DB Inetraction
  • Oracle DB作为数据库

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

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

可能的解决方案是

  • 只需为CSV等效表定义基本的JPA实体和表结构(例如ID和FK链接到其他表等),
  • 然后在CSV文件到达时,通过从应用程序运行ALTER table命令将列添加到表中
  • 在将来的第一个CSV中,如果添加/删除了列,请使用类似的alter命令

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

1 个答案:

答案 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();
    }
}

来源:Please refer this article for more detail