Hibernate - tworzenie własnego dialektu
Jednym z ogromnych plusów wykorzystywania w swoim projekcie biblioteki Hibernate jest możliwość użycia języka zapytań HQL. Jego wielką zaletą jest niezależność zapytań od wykorzystania konkretnego systemu zarządzania bazą danych. Niestety ta zaleta bywa równocześnie wadą w sytuacji, w której chcielibyśmy wywołać jakąś funkcję specyficzną dla danego dostawcy. Na całe szczęście, dzięki dialektom, nie trzeba tworzyć natywnych zapytań, żeby móc korzystać z możliwości naszego dostawcy bazy danych.
Przykładowo, mając schemat bazy danych złożony z następujących tabel:
create table if not exists person(
id integer primary key not null auto_increment,
first_name varchar(256) not null,
last_name varchar(256) not null
);
create table if not exists phone(
id integer primary key not null auto_increment,
number varchar(16) not null,
type varchar(16) not null,
person_id integer not null,
foreign key (person_id) references person(id)
);
może przytrafić się nam potrzeba zwrócenia zgrupowanego wyniku z listą osób oraz listą telefonów połączonych za pomocą przecinków.
Korzystając z natywnego zapytania (tu i w dalszej części artykułu przyjmijmy, że korzystamy z bazy MySQL) możliwe jest wykorzystanie funkcji group_concat
:
select first_name, last_name, group_concat(type, ': ', number separator ', ')
from person
join phone on phone.person_id = person.id
group by person.id;
W pierwszej kolejności należy stworzyć odpowiednię implementację dialektu, najlepiej rozszerzając istniejący dialekt:
import org.hibernate.dialect.MySQLDialect;
public class ExtendedMySQLDialect extends MySQLDialect {
(...)
}
Kolejnym krokiem będzie dodanie konstruktora bezparametrowego, który wywoła bazowy konstruktor i zarejestruje dodatkową funkcję:
public ExtendedMySQLDialect() {
super();
registerFunction("group_concat", new VarArgsSQLFunction("group_concat(", ",", " separator ', ')"));
}
W związku z tym, że funkcja group_concat
przyjmuje zmienną liczbę argumentów, wykorzystałem obiekt klasy org.hibernate.dialect.function.VarArgsSQLFunction
, który jest dużo bardziej generyczny w stosunku do org.hibernate.dialect.function.StandardSQLFunction
.
Pozwala on na określenie początku funkcji (czyli nazwa z nawiasem otwierającym), separatora argumentów (czyli przecinek) oraz zakończenia (czyli separator dla funkcji i nawias zamykający).
Aby móc skorzystać z nowo utworzonego dialektu, wystarczy określić go w konfiguracji Hibernate:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">pl.wercia.example.dialect.ExtendedMySQLDialect</property>
(...)
</session-factory>
</hibernate-configuration>
W efekcie, dopuszczalne będzie teraz użycie funkcji group_concat
w zapytaniach HQL:
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
try {
List<?> results = session.createQuery("select person.firstName, person.lastName,"
+ " group_concat(phone.type, ': ', phone.number) from Person as person"
+ " join person.phones as phone group by person.id").list();
(...)
}
finally {
session.close();
}
Oczywiście nic nie stoi na przeszkodzie, aby zarejestrować w ten sposób inne funkcje dostępne w używanej bazie danych. Włącznie ze zdefiniowanymi samodzielnie (np. za pomocą PL/SQL).
Przykładowy projekt: