воскресенье, 27 марта 2011 г.

Кириллица в идентификаторах

        final String строка = "Неужели они разрешены?";
        for (int бегунок = 0; бегунок < строка.length(); ++бегунок) {
            
            System.out.print(строка.charAt(бегунок));
        }

И весь ужас в том, что это компилится и работает...
Для знатоков кодировок: буквы "И", "ш" тоже работают корректно.

суббота, 19 марта 2011 г.

Поиск по всем таблицам в Oracle

Задача: полнотекстовый поиск в Oracle
То есть, мы знаем значение какой-то колонки какой-то таблицы. И ничего больше не знаем, ни названия таблицы, ни названия колонки. Такое может быть нужным, когда надо определить, в какую таблицу БД попадают определённые данные с веб-формы. Вызвано это, например, может быть тем, что у нас есть доступ к БД, но нет исходников приложения. А таблицы кто-то называл в стиле TABLE_1, TABLE_2, OT_1 и так далее.
К счастью, БД была Oracle, поэтому можно использовать тёмные стороны PL/SQL.
Думаю, смысл действа понятен, поэтому сразу к коду:

create or replace procedure whereIsValue(valueParam varchar2)
AS
  TYPE VALCUR IS REF CURSOR;
  cursor tabl is select table_name from user_tables;
  cursor col (tablename varchar2) is select column_name 
                                     from user_tab_columns
                                     where table_name like tableName;
  valueCursor VALCUR;
  tableName varchar2(50);
  columnName varchar2(50);
  columnValue varchar2(500);
  qq number(3);
begin
  open tabl;
  LOOP
    fetch tabl into tableName;
    EXIT WHEN tabl%NOTFOUND;
    OPEN col(tableName);
    LOOP     
      fetch col into columnName;
      EXIT WHEN col%NOTFOUND;
        OPEN valueCursor for 'select ' || columnName 
                         || ' from ' || tableName;
        LOOP
          BEGIN
            fetch valueCursor into columnValue;        
            EXIT WHEN valueCursor%NOTFOUND;
            if (columnValue like valueParam) then
              dbms_output.put_line(tableName);
              exit;
            end if;
          EXCEPTION
            WHEN OTHERS then
              qq := 4;
          END;
        END LOOP;
        CLOSE valueCursor;
    END LOOP;
    CLOSE col;
  END LOOP;

end;

Вкратце о том, что же происходит:

  • проходим по всем таблицам схемы;
  • для каждой таблицы проходим по всем её колонкам;
  • для каждой колонки забираем список значений и сравниваем с тем, что передано в параметре процедуры;
  • если совпало, то печатаем имя таблицы в DBMS_OUTPUT;

пятница, 18 марта 2011 г.

Autogenerated FK in Hibernate

Страдаете от Hibernate'овского identifier too long? Тогда мы идём к вам!

Hibernate даёт свои автогенеренные названия для Foreign Key по одним разработчикам понятному принципу.
Вообще говоря, ничего необычного: 'FK' и ещё 16 символов - по 8 от hashcode имени каждой из участвующих таблиц (там не совсем hashCode, но и неважно).
Что получается, когда сущности (@Entity) наследуются? FK разрастается на 8 символов за каждый уровень наследования. То есть, для одиночного наследования длина FK будет уже не 18, а 18+8=26 символов, а при добавлении в эту иерархию ещё 1 сущности уже 18+2*8=34.
Самое время вспомнить о всеми любимом ограничении Oracle (и некоторых ещё СУБД) на длину идентификаторов в 30 символов.

Решение
Баг в Hibernate в открытом состоянии уже 3 года. Видимо, не сильно мешает, т.к. исправить ситуацию довольно просто: нужно дать своё имя для FK аннотацией в стиле:
@ForeignKey(name="myFKname")

суббота, 12 марта 2011 г.

EJB: используем TimerService. Часть 1

Пример использования Singleton

В прошлой заметке был показан механизм создания Singleton, используя возможности EJB 3.1. Однако созданный сервис не выполнял никакой полезной работы, поэтому смысл действа казался несколько туманным.

Создадим с помощью Singleton некоторую задачу, которая требует постоянного выполнения, независимо от действией пользователей системы. Например, требуется написать некоторый оповещатель пользователя о том, что пришла новая почта. В этом случае нам потребуется механизм, способный запускать функцию проверки почтового ящика, например, каждую минуту.


Как это сделать?
Открываем заготовку Singleton из прошлой заметки и наполняем её содержанием:


  • Необходим специальный интерфейс TimerService, позволяющий создавать различные виды таймеров.
    @Resource
    private TimerService timerService;

  • В метод инициализации, выполняемый сразу после инстанцирования объекта добавляем задание. Этим мы добьёмся того, что каждые 60 секунд будет срабатывать таймер и пытаться вызвать метод, помеченный аннотаций @Timeout.
    @PostConstruct
    public void init() {
    System.out.println("Инициализируем проверку почты");

    long interval = 60000L; // 1 minute
    timerService.createIntervalTimer(0L,
    interval, new TimerConfig(null, false));
    }

  • Наконец, нужно описать само действие, которое должно выполняться раз в минуту:
    @Timeout
    public void process(Timer timer) {
    checkMail();
    }


Собственно, это весь код, необходимый для организации событий, которые бы срабатывали с определённой частотой. Можно лишь добавить, что TimerService предоставляет несколько разных видов таймеров. В этом примере использовался createIntervalTimer, когда события возникают всегда через одинаковый интервал времени. Помимо этого доступны одноразовые таймеры (сработает единожды через определённое количество секунд, или же в определённое время), таймеры на основе CRON (с возможностью задать временное условие вида "каждый будний день в 9:00").


В следующий раз закончим тему таймеров, рассмотрев возможность параметризировать возникающее при достижении "часа Х" событие.

среда, 9 марта 2011 г.

Singleton стандартными средствами EJB

Создание Singleton в j2ee-приложениях
EJB позволяет быстро и просто реализовать Singleton, выполняющийся в отдельном потоке. Фактически, мы получаем тот же Stateful-bean, но с постоянным временем жизни. Так как это обычный bean, в нашем распоряжении оказываются механизмы Dependency Injection и аннотации @PostConstuct, @PreDestroy.


Итак, создим простой Singleton:


@Singleton
@Startup
public class ApplicationWatchDog implements Serializable {

@PostConstruct
public void init() {
System.out.println("Singleton успешно стартовал");
}
}



Что же мы сделали?

Аннотации над классом говорят сами за себя:


  • описываемый класс является Синглтоном (присутствует лишь в единственном экземпляре);

  • экземпляр создастся сразу же после развёртывая (deploy) приложения на сервере.

Метод, аннотированный @PostConstruct вызывается сразу же после создания экземпляра класса и обычно служит для настроек деятельности этого сервиса.



В следующей главе рассмотрим применение данного механизма для решения реальных задач.

суббота, 5 марта 2011 г.

CDI Events

Для чего нам может быть нужен Observer?
Первое, что приходит в голову: логирование операций пользователя. Сохраняет/изменяет человек какие-то данные на портале - одновременно с сохранением требуемой информации нужно ещё и сделать запись в лог, что такой-то пользователь в такое-то время изменил такую-то информацию.

Как реализуется?
Для начала необходимо определить тип (название) события, которое мы хотим ловить. Для этого создаём аннотацию:


@Qualifier

@Target({FIELD, PARAMETER, METHOD, TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface DataChangeEvent {   

}

Теперь необходимо создать класс, для передачи всех необходимых данных от возбудителя события к его подписчикам.

public class DataChangeInfo implements Serializable {

    public User user;

    public Data data;

    public DataChangeInfo(User user, Data data) {

        this.user = user;

        this.data = data;

    }

}

В сервисе, ответственном за сохранение, с помощью Dependency Injection определяем объект-событие:

@Inject 
@DataChangeEvent

private Event<DataChangeInfo> eventHandler;


И теперь в методу сохранения можно создать данной событие:
public void saveData(Data data, User user) {

    entityManager.merge(data);

    eventHandler.fire(new DataChangeInfo(user,data));

}

Осталось лишь реализовать подписчиков на это событие:

@Stateless

public class DataChangeSubscriber {

   

    public void dataWasChanged(@Observes @DataChangeEvent DataChangeInfo info) {

        Date date = new Date();

        User user = info.user;

        Data data = info.data;

        //Сохраняем data, user, date в лог.

    }

}

Вот и всё, что нужно для функционирования механизма.