Спящий режим быстрее Создание 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
.
2nd Run:
create EMF: 1275ms
build EMF: 970ms
EJB3Configuration configure: 305ms
EJB3Configuration <clinit>: not visible, probably 0ms
.
В сравнении дерева вызовов вы можете увидеть, что некоторые методы значительно быстрее (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
Сравнение горячих точек теперь имеет интересные результаты:
.
ClassLoader.loadClass: -1686ms
XMLSchemaFactory.newSchema: -184ms
ClassFile.<init>: -109ms
Я не уверен, что это загрузка классов Hibernate или моих классов Entity.
Первым улучшением будет создание EMF, как только приложение начнет просто инициализировать все необходимые классы (у меня есть пустой файл db в качестве прототипа, уже поставляемого вместе с моим приложением). @sharakan благодарит вас за ответ, возможно, DeferredConnectionProvider уже будет решением этой проблемы.
Я попробую DeferredConnectionProvider дальше! Но мы могли бы ускорить его еще больше. У вас есть еще предложения?
Ответы
Ответ 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).