Динамическая карта файла CSV с таблицами базы данных с использованием Hibernate - PullRequest
0 голосов
/ 14 марта 2019

У нас есть требование, чтобы около 25 CSV-файлов приходило каждый день и сохраняло базу данных в эквивалентной структуре таблицы.

Любая структура столбцов CSV-файла может измениться в будущем, добавив новые / удалив столбцы и базу данных.Таблица будет соответствовать новому формату без изменения кода или перераспределения.

Вот выбор технологии.

  • SpringBoot как время выполнения
  • Hibernate как JPA / DB Inetraction
  • Oracle DB как база данных

Если использовать Hibernate, как добиться этого динамического управления столбцами таблицы согласно входящему CSV?

Насколько я знаю, Hibernate будет иметь классы Java Entity, эквивалентные таблице,который будет использоваться для сохранения данных.Любое изменение таблицы также требует изменения класса сущностей.

Возможное решение может быть

  • , просто определите базовую сущность JPA и структуру таблицы (например, идентификаторы и FK, связанные с другими таблицами и т. Д.) Для эквивалента CSVtable,
  • , затем по прибытии CSV-файлов добавьте столбцы в таблицу, выполнив команду таблицы ALTER из приложения
  • В будущих 1-ых CSV, если столбец будет добавлен / удален, используйте аналогичные команды alter

Это достижимо с помощью Hibernate?Или любой другой продукт, лучше подходящий для такого рода задач.

1 Ответ

0 голосов
/ 14 марта 2019

Определение задачи Нам нужно будет реализовать механизм, позволяющий создавать / удалять настраиваемые поля в режиме реального времени, избегая перезапуска приложения, добавлять в него значение и следить за тем, чтобы это значение присутствовало в базе данных приложения. Кроме того, мы должны убедиться, что настраиваемое поле может использоваться в запросах.

Решение Модель предметной области Сначала нам понадобится класс бизнес-объекта, с которым мы будем экспериментировать. Пусть это будет контактный класс. Будет два постоянных поля: id и name.

Однако помимо этих постоянных и неизменяемых полей класс должен быть своего рода конструкцией для хранения значений пользовательских полей. Карта была бы идеальной конструкцией для этого.

Давайте создадим базовый класс для всех бизнес-объектов, поддерживающих настраиваемые поля - 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

Наследовать наш класс. Контакт из этого базового класса:

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 говорит, что точка динамического компонента:

"Семантика сопоставления идентична. Преимуществом этого вида сопоставления является возможность определения фактических свойств компонента во время развертывания, просто путем редактирования документа сопоставления. Также выполняется манипуляция во время выполнения документа сопоставления. возможно, используя анализатор DOM. Более того, вы можете получить доступ (и изменить) метамодель времени конфигурации Hibernate через объект конфигурации. " На основе этого правила из документации Hibernate мы будем создавать этот функциональный механизм.

HibernateUtil и hibernate.cfg.xml После того, как мы определились с предметной моделью нашего приложения, мы должны создать необходимые условия для работы фреймворка Hibernate. Для этого нам нужно создать файл конфигурации hibernate.cfg.xml и класс для работы с основными функциями Hibernate.

<?xml version='1.0' encoding='utf-8'?>

 <!DOCTYPE hibernate-configuration

 PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"

 "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 <hibernate-configuration>

 <session-factory>

     <property name="show_sql">true</property>
     <property name="dialect">
org.hibernate.dialect.MySQLDialect</property>
     <property name="cglib.use_reflection_optimizer">true</property>
     <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
     <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/custom_fields_test</property>
     <property name="hibernate.connection.username">root</property>
     <property name="hibernate.connection.password"></property>
     <property name="hibernate.c3p0.max_size">50</property>
     <property name="hibernate.c3p0.min_size">0</property>
     <property name="hibernate.c3p0.timeout">120</property>
     <property name="hibernate.c3p0.max_statements">100</property>
     <property name="hibernate.c3p0.idle_test_period">0</property>
     <property name="hibernate.c3p0.acquire_increment">2</property>
     <property name="hibernate.jdbc.batch_size">20</property>
     <property name="hibernate.hbm2ddl.auto">update</property>
 </session-factory>
 </hibernate-configuration>

Шаг 4 - файл конфигурации Hibernate.

Файл hibernate.cfg.xml не содержит ничего заметного, кроме этой строки:

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

Шаг 5 - с помощью автообновления.

Позже мы подробно объясним его назначение и расскажем подробнее, как мы можем обходиться без него. Есть несколько способов реализовать класс HibernateUtil. Наша реализация будет немного отличаться от хорошо известной из-за изменений в конфигурации Hibernate.

package com.enterra.customfieldsdemo;

import org.hibernate.*;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.hibernate.cfg.Configuration;
import com.enterra.customfieldsdemo.domain.Contact;

public class HibernateUtil {

private static HibernateUtil instance;
private Configuration configuration;
private SessionFactory sessionFactory;
private Session session;

public synchronized static HibernateUtil getInstance() {
    if (instance == null) {
        instance = new HibernateUtil();
    }
    return instance;
}

private synchronized SessionFactory getSessionFactory() {
    if (sessionFactory == null) {
        sessionFactory = getConfiguration().buildSessionFactory();
    }
    return sessionFactory;
}

public synchronized Session getCurrentSession() {
    if (session == null) {
        session = getSessionFactory().openSession();
        session.setFlushMode(FlushMode.COMMIT);
        System.out.println("session opened.");
    }
    return session;
}

private synchronized Configuration getConfiguration() {
    if (configuration == null) {
        System.out.print("configuring Hibernate ... ");
        try {
            configuration = new Configuration().configure();
            configuration.addClass(Contact.class);
            System.out.println("ok");
        } catch (HibernateException e) {
            System.out.println("failure");
            e.printStackTrace();
        }
    }
    return configuration;
}
public void reset() {
    Session session = getCurrentSession();
    if (session != null) {
        session.flush();
        if (session.isOpen()) {
            System.out.print("closing session ... ");
            session.close();
            System.out.println("ok");
        }
    }
    SessionFactory sf = getSessionFactory();
    if (sf != null) {
        System.out.print("closing session factory ... ");
        sf.close();
        System.out.println("ok");
    }
    this.configuration = null;
    this.sessionFactory = null;
    this.session = null;
}

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

Шаг 6 - Класс HibernateUtils.

Наряду с обычными методами, такими как 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 - сброс метода ()

Метод getClassMapping (Class entityClass) возвращает объект PersistentClass, который содержит полную информацию о сопоставлении связанного объекта. В частности, манипуляции с объектом PersistentClass позволяют изменять набор атрибутов класса сущности во время выполнения.

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

Шаг 8 - метод getClassMapping (Class entityClass).

Манипуляции с отображениемКак только у нас будет доступный класс бизнес-сущности (контакт) и основной класс для взаимодействия с Hibernate, мы можем начать работать. Мы можем создавать и сохранять образцы класса Contact. Мы даже можем поместить некоторые данные в наши пользовательские свойства карты, однако следует помнить, что эти данные (хранящиеся в пользовательских картах свойств) не сохраняются в БД.

Чтобы сохранить данные, мы должны предусмотреть механизм создания пользовательских полей в нашем классе и сделать так, чтобы Hibernate знал, как с ними работать.

Чтобы обеспечить манипулирование отображением классов, мы должны создать некоторый интерфейс. Давайте назовем это CustomizableEntityManager. Его имя должно отражать назначение интерфейса, управляющего бизнес-объектом, его содержимое и атрибуты:

package com.enterra.customfieldsdemo;

 import org.hibernate.mapping.Component;

 public interface CustomizableEntityManager {
     public static String CUSTOM_COMPONENT_NAME = "customProperties";

     void addCustomField(String name);

     void removeCustomField(String name);

     Component getCustomProperties();

     Class getEntityClass();
 }

Шаг 9 - Интерфейс CustomizableEntityManager

Основными методами интерфейса являются: void addCustomField (String name) и void removeCustomField (String name). Они должны создать и удалить наше настраиваемое поле в отображении соответствующего класса.

Ниже приведен способ реализации интерфейса:

package com.enterra.customfieldsdemo;

 import org.hibernate.cfg.Configuration;
 import org.hibernate.mapping.*;
 import java.util.Iterator;

 public class CustomizableEntityManagerImpl implements CustomizableEntityManager {
     private Component customProperties;
     private Class entityClass;

     public CustomizableEntityManagerImpl(Class entityClass) {
         this.entityClass = entityClass;
     }

     public Class getEntityClass() {
         return entityClass;
     }

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

     public void addCustomField(String name) {
         SimpleValue simpleValue = new SimpleValue();
         simpleValue.addColumn(new Column("fld_" + name));
         simpleValue.setTypeName(String.class.getName());

         PersistentClass persistentClass = getPersistentClass();
         simpleValue.setTable(persistentClass.getTable());

         Property property = new Property();
         property.setName(name);
         property.setValue(simpleValue);
         getCustomProperties().addProperty(property);

         updateMapping();
     }

     public void removeCustomField(String name) {
         Iterator propertyIterator = customProperties.getPropertyIterator();

         while (propertyIterator.hasNext()) {
             Property property = (Property) propertyIterator.next();
             if (property.getName().equals(name)) {
                 propertyIterator.remove();
                 updateMapping();
                 return;
             }
         }
     }

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

     private PersistentClass getPersistentClass() {
         return HibernateUtil.getInstance().getClassMapping(this.entityClass);
     }
 }

Шаг 10 - реализация интерфейса CustomizableEntityManager

Прежде всего мы должны указать, что при создании класса CustomizableEntityManager мы указываем класс бизнес-объекта, которым будет управлять менеджер. Этот класс передается в качестве параметра конструктору CustomizableEntityManager:

private Class entityClass;

 public CustomizableEntityManagerImpl(Class entityClass) {
     this.entityClass = entityClass;
 }

 public Class getEntityClass() {
     return entityClass;
 }

Шаг 11 - дизайнер классов CustomizableEntityManagerImpl

Теперь нам нужно больше интересоваться, как реализовать метод void addCustomField (String name):

public void addCustomField(String name) {
     SimpleValue simpleValue = new SimpleValue();
     simpleValue.addColumn(new Column("fld_" + name));
     simpleValue.setTypeName(String.class.getName());

     PersistentClass persistentClass = getPersistentClass();
     simpleValue.setTable(persistentClass.getTable());

     Property property = new Property();
     property.setName(name);
     property.setValue(simpleValue);
     getCustomProperties().addProperty(property);

     updateMapping();
 }

Шаг 12 - создание настраиваемого поля.

Как видно из реализации, Hibernate предлагает больше опций для работы со свойствами постоянных объектов и их представления в БД. По сути метода:

1) Мы создаем класс SimpleValue, который позволяет нам обозначать, как значение этого настраиваемого поля будет храниться в БД, в каком поле и таблице БД:

SimpleValue simpleValue = new SimpleValue();
simpleValue.addColumn(new Column("fld_" + name));
simpleValue.setTypeName(String.class.getName());

PersistentClass persistentClass = getPersistentClass();
simpleValue.setTable(persistentClass.getTable());

Шаг 13 - создание нового столбца таблицы.

2) Мы создаем свойство постоянного объекта и добавляем в него динамический компонент (!), Который мы планировали использовать для этой цели:

Property property = new Property()
property.setName(name)
property.setValue(simpleValue)
getCustomProperties().addProperty(property)

Шаг 14 - создание свойства объекта.

3) И наконец, мы должны заставить наше приложение выполнить определенные изменения в файлах xml и обновить конфигурацию Hibernate. Это можно сделать с помощью метода updateMapping ();

Необходимо уточнить назначение еще двух методов get, которые использовались в приведенном выше коде. Первый метод - getCustomProperties ():

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

Шаг 15 - получение пользовательских свойств в качестве компонента.

Этот метод находит и возвращает Компонент объекта, соответствующий тегу в отображении нашей бизнес-сущности.

Второй метод - 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 для дальнейших манипуляций с ним;Находит элемент этого документа.В частности, здесь мы храним пользовательские поля и их содержимое мы меняем;Удалить (!) Все встроенные элементы из этого элемента;Для любого постоянного свойства, содержащегося в нашем компоненте, который отвечает за хранение пользовательских полей, мы создаем определенный элемент документа и определяем атрибуты для элемента из соответствующего свойства;Сохраните этот недавно созданный файл сопоставления.При манипулировании 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();
    }
}

Источник: Пожалуйста, обратитесь к этой статье для более подробной информации

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...