Расширяем диалекты через … аспекты.
Говорят (только не говорите этого женщинам), что в мире нет совершенства. Но почему-то все к нему стремятся. Вот и получается, что при разработке продуктов, которые должны уметь работать с различными СУБД (если не одновременно, то хотя бы в зависимости от опций развертывания), нам приходится учитывать все эти стремления их авторов к абсолютному и безусловному совершенству. Наверное, исходя из того, что клин клином вышибают, известный всем и уважаемый мною господин Gavin King добавил в любимый народом Hibernate поддержку различных SQL-диалектов, и даже сделал этот набор расширяемым.
Теперь представим ситуацию: мы разрабатываем продукт, который должен уметь работать с несколькими различными СУБД, например MySQL версий 4.x и 5.x, ну и postgresql. Для диалектов этих СУБД Hibernate предоставляет несколько классов - MySQLDialect, MySQLInnoDBDialect, MySQLMyISAMDialect, MySQL5Dialect, MySQL5InnoDBDialect и, наконец, PostgreSQL. Однако ни в одном из них не поддерживаются, например, побитовые операции. А что делать, если они нужны? Авторы рекомендуют расширить стандартные классы и в конструкторе каждого из них добавить в диалект реализацию недостающих функций. Т.е. в нашем случае нужно добавить шесть классов. А что делать, если количество планируемых к поддержке СУБД больше? Классический пример неудачно спроектированной иерархии, когда метаданные статически проецируют в поведение.
Однако кто вам сказал, что в мире нет совершенства? Наверное, он забыл вас предупредить, что есть AOP, и ApectJ как инструмент его реализации применительно к java. C помощью одного нехитрого аспекта можно расширить все используемые в продукте диалекты.
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.SuppressAjWarnings;
import org.hibernate.Hibernate;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.SQLFunctionTemplate;
import java.util.Map;
/**
* @author Alexander Gavrilov
*/
@Aspect
public class ExtendDialectsAspect {
@SuppressWarnings({"unchecked"})
@SuppressAjWarnings("adviceDidNotMatch")
@AfterReturning(value = "initialization(org.hibernate.dialect.MySQLDialect+.new(..)) && this(dialect)",
argNames = "dialect")
public void extendMySQLDialects(Dialect dialect) {
addBitwise(dialect.getFunctions());
}
@SuppressWarnings({"unchecked"})
@SuppressAjWarnings("adviceDidNotMatch")
@AfterReturning(value = "initialization(org.hibernate.dialect.PostgreSQLDialect+.new(..)) && this(dialect)",
argNames = "dialect")
public void extendPostgreSQLDialect(Dialect dialect) {
addBitwise(dialect.getFunctions());
}
protected void addBitwise(Map functions) {
functions.put("bit_or", new SQLFunctionTemplate(Hibernate.INTEGER, "?1 | ?2"));
functions.put("bit_not", new SQLFunctionTemplate(Hibernate.INTEGER, "~?1"));
}
}
Теоретически, в данном конкретном случае, можно было бы отрефакторить все в один advice и изменить его pointcut так, что бы он описывал точки создания или MySQLDialect или PostgreSQLDialect, однако я решил оставить задел на будущее - вдруг придется расширить диалекты функциями, синтаксис которых у MySQL и PostreSQL различается. Не трудно заметить, что добавление поддержки еще одного диалекта выливается в добавление всего лишь одного advice-метода в класс. А то, что расширения всех диалектов сосредоточены в одном месте, является неоспоримым плюсом в долгосрочной перспективе нелегкого труда по сопровождению нашего совершенного кода в этом далеком от совершенства мире
P.S. В следующий раз я планирую рассказать о том, как я компилирую аспекты средствами IntelliJ IDEA+ или maven2+, а так же когда и как использую LTW и CTW
Изменен

aefimov заявил:
Добавлено 18 января, 2007 в 11:20“Эт все харашо, да. Все праильн, да. Все праильн.” (c)
А как это дебажить то?
lucker (чел в теме) заявил:
Добавлено 18 января, 2007 в 11:28А что тут дебажить то? Hibernate дебажить? Или эти пару строк?
А вообще надо писать интеграционные тесты!