SDK
Haladó fejezet

Plugin adatbázis-hozzáférés

Ha egy plugin saját adatot ír vagy olvas relációs adatbázisból, az SDK két eszközt biztosít: az IPluginConfigurer inicializáló hook-ot és az AbstractPluginJpaConfigurer alaposztályt. Ezekkel a plugin saját táblákhoz és entitásokhoz férhet hozzá JPA-n keresztül, a platform adatbázisaitól és tranzakciókezelésétől teljesen függetlenül.

Mikor van szükség erre?

A platform alapvetően az ArangoDB-n és a saját MariaDB sémáján keresztül kezeli az adatokat. A plugin fejlesztőjének általában nincs szüksége közvetlen adatbázis-hozzáférésre: az LLM-hívások, a RAG retrieval és a tudásgráf műveletek mind elérhetők az SDK interfészein keresztül.

Az alábbi esetekben azonban indokolt lehet saját adatbázis-logika:

⚠️
A plugin fejlesztő kizárólag saját táblákhoz férhet hozzá JPA-n keresztül. A platform adatbázis sémája és entitásai nem elérhetők plugin fejlesztői szinten – ezekhez csak a platform SDK interfészein (RAG retrieval, tudásgráf, LLM) keresztül lehet hozzájutni.

IPluginConfigurer – a plugin inicializáló hook

Az IPluginConfigurer interfész egyetlen metódust ír elő:

public interface IPluginConfigurer {
    void configure(ConfigurableApplicationContext ctx);
}

A PluginLoader a tool-ok betöltése előtt hívja meg a configure() metódust. Így az itt regisztrált Spring beanek már elérhetők, mire a tool-ok @Autowired mezői feltöltődnek.

Discovery: ServiceLoader

A IPluginConfigurer implementációkat ugyanúgy a ServiceLoader találja meg, mint a tool-okat – de egy külön services fájlban:

META-INF/services/hu.medical.rag.common.plugin.IPluginConfigurer
→ com.example.plugin.MyPluginConfigurer

Ez a fájl opcionális. Ha a plugin nem igényel inicializálást (nincs JPA, nincs extra Spring bean), egyszerűen nem veszi fel.

Sorrend és életciklus

  1. Platform elindul, Spring kontextus felépül.
  2. PluginLoader betölti a plugin JAR-t.
  3. IPluginConfigurer.configure(ctx) hívódik – itt regisztrálhatók extra beanek.
  4. Tool-ok betöltődnek, @Autowired mezők feltöltődnek a kontextusból.
ℹ️
Az IPluginConfigurer nem csak JPA-ra való. Bármilyen inicializáló logikát elvégezhet: cache előmelegítés, külső kapcsolat ellenőrzése, metric regisztráció stb.

AbstractPluginJpaConfigurer – saját persistence unit

Ha a plugin saját entitásokkal és saját táblákkal dolgozik, az AbstractPluginJpaConfigurer alaposztályt érdemes kiterjeszteni. Ez elvégzi az EntityManagerFactory létrehozását és regisztrálását a platform DataSource-ára alapozva – nincs szükség új connection pool-ra.

public class MyPluginJpaConfigurer extends AbstractPluginJpaConfigurer {

    @Override
    protected String getPersistenceUnitName() {
        return "my-plugin-unit";  // egyedi név a saját persistence unit-hoz
    }

    @Override
    protected Class<?>[] getEntityClasses() {
        return new Class[]{ MyEntity.class, AnotherEntity.class };
    }

    // Opcionális: extra EclipseLink propertiák
    @Override
    protected Map<String, Object> getExtraJpaProperties() {
        return Map.of("eclipselink.logging.level", "FINE");
    }
}

A configure() metódus automatikusan regisztrálja az EMF-et és az entitás–persistence unit leképezést. Ezután a tool-ból elérhető:

Spring @Transactional – a megszokott minta

Ha a plugin Spring @Transactional service osztályokat is szeretne, felül kell írni az afterConfigure() hook-ot. Ez az EMF regisztráció után hívódik, és itt regisztrálhatók a JpaTransactionManager, a SharedEntityManagerCreator proxy és a service beanek.

Ehhez a spring-orm könyvtár szükséges – a platform nem tartalmazza, ezért compile scope-ban kell felvenni a plugin pom.xml-jébe (a lib/ könyvtárba kerül):

<!-- JPA annotációk és EntityManager – verzió és provided scope a platform-bom-ból -->
<dependency>
    <groupId>jakarta.persistence</groupId>
    <artifactId>jakarta.persistence-api</artifactId>
</dependency>

<!-- Spring @Transactional infrastruktúra – compile scope, bekerül a lib/-be -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <scope>compile</scope>
</dependency>

A konfigurátor:

public class MyPluginJpaConfigurer extends AbstractPluginJpaConfigurer {

    public static final String TX_MANAGER  = "myPluginTxManager";
    public static final String SHARED_EM   = "myPluginEntityManager";
    public static final String MY_SERVICE  = "myService";

    @Override protected String getPersistenceUnitName() { return "my-plugin-unit"; }
    @Override protected Class<?>[] getEntityClasses() { return new Class[]{ MyEntity.class }; }

    @Override
    protected void afterConfigure(ConfigurableApplicationContext ctx, EntityManagerFactory emf) {
        // JpaTransactionManager – @Transactional alapja
        JpaTransactionManager tx = new JpaTransactionManager(emf);
        tx.afterPropertiesSet();
        ctx.getBeanFactory().registerSingleton(TX_MANAGER, tx);

        // Tranzakció-aware EntityManager proxy
        EntityManager sharedEm = SharedEntityManagerCreator.createSharedEntityManager(emf);
        ctx.getBeanFactory().registerSingleton(SHARED_EM, sharedEm);

        // Service osztály – createBean() alkalmaz AOP proxy-t (@Transactional működik)
        MyService svc = ctx.getAutowireCapableBeanFactory().createBean(MyService.class);
        ctx.getBeanFactory().registerSingleton(MY_SERVICE, svc);
    }
}
ℹ️
Miért nincs @Service a service osztályon?
A plugin JAR-t a Spring kontextus frissítése után tölti be a PluginLoader, ezért a component scan nem ér el a plugin osztályaihoz. A service-t createBean() + registerSingleton() hívással regisztráljuk programmatikusan – a @Service ebben a kontextusban nincs hatással sem a bean létrehozásra, sem a @Transactional AOP proxy alkalmazására. A createBean() maga gondoskodik az összes BeanPostProcessor lefuttatásáról, @Service nélkül is.

A service osztály a megszokott Spring mintával:

public class MyService {

    @Autowired @Qualifier("myPluginEntityManager")
    private EntityManager em;

    @Transactional(readOnly = true, transactionManager = "myPluginTxManager")
    public List<MyEntity> findAll() {
        return em.createQuery("SELECT e FROM MyEntity e", MyEntity.class).getResultList();
    }

    @Transactional(transactionManager = "myPluginTxManager")
    public void save(MyEntity e) {
        em.persist(e);
    }
}

A tool egyszerűen autowire-olja a service-t:

public class MyTool implements IChatbotTool<MyRequest> {

    @Autowired @Qualifier("myService")
    private MyService service;

    // ...
}
ℹ️
createBean() vs autowireBean() – a különbség kritikus. Az autowireBean() csak az @Autowired mezőket tölti fel, AOP proxy-t nem alkalmaz. A createBean() a teljes Spring bean életciklust lefuttatja – csak így működik a @Transactional.

Egyszerűbb alternatíva: statikus segédek

Ha nincs szükség @Transactional service osztályra, az AbstractPluginJpaConfigurer statikus segédei is elegendők (nem kell spring-orm sem):

// Olvasás
List<MyEntity> items = AbstractPluginJpaConfigurer.executeQuery(
    "my-plugin-unit",
    em -> em.createQuery("SELECT e FROM MyEntity e", MyEntity.class).getResultList()
);

// Írás natív JPA tranzakcióban
AbstractPluginJpaConfigurer.executeInTransaction("my-plugin-unit", em -> {
    em.persist(new MyEntity("teszt"));
    return null;
});
ℹ️
Az AbstractPluginJpaConfigurer az IPluginConfigurer leszármazottja – a configure() implementálva van, felülírni nem kell. A ServiceLoader fájlban az IPluginConfigurer nevű fájlba kell felvenni az osztályt.

JPA provider – EclipseLink elfogadása

A platform JPA providere az EclipseLink. A plugin a platform DataSource-ára és EclipseLink EMF-jére épít, ezért:

ℹ️
Technikailag lehetséges Hibernate-et is használni plugin lib/-ként (a persistence.xml-ben explicit <provider> megadással), de a plugin lib/ mérete jelentősen megnő, és a provider-specifikus viselkedés tesztelési terhet jelent. EclipseLink a standard JPA API szintjén teljes értékű megoldást nyújt, provider-csere szükségtelen.

Services fájlok

Minden plugin-specifikus ServiceLoader fájl a META-INF/services/ könyvtárban él a plugin JAR-ban:

src/main/resources/META-INF/services/
  hu.medical.rag.common.plugin.IPluginConfigurer   ← konfigurátor(ok)
  hu.medical.rag.common.tool.intf.IChatbotTool     ← tool(ok)
# hu.medical.rag.common.plugin.IPluginConfigurer
com.example.plugin.MyPluginJpaConfigurer

# hu.medical.rag.common.tool.intf.IChatbotTool
com.example.plugin.MyTool

Döntési útmutató

Helyzet Ajánlott megközelítés
Saját táblák olvasása és írása AbstractPluginJpaConfigurer kiterjesztése (saját persistence unit), executeInTransaction() és executeQuery() segédekkel
Extra Spring bean regisztrálása (nem JPA célra) IPluginConfigurer közvetlen implementálása + ctx.getBeanFactory().registerSingleton()
Platform adatok elérése SDK interfészek: RAG retrieval, ILlmAssistant, tudásgráf – közvetlen adatbázis-hozzáférés nem engedélyezett

Deploy szempontok