Подтвердить что ты не робот

Спящий режим быстрее Создание EntityManagerFactory

В моем настольном приложении новые базы данных открываются довольно часто. Я использую Hibernate/JPA как ORM. Проблема в том, что создание EntityManagerFactory происходит довольно медленно, примерно на 5-6 секунд на быстрой машине. Я знаю, что EntityManagerFactory должен быть тяжеловесным, но это слишком медленно для настольного приложения, где пользователь ожидает, что новая база данных будет быстро открыта.

  • Можно ли отключить некоторые функции EntityManagerFactory, чтобы получить экземпляр   Быстрее? Или можно лениво создать некоторую часть EntityManagerFactory, чтобы ускорить формирование?

  • Можно ли каким-либо образом создать объект EntityManagerFactory раньше   зная адрес базы данных? Я был бы рад отключить все   чтобы это было возможно.

  • Таким образом, могу ли я объединить EntityManagerFactorys для дальнейшего использования?

  • Любая другая идея, как быстрее создать EntityManagerFactory?

Обновление с дополнительной информацией и профилированием JProfiler

Настольное приложение может открывать сохраненные файлы. Формат файла документа приложения составляет 1 базу данных SQLite + и некоторые двоичные данные в ZIP файле. При открытии документа ZIP извлекается, а db открывается с помощью Hibernate. Все базы данных имеют одну и ту же схему, но разные данные.

Кажется, что в первый раз, когда я открываю файл, он занимает значительно больше времени, чем в следующий раз. Я профилировал первый и второй запуск с JProfiler и сравнивал результаты.

1-й прогон:

create EMF: 4385ms
    build EMF: 3090ms
    EJB3Configuration configure: 900ms
    EJB3Configuration <clinit>: 380ms

calltree1.png.

2nd Run:

create EMF: 1275ms
    build EMF: 970ms
    EJB3Configuration configure: 305ms
    EJB3Configuration <clinit>: not visible, probably 0ms

compare_calltree.png.

В сравнении дерева вызовов вы можете увидеть, что некоторые методы значительно быстрее (DatabaseManager. как отправная точка):

create EMF: -3120ms
    Hibernate create EMF: -3110ms
        EJB3Configuration configure: -595ms
        EJB3Configuration <clinit>: -380ms
        build EMF: -2120ms
            buildSessionFactory: -1945ms
                secondPassCompile: -425ms
                buildSettings: -346ms
                SessionFactoryImpl.<init>: -1040ms

Сравнение горячих точек теперь имеет интересные результаты:

screenshot compare_hotspot.png.

ClassLoader.loadClass: -1686ms
XMLSchemaFactory.newSchema: -184ms
ClassFile.<init>: -109ms

Я не уверен, что это загрузка классов Hibernate или моих классов Entity.

Первым улучшением будет создание EMF, как только приложение начнет просто инициализировать все необходимые классы (у меня есть пустой файл db в качестве прототипа, уже поставляемого вместе с моим приложением). @sharakan благодарит вас за ответ, возможно, DeferredConnectionProvider уже будет решением этой проблемы.

Я попробую DeferredConnectionProvider дальше! Но мы могли бы ускорить его еще больше. У вас есть еще предложения?

4b9b3361

Ответ 1

Вы должны сделать это, реализовав свой собственный ConnectionProvider в качестве декоратора вокруг реального ConnectionProvider.

Основное замечание здесь состоит в том, что ConnectionProvider не используется до создания EntityManager (см. комментарий в supportsAggressiveRelease() для предостережения). Таким образом, вы можете создать класс DeferredConnectionProvider и использовать его для построения EntityManagerFactory, но затем дождаться ввода пользователя и выполнить отложенную инициализацию до фактического создания каких-либо экземпляров EntityManager. Я написал это как обертку вокруг ConnectionPoolImpl, но вы должны иметь возможность использовать любую другую реализацию ConnectionProvider в качестве базы.

public class DeferredConnectionProvider implements ConnectionProvider {

    private Properties configuredProps;
    private ConnectionProviderImpl realConnectionProvider;

    @Override
    public void configure(Properties props) throws HibernateException {
        configuredProps = props;
    }

    public void finalConfiguration(String jdbcUrl, String userName, String password) {
        configuredProps.setProperty(Environment.URL, jdbcUrl);
        configuredProps.setProperty(Environment.USER, userName);
        configuredProps.setProperty(Environment.PASS, password);

        realConnectionProvider = new ConnectionProviderImpl();
        realConnectionProvider.configure(configuredProps);
    }

    private void assertConfigured() {
        if (realConnectionProvider == null) {
            throw new IllegalStateException("Not configured yet!");
        }
    }        

    @Override
    public Connection getConnection() throws SQLException {
        assertConfigured();

        return realConnectionProvider.getConnection();
    }

    @Override
    public void closeConnection(Connection conn) throws SQLException {
        assertConfigured();

        realConnectionProvider.closeConnection(conn);
    }

    @Override
    public void close() throws HibernateException {
        assertConfigured();

        realConnectionProvider.close();
    }

    @Override
    public boolean supportsAggressiveRelease() {
        // This gets called during EntityManagerFactory construction, but it 
        // just a flag so you should be able to either do this, or return
        // true/false depending on the actual provider.
        return new ConnectionProviderImpl().supportsAggressiveRelease();
    }
}

приблизительный пример того, как его использовать:

    // Get an EntityManagerFactory with the following property set:
    //     properties.put(Environment.CONNECTION_PROVIDER, DeferredConnectionProvider.class.getName());
    HibernateEntityManagerFactory factory = (HibernateEntityManagerFactory) entityManagerFactory;

    // ...do user input of connection info...

    SessionFactoryImpl sessionFactory = (SessionFactoryImpl) factory.getSessionFactory();
    DeferredConnectionProvider connectionProvider = (DeferredConnectionProvider) sessionFactory.getSettings()
                    .getConnectionProvider();

    connectionProvider.finalConfiguration(jdbcUrl, userName, password);

Вы можете поместить начальную настройку EntityManagerFactory в отдельный поток или что-то в этом роде, чтобы пользователь никогда не дождался его. Тогда единственное, что они будут ждать после указания информации о подключении, это настройка пула соединений, который должен быть довольно быстрым по сравнению с анализом объектной модели.

Ответ 2

Можно ли отключить некоторые функции EntityManagerFactory, чтобы быстрее получить экземпляр?

Не верьте. В EMF нет слишком много функций, кроме инициализации соединения/пула JDBC.

Или можно создать некоторую часть EntityManagerFactory лениво ускорить разбивку?

Вместо того, чтобы создавать EMF лениво, когда пользователь заметит удар производительности, я предлагаю вам двигаться в противоположном направлении - создать EMF проактивно, прежде чем пользователь действительно это захочет. Создайте его один раз, вверх, возможно, в отдельном потоке во время инициализации приложения (или, по крайней мере, сразу же, как вы знаете о своей базе данных). Повторно используйте его во время существования вашего приложения/базы данных.

Можно ли каким-либо образом создать объект EntityManagerFactory, прежде чем узнать URL-адрес базы данных?

Нет - он создает соединение JDBC.

Я думаю, что лучший вопрос: почему ваше приложение динамически обнаруживает URL-адреса подключения к базе данных? Вы говорите, что ваши базы данных создаются/становятся доступными "на лету", и нет способа заранее предусмотреть параметры соединения. Этого действительно следует избегать.

Таким образом, могу ли я объединить EntityManagerFactorys для дальнейшего использования?

Нет, вы не можете объединить EMF. Это соединения, которые вы можете объединить.

Любая другая идея, как быстрее создать EntityManagerFactory?

Я согласен - 6 секунд слишком медленно для инициализации ЭДС.

Я подозреваю, что это больше связано с выбранной вами технологией базы данных, чем с JPA/JDBC/JVM. Я предполагаю, что, возможно, ваша база данных инициализируется при подключении. Вы используете Access? Какую БД вы используете?

Вы подключаетесь к базе данных удаленно? Через WAN? Хорошая ли скорость/латентность сети?

Являются ли клиентские ПК ограниченными по производительности?

EDIT: добавлено после комментариев

Реализация собственного ConnectionProvider в качестве декоратора вокруг реального ConnectionProvider не ускорит работу пользователя вообще. Экземпляр базы данных по-прежнему необходимо инициализировать, создать EMF и EM и соединение JDBC по-прежнему необходимо установить.

Параметры:

  • Поделитесь общим предустановленным экземпляром DB: кажется, что это невозможно для вашего бизнес-сценария (хотя технология JSE поддерживает это, а также поддерживает дизайн клиент-сервер).
  • Переход на БД с более быстрым запуском: Derby (a.k.a. Java DB) включен в современные JVM и имеет время запуска около 1,5 секунд (холодное) и 0,7 секунды (теплые - предварительно загруженные данные).
  • Во многих сценариях (наиболее?) самым быстрым решением будет загрузка данных непосредственно в Java-объекты в памяти с использованием JAXB с помощью STAX. Впоследствии используйте данные в кэше в памяти (в частности, с использованием интеллектуальных структур, таких как карты, хэширование и arraylists). Так же, как JPA может сопоставлять классы POJO с таблицами и столбцами базы данных, поэтому JAXB может сопоставлять классы POJO с XML-схемой и работать с экземплярами XML doc. Если у вас очень сложные запросы, использующие логику SQL с множеством соединений и сильное использование индексов DB, ​​это было бы менее желательно.

(2), вероятно, даст лучшее улучшение для ограниченного усилия.
Дополнительно:  - попытайтесь разархивировать файлы данных во время развертывания, а не во время использования приложения.  - инициализировать EMF в загрузочном потоке, который выполняется параллельно с запуском пользовательского интерфейса - попробуйте запустить инициализацию БД как один из первых шагов приложения (что означает подключение к реальному экземпляру с использованием JDBC).