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