среда, 31 декабря 2008 г.

Миграция данных между БД

Перемещение/преобразование данных между разными БД требует использование хорошо отлаженного механизма выполнения sql-запросов, которые необходимы для импорта/экспорта данных между БД. Написание такого рода движка потребует не мало времени и усилий.

Среди open-source решений на java мне известны два инструмента решающих подобную задачу - JDBCImporter и Scriptella. Первый инструмент делает экспорт данных сначала в xml, а затем производит из него импорт, т.е. имеет промежуточный xml при обработке данных. Сам JDBCImporter имеет утечки памяти при обработки данных больших объемов, да и как-то не активно развивается.

Scriptella - это инструмент для выполнения ETL (Extract-Transform-Load) процесса, который позволяет собирать данные из одного или нескольких источников данных и загружать их в другие позволяя выполнять различного рода трасформацию самих данных. При трансформации данных можно использовать скрипты прямо в sql-запросах, написанных на Velocity, JavaScript, JEXL... Сама конфигурация etl-процесса описывается в xml файле:

<!DOCTYPE etl SYSTEM "http://scriptella.javaforge.com/dtd/etl.dtd">
<etl>
<description></description>
<properties>
<include href="script.properties"/>
driver=org.jdcDriver
</properties>
<connection id="con1" driver="$driver" url="${url}" user="$user" password="12345678">
driver.property=value
</connection>
<connection id="con2" url="jdbc:hsqldb:file:db" user="sa" password=""/>
<script connection-id="out">
<include href="dbschema.sql"/>
</script>
<query connection-id="in">
SELECT * from Bug
<script connection-id="out">
INSERT INTO Bug VALUES (?ID, ?priority, ?summary, ?status);
</script>
</query>
</etl>

В описании конфигурации выделяют три основных элемента:

  • connection - представляет собой соединение с источником данных, таковым может являться как БД, XLS, CSV, XML... Можно также реализовать свой драйвер источника данных для Scriptella.

  • script - скрипт, написанный на языке источника данных к которому он относится через connection-id. Именно здесь выполняются различные скрипты на Velocity, JavaScript, JEXL и т.д.

  • query - запрос, выполняемый на источнике данных connection-id. Может содержать любое кол-во вложенных query и script элементов.

Запуск Scriptella можно произвести из командной строки, с помощью Ant-а, либо прямиком из Java.

четверг, 11 декабря 2008 г.

Event-Driven Development в Spring. Часть 2

Механизм рассылки event-ов в контексте Spring реализован одним интерфейсом org.springframework.context.event.ApplicationEventMulticaster. Именно ему и будут делегироваться все добавления, удаления listener-ов, а также публикация event-ов для них. По-умолчанию, в качестве реализации такого интерфейса будет использоваться объект класса org.springframework.context.event.SimpleApplicationEventMulticaster. В самом же контексте данный bean регистрируется под именем applicationEventMulticaster. Однако, если под таким именем bean уже существует и реализует нужный интерфейс, то будет использоваться именно он.

Необоходимость создания своего ApplicationEventMulticaster-а может возникнуть, например, чтобы рассылать event-ы по spring-контекстам целого кластера. Именно этот случай и реализован в моем примере. Сам механизм коммуникации между нодами обеспечивается фреймворком JGroups, который рассылает сообщения по сети multicast-ом через UDP-протокол. Объект com.blogspot.nkoksharov.springevents.jgroups.JGroupsMulticaster переопределяет метод void multicastEvent(ApplicationEvent event) и рассылает по сети лишь те экземпляры event-ов, классы которых являются подклассами com.blogspot.nkoksharov.springevents.jgroups.JGroupsEvent. А вот и сам код JGroupsMulticaster-а:

package com.blogspot.nkoksharov.springevents.jgroups;

import org.jgroups.*;
import org.slf4j.*;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;

/**
* Alternative multicaster to org.springframework.context.event.SimpleApplicationEventMulticaster
*
* @author nkoksharov
*
*/
public class JGroupsMulticaster extends SimpleApplicationEventMulticaster implements InitializingBean, DisposableBean, LocalEventMulticaster {

private final Logger logger = LoggerFactory.getLogger(getClass());
private JChannel jchannel;
private JGroupsReceiver receiver = new JGroupsReceiver();
private String configFile;
private String clusterName;

public void multicastEvent(ApplicationEvent event) {
if (event instanceof JGroupsEvent) {
try {
jchannel.send(new Message(null, null, event));
} catch (ChannelNotConnectedException e) {
logger.error("channel not connected", e);
} catch (ChannelClosedException e) {
logger.error("channel closed", e);
}
} else {
super.multicastEvent(event);
}
}

public void localMulticastEvent(JGroupsEvent event) {
super.multicastEvent(event);
}

public void afterPropertiesSet() throws Exception {
jchannel = new JChannel(configFile);
receiver.setLocalMulticater(this);
jchannel.setReceiver(receiver);
jchannel.connect(clusterName);
}

public void destroy() throws Exception {
jchannel.close();
}
public void setConfigFile(String configFile) {
this.configFile = configFile;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
}

Рассылка же абсолютно всех event-ов, в частности системных, может привести к некорректной работе всего spring-контекста.

Проект собирается с помощью Maven-а и запускается командой:

mvn compile exec:java

Каждый запуск - это новый контекст, т.е. можно запустить приложение на разных компьютерах в локальной сети и наблюдать появление сообщений в консоли о получении event-ов. Также можно запустить несколько экземпляров приложения на одном компьютере, все должно работать.

Скачать исходники
Event-Driven Development в Spring. Часть 1

понедельник, 1 декабря 2008 г.

Rhino и Janino для вычисления математических выражений

Для вычисления математических формул в run-time можно воспользоваться script-движком реализующим JSR-223. В Java 1.6 реализация этой спецификации представлена javascript-движоком Rhino. Однако применение целого script-движка для обычного вычисления формул будет довольно неоправданным решением. Мой поиск привел к Janino - это целый фреймворк для динамической компиляции и исполнения java. То есть он позволяет выполнять скрипты на java внутри самой java. Одно из его призваний это компиляция и выполнение различных математических выражений с помощью org.codehaus.janino.ExpressionEvaluator.

Я решил сравнить производительность Rhino и Janino. Для теста выбрал простую формулу: (x-10)*Math.sin(0.1)*2, вычисление которой будет осуществляться 1000000 раз.

package com.blogspot.nkoksharov;

import javax.script.*;

import org.apache.commons.lang.time.StopWatch;
import org.codehaus.janino.ExpressionEvaluator;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.testng.annotations.Test;

public class ExpressionTest {

@Test
public void testRhino() throws ScriptException {
ScriptEngineManager scriptManager = new ScriptEngineManager();
ScriptEngine se = scriptManager.getEngineByName("JavaScript");
SimpleBindings bindings = new SimpleBindings();
bindings.put("x", 2);

StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 1000000; i++) {
Double result = (Double) se.eval("(x-10)*Math.sin(0.1)*2", bindings);
}
stopWatch.stop();
System.out.println("JDK Rhino time: " + stopWatch);
}

@Test
public void testRhinoFunc() throws ScriptException, NoSuchMethodException {
ScriptEngineManager scriptManager = new ScriptEngineManager();
ScriptEngine se = scriptManager.getEngineByName("JavaScript");

String script = "function count(x) { return (x-10)*Math.sin(0.1)*2; }";
se.eval(script);

StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 1000000; i++) {
Invocable inv = (Invocable) se;
Double result = (Double) inv.invokeFunction("count", 2);
}
stopWatch.stop();
System.out.println("JDK Rhino func time: " + stopWatch);
}

@Test
public void testNativeRhinoCompiledFunc() {
ContextFactory cf = new ContextFactory();
Context cx = cf.enterContext();
Scriptable scope = cx.initStandardObjects();
Function f = cx.compileFunction(scope, "function count(x) { return (x-10)*Math.sin(0.1)*2; }", null, 0, null);

StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 1000000; i++) {
Double value = (Double) f.call(cx, scope, scope, new Object[] {2});
}
stopWatch.stop();
System.out.println("Mozilla Rhino compiled-func time: " + stopWatch);
}

@Test
public void testJanino() throws Exception {
ExpressionEvaluator ee = new ExpressionEvaluator("(x-10)*Math.sin(0.1)*2",
Double.class, new String[] {"x"}, new Class[] {Integer.class});

StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 1000000; i++) {
Double result = (Double) ee.evaluate(new Object[] {2});
}
stopWatch.stop();
System.out.println("Janino time: " + stopWatch);
}
}

Результат вполне оправдал мои ожидания:

                                 (ч:мм:сс.мс)
JDK Rhino time: 0:01:50.061
JDK Rhino func time: 0:00:59.040
Mozilla Rhino compiled-func time: 0:00:00.344
Janino time: 0:00:00.156

В итоге получается, что скорость выполнения с Rhino использующейся в JDK заметно отстает от Janino, даже не смотря на выполнение через javascript-функцию. Однако, если вы будете использовать реализацию Rhino от Mozilla, с возможностью компиляции в bytecode, то получите лишь 2-х кратное отставание по скорости. И так первенство у Janino, который также компилирует выражения перед выполнением. При этом обращения к методу ExpressionEvaluator.evaluate будут thread-safe.