Perzisztens réteg technológiák és a MyBatis
Bevezetés
Amikor adatbázist kell választani, főleg a relációs adatbázisok és az SQL kerülnek szóba. Az ezzel való kapcsolattartásra az alkalmazás oldalon főleg a JPA szabványt, és annak valamelyik implementációját, pl. a Hibernate-et használjuk.
Régóta sokan ódzkodnak ettől a technológiától, különböző okok miatt. Hamar lehet vele eredményeket elérni, azonban nagyon nehéz megérteni a mélységeit. Ennek hiányában azonban az alkalmazásunk rosszul teljesíthet, az N + 1 probléma miatt nagyon sok SQL utasítást adhat ki, és mivel a JPA implementáció ezeket generálja, nem tudjuk finoman szabályozni. A JPA ezen kívül nehezebben alkalmazható egy már meglévő, esetleg nem körültekintően megtervezett adatbázisra, sokkal inkább támogatja azt, ha a Java osztályokból indulunk ki.
Ezért érdemes megnézni, hogy milyen alternatív technológiák vannak, és ezek milyen jellemzőkkel rendelkeznek.
A technológia kiválasztása valamilyen szinten hat az alkalmazásunk architektúrájára is. Többrétegű alkalmazás esetén a perzisztens réteg tartja a kapcsolatot az adatbázissal. Itt különböző architektúrális mintákat használhatunk, ebből egy pár darab:
- Repository pattern, a Domain Driven Design (DDD) könyvből
- Table és Row Data Gateway a Patterns of Enterprise Application Architecture (Martin Fowler) könyvből
- Data Mapper ugyanonnan
- Data Access Object (DAO) a Java EE tervezési minták közül
Bizonyos neveket ráadásul bizonyos technológiák is használnak, ilyen pl. a repository, melyet a Spring Framework is használ a perzisztens rétegének elnevezésére, amit a Spring Data JPA is átvett.
Az eredeti tervem az volt, hogy ezek jelentését részletesen kifejtem, és összehasonlítom őket. De rossz hírem van. Arra jöttem rá, hogy ezeket a fogalmak nincsenek jól definiálva, nem összehasonlíthatóak, és mindenki másra használja ezeket. És ezt a különböző technológiák tovább bonyolítják, ugyanis saját komponenseik elnevezésére használják ezeket a fogalmakat, helytelenül.
(Egy szemléletes példa erre, hogy a DDD szerint a repository egy olyan objektum, mely az üzleti logika és az és az üzleti objektumok adatbázisból olvasásáért vagy oda írásáért felelős ún. mapping réteg között helyezkedik el, és az üzleti objektumokat a kollekciókhoz hasonlóan kezeli. Ezen túl komplex lekérdezési lehetőséget is biztosít úgy, hogy a lekérdezési feltételeket dinamikusan lehet összeállítani. A Spring Data JPA repository-ja ezzel pont ellentétes, előre definiálnunk kell a metódusokat, melyek csak egy jól meghatározott feltétellel hívhatóak meg. Saját véleményem szerint a repository-nak a JPA Criteria Query API-ja sokkal jobban megfelel.)
Ezért úgy döntöttem, nem én fogok ezekben a fogalmakban rendet szabni, hanem inkább egy tulajdonság rendszert állítok össze, ami alapján a perzisztens technológiák osztályozhatóak. Ez legyen a következő:
- Szabvány-e vagy egyedi implementáció
- Ingyenes-e
- SQL lekérdező nyelvet kell használni, vagy saját nyelve van, amiből maga generál le SQL utasításokat
- Van-e benne mapping, azaz az adatbázisból jövő adatokat automatikusan meg tudja-e feleltetni az objektumokkal
- Elég-e interfészeket definiálni, amihez maga generálja ki az implementációt.
- Képes-e metódusnév alapján implementációt generálni
A mapping tipikusan reflectionnel működik, és rendelkezik alapkonfigurációval (pl. az attribútum neve megegyezik az oszlopnévvel), de ez személyre is szabható.
Azt is eldöntöttem, hogy nem a különböző tervezési minták neveit fogom használni, hanem a különböző technológiák elnevezési konvencióit.
Ezek alapján a JPA:
- Szabványos, a Java EE szabvány része, implementációi pl. a Hibernate vagy az EclipseLink
- Mindkét elterjedt implementációja ingyenes
- Saját nyelve van, a JPQL, vagy a Criteria Query API is használható
- (Object-Relational Mapping) ORM eszköz, azaz megfelelteti az objektumokat az adatbázisból jövő adatokkal
- Nem elég interfészeket definiálni
- Ezért metódusnév alapján sem tud implementációt generálni
Technológiák
Azonban sok egyéb technológia is van, számomra azok különösen érdekesek, melyet a Spring Boot is támogat. Ezek:
- Natív JDBC
- Natív Hibernate
- Natív JPA
- Spring JdbcTemplate
- Spring Data JDBC
- Spring Data R2DBC
- Spring Data JPA
- MyBatis
- JOOQ
Java EE környezetben érdemes még megemlítenem az Apache DeltaSpike Data technológiát is, mely a Spring Data JPA megfelelője Java EE környezetben.
A natív JDBC-t már semmiképp nem érdemes használni, hiszen rendkívül körülményes. A Spring JdbcTemplate egy jó alternatíva, egy vékony réteg a JDBC fölé.
A Hibernate-et én már nem választanám magába, csak JPA-n keresztül.
A Spring Data egy projekt gyűjtemény, és a benne lévő projektek célja, hogy különböző adatbázisokat és technológiákat egy egységes modell alapján lehessen kezelni. Egyszerűen lehessen a mappinget elvégezni és lehetőleg az interfész és a metódus nevek alapján is tudjon implementációt generálni. Az R2DBC reaktív programozást tesz lehetővé a reaktív R2DBC adatbázis driver használatával. A JPA-t önmagában nem választanám, kizárólag a Spring Data JPA-val.
A MyBatis egy egyedi implementáció, ami egy jó átmenet a JDBC és a JPA között, ugyanis a nyelve még az SQL, azonban képes a mappingre.
Tud interfész implementációt generálni, sőt van egy MyBatis-Plus kiegészítése, ami képes metódusnevek alapján implementációt generálni. Van egy MyBatisX IDEA plugin is, mely segít a fejlesztésben.
A MyBatis jól jöhet, ha egy már létező adatbázishoz akarunk kapcsolódni.
Elterjedt még a JOOQ, mely egy egyedi eszköz, van ingyenes és fizetős verziója is. Segítségével az adatbázisból tudunk Java osztályokat generálni, majd fluent API-val SQL lekérdezéseket megfogalmazni.
Nagy félreértés az, hogyha van egy perzisztens technológiánk, akkor mindenre azt kell használni. A Java EE kifejezetten azt írja, hogy a JPA megfelelő kevés számú, de bonyolult objektumgráf kezelésére, és ha nagytömegű adatot akarunk kezelni, akkor használjuk JDBC-t. A JOOQ sem a JPA helyére akar lépni, hanem kiegészítő technológiaként arra az esetre, ha elértük a JPA határait.
MyBatis
Ebben a posztban azonban a MyBatisról írok a továbbiakban. A MyBatisnak van Spring Boot illesztése, ez a MyBatis-Spring-Boot-Starter library. A MyBatis a http://start.spring.io címen is kiválasztható.
A példaprojekt elérhető a GitHubon.
A MyBatist többféleképp is használhatjuk. Egyrészt használhatunk interfészeket, és annotációkat a következő módon:
@Mapper
public interface EmployeeMapper {
@Insert("insert into employees(id, name) values (seq_employees.nextval, #{name})")
@Options(useGeneratedKeys = true, keyProperty="id")
void save(Employee employee);
@Select("select id, name from employees where id = #{id}")
Employee findById(long id);
}
Ezután már csak injektálnunk kell, és meghívni a metódusait. Látható, hogy az annotációk SQL utasításokat tartalmaznak.
A másik megoldásban ún. mapper XML állományokat készítünk,
amiben leírjuk az adatbázis műveleteket, és ezekre hivatkozunk egy SqlSession
példány használatakor.
Az application.properties
-ben először meg kell mondani ezen állományok helyét:
mybatis.mapper-locations=/mappers/employees.xml
Ekkor a mapper xml állományt az src/main/resources/mappers/employees.xml
elérési útvonalon helyezzük el
a következő tartalommal:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jtechlog.mybatis.EmployeeRepository">
<insert id="saveEmployee" useGeneratedKeys="true" keyProperty="id">
insert into employees(id, name) values (seq_employees.nextval, #{name})
</insert>
<select id="findEmployeeById" resultType="jtechlog.mybatis.Employee">
select id, name from employees where id = #{id}
</select>
</mapper>
(Ezt az IDEA megfelelő pluginnal automatikusan tudja kódkiegészíteni.)
Majd hozzunk létre egy Spring repository-t:
@Repository
@AllArgsConstructor
public class EmployeeRepository {
private SqlSession session;
public void save(Employee employee) {
session.insert("saveEmployee", employee);
}
public Employee findById(long id) {
return session.selectOne("findEmployeeById", id);
}
}
MyBatis-Plus
A MyBatis-Plus egyik hátránya, hogy a dokumentációja csak kínai nyelven elérhető.
A példaprojekt elérhető a GitHubon.
Ehhez is van starter:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
Itt is egy interfészt kell írnunk, de öröklődnie kell a
BaseMapper
interfészből, mely sok előregyártott metódust tartalmaz.
@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}
Az Employee
osztályon van pár magáért beszélő annotáció:
@TableName("employees")
@KeySequence("seq_employees")
Ezután ezeket a metódusokat máris használhatjuk, pl. a tesztesetben:
var employee = new Employee("John Doe");
employeeMapper.insert(employee);
var employeeToSelect = employeeMapper.selectById(employee.getId());
assertEquals("John Doe", employeeToSelect.getName());
Ezen kívül lehetőség van metódusokat névkonvenció szerint megadni, amihez automatikusan fog implementációt gyártani a MyBatis-Plus.
A kigenerált SQL utasításokat a naplózás állításával tudjuk megnézni,
amihez az application.properties
-ben a következő bejegyzést kell
tennünk:
logging.level.jtechlog.mybatis.EmployeeMapper=debug