30 сентября 2012 г.

Пример простого EJB 3.0 на JBoss 6.1.0

Рассмотрим пример создания простого компонента EJB (Enterprise JavaBean) версии 3.0, развёрнутого на сервере приложений JBoss 6.1.0. Пример состоит из самого компонента, а также клиентской части, которая его вызывает. Главное достоинство EJB заключается в том, что клиент взаимодействует с ним как с обычным классом, при этом EJB может находиться на удалённом сервере. При этом от клиента скрыта вся сложность сетевого взаимодействия.

Создадим компонент, который содержит единственный метод getCurrentDate. Этот метод возвращает текущее время. Для начала определим remote-интерфейс:
@Remote
public interface MyTestBeanRemote {
public Date getCurrentDate();
}
Метод EJB может возвращать абсолютно любой объект. Главное, чтобы на стороне клиента имелось описание этого объекта.

Далее создадим сам класс EJB, унаследованный от remote-интерфейса:
@Stateless
public class MyTestBean implements MyTestBeanRemote {
@Override
public Date getCurrentDate() {
return new Date();
}
}
Обратите внимание на аннотацию @Stateless перед определением класса. Она означает, что данный EJB не сохраняет своё состояние для каждого клиента. Иными словами, все клиенты работают с одним экземпляром этого компонента. В нашем примере это не имеет значения, а вот если бы у нашего класса имелось какое-нибудь свойство, тогда разумнее было бы вместо @Stateless поставить @Stateful. Тогда каждый клиент работал бы со своим экземпляром.

Также у EJB может быть определён local-интерфейс, похожий на remote-интерфейс, но в нашем примере это было бы лишним.

Для развёртывания EJB достаточно его скомпилировать в jar-файл и поместить в папку server/your_config/deploy в JBoss, где your_config - имя вашей конфигурации, например, all.

Теперь перейдём к созданию клиента. Если клиент и EJB развёрнуты на разных серверах, то на клиенте должен быть хотя бы интерфейс данного EJB. В качестве клиента создадим вспомогательный класс Invoker.
public class Invoker {
private InitialContext getContext() throws NamingException {
final Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY,
"org.jnp.interfaces.NamingContextFactory");
prop.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099");
return new InitialContext(prop);
}
public Date getCurrentDate()
{
try {
InitialContext ctx = getContext();
MyTestBeanRemote myTestBeanRemote = (MyTestBeanRemote) ctx
.lookup("MyTestBean/remote");
return myTestBeanRemote.getCurrentDate();
} catch (NamingException e) {
e.printStackTrace();
}
return null;
}
}
Метод getContext устанавливает подключение к JNDI-сервису, который обычно работает на порту 1099 сервера с JBoss. В данном примере идёт обращение на локальный сервер. Если вы используете удалённый сервер для EJB, просто измените адрес сервера в этой строке:
prop.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099");
Метод getCurrentDate вызывает необходимый нам метод EJB. Для этого он получает его удалённый интерфейс при помощи метода lookup("MyTestBean/remote"). В качестве значения передаётся имя EJB, затем слеш, затем имя интерфейса (remote или local).

Наш пример готов. Теперь вспомогательный класс можно вызвать, например, с jsp-страницы для получения результата работы EJB.

P.S. В промышленных системах использовать null в качестве возвращаемого значения не рекомендуется. Это вынуждает использовать дополнительную проверку на неопределённом значение, а также ухудшает читаемость кода. Вместо этого можно возвращать пустой экземпляр объекта (в случае коллекций это будет список, не содержащий ни одного элемента) или генерировать исключение.

Создание временных файлов в Java

В процессе разработки часто сталкиваюсь с необходимостью создания временного файла. И я вовремя узнал, что в JDK есть стандартный метод для создания временных файлов.
File tempFile = File.createTempFile("abc", "");
Этот метод создаёт временный файл в папке, специально предназначенной для временных файлов в данной операционной системе. Эта папка в разных ОС может называться по-разному, но для временного файла не очень важно, где он будет создан и как он будет называться. Важнее факт наличия специального каталога. После создания вы сразу получаете указатель на этот файл для дальнейшего манипулирования с ним.

Метод принимает два строковых параметра: префикс и суффикс. Префикс должен обязательно содержать не менее трёх символов. Префикс определяет начало имени временного файла. Суффикс определяет его конец, но он не обязателен. Если в качестве суффикса передать null, то временный файл получит расширение .tmp. Но ведь расширение ни на что не влияет, так что можно передать в качестве суффикса пустую строку, чтобы в конец имени временного файла ничего не добавлялось. Серединой же имени временного файла будет набор цифр.

Итак, для значений параметров, приведённых выше, метод создаст, например, такой файл:
/tmp/abc8859980780322653785
А вот этот файл может получиться, если в качестве суффикса передать null:
/tmp/abc7890857977307054209.tmp
Ну а если хотим заменить расширение tmp на что-нибудь другое, достаточно в качестве суффикса передать, например, ".temporary":
/tmp/abc7751462332148781358.temporary

23 сентября 2012 г.

Hibernate: чтение данных из postgres

Предположим, что у нас есть базе данных postgres есть таблица messages, состоящая всего из двух столбцов: bigint id, varchar(100) msg_text. О том, как подключиться к БД при помощи datasource, мы уже говорили ранее. Для того, чтобы автоматически помещать данные из этой таблицы в поля нашего класса, он должен выглядеть следующим образом:

@Entity
@Table(name = "messages")
public class MessageEntity {
Обычный класс размечается специальными аннотациями. @Entity говорит о том, что данный класс является сущностью, которая хранит данные, полученные из БД. @Table позволяет указать имя соответствующей таблицы. Обратите внимание, имя класса и имя таблицы могут быть разными - в этом проявляется гибкость использования аннотаций.

Далее следуют поля класса, которые также размечены аннотациями. Тип поля выбирается соответственно типу поля в таблице. bigint в postgres - это Long в java. Обратите внимание, что для id-поля в классе можно использовать только те типы, которые допускают null-значения. Это связано с тем, что при создании новой сущности id не должно иметь значение - Hibernate сам присвоит его при сохранении объекта в таблицу. Модификатор доступа для полей обязательно должен быть protected - это ограничение, накладываемое Hibernate.
@Id
@SequenceGenerator(name = "message_id_generator", sequenceName = "message_id_seq", initialValue = 1, allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "message_id_generator")
@Column(name = "id")
protected Long id;
Аннотация @Id показывает, что именно это поле будет хранить идентификатор записи. @SequenceGenerator задаёт sequence в postgres, который будет генерировать значения для этого поля. sequenceName - это название в самом postgres, тогда как name - имя внутри Hibernate, используемое в следующей аннотации @GeneratedValue. Наконец аннотация @Column задаёт имя поля в таблице. Опять же не обязательно должно совпадать с именем поля в нашем классе.

@Column(name = "msg_text", length = 100)
protected String messageText;

Второе поля будет содержать сам текст сообщения. Поэтому тип у него - String. Аннотация @Column задаёт имя соответствующего столбца в таблице и максимальную длину текста. Указывать параметр length у @Column рекомендуется для любого текстового поля. Это будет полезно не только для проверки длины текста в процессе работы программы, но и, например, при генерации скрипта создания таблицы на основе данной сущности. Такая возможность имеется, но это тема отдельного поста.

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getMessageText() {
return messageText;
}

public void setMessageText(String messageText) {
this.messageText = messageText;
}

}

Наконец, добавляем геттеры и сеттера для обоих полей. Большинство IDE позволяют автоматизировать этот рутинный процесс.

Сама по себе такая сущность мало чего умеет. Как можно, например, получить все записи из таблицы? Для этого можно использовать именованный запрос. Поместим его перед определением класса.

@NamedQueries
({
    @NamedQuery
    (
    name = "getMessageList",
    query = "select m from MessageEntity m order by m.id desc"
    )
})
Аннотация @NamedQueries определяет множество именованных запросов. @NamedQuery - конкретный запрос, содержащий его имя и сам запрос. Обратите внимание, что, хоть запрос и похож на sql-запрос, но таковым не является. Вместо полей таблицы здесь используются поля нашей сущности, да и выборка производится из неё же. m - любое имя. В процессе работы Hibernate трансформирует этот запрос в родной запрос sql для конкретной БД. В это проявляется очередная гибкость Hibernate.


Всё, наша сущность готова. Теперь можно посмотреть её в деле.

В любом классе, где необходимо работать с БД, мы можем определить поле:


@PersistenceContext(name = "manager1")
private EntityManager entityManager;

manager1 - это значение из секции persistence-unit в файле persistence.xml.

Выборка всех сообщений с использованием нашего именованного запроса будет выглядеть вот так:
List<MessageEntity> messageList = (List<MessageEntity>) entityManager.createNamedQuery("getMessageList").getResultList();
В результате мы получим список сущностей, в каждой из которых будут содержаться значения полей id и msg_text из таблицы.

22 сентября 2012 г.

Как принудительно завершить процесс в linux

Довольно часто бывает такая ситуация, что та или иная программа в системе "зависает". И хорошо, если зависнет только она, а не вся система целиком. Чтобы восстановить работоспособность системы, вам нужно завершить этот процесс. В linux это можно сделать в два шага. Команды я тестировал на Ubuntu.

Во-первых, нам нужно выяснить идентификатор процесса:
ps aux | grep часть_имени_программы
Это составная команда. Она состоит из двух команд, разделённых вертикальной чертой. Результат работы первой команды является исходными данными для второй. ps aux возвращает информацию обо всех процессах в системе. Разумеется, инфы будет много, да и нас интересует только та информация, которая конкретно связана с данными процессом. Поэтому выполним поиск по части имени программы - эту операцию выполняет вторая команда.

Вполне возможно, что даже с фильтрацией будет найдено несколько совпадений. Выберите из них наиболее подходящее (например, самое большое). Первое четырёх- или пятизначное число в строке будет являться идентификатором процесса. Запомните его, а затем введите вторую команду:
kill -9 числовой_индентификатор_процесса
При помощи этой команды вы принудительно завершаете указанный процесс. Числовой параметр -9 означает, что эту команду никак нельзя перехватить и отменить. Будьте аккуратны при использовании этой команды!

13 сентября 2012 г.

Как настроить уровень логирования в JBoss 6.x

В большинстве информационных систем среди уровней логирования выделяют следующие (от более низкого к более высокому):
  1. TRACE - сообщения самого низкого уровня, наиболее подробные
  2. DEBUG - отладочные сообщения, менее подробные, чем TRACE
  3. INFO - стандартные информационные сообщения
  4. WARN - некритичные ошибки, не препятствующие работе приложения
  5. ERROR - ошибки, которые могут привести к неверному результату
  6. FATAL - ошибки, препятствующие дальнейшей работе приложения
По умолчанию в JBoss 6.x логируются все сообщения с уровнем не ниже INFO. В целях отладки бывает полезно включить более низкий уровень логирования DEBUG. Для этого достаточно отредактировать файл jboss-logging.xml, который лежит в папке jboss-6.1.0.Final/server/all/deploy (обратите внимание, я использую конфигурацию all, у вас она может быть другая).

Давайте зададим уровень логирования для вывода в консоль. Для этого неподалёку от начала файла найдите следующие строки:

<console-handler name="CONSOLE" autoflush="true" target="System.out">
<error-manager>
<only-once/>
</error-manager>

<level name="INFO"/>

<formatter>
<pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] %s%E%n"/>
</formatter>
</console-handler>

И просто замените level name на DEBUG. Но это ещё не всё. Неважно, для консоли вы задали уровень логирования, для файла или для чего-нибудь ещё, также следует понизить корневой уровень логирования. Для этого от конца файла отмотайте немного назад и найдите там следующие строки:

<root-logger>
<!-- Set the root logger priority via a system property, with a default value. -->
<level name="${jboss.server.log.threshold:INFO}"/>
<handlers>
<handler-ref name="CONSOLE"/>
<handler-ref name="FILE"/>
</handlers>
</root-logger>

Теперь также поменяйте level name с INFO на DEBUG.

После того, как этот файл будет сохранён, изменения вступят в силу сразу, без перезагрузки сервера, о чём будет свидетельствовать запись в логе:
[service] Restored bootstrap log handlers
[service] Removing bootstrap log handlers