вторник, 19 апреля 2011 г.

Массив в коллекцию, пожалуйста. И чтобы покороче

Особенности Arrays.asList(T... elements)
Arrays.asList(T... elements) и Collection.toArray() являются стандартным (так говорят javadoc'и) мостом между библиотеками, работающими с массивами и коллекциями. Звучит красиво, что уж там.
Немногие дочитывают до того места, в котором говорится, что полученный List является fixed-size. А это означает, например, что оттуда нельзя удалить элемент. Этот баг можно быстро найти и понять, но тем не менее. В общем, я был неприятно удивлён. Что побудило Sun реализовать метод именно так я не смог понять (поэтому мне сложно решить, пометить запись меткой "фича" или "кривота"...выберу обе).
Как лечится?
Например, так:
List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 3));

понедельник, 11 апреля 2011 г.

Переименовать таблицу или колонку

Парочка мелких вещей в SQL, которые меня слегка удивили.
  • В БД при изменении имени таблиц все constraints автоматически подстраиваются под это изменение. То есть никаких ошибок не возникнет (входит в стандарт и работает на абсолютном большинстве БД).
    ALTER TABLE MyTable RENAME TO MY_TABLE;
    
  • Но Oracle пошёл и того дальше. Он умеет даже переименовывать колонки. При этом даже если это Primary Key. Даже если на колонку навешены другие индексы, даже если на неё ссылается Foreign Key из другой таблицы. К сожалению, эта фишка не из стандарта, поэтому в других БД может и не быть (в MySQL нет, например).
    ALTER TABLE MyTable RENAME COLUMN id TO identifier;
    

воскресенье, 3 апреля 2011 г.

Асимметрия

Вопрос:
Может ли функция Math.abs(int) вернуть отрицательное число?

Ответ:
Как же хочется написать "нет", улыбнуться и подумать, что я всех обманул таким вопросом. На самом деле может. Так уж повелось, что отрицательных чисел больше, чем положительных. Так уж повелось, что если byte, то диапазон -128..127, ну и так далее с увеличением количества байт на хранение. К чему это приводит?
int minValue = Integer.MIN_VALUE;
int abs = Math.abs(minValue);
System.out.println(abs);
Закрываем, трясём, смотрим (©):
> -2147483648

воскресенье, 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").


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