Показаны сообщения с ярлыком tool. Показать все сообщения
Показаны сообщения с ярлыком tool. Показать все сообщения

пятница, 25 мая 2012 г.

Проект Hibernate Dynamic SQL Cache

Выложил на github проект Hibernate Dynamic SQL Cache.

Описание

Данный проект успешно используется мной в нескольких проектах и показал хорошую эффективность. Причиной для создания проекта послужил неэффективный механизм кэширования в Hibernate, впрочем сами разработчики открыто пишут об этом в документации.

  • HQL-кэш, а также кэш коллекций entity-объектов будут сбрасываться при каждом обновлении данных в хотя бы в одной из таблиц, участвующих в запросе. При очень частых обновлениях таблиц польза от такого рода кэшей сводится к 0.
  • SQL-кэш держит в памяти лишь результат первого вызова запроса и не сбрасывается.

Поэтому возникла идея создания динамически обновляемого кэша. Результат запроса обновляется при INSERT или DELETE объекта в базе. Для использования данного решения следует немного изменить подход к построению запросов, а также использованию различных коллекций внутри entity-объектов.

  • Запрос должен возвращать только id объекта. По этому id вы можете легко загрузить объект из кэша. (Таким образом получается два обращения к кэшу, что всегда быстрее выполнения запросов к базе)
  • Запрос должен быть только по неизменяемым полям объекта.
  • Вместо использования коллекций в entity-объекте использовать соответствующий SQL-запрос c динамическим обновлением.

Cache provider может использоваться абсолютно любой (Infinispan, Hazelcast, EHCache ...)

Проект опубликован под Apache License 2.0

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

Для наглядности примера используется Spring, хотя сам проект не привязан к нему, можно использовать любой другой контейнер.

Конфигурация hibernate. Здесь важно отметить использование собственного hibernate.cache.query_cache_factory через который и осуществляется вся логика управления обновлениями кэшей:

Регистрируем com.corundumstudio.hibernate.dsc.QueryCacheEntityListener который будет отслеживать основные операции по всем entity.

Пример DAO-сервиса. com.corundumstudio.hibernate.dsc.CacheCallback - callback через который будет обслуживать операции "insert" или "delete" на объекте (в данном примере SimpleEntity), чтобы результаты запроса были всегда "свежими".

Теперь при вызове SimpleEntityDao.getEntityByPhone, первый раз будет произведено обращение к БД. Последующие вызовы метода будут возвращать значение из кэша, причем если объект SimpleEntity с искомым значением phone будет добавлен в базу он также появится и в результате данного запроса. И наоборот, если объект с таким значением phone будет удален, то результат запроса будет возвращать null

Если результат запроса возвращает список объектов, то в этом случае, при удалении/создании объекта, в списке будет удаляться/добавляться его id

пятница, 13 апреля 2012 г.

BoneCP - производительный пул соединений для БД

BoneCP - библиотека управления соединениями с БД. Использую ее уже более 2-х лет в разных проектах, в том числе и в системах с нагрузкой. Автор позиционирует ее как более производительную замену библиотекам c3p0 и dbcp. Представленные им бенчмарки это только подтверждают.

Среди полезных возможностей можно отметить:

  • логирование всех sql-операций

  • доступ к статистике через JMX

  • кэширование PreparedStatement-ов

суббота, 7 февраля 2009 г.

Инструмент для просмотра логов

В повседневной работе довольно часто приходится смотреть логи от log4j. Прежде пользовался лишь обычным view-ром, пока не нашел довольно полезный open-source инструмент MindTree Insight, предназначенный именно для этой задачи.

Из настроек отмечу лишь, то что поле Preferences -> Maintain Preferences -> Primary Pattern должно содержать ту же строку форматирования, которую вы указали в настройках log4j.

среда, 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.

понедельник, 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.

среда, 19 ноября 2008 г.

Генерация javabean в runtime

Если требуется создать javabean в runtime по набору полей, то никакими стандартными средствами java эту задачу решить не удастся.

По началу я решил, что с этой задачей может справиться какая-либо из реализаций org.apache.commons.beanutils.DynaBean. Однако работать с ним можно лишь так if (object instanceof DynaBean) ... . То есть, если модуль использующий этот объект работает с ним только через reflection и ничего не знает о DynaBean, то такое решение не подходит.

В итоге, было решено использовать Javassist. Очень удобный инструмент для подобного рода действий, позволяет производить как создание так и модификацию уже загруженных JVM классов. Предоставляет два вида API, на уровне исходного кода и собственно самого байткода. Использовать конечно лучше первый, т.к. мало кого устроит работать напрямую с байткодом.

В альтернативу classloader-у используется класс javassist.ClassPool, он позволяет не только загружать, но и создавать классы javassist.CtClass. При этом класс не будет доступен приложению сразу же, а лишь после преобразования в обычный класс java.lang.Class. Сам javassist.CtClass обладает богатыми возможностями для создания, удаления методов и полей, изменения наследования или даже названия уже загруженного класса. Для доступа ко всем классам загруженным JVM следует использовать ClassPool.getDefault(), хотя можно реализовать и свой, чтобы, например, загружать классы самостоятельно.

Итак, решение с использованием Javassist будет выглядеть так:

public Class<?> createClass(String className, Map<String, Class<?>> props) throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass cc = classPool.makeClass(className);

for (Entry<String, Class<?>> entry : props.entrySet()) {
String name = entry.getKey();
Class<?> type = entry.getValue();
CtClass fieldType = classPool.get(type.getName());
CtField field = new CtField(fieldType, name, cc);
cc.addField(field);

String n = camelize(name);
CtMethod setter = CtNewMethod.setter("set" + n, field);
cc.addMethod(setter);
CtMethod getter = CtNewMethod.getter("get" + n, field);
cc.addMethod(getter);
}

return cc.toClass();
}

private String camelize(String s) {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}

воскресенье, 16 ноября 2008 г.

Log4jdbc логгер для sql-запросов

Log4jdbc представляет собой jdbc-драйвер, который может логировать sql-вызовы используя SLF4J. Реализует как JDBC 3 так и JDBC 4 спецификации. Для настройки к вашему jdbc-url необходимо просто добавить префикс jdbc:log4. Например:

jdbc:postgresql://localhost:5432/test, заменить на jdbc:log4jdbc:postgresql://localhost:5432/test

Затем в log4j.xml или log4j.properties настроить logger на любую из категорий:

  • jdbc.sqlonly - логирование только sql запросов,

  • jdbc.sqltiming - логирование sql-запросов и их времени выполнения,

  • jdbc.audit - логирование всех jdbc-операций за исключением ResultSet-ов,

  • jdbc.resultset - самое полное логирование, включающее в себя операции со всеми ResultSet-объектами,

  • jdbc.connection - логирование операций с jdbc-соединениями

воскресенье, 9 ноября 2008 г.

Slf4j на смену commons-logging

Наверное каждый из вас знаком с commons-logging. Однако пользы от него не так много. Наличие трюков с class-loader-ами, а также излишнее использование reflection-а и отсутствие возможности использования параметризированных сообщений, мягко говоря огорчают.

Довольно популярной заменой commons-logging является Slf4j (Simple Logging Framework for Java). Во-первых, в нем реализована поддержка MDC (пока что такая возможность реализована в logback и log4j). Этот механизм удобно использовать, когда у вас в контексте одного потока в лог записывается много параметра, которые действительны только для данного контекста. Например, для каждого сообщения необходимо выводить также имя пользователя, его логин и т.д. Вместо того чтобы указывать эти параметры каждый раз в сообщении. Можно положить их в MDC-контекст:

MDC.put("ipAddress", request.getRemoteAddr());
Principal user = request.getUserPrincipal();
MDC.put("user", user.getName());

И указать в ConversionPattern log4j эти параметры:

%X{ipAddress} - %X{user} %n%m

Во-вторых, есть наличие параметризованных сообщений. То есть, если раньше надо было писать так:

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

Если у нас будет не debug уровень, то будет тратиться время на сборку строки, которая не будет выводиться. Потому надо будет написать так:

if(logger.isDebugEnabled()) { 
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

Используя Slf4j все это можно заменить одной строкой:

logger.debug("Value {} was inserted between {} and {}.", 
new Object[] {newVal, below, above});

Символами {} как раз и обозначаются параметры строки, которые передаются последним аргументом. При этом сборка строки не будет производиться при уровне ниже чем debug.

В-третьих, сама архитектура представляет собой два модуля, api slf4j-api.jar и реализация slf4j-XXXXX.jar. Реализация представлена 6-ью модулями:

На главной странице этого проекта перечислены проекты, которые используют данный фреймворк и их число растет.

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class TestClass {

private final Logger log = LoggerFactory.getLogger( TestClass.class );

...

log.debug( "..." );
}

вторник, 28 октября 2008 г.

Миграция данных с LiquiBase

Во всех проектах, в которых мне довелось принимать участие, реализуемых на Java-платформе, я наблюдал примерно одну и ту же картину. Когда дело доходило до релиза и дальнейшего сопровождения изменений в БД, разработчикам всегда приходилось писать, либо уже использовать некий самописный механизм инкрементальной миграции данных.

Однако, я был убежден в том, что такого рода фреймворк должен быть уже кем-то написан. Им оказался LiquiBase написанный на той же Java и выпущенный под лицензией LGPL. Вот основные фичи этого проекта:

  • Не зависит от реализации БД, поддерживает: MySQL, PostgreSQL, Oracle, MS SQL, DB2, Derby, HSQL
  • Возможность проведения миграций от нескольких пользователей одновременно
  • Поддержка rollback-а миграций
  • Описание миграций в XML файле, с помощью внутренних команд.
  • Интеграция c Maven, Ant, Spring, Hibernate, Grails, возможность запуска из коммандой строки.

Формат документа, в котором описываются сами миграции выглядит примерно следующим образом:

<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.8"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.8
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.8.xsd">

<changeSet id="1" author="bob">
<comment>Create table<comment>
<createTable tableName="department">
<column name="id" type="int">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(50)">
<constraints nullable="false"/>
</column>
<column name="active" type="boolean" defaultValue="1"/>
</createTable>
</changeSet>

<changeSet id="2" author="sam">
<comment>Change name</comment>
<sql>
UPDATE TABLE SET name = 'someone' WHERE name = 'foo';
</sql>
<rollback>
<sql>
UPDATE TABLE SET name = 'foo' WHERE name = 'someone';
</sql>
</rollback>
</changeSet>
</databaseChangeLog>
Также хорошую статью на эту тему можно прочитать здесь.



Использование liquibase с hibernate и maven. Часть 1

воскресенье, 26 октября 2008 г.

ConceptDraw Office - хорошая замена MS Visio и MS Project

Хочу представить вашему вниманию такой замечательный пакет, как ConceptDraw Office. Используя этот пакет, можно управлять всеми аспектами проекта - от мозгового штурма до создания команды, планирования, распределения ресурсов и анализа результатов. В состав пакета входят продукты ConceptDraw MINDMAP, ConceptDraw PROJECT и ConceptDraw PRO - в таком единстве, набор обеспечивает все возможности для управлениями рабочими проектами: планирование и составление графика, организация процесса работы, процесс отслеживания и управления проектом.



Интерфейс выполнен в стиле MacOS - элементы на диаграммах с закругленными краями, гибкие связи и т.д. Соответственно и сам пакет существует под две платформы MacOS и Windows.

Я посмотрел лишь ConceptDraw PRO и ConceptDraw PROJECT. ConceptDraw PRO - полноценная замена Microsoft Visio, имеет богатый набор графических компонентов, с первого взгляда впечатлила фича авто-лайаута по краям соседних элементов. ConceptDraw PROJECT - отличная замена Microsoft Project, с возможность экспорта и импорта из него. ConceptDraw PROJECT более корректно работает с единицей измерения длительности задач - час. К примеру, не пишет для группы задач выраженных в часах, длительность в 1,78 дней. А воозможность экспорта набора задач в ConceptDraw MINDMAP оставила неизгладимое впечатление.

Вообщем, рекомендую к использованию!