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 saját táblákkal dolgozik, amelyek ugyanazon az adatbázis-szerveren élnek, de nem részei a platform sémájának.
- A feladat természeténél fogva tranzakciót igényel – például több saját entitást kell egyszerre konzisztensen frissíteni.
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
- Platform elindul, Spring kontextus felépül.
PluginLoaderbetölti a plugin JAR-t.IPluginConfigurer.configure(ctx)hívódik – itt regisztrálhatók extra beanek.- Tool-ok betöltődnek,
@Autowiredmezők feltöltődnek a kontextusból.
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);
}
}
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;
// ...
}
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;
});
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:
- Maradj a szabványos JPA API-nál:
@Entity,@ManyToOne, JPQL,EntityManager– ezek provider-függetlenek. - Kerüld az EclipseLink-specifikus annotációkat és kiterjesztéseket
(pl.
@ReadOnly,@Cache, EclipseLink-dialektusú lekérdezések), ha a kód hordozhatóságára törekszel. - A
eclipselink.weaving=falsebeállítás az egész platformra érvényes – lazy loading proxy-k nem generálódnak fordítási időben. Ez a standard JPA@ManyToOne,@OneToManyrelációkra is hatással van: betöltésük eager vagy explicit fetch join szükséges.
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
- A saját entitások és a
persistence.xmla plugin JAR-ban kell hogy legyenek – a platform nem tud róluk. - Ha a plugin saját JPA függőséget igényel (pl. egy speciális JDBC driver),
azt
compilescope-ban kell felvenni – így kerül alib/könyvtárba, amelyet aPluginClassLoadertölt be. - Az
IPluginConfigurerservices fájl az ellenőrzőlistában is szerepel (→ 11. fejezet).