Programozási tételek
C# / Vue
Java / React
  • C#
    • Konzolos
    • WFA
    • WPF
  • Laravel
  • SQL
  • JavaScript
  • Vue
  • Bootstrap
  • Szoftverteszt
  • Linkek

Adatbázis és tábla létrehozása – CREATE

A CREATE DATABASE utasítás új adatbázist hoz létre. A CREATE TABLE utasítás a tábla szerkezetét (oszlopokat és típusaikat) definiálja. Az oszlopok meghatározásakor meg kell adni a típust, és opcionálisan különböző megszorításokat (constraint).

  • INT – egész szám; VARCHAR(n) – max. n karakteres szöveg; DATE – dátum; DECIMAL(p,s) – tizedes tört p számjegy, s tizedes
  • NOT NULL – kötelező mező (nem lehet üres)
  • AUTO_INCREMENT – automatikusan növekvő szám (ID-khoz)
  • PRIMARY KEY – elsődleges kulcs: egyedileg azonosítja a rekordot
  • DEFAULT érték – alapértelmezett érték, ha nem adjuk meg
  • FOREIGN KEY ... REFERENCES – idegen kulcs: egy másik tábla elsődleges kulcsára mutat
Az elsődleges kulcs (PRIMARY KEY) egyedi és kötelező: egy táblában minden rekordnak más értékű. Az idegen kulcs (FOREIGN KEY) a két tábla közti kapcsolatot teremti meg.
-- Adatbázis létrehozása (magyar karakterkészlettel)
CREATE DATABASE iskola DEFAULT CHARACTER SET utf8 COLLATE utf8_hungarian_ci;

-- Tábla létrehozása
CREATE TABLE tanulok (
    id          INT NOT NULL AUTO_INCREMENT,
    nev         VARCHAR(100) NOT NULL,
    osztaly     VARCHAR(10) NOT NULL,
    szuletesi   DATE,
    atlag       DECIMAL(4,2),
    aktiv       BOOLEAN DEFAULT TRUE,
    PRIMARY KEY (id)
);

-- Idegen kulcsos kapcsolt tábla
CREATE TABLE jegyek (
    id          INT NOT NULL AUTO_INCREMENT,
    tanulo_id   INT NOT NULL,
    tantargy    VARCHAR(50) NOT NULL,
    jegy        INT NOT NULL,
    PRIMARY KEY (id),
    FOREIGN KEY (tanulo_id) REFERENCES tanulok(id)
);

Adatok beszúrása – INSERT INTO

Az INSERT INTO utasítás új rekordot (sort) szúr be a táblába. Meg kell adni, mely oszlopokba írunk, majd a VALUES kulcsszó után az értékeket ugyanabban a sorrendben.

  • INSERT INTO tabla (oszlop1, oszlop2) VALUES (ertek1, ertek2) – egy sor hozzáadása
  • Szöveges és dátum értékeket egyszeres idézőjelbe kell tenni: 'Kovács'
  • Az AUTO_INCREMENT oszlopot (id) nem kell megadni, automatikusan kitöltődik
  • Több sort egyszerre is be lehet szúrni: a VALUES után vesszővel elválasztott zárójelcsoportokkal
-- Egy sor beszúrása
INSERT INTO tanulok (nev, osztaly, szuletesi, atlag)
VALUES ('Kovács Béla', '13D', '2006-03-15', 4.20);

-- Több sor egyszerre
INSERT INTO tanulok (nev, osztaly, szuletesi, atlag) VALUES
('Nagy Anna',   '13D', '2006-07-22', 4.80),
('Kiss Péter',  '13C', '2006-01-10', 3.50),
('Tóth Zsófia', '13D', '2006-11-05', 4.60);

Alap lekérdezés – SELECT

A SELECT utasítás az adatok lekérdezésére szolgál. Megadható, hogy mely oszlopokat szeretnénk látni, és mely táblából. A * jel minden oszlopot jelent.

  • SELECT * FROM tabla – minden oszlop, minden rekord
  • SELECT oszlop1, oszlop2 FROM tabla – csak a megadott oszlopok
  • AS 'Új név' – az oszlop neve helyett más felirat jelenik meg a kimenetben (alias)
  • DISTINCT – csak az egyedi értékeket adja vissza (duplikátumok nélkül)
-- Minden mező, minden sor
SELECT * FROM tanulok;

-- Csak bizonyos oszlopok
SELECT nev, osztaly FROM tanulok;

-- Alias: a kimenetben más felirattal jelenik meg
SELECT nev AS 'Tanuló neve', atlag AS 'Átlag' FROM tanulok;

-- Egyedi értékek (duplikátumok kizárása)
SELECT DISTINCT osztaly FROM tanulok;

Szűrés – WHERE és operátorok

A WHERE záradékkal feltételt adunk meg: csak azok a rekordok kerülnek a lekérdezés eredményébe, amelyekre a feltétel teljesül. Több feltételt az AND (mindkettő teljesül) és OR (legalább az egyik teljesül) kulcsszavakkal kombinálhatunk.

  • = egyenlő, <> nem egyenlő, < kisebb, > nagyobb, <= kisebb-egyenlő, >= nagyobb-egyenlő
  • LIKE 'K%' – K-val kezdődő értékek (% = bármennyi karakter helyettesítője)
  • LIKE '%Anna%' – tartalmazza: Anna
  • IS NULL – üres (nem kitöltött) mező; IS NOT NULL – nem üres
  • IN ('A', 'B') – a felsorolt értékek valamelyike; NOT IN – egyik sem
SELECT * FROM tanulok WHERE osztaly = '13D';
SELECT * FROM tanulok WHERE osztaly <> '13D';
SELECT * FROM tanulok WHERE atlag >= 3.5 AND atlag <= 4.5;

-- LIKE: minta illesztés
SELECT * FROM tanulok WHERE nev LIKE 'K%';       -- K-val kezdődik
SELECT * FROM tanulok WHERE nev LIKE '%Anna%';   -- tartalmazza: Anna

-- NULL vizsgálat
SELECT * FROM tanulok WHERE atlag IS NULL;
SELECT * FROM tanulok WHERE atlag IS NOT NULL;

-- IN: felsorolásból
SELECT * FROM tanulok WHERE osztaly IN ('13C', '13D');

Rendezés és korlátozás – ORDER BY, LIMIT

Az ORDER BY a lekérdezés eredményét rendezi egy vagy több oszlop alapján. A LIMIT meghatározza, hány rekordot adjon vissza maximum — ez hasznos pl. "top 3" típusú feladatoknál.

  • ORDER BY oszlop ASC – növekvő sorrend (alapértelmezett), szövegnél ABC-sorrend
  • ORDER BY oszlop DESC – csökkenő sorrend
  • LIMIT 3 – csak az első 3 rekordot adja vissza
  • Több oszlop szerinti rendezésnél: ORDER BY oszlop1 ASC, oszlop2 DESC
-- Növekvő sorrend átlag szerint
SELECT * FROM tanulok ORDER BY atlag ASC;

-- Csökkenő sorrend (legjobb előre)
SELECT * FROM tanulok ORDER BY atlag DESC;

-- Legjobb 3 tanuló
SELECT * FROM tanulok ORDER BY atlag DESC LIMIT 3;

-- Osztály szerint, azon belül névsor
SELECT * FROM tanulok ORDER BY osztaly ASC, nev ASC;

Aggregáló függvények – COUNT, SUM, AVG, MAX, MIN

Az aggregáló (összesítő) függvények több rekordból egyetlen értéket számítanak. Mindig egy egész táblára vagy egy csoportra vonatkoznak — önmagukban nem jelenítenek meg egyedi sorokat.

  • COUNT(*) – megszámolja az összes rekordot (NULL-t is); COUNT(oszlop) – csak a nem NULL értékeket számolja
  • SUM(oszlop) – numerikus értékek összege
  • AVG(oszlop) – numerikus értékek átlaga
  • MAX(oszlop) – legnagyobb érték; MIN(oszlop) – legkisebb érték
  • Dátum függvények: YEAR(datum), MONTH(datum), DAY(datum) – adott részét adja vissza a dátumnak
SELECT COUNT(*) AS osszes_tanulo FROM tanulok;
SELECT COUNT(*) AS tizenharmad_d FROM tanulok WHERE osztaly = '13D';

SELECT SUM(atlag)  AS atlagok_osszege FROM tanulok;
SELECT AVG(atlag)  AS atlag_atlag     FROM tanulok;
SELECT MAX(atlag)  AS legjobb         FROM tanulok;
SELECT MIN(atlag)  AS leggyengebb     FROM tanulok;

-- Dátumból csak az év
SELECT nev, YEAR(szuletesi) AS szuletesi_ev FROM tanulok;

Csoportosítás – GROUP BY és HAVING

A GROUP BY csoportokra osztja a rekordokat egy adott oszlop értékei szerint, majd az aggregáló függvények csoportonként számolnak. A HAVING a WHERE megfelelője csoportokra: csak az aggregált értékre vonatkozó feltétel megadására használható.

  • GROUP BY osztaly – az osztályonként különböző értékű rekordokat csoportosítja
  • WHERE – az egyedi rekordokat szűri (csoportosítás előtt)
  • HAVING – a csoportokat szűri (csoportosítás után); aggregáló függvénnyel kell használni
  • Sorrendje: WHERE → GROUP BY → HAVING → ORDER BY
Ökölszabály: ha a feltételben COUNT(), AVG(), SUM() stb. szerepel, akkor HAVING kell. Ha egyszerű mezőértékre szűrsz, akkor WHERE.
-- Osztályonkénti tanulószám
SELECT osztaly, COUNT(*) AS letszam
FROM tanulok
GROUP BY osztaly;

-- Osztályonkénti átlag, csökkenő sorrendben
SELECT osztaly, AVG(atlag) AS atlag_atlag
FROM tanulok
GROUP BY osztaly
ORDER BY atlag_atlag DESC;

-- HAVING: csak azok az osztályok, ahol az átlag > 4.0
SELECT osztaly, AVG(atlag) AS atlag_atlag
FROM tanulok
WHERE aktiv = TRUE
GROUP BY osztaly
HAVING AVG(atlag) > 4.0;

Táblakapcsolat – INNER JOIN

Az INNER JOIN két táblát kapcsol össze egy közös mezőn keresztül. Csak azok a rekordok kerülnek az eredménybe, amelyekre mindkét táblában van egyező érték az összekapcsolási feltételben. Az összekapcsolás feltételét az ON kulcsszó után adjuk meg.

  • FROM tabla1 INNER JOIN tabla2 ON tabla1.mezo = tabla2.mezo – alap szintaxis
  • Táblanév alias (AS t) rövidíti az írást és egyértelműsíti, melyik tábla melyik mezőjéről van szó
  • Az összekapcsolt lekérdezésre is alkalmazható WHERE, ORDER BY, GROUP BY
-- Tanulók és jegyeik összekapcsolva
SELECT t.nev, j.tantargy, j.jegy
FROM tanulok AS t
INNER JOIN jegyek AS j ON t.id = j.tanulo_id;

-- Szűrve: csak matematika jegyek
SELECT t.nev, j.jegy
FROM tanulok AS t
INNER JOIN jegyek AS j ON t.id = j.tanulo_id
WHERE j.tantargy = 'Matematika'
ORDER BY t.nev ASC;

-- Tantárgyanként átlag jegy
SELECT j.tantargy, AVG(j.jegy) AS atlag_jegy
FROM tanulok AS t
INNER JOIN jegyek AS j ON t.id = j.tanulo_id
GROUP BY j.tantargy
ORDER BY atlag_jegy DESC;

Adatmódosítás – UPDATE

Az UPDATE utasítás meglévő rekordok értékeit módosítja. A SET után adjuk meg, melyik oszlopot milyen értékre állítsuk. A WHERE nélküli UPDATE az összes rekordot módosítja — ezt általában el kell kerülni!

  • UPDATE tabla SET oszlop = ertek WHERE feltétel – alapszintaxis
  • Több mező egyszerre: SET oszlop1 = ertek1, oszlop2 = ertek2
  • A WHERE nélküli UPDATE minden sort módosít — csak szándékosan hagyjuk el!
-- Egy rekord módosítása
UPDATE tanulok SET atlag = 4.00 WHERE id = 1;

-- Több mező egyszerre
UPDATE tanulok
SET atlag = 4.50, osztaly = '13D'
WHERE nev = 'Kiss Péter';

-- Feltétel szerinti tömeges módosítás
UPDATE tanulok SET aktiv = FALSE WHERE atlag < 2.0;

Rekord és tábla törlése – DELETE, DROP

A DELETE a tábla tartalmát törli (a szerkezet megmarad), míg a DROP TABLE a teljes táblát (szerkezettel együtt) megszünteti. A DROP DATABASE az egész adatbázist törli.

  • DELETE FROM tabla WHERE feltétel – csak a feltételnek megfelelő rekordok törlése
  • DELETE FROM tabla – az összes rekord törlése (szerkezet marad)
  • DROP TABLE tabla – a tábla megszüntetése (szerkezettel együtt, visszaállíthatatlan!)
  • DROP DATABASE nev – az egész adatbázis törlése
IDEGEN KULCS miatt a kapcsolódó táblát (pl. jegyek) mindig a főtábla (tanulok) előtt kell törölni, különben hibaüzenetet kapunk.
-- Egy rekord törlése
DELETE FROM tanulok WHERE id = 3;

-- Feltétel szerinti törlés
DELETE FROM tanulok WHERE aktiv = FALSE;

-- Teljes tartalom törlése (szerkezet marad)
DELETE FROM tanulok;

-- Tábla megszüntetése (szerkezettel együtt)
DROP TABLE jegyek;   -- előbb a kapcsolt táblát!
DROP TABLE tanulok;

-- Adatbázis törlése
DROP DATABASE iskola;

1. Grid Rendszer (Rácsszerkezet)

A Bootstrap rácsszerkezete 12 oszlopra osztja a képernyőt. A flexbox alapú rendszer reszponzív töréspontokat (xs, sm, md, lg, xl, xxl) használ, így beállíthatjuk, hány oszlopot foglaljon el egy elem az adott képernyőméreten.

  • .container – fix szélességű tartály (középre igazítva), .container-fluid – 100% széles
  • .row – egy sor a rácsban
  • .col-12 .col-md-6 .col-lg-4 – Mobilon teljes szélesség (12), tableten fél (6), asztalin harmad (4)
<div class="container">
  <div class="row">
    <div class="col-12 col-md-6 col-lg-4">
      Bal oszlop (mobilon 100%, asztalin 33%)
    </div>
    <div class="col-12 col-md-6 col-lg-4">
      Középső oszlop
    </div>
    <div class="col-12 col-md-12 col-lg-4">
      Jobb oszlop
    </div>
  </div>
</div>

2. Térközök és Igazítások (Utilities)

A Bootstrap beépített segédosztályokat kínál margók (m), belső térközök (p), szövegigazítások és flexbox elrendezések gyors megadására anélkül, hogy saját CSS-t kéne írni.

  • m / p – Margin / Padding
  • t, b, s, e, x, y – Top, Bottom, Start(Left), End(Right), X-tengely, Y-tengely
  • Méretek: 0-tól 5-ig (pl. mt-3 = margin-top: 1rem)
  • text-center – Szöveg középre, text-end – Szöveg jobbra
  • d-flex justify-content-center align-items-center – Flexbox középre igazítás
<!-- m-5 = külső margó minden irányban, p-3 = belső margó -->
<div class="m-5 p-3 bg-light text-center">
  <h3 class="mb-4 text-primary">Cím</h3>
  <p class="mt-2">Ez egy szöveg</p>
</div>

<!-- Flexbox használata elemek egymás mellé rendezéséhez -->
<div class="d-flex justify-content-between align-items-center">
  <div>Bal oldali elem</div>
  <div>Jobb oldali elem</div>
</div>

3. Alap Komponensek (Card, Button)

A Bootstrap előre formázott UI elemekkel rendelkezik. A leggyakoribbak a Kártyák (Card) és Gombok (Button), de ide tartoznak a Modal ablakok és a Navbar-ok is.

  • .btn .btn-primary – Kék alapgomb. (Színek: secondary, success, danger, warning, info, light, dark)
  • .card, .card-body, .card-title – Kártya szerkezet létrehozása.
<!-- Kártya komponens gombbal -->
<div class="card" style="width: 18rem;">
  <img src="..." class="card-img-top" alt="...">
  <div class="card-body">
    <h5 class="card-title">Kártya címe</h5>
    <p class="card-text">Ide jön a kártya leírása...</p>
    <a href="#" class="btn btn-success">Részletek</a>
    <a href="#" class="btn btn-outline-danger">Törlés</a>
  </div>
</div>

DOM szelektálás – elemek kiválasztása

A DOM (Document Object Model) a HTML oldal fa-szerkezetű reprezentációja JavaScriptben. A DOM-szelektálás az a folyamat, amellyel JavaScriptből hivatkozunk egy HTML elemre, hogy azt aztán módosíthassuk vagy eseményt rendeljünk hozzá.

  • document.getElementById('id') – egyetlen elemet ad vissza ID alapján (leggyorsabb)
  • document.querySelector('.class') – CSS szelektor alapján az első illő elemet adja vissza
  • document.querySelectorAll('li') – CSS szelektor alapján az összes illő elemet adja vissza (NodeList)
  • Szelektálható: ID (#id), osztály (.class), tag (div), attribútum (input[type="text"])
// ID alapján – egyetlen elemet ad vissza
const gomb = document.getElementById('mentesBtn');

// CSS szelektor – az első illő elem
const elso = document.querySelector('.card');
const input = document.querySelector('#nev');

// CSS szelektor – összes illő elem (NodeList, mint egy tömb)
const cardok     = document.querySelectorAll('.card');
const inputok    = document.querySelectorAll('input[type="text"]');

// NodeList bejárása forEach-csel
cardok.forEach(function(card) {
    console.log(card);
});

DOM módosítás – tartalom és stílus

A DOM-szelektálás után az elemet módosíthatjuk: megváltoztathatjuk a szövegét, a HTML tartalmát, a CSS stílusát vagy az osztályait. A classList a CSS osztályok kezelésének legkényelmesebb módja.

  • elem.textContent = 'szöveg' – csak szöveget ír, HTML-t nem értelmez (biztonságos)
  • elem.innerHTML = '<b>vastag</b>' – HTML-t is értelmez
  • elem.style.color = 'red' – inline CSS beállítása (camelCase!)
  • elem.classList.add('aktiv') – CSS osztály hozzáadása
  • elem.classList.remove('rejtett') – CSS osztály eltávolítása
  • elem.classList.toggle('kiemelt') – váltogatja: ha van, leveszi; ha nincs, hozzáadja
  • input.value – beviteli mező értéke (olvasás és írás)
const doboz = document.getElementById('doboz');

// Tartalom módosítás
doboz.textContent = 'Szia!';
doboz.innerHTML   = '<b>Vastag szöveg</b>';

// Stílus módosítás (CSS property nevei camelCase-ben)
doboz.style.color           = 'red';
doboz.style.backgroundColor = '#1e1e1e';
doboz.style.display         = 'none';   // elrejt
doboz.style.display         = 'block';  // megjelenít

// CSS osztályok kezelése
doboz.classList.add('aktiv');
doboz.classList.remove('rejtett');
doboz.classList.toggle('kiemelt');
const vanE = doboz.classList.contains('aktiv'); // true / false

// Input mező értékének kezelése
const input = document.querySelector('#felhasznalo');
input.value = 'Kovács Béla';         // érték beírása
console.log(input.value);            // érték kiolvasása

Eseménykezelés – addEventListener

Az eseménykezelés (event handling) lehetővé teszi, hogy a felhasználói interakciókra (kattintás, billentyűleütés, form beküldése) reagáljon a kód. Az addEventListener() metódus egy eseménytípushoz rendel egy callback (visszahívó) függvényt.

  • 'click' – egérkattintás; 'submit' – form beküldése; 'keydown' – billentyű lenyomás; 'input' – mező tartalmának változása
  • event.preventDefault() – megakadályozza az alapértelmezett viselkedést (pl. form nem tölt újra oldalt)
  • event.key – a lenyomott billentyű neve (pl. 'Enter', 'a')
  • Arrow function (() => { }) a function() { } rövidebb alternatívája
// Kattintás esemény – hagyományos függvény
document.getElementById('mentesBtn').addEventListener('click', function() {
    alert('Mentve!');
});

// Kattintás – arrow function (rövidebb)
document.getElementById('mentesBtn').addEventListener('click', () => {
    console.log('Gombra kattintottak!');
});

// Form beküldés – oldal újratöltés megakadályozása
document.getElementById('urlapom').addEventListener('submit', function(event) {
    event.preventDefault(); // nélküle az oldal újratöltődne
    const nev = document.getElementById('nev').value;
    console.log('Bekért név:', nev);
});

// Billentyű esemény – Enter-re szűrés
document.getElementById('keresomező').addEventListener('keydown', function(event) {
    if (event.key === 'Enter') {
        console.log('Keresés indul...');
    }
});

// Több elemen egyszerre (loop)
document.querySelectorAll('.torles-btn').forEach(btn => {
    btn.addEventListener('click', function() {
        this.closest('.card').remove(); // legközelebbi szülő .card törlése
    });
});

JSON kezelés – stringify és parse

A JSON (JavaScript Object Notation) egy szöveg alapú adatcsere-formátum. JavaScriptben a JSON.stringify() objektumot JSON szöveggé alakít (pl. tároláshoz vagy küldéshez), a JSON.parse() pedig JSON szöveget alakít vissza JavaScript objektummá.

  • JSON.stringify(obj) – JS objektum → JSON string (pl. '{"nev":"Béla","kor":20}')
  • JSON.stringify(obj, null, 2) – formázott, olvasható JSON (2 szóköz behúzással)
  • JSON.parse(szoveg) – JSON string → JS objektum; ha érvénytelen a JSON, kivételt dob
  • JSON szabályok: kulcsok mindig idézőjelben, utolsó elem után nincs vessző, csak null (nem undefined)
// JS objektum → JSON string
const szemely = {
    nev: 'Kovács Béla',
    kor: 20,
    aktiv: true,
    cimkek: ['fejlesztő', 'tesztelő']
};

const jsonString = JSON.stringify(szemely);
// {"nev":"Kovács Béla","kor":20,"aktiv":true,"cimkek":["fejlesztő","tesztelő"]}

const szepJson = JSON.stringify(szemely, null, 2); // formázott

// JSON string → JS objektum
const visszaAlakitva = JSON.parse(jsonString);
console.log(visszaAlakitva.nev);  // 'Kovács Béla'
console.log(visszaAlakitva.kor);  // 20

// Tömb JSON-ként
const lista = [{ id: 1, nev: 'Alma' }, { id: 2, nev: 'Körte' }];
const visszaLista = JSON.parse(JSON.stringify(lista));
console.log(visszaLista[0].nev); // 'Alma'

localStorage – böngészőben tárolás

A localStorage a böngésző helyi tárolója: kulcs–érték párokat ment el, amelyek az oldal bezárása után is megmaradnak (ellentétben a sessionStorage-dzsal, amely csak a munkamenet végéig él). Csak szöveget tud tárolni, ezért objektumokat JSON-ná kell alakítani.

  • localStorage.setItem('kulcs', 'érték') – ment; ha a kulcs már létezik, felülírja
  • localStorage.getItem('kulcs') – kiolvas; ha nincs ilyen kulcs, null-t ad vissza
  • localStorage.removeItem('kulcs') – egy bejegyzés törlése
  • localStorage.clear() – az összes bejegyzés törlése
  • Objektum mentéséhez: JSON.stringify(); visszaolvasáshoz: JSON.parse()
A || [] rész azt jelenti: ha getItem() null-t ad vissza (nincs még adat), akkor üres tömböt használj helyette.
// Egyszerű érték mentése és olvasása
localStorage.setItem('felhasznalo', 'Kovács Béla');
const nev = localStorage.getItem('felhasznalo'); // 'Kovács Béla'

// Objektum mentése (JSON-ná kell alakítani)
const beallitasok = { tema: 'sotet', nyelv: 'hu' };
localStorage.setItem('beallitasok', JSON.stringify(beallitasok));

// Objektum visszaolvasása
const mentett = JSON.parse(localStorage.getItem('beallitasok'));
console.log(mentett.tema); // 'sotet'

// Lista kezelése (elem hozzáadása)
function listaMentes(ujElem) {
    let lista = JSON.parse(localStorage.getItem('lista')) || [];
    lista.push(ujElem);
    localStorage.setItem('lista', JSON.stringify(lista));
}

// Egy elem törlése / összes törlése
localStorage.removeItem('felhasznalo');
localStorage.clear();

fetch API – adatlekérés szerverről (AJAX)

A fetch() aszinkron HTTP kéréseket küld a szerver felé anélkül, hogy az oldalt újratöltené. A válasz feldolgozása kétlépéses: először a HTTP válaszobjektumot kapjuk meg, majd azt .json()-nal értelmezzük. Az async/await olvashatóbbá teszi az aszinkron kódot.

  • fetch(url) – GET kérést küld; egy Promise-t ad vissza
  • response.json() – a válasz szövegét JSON objektummá alakítja (szintén Promise)
  • async function neve() { } – aszinkron függvény deklarációja
  • await – megvárja a Promise teljesülését (csak async függvényen belül használható)
  • method: 'POST', headers, body – POST kérés paraméterei
  • try/catch – hibakezelés aszinkron kódban
// GET kérés – async/await-tel (ajánlott)
async function termekekLekerese() {
    try {
        const response = await fetch('https://api.example.com/termekek');
        const adatok   = await response.json();
        console.log(adatok);
    } catch (hiba) {
        console.error('Hiba:', hiba);
    }
}
termekekLekerese();

// POST kérés – új adat küldése JSON-ban
async function ujTermekMentes(termek) {
    const response = await fetch('https://api.example.com/termek/create', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(termek)   // objektumot JSON string-gé kell alakítani
    });
    const eredmeny = await response.json();
    console.log('Mentve:', eredmeny);
}

ujTermekMentes({ nev: 'Laptop', ar: 349990 });

Arrow functions és template literals

Az arrow function (nyíl függvény) a hagyományos function kulcsszó rövidebb alternatívája — főleg callback függvényeknél (pl. forEach, filter, eseménykezelők) terjedt el. A template literal (sablon szöveg) a szövegbe való változó beágyazásának modern módja.

  • (a, b) => a + b – egysoros: a visszatérési érték implicit (nincs szükség return-re)
  • x => x * 2 – egy paraméternél a zárójelek elhagyhatók
  • () => { ... } – több utasítás esetén kapcsos zárójel és return szükséges
  • Template literal: backtick karakterek (`) között; változók: ${valtozo}
  • tömb.filter(x => x > 0) – feltételnek megfelelő elemeket adja vissza
  • tömb.map(x => x * 2) – minden elemre alkalmaz egy műveletet, új tömböt ad vissza
// Hagyományos függvény vs arrow function
function osszead(a, b) { return a + b; }
const osszead2 = (a, b) => a + b;          // egysoros
const duplazo  = x => x * 2;              // egy paraméter
const udvozles = () => console.log('Szia!'); // paraméter nélkül

// Template literal (backtick közt, ${} a beágyazáshoz)
const nev = 'Béla';
const kor = 20;
const szoveg = `Szia, ${nev}! ${kor} éves vagy.`;
// "Szia, Béla! 20 éves vagy."

// Többsoros string template literal-lal
const html = `
    <div class="card">
        <h2>${nev}</h2>
        <p>Kora: ${kor} év</p>
    </div>
`;

// Array metódusok arrow functionnel
const szamok  = [1, 2, 3, 4, 5];
const parosak = szamok.filter(x => x % 2 === 0);     // [2, 4]
const dupla   = szamok.map(x => x * 2);               // [2, 4, 6, 8, 10]
const osszeg  = szamok.reduce((acc, x) => acc + x, 0); // 15

Fájlbeolvasás szöveges fájlból

A C# System.IO névtere két fő módszert kínál szöveges fájlok beolvasásához. A File.ReadAllLines() az egész fájlt egyszerre olvassa be egy string[] tömbként — kisebb fájlokhoz ideális. A StreamReader soronként dolgozik, ezért nagy fájlok esetén memóriatakarékosabb.

  • File.ReadAllLines("fajl.txt") – az összes sort egy string tömbben adja vissza
  • new StreamReader("fajl.txt") – soronkénti olvasáshoz, manuálisan kell lezárni
  • olvaso.EndOfStream – true, ha elértük a fájl végét
  • olvaso.ReadLine() – következő sort olvassa, majd lép egyet
  • sor.Split(';') – a sort elválasztó karakter mentén szétbontja
  • A StreamReader-t mindig .Close()-zal kell lezárni a fájl felszabadításához!
A 2D-s string[,] tömbnél előre meg kell adni a méretet: new string[sorok, oszlopok]. Az első dimenzió a sorokat, a második az oszlopokat (mezőket) jelöli.
string[] adatok = File.ReadAllLines("asd.txt");

string[,] asd = new string[adatok.Length, 3];
StreamReader olvaso = new StreamReader("asd.txt");
int n = 0;
while (!olvaso.EndOfStream)
    {
        string[] sor = olvaso.ReadLine().Split(';');
        for (int i = 0; i < 3; i++)
    {
        asd[n, i] = sor[i];
    }
        n++;
    }
olvaso.Close();

A Java java.nio.file és java.io csomagjai több módszert is kínálnak a fájlkezelésre. A Files.readAllLines() kényelmesen, egyetlen lépésben olvassa be a fájlt egy List<String>-be. A BufferedReader soronkénti feldolgozást tesz lehetővé, ami memóriatakarékosabb nagy adatmennyiség esetén.

  • Files.readAllLines(path) – a teljes fájl beolvasása string listaként
  • new BufferedReader(new FileReader(fajl)) – soronkénti olvasáshoz
  • olvaso.readLine() – beolvassa a következő sort, null-t ad ha vége
  • sor.split(";") – a sort darabolja a megadott elválasztó mentén
  • try-with-resources – (a példában manuális close) automatikusan lezárja az erőforrásokat
Java-ban a 2D tömb String[][] formátumú. Az inicializáláskor new String[sorok][oszlopok] módon kell megadni a méretet. A lista méretét az adatok.size() adja meg.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class Fajlbeolvasas {
    public static void main(String[] args) throws IOException {
        // Egész fájl beolvasása listába
        List<String> adatok = Files.readAllLines(Paths.get("asd.txt"));

        // 2D tömb létrehozása előre adott méretekkel: [sorok][oszlopok]
        String[][] asd = new String[adatok.size()][3];

        // Soronkénti olvasás try-with-resources-szal (automatikus close)
        BufferedReader olvaso = new BufferedReader(new FileReader("asd.txt"));
        int n = 0;
        String sor;
        while ((sor = olvaso.readLine()) != null) {
            String[] mezok = sor.split(";");
            for (int i = 0; i < 3; i++) {
                asd[n][i] = mezok[i];
            }
            n++;
        }
        olvaso.close();
    }
}

Statisztika készítése – Dictionary

A Dictionary<TKey, TValue> (szótár) kulcs–érték párokat tárol. Statisztika készítésekor a kulcs az adott érték (pl. egy szám, egy szó), az érték pedig az előfordulások száma. A szótár kulcsonként egy elemet tárol, és O(1) idő alatt keres.

  • ContainsKey(kulcs) – ellenőrzi, hogy a kulcs már szerepel-e
  • dict[kulcs]++ – ha megvan a kulcs, növeli az értékét
  • dict.Add(kulcs, 1) – ha nincs meg, hozzáadja 1-es kezdőértékkel
  • foreach (var x in dict) – kulcs: x.Key, érték: x.Value
  • dict.Count – az egyedi kulcsok száma
Dictionary<int, int> stat = new Dictionary<int, int>();

for (int i = 0; i < szamok.Length; i++)
{
    if (stat.ContainsKey(szamok[i]))
    {
        stat[szamok[i]]++;
    }
    else
    {
        stat.Add(szamok[i], 1);
    }
}

foreach (var x in stat)
{
    if (x.Value > 3)
    {
        Console.WriteLine(x.Key + "-----" + x.Value + " db");
    }
}
Console.WriteLine("Összes adat db: " + stat.Count);

A HashMap<K, V> kulcs–érték párokat tároló adatszerkezet (a C# Dictionary megfelelője). Statisztika készítésekor a kulcs a vizsgált elem, az érték pedig a gyakorisága. A Java 8-as getOrDefault() metódusa jelentősen leegyszerűsíti a számlálást.

  • containsKey(kulcs) – megvizsgálja, szerepel-e már az elem
  • put(kulcs, ertek) – behelyezi vagy felülírja az értéket
  • get(kulcs) – visszaadja a kulcshoz tartozó értéket
  • getOrDefault(kulcs, alapertelmezett) – visszaadja az értéket, vagy az alapértelmezettet, ha a kulcs nem létezik
  • entrySet() – a szótár bejárásához szükséges (kulcs és érték párok)
A Map.Entry<Integer, Integer> x : stat.entrySet() ciklussal tudunk végigmenni a szótáron, ahol az x.getKey() és x.getValue() adja vissza az adatokat.
import java.util.HashMap;
import java.util.Map;

public class Statisztika {
    public static void main(String[] args) {
        int[] szamok = {1, 2, 2, 3, 3, 3, 4, 4, 4, 4};
        Map<Integer, Integer> stat = new HashMap<>();

        for (int i = 0; i < szamok.length; i++) {
            // Ha már szerepel, növeljük; ha nem, 1-et adunk hozzá
            if (stat.containsKey(szamok[i])) {
                stat.put(szamok[i], stat.get(szamok[i]) + 1);
            } else {
                stat.put(szamok[i], 1);
            }
            }
            
        // Rövidebb forma getOrDefault-tal:
            // stat.put(szamok[i], stat.getOrDefault(szamok[i], 0) + 1);

        for (Map.Entry<Integer, Integer> x : stat.entrySet()) {
            if (x.getValue() > 3) {
                System.out.println(x.getKey() + "-----" + x.getValue() + " db");
            }
        }
        System.out.println("Összes adat db: " + stat.size());
    }
}

Bringa – feladat példa

Összetett konzolos feladat: kölcsönzési adatokat olvas be CSV fájlból 2D tömbé, majd különböző statisztikákat számol (napi bevétel, keresés névre, típus szerinti statisztika, fájlba írás). Jó összefoglaló példa a fájlkezelés, tömbök és alapszámítások kombinációjára.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace vizib
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string[] hadatok = File.ReadAllLines("kolcsonzesek.txt");
                int hossz = hadatok.Length;
                Console.WriteLine("5. feladat: Napi kölcsönzések száma: "+(hossz-1));
            string[,] kocsonzesek = new string[hossz,6];
            StreamReader olvaso = new StreamReader("kolcsonzesek.txt");
            int n = 0;
            while (!olvaso.EndOfStream)
            {
                string[] sor = olvaso.ReadLine().Split(';');
                for (int i = 0; i < 6; i++)
                {
                    kocsonzesek[n, i] = sor[i];
                }
                n++;
            }
            olvaso.Close();
            Console.Write("6. feladat: Kérek egy nevet: ");
            string nev = Console.ReadLine();
            int volte = 0;
            foreach (var y in kocsonzesek)
            {
                if (y.Contains(nev))
                {
                    volte++;
                }
            }
            if (volte == 0)
            {
                Console.WriteLine("Nem volt kölcsönzés " + nev + " néven!");
            }
            else
            {
                Console.WriteLine(nev + " Kölcsönzései: ");
            }
            for (int i = 0; i < hossz; i++)
            {
                if (kocsonzesek[i, 0] == nev)
                {
                    Console.WriteLine(kocsonzesek[i, 2] + ":" + kocsonzesek[i, 3] + "-" + kocsonzesek[i, 4] + ":" + kocsonzesek[i, 5]);
                }
            }
            int[] percek = new int[hossz];
            for (int i = 1; i < hossz; i++)
            {
                percek[i-1] = (int.Parse(kocsonzesek[i, 5])+ int.Parse(kocsonzesek[i, 4])*60)- (int.Parse(kocsonzesek[i, 3]) + int.Parse(kocsonzesek[i, 2]) * 60);
            }
            double kolcs = 0;
            foreach (var y in percek)
            {
                kolcs = kolcs + (y / 30) * 2400;
                if ((y % 30) > 0)
                {
                    kolcs = kolcs + 2400;
                }
            }
            Console.WriteLine("8. feladat: A napi bevétel: " + kolcs + " Ft");
            StreamWriter iro = new StreamWriter("F.txt");
            for (int i = 0; i < hossz; i++)
            {
                if (kocsonzesek[i, 1] == "F")
                {
                    iro.WriteLine(kocsonzesek[i, 2] + ":" + kocsonzesek[i, 3] + "-" + kocsonzesek[i, 4] + ":" + kocsonzesek[i, 5] + " - " + kocsonzesek[i, 0]);
                }
            }
            iro.Close();
            Console.ReadLine();
        }
    }
}

Összetett konzolos feladat Java-ban: kölcsönzési adatok beolvasása CSV fájlból 2D tömbbe, statisztikák számítása (napi bevétel, keresés, szűrés) és fájlba írás. Bemutatja a Files.readAllLines, Scanner és BufferedWriter együttes használatát.

  • Fájlbeolvasás és adatok széttördelése 2D tömbbe
  • Felhasználói input kezelése Scanner-rel
  • Logikai keresés és egyezések vizsgálata
  • Számítások elvégzése (percek, összegek)
  • Eredmények exportálása BufferedWriter segítségével
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Scanner;

public class Bringa {
    public static void main(String[] args) throws IOException {
        List<String> hadatok = Files.readAllLines(Paths.get("kolcsonzesek.txt"));
        int hossz = hadatok.size();
        System.out.println("5. feladat: Napi kölcsönzések száma: " + (hossz - 1));

        String[][] kolcsonzesek = new String[hossz][6];
        for (int n = 0; n < hossz; n++) {
            String[] sor = hadatok.get(n).split(";");
            for (int i = 0; i < 6; i++) {
                kolcsonzesek[n][i] = sor[i];
            }
        }

        Scanner sc = new Scanner(System.in);
        System.out.print("6. feladat: Kérek egy nevet: ");
        String nev = sc.nextLine();

        int volte = 0;
        for (int i = 0; i < hossz; i++) {
            for (int j = 0; j < 6; j++) {
                if (kolcsonzesek[i][j].contains(nev)) {
                    volte++;
                }
            }
        }
        if (volte == 0) {
            System.out.println("Nem volt kölcsönzés " + nev + " néven!");
        } else {
            System.out.println(nev + " Kölcsönzései: ");
        }

        for (int i = 0; i < hossz; i++) {
            if (kolcsonzesek[i][0].equals(nev)) {
                System.out.println(kolcsonzesek[i][2] + ":" + kolcsonzesek[i][3]
                        + "-" + kolcsonzesek[i][4] + ":" + kolcsonzesek[i][5]);
            }
        }

        int[] percek = new int[hossz];
        for (int i = 1; i < hossz; i++) {
            percek[i - 1] = (Integer.parseInt(kolcsonzesek[i][5])
                    + Integer.parseInt(kolcsonzesek[i][4]) * 60)
                    - (Integer.parseInt(kolcsonzesek[i][3])
                    + Integer.parseInt(kolcsonzesek[i][2]) * 60);
        }

        double kolcs = 0;
        for (int y : percek) {
            kolcs += (y / 30) * 2400;
            if ((y % 30) > 0) {
                kolcs += 2400;
            }
        }
        System.out.println("8. feladat: A napi bevétel: " + kolcs + " Ft");

        BufferedWriter iro = new BufferedWriter(new FileWriter("F.txt"));
        for (int i = 0; i < hossz; i++) {
            if (kolcsonzesek[i][1].equals("F")) {
                iro.write(kolcsonzesek[i][2] + ":" + kolcsonzesek[i][3]
                        + "-" + kolcsonzesek[i][4] + ":" + kolcsonzesek[i][5]
                        + " - " + kolcsonzesek[i][0]);
                iro.newLine();
            }
        }
        iro.close();
    }
}

Választás – feladat példa

Struktúra (struct) alkalmazása: a jelöltek adatait struct adat típussal tárolja, az Olvas() metódus statikus és fájlból tölti fel a tömböt. Jó példa struktúra, statikus metódus és FileStream+StreamReader kombinációjára.

using System;
using System.Collections.Generic;
using System.IO;
namespace valasztas
{
    internal class Program
    {
        struct adat
        {
            public string vezeteknev, keresztnev, partos;
            public int kerulet, szavazat;
        }
        static adat[] sa = new adat[100];
        static int n = 0;
        static void Olvas()
        {
            FileStream fajl = new FileStream("szavazatok.txt", FileMode.Open);
            StreamReader olv = new StreamReader(fajl);
            while (!olv.EndOfStream)
            {
                string[] sor = olv.ReadLine().Split(' ');
                sa[n].kerulet   = int.Parse(sor[0]);
                sa[n].szavazat  = int.Parse(sor[1]);
                sa[n].vezeteknev  = sor[2];
                sa[n].keresztnev  = sor[3];
                sa[n].partos    = sor[4];
                n++;
            }
            olv.Close();
            fajl.Close();
        }
        static void Main(string[] args)
        {
            Olvas();
            Console.WriteLine("A helyhatósági választáson " + n + " képviselőjelölt indult.");
            for (int i = 0; i < n; i++)
            {
                Console.WriteLine(sa[i].vezeteknev + " " + sa[i].keresztnev);
            }
            Console.ReadLine();
        }
    }
}

Adatszerkezet (a C# struct megfelelője) használata Java-ban belső osztály vagy modern record segítségével. A példa bemutatja az adatok metóduson keresztüli beolvasását és statikus tömbben való tárolását.

  • static class Adat – adatszerkezet definiálása mezőkkel
  • sa[n] = new Adat() – az objektum példányosítása a tömbben
  • Integer.parseInt() – szöveges adat számmá alakítása
  • Statikus metódusok használata az adatok eléréséhez
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Valasztas {

    // C# struct megfelelője: record vagy egyszerű osztály
    static class Adat {
        String vezeteknev, keresztnev, partos;
        int kerulet, szavazat;
    }

    static Adat[] sa = new Adat[100];
    static int n = 0;

    static void olvas() throws IOException {
        BufferedReader olv = new BufferedReader(new FileReader("szavazatok.txt"));
        String sor;
        while ((sor = olv.readLine()) != null) {
            String[] mezok = sor.split(" ");
            sa[n] = new Adat();
            sa[n].kerulet    = Integer.parseInt(mezok[0]);
            sa[n].szavazat   = Integer.parseInt(mezok[1]);
            sa[n].vezeteknev = mezok[2];
            sa[n].keresztnev = mezok[3];
            sa[n].partos     = mezok[4];
            n++;
        }
        olv.close();
    }

    public static void main(String[] args) throws IOException {
        olvas();
        System.out.println("A helyhatósági választáson " + n + " képviselőjelölt indult.");
        for (int i = 0; i < n; i++) {
            System.out.println(sa[i].vezeteknev + " " + sa[i].keresztnev);
        }
    }
}

IComparable és IComparer interfész

Az IComparable<T> interfész lehetővé teszi, hogy egy saját osztály objektumai összehasonlíthatóak legyenek egymással — így a List.Sort() működni tud rajtuk. Az IComparer<T> egy külön összehasonlító osztályt jelent, amellyel alternatív rendezési logikát lehet megadni.

  • IComparable<T> – az osztályon belül definiáljuk a CompareTo() metódust (alapértelmezett sorrend)
  • CompareTo() – negatív: this kisebb, 0: egyenlők, pozitív: this nagyobb
  • IComparer<T> – külön osztályban definiáljuk a Compare(x, y) metódust (alternatív sorrend)
  • lista.Sort() – az IComparable szerinti rendezés
  • lista.Sort(new OsztalyComparer()) – az IComparer szerinti rendezés
class BookYearComparer : IComparer<Book>
{
    public int Compare(Book? x, Book? y)
    {
        if (x?.Year == y?.Year)
            return x.Title.CompareTo(y?.Title);
        else
            return x.Year.CompareTo(y?.Year);
    }
}

class Book : IComparable<Book>
{
    public string Title { get; set; }
    public string Author { get; set; }
    public int Year { get; set; }
    public double Price { get; set; }

    public Book(string title, string author, int year, double price)
    {
        Title = title; Author = author; Year = year; Price = price;
    }

    // Alapértelmezett rendezés: ár szerint, egyenlőnél cím szerint
    public int CompareTo(Book? other)
    {
        if (Price == other?.Price)
            return Title.CompareTo(other?.Title);
        else
            return Price.CompareTo(other?.Price);
    }

    public override string ToString() =>
        $"Cím: {Title}, Szerző: {Author}, Év: {Year}, Ár: {Price} Ft";
}

// Használat
var books = new List<Book> { ... };
books.Sort();                         // IComparable szerint (ár)
books.Sort(new BookYearComparer());   // IComparer szerint (év)

A Comparable<T> interfész egy "természetes" rendezést ad az osztálynak (pl. List.sort(null)). A Comparator<T> segítségével pedig tetszőleges számú, külső rendezési szempontot (pl. évszám, ár, név) definiálhatunk.

  • Comparable<T> – az osztályon belül, a compareTo() metódust implementáljuk
  • Comparator<T> – külön osztályban (vagy lambdával), a compare(x, y) metódust írjuk meg
  • Double.compare(a, b) – biztonságos lebegőpontos összehasonlítás
  • Integer.compare(a, b) – egész számok összehasonlítása
Java-ban a List.sort(null) hívja meg az objektum saját compareTo metódusát. Ha egyedi sorrend kell, a sort-nak átadjuk a Comparator példányát.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Konyvek {

    static class BookYearComparator implements Comparator<Book> {
        @Override
        public int compare(Book x, Book y) {
            if (x.year == y.year) {
                return x.title.compareTo(y.title);
            }
            return Integer.compare(x.year, y.year);
        }
    }

    static class Book implements Comparable<Book> {
        String title;
        String author;
        int year;
        double price;

        public Book(String title, String author, int year, double price) {
            this.title = title;
            this.author = author;
            this.year = year;
            this.price = price;
        }

        // Alapértelmezett rendezés: ár szerint, egyenlőnél cím szerint
        @Override
        public int compareTo(Book other) {
            if (Double.compare(this.price, other.price) == 0) {
                return this.title.compareTo(other.title);
            }
            return Double.compare(this.price, other.price);
        }

        @Override
        public String toString() {
            return String.format("Cím: %s, Szerző: %s, Év: %d, Ár: %.0f Ft",
                    title, author, year, price);
        }
    }

    public static void main(String[] args) {
        List<Book> books = new ArrayList<>();
        books.add(new Book("Egri csillagok", "Gárdonyi", 1899, 3500));
        books.add(new Book("A kőszívű ember fiai", "Jókai", 1869, 4200));

        books.sort(null);                          // Comparable szerint (ár)
        books.sort(new BookYearComparator());      // Comparator szerint (év)
    }
}

Betűstatisztika – Dictionary<char, int>

Karakterek előfordulási gyakoriságának megszámlálása Dictionary<char, int> segítségével. A string karakterein foreach-csel iterálunk, és minden karakternél növeljük a számlálót. Az OrderByDescending() LINQ metódussal csökkenő sorrendbe rendezhetjük az eredményt.

  • input.ToLower() – kis- és nagybetűk egységesítése
  • foreach (var betu in input) – karakterenként iterál a stringen
  • OrderByDescending(x => x.Value) – érték szerint csökkenő rendezés (LINQ)
static void CharCounter(string input)
{
    Dictionary<char, int> charCount = new Dictionary<char, int>();
    input = input.ToLower();
    foreach (var betu in input)
    {
        if (!charCount.ContainsKey(betu))
            charCount.Add(betu, 1);
        else
            charCount[betu]++;
    }

    foreach (var item in charCount.OrderByDescending(x => x.Value))
    {
        Console.WriteLine(item.Key + ": " + item.Value);
    }
}

static void Main()
{
    Console.Write("Adj meg egy szót: ");
    CharCounter(Console.ReadLine());
}

Karakterek előfordulási gyakoriságának megszámlálása Java-ban HashMap<Character, Integer> használatával. A stringet karaktertömbbé alakítjuk, majd a Java 8-as Stream API segítségével rendezzük a statisztikát érték szerint csökkenő sorrendbe.

  • input.toLowerCase() – kisbetűssé alakítás
  • input.toCharArray() – karakterenkénti bejáráshoz
  • stream() – a Map bejegyzéseinek folyamattá alakítása
  • comparingByValue().reversed() – érték szerinti csökkenő rendezés
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Betustatisztika {

    static void charCounter(String input) {
        Map<Character, Integer> charCount = new HashMap<>();
        input = input.toLowerCase();

        for (char betu : input.toCharArray()) {
            if (!charCount.containsKey(betu)) {
                charCount.put(betu, 1);
            } else {
                charCount.put(betu, charCount.get(betu) + 1);
            }
        }

        // Rendezett kiírás csökkenő érték szerint
        charCount.entrySet().stream()
                .sorted(Map.Entry.<Character, Integer>comparingByValue().reversed())
                .forEach(item -> System.out.println(item.getKey() + ": " + item.getValue()));
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("Adj meg egy szót: ");
        charCounter(sc.nextLine());
    }
}

Generikus osztály – Verem<T>

A generikus (generic) osztály típusparaméterrel dolgozik: <T> helyére bármilyen konkrét típus behelyettesíthető (int, string, saját osztály stb.). Ez lehetővé teszi, hogy ugyanaz az osztály többféle adattípussal is működjön, anélkül hogy duplikálni kellene a kódot.

  • class Verem<T> – T a típusparaméter neve (konvenció szerint nagybetű)
  • Push(T data) – elemet tesz a veremre (LIFO: Last In, First Out)
  • Pop() – kiveszi és visszaadja a legfelső elemet
  • var v = new Verem<int>() – konkrét típussal példányosítás
A Stack<T> beépített C# osztály ugyanezt nyújtja — a Verem<T> egy saját implementáció, amely megmutatja a generikus elveket.
public class Verem<T>
{
    private List<T> _values = new List<T>();

    public void Push(T data)
    {
        _values.Add(data);
    }

    public T Pop()
    {
        int length = _values.Count();
        T data = _values[length - 1];
        _values.RemoveAt(length - 1);
        return data;
    }

    public int Count() => _values.Count;
}

// Használat
var verem = new Verem<int>();
verem.Push(10);
verem.Push(20);
verem.Push(30);
Console.WriteLine(verem.Pop()); // 30
Console.WriteLine(verem.Pop()); // 20

A generikus (generic) osztály Java-ban is típusparaméterrel <T> dolgozik. Mivel Java-ban a generikusok csak objektumokkal működnek, primitív típusok (int, double) helyett azok wrapper osztályait (Integer, Double) kell használni.

  • class Verem<T> – T a típusparaméter, ami példányosításkor dől el
  • List<T> values – generikus lista az elemek tárolására
  • values.get(index) – elem elérése index alapján
  • Verem<Integer> v = new Verem<>() – példányosítás konkretizált típussal
Java-ban a Type Erasure miatt futásidőben a típusparaméterek elvesznek, ezért nem lehet például new T()-t hívni vagy instanceof T-t vizsgálni.
import java.util.ArrayList;
import java.util.List;

public class Verem<T> {

    private List<T> values = new ArrayList<>();

    public void push(T data) {
        values.add(data);
    }

    public T pop() {
        int length = values.size();
        T data = values.get(length - 1);
        values.remove(length - 1);
        return data;
    }

    public int count() {
        return values.size();
    }

    public static void main(String[] args) {
        Verem<Integer> verem = new Verem<>();
        verem.push(10);
        verem.push(20);
        verem.push(30);
        System.out.println(verem.pop()); // 30
        System.out.println(verem.pop()); // 20
    }
}

Kivételkezelés – try-catch-finally

A kivételkezelés (exception handling) célja, hogy a program futása közben keletkező hibákat (kivételeket) ne a program összeomlásával kezeljük, hanem kontrolláltan. A try blokkban fut a veszélyes kód, a catch elkapja a hibát, a finally pedig mindig lefut (pl. fájl lezárására).

  • try { } – ide kerül a kód, ami kivételt dobhat
  • catch (TipusException ex) { } – adott típusú kivételt kap el; ex.Message a hibaüzenet
  • catch (Exception ex) { } – minden egyéb kivételt elkap (általános eset)
  • finally { } – akkor is lefut, ha volt kivétel (pl. fájlzárás, kapcsolat bontása)
  • Sorrend fontos: specifikusabb kivétel mindig az általánosabb elé kerüljön!
Gyakori kivételtípusok: IndexOutOfRangeException, FormatException, NullReferenceException, DivideByZeroException, FileNotFoundException
try
{
    int[] t = { 1, 2, 3 };
    Console.WriteLine(t[10]); // IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
    Console.WriteLine("Tömbön kívüli index: " + ex.Message);
}
catch (Exception ex)
{
    Console.WriteLine("Ismeretlen hiba: " + ex.Message);
}
finally
{
    Console.WriteLine("Ez mindig lefut.");
}

A Java kivételkezelése a try-catch-finally szerkezetre épül. Különbséget tesz az ellenőrzött (checked) és nem ellenőrzött (unchecked) kivételek között. Az ellenőrzött kivételeket kötelező elkapni vagy továbbdobni a throws kulcsszóval.

  • try { } – a hibalehetőséget hordozó kód
  • catch (ExceptionType e) { } – specifikus hibák elkapása
  • e.getMessage() – a hiba szöveges leírása
  • finally { } – garantáltan lefutó kód (pl. fájlzárás)
  • throw new MyException() – saját hiba kiváltása manuálisan
Modern Java-ban (7+) a try-with-resources használata ajánlott az AutoCloseable erőforrásokhoz (pl. fájlok), így nincs szükség manuális finally blokkra a lezáráshoz.
public class Kivetelkezeles {
    public static void main(String[] args) {
        try {
            int[] t = {1, 2, 3};
            System.out.println(t[10]); // ArrayIndexOutOfBoundsException
        } catch (ArrayIndexOutOfBoundsException ex) {
            System.out.println("Tömbön kívüli index: " + ex.getMessage());
        } catch (Exception ex) {
            System.out.println("Ismeretlen hiba: " + ex.getMessage());
        } finally {
            System.out.println("Ez mindig lefut.");
        }
    }
}

Collections – List, Stack, Queue, HashSet

A System.Collections.Generic névtér speciális adatstruktúrákat kínál, amelyek a sima tömbnél rugalmasabbak. Mindegyik más-más hozzáférési sorrendet és tulajdonságot biztosít.

  • List<T> – dinamikus tömb; rendezés, keresés, hozzáadás/törlés bármely pozícióban
  • Stack<T> – LIFO (Last In, First Out): verem; Push() = betesz, Pop() = kivesz felülről
  • Queue<T> – FIFO (First In, First Out): sor; Enqueue() = betesz, Dequeue() = kivesz elejéről, Peek() = megnéz, de nem vesz ki
  • HashSet<T> – csak egyedi elemeket tárol; halmazműveletek: UnionWith(), IntersectWith(), ExceptWith()
// List<T>
var orszagok = new List<string> { "Magyarország", "Szlovákia", "Lengyelország" };
orszagok.Add("Csehország");
orszagok.Sort();
Console.WriteLine(string.Join(", ", orszagok));
Console.WriteLine(orszagok.Contains("Ukrajna")); // False

// Stack<T> – LIFO (utoljára be, elsőnek ki)
Stack<string> stack = new Stack<string>();
stack.Push("első");
stack.Push("második");
Console.WriteLine(stack.Pop());  // második

// Queue<T> – FIFO (elsőnek be, elsőnek ki)
Queue<string> sor = new Queue<string>();
sor.Enqueue("első");
sor.Enqueue("második");
Console.WriteLine(sor.Dequeue()); // első
Console.WriteLine(sor.Peek());    // következő elem, de nem veszi ki

// HashSet<T> – csak egyedi elemek
HashSet<int> halmaz = new HashSet<int> { 1, 2, 3, 2, 1 };
Console.WriteLine(string.Join(", ", halmaz)); // 1, 2, 3

HashSet<int> paros  = new HashSet<int> { 2, 4, 6, 8 };
HashSet<int> negyes = new HashSet<int> { 4, 8, 12 };
paros.UnionWith(negyes);     // egyesítés
paros.IntersectWith(negyes); // metszet
paros.ExceptWith(negyes);    // különbség

A java.util csomag tartalmazza a Java Collection Framework-öt. Java-ban az interfészek (List, Set, Queue) és az implementációk (ArrayList, HashSet, LinkedList) különválnak, ami nagyfokú rugalmasságot biztosít.

  • ArrayList<T> – a leggyakoribb dinamikus lista; gyors elérés index alapján
  • Stack<T> – LIFO verem (régebbi osztály, ma gyakran Deque-t használnak helyette)
  • LinkedList<T> – kétirányú lista; ideális Queue (FIFO) implementációnak
  • HashSet<T> – egyedi elemek halmaza; gyors keresés
Java-ban a halmazműveletek metódusnevei: addAll() (unió), retainAll() (metszet), removeAll() (különbség).
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;

public class CollectionsPelda {
    public static void main(String[] args) {
        // ArrayList<T> (mint a C# List<T>)
        List<String> orszagok = new ArrayList<>(Arrays.asList(
                "Magyarország", "Szlovákia", "Lengyelország"));
        orszagok.add("Csehország");
        Collections.sort(orszagok);
        System.out.println(String.join(", ", orszagok));
        System.out.println(orszagok.contains("Ukrajna")); // false

        // Stack<T> – LIFO (utoljára be, elsőnek ki)
        Stack<String> stack = new Stack<>();
        stack.push("első");
        stack.push("második");
        System.out.println(stack.pop()); // második

        // Queue<T> – FIFO (LinkedList implementáció)
        Queue<String> sor = new LinkedList<>();
        sor.offer("első");
        sor.offer("második");
        System.out.println(sor.poll()); // első – kiveszi
        System.out.println(sor.peek()); // második – csak megnézi

        // HashSet<T> – csak egyedi elemek
        Set<Integer> halmaz = new HashSet<>(Arrays.asList(1, 2, 3, 2, 1));
        System.out.println(halmaz); // [1, 2, 3]

        Set<Integer> paros = new HashSet<>(Arrays.asList(2, 4, 6, 8));
        Set<Integer> negyes = new HashSet<>(Arrays.asList(4, 8, 12));
        paros.addAll(negyes);     // egyesítés (UnionWith)
        paros.retainAll(negyes);  // metszet  (IntersectWith)
        paros.removeAll(negyes);  // különbség (ExceptWith)
    }
}

OOP – osztály, property, konstruktor

Az objektumorientált programozás (OOP) alapja az osztály (class): egy sablon, amely adatokat (property) és viselkedést (metódus) egyesít. A konstruktor az az inicializáló metódus, amely az objektum létrejöttekor automatikusan lefut.

  • public string Nev { get; set; } – automatikus property (getter + setter)
  • public string Nev { get; private set; } – kívülről csak olvasható property
  • Konstruktor neve megegyezik az osztályéval, nincs visszatérési típusa
  • var obj = new Bankszamla("Béla", 200000) – példányosítás, konstruktor meghívása
  • override ToString() – az objektum szöveges reprezentációját adja vissza (Console.WriteLine(obj))
  • Expression-bodied metódus: public double Lekerdez() => Egyenleg;
public class Bankszamla
{
    public string Tulajdonos { get; set; }
    public double Egyenleg { get; set; }

    // Konstruktor: automatikusan lefut példányosításkor
    public Bankszamla(string tulajdonos, double egyenleg)
    {
        Tulajdonos = tulajdonos;
        Egyenleg   = egyenleg;
    }

    public void Befizet(double osszeg) => Egyenleg += osszeg;
    public void Kivesz(double osszeg)  => Egyenleg -= osszeg;
    public double Lekerdez()           => Egyenleg;

    public override string ToString() =>
        $"Tulajdonos: {Tulajdonos}, Egyenleg: {Egyenleg} Ft";
}

// Használat
var szamla = new Bankszamla("Kovács Béla", 200000);
szamla.Befizet(50000);
szamla.Kivesz(30000);
Console.WriteLine(szamla); // Tulajdonos: Kovács Béla, Egyenleg: 220000 Ft

Java-ban minden kód egy osztályon (class) belül kell, hogy legyen. A C# property-k megfelelője itt a privát mezők és a hozzájuk tartozó publikus getter/setter metódusok együttese.

  • private mezők – az adatok elrejtése (encapsulation)
  • getNev() és setNev() – hozzáférés biztosítása
  • this.nev – hivatkozás az aktuális példány mezőjére (pl. konstruktorban)
  • @Override – jelzi, hogy egy ősosztálybeli metódust (pl. toString) írunk felül
Java-ban a System.out.println(obj) automatikusan az objektum toString() metódusát hívja meg a szöveges megjelenítéshez.
public class Bankszamla {
    private String tulajdonos;
    private double egyenleg;

    // Konstruktor: automatikusan lefut példányosításkor
    public Bankszamla(String tulajdonos, double egyenleg) {
        this.tulajdonos = tulajdonos;
        this.egyenleg = egyenleg;
    }

    public String getTulajdonos() { return tulajdonos; }
    public void setTulajdonos(String tulajdonos) { this.tulajdonos = tulajdonos; }

    public double getEgyenleg() { return egyenleg; }
    public void setEgyenleg(double egyenleg) { this.egyenleg = egyenleg; }

    public void befizet(double osszeg) {
        egyenleg += osszeg;
    }

    public void kivesz(double osszeg) {
        egyenleg -= osszeg;
    }

    public double lekerdez() {
        return egyenleg;
    }

    @Override
    public String toString() {
        return "Tulajdonos: " + tulajdonos + ", Egyenleg: " + egyenleg + " Ft";
    }

    public static void main(String[] args) {
        Bankszamla szamla = new Bankszamla("Kovács Béla", 200000);
        szamla.befizet(50000);
        szamla.kivesz(30000);
        System.out.println(szamla); // Tulajdonos: Kovács Béla, Egyenleg: 220000.0 Ft
    }
}

Projekt létrehozása (Laravel)

A Laravel egy PHP alapú, MVC (Model-View-Controller) architektúrát követő webes keretrendszer. Telepítéséhez Composer csomagkezelő szükséges. Az artisan a Laravel saját parancssori eszköze, amely fejlesztési feladatok (migrációk, osztályok generálása, szerver indítása) elvégzésére való.

  • composer global require laravel/installer – a Laravel telepítő egyszeri globális telepítése
  • laravel new projekt-neve – új Laravel projekt létrehozása
  • php artisan install:api – API végpontok kezeléséhez szükséges fájlok telepítése
  • php artisan serve – fejlesztői szerver indítása (alapból: http://localhost:8000)
  • Telepítéskor: No Starter Kit, MySQL adatbázis, migrate: No (az .env beállítása után migráljunk)
# 1. Laravel Installer globális telepítése Composerrel
composer global require laravel/installer

# 2. Új projekt létrehozása az installerrel
laravel new projekt-neve

# vagy új projekt létrehozása Composerrel:
composer create-project laravel/laravel projekt-neve

# --- Telepítés közben felteendő kérdések ---
# Which Starter Kit would you like?    → No Starter Kit
# Which testing framework?             → Pest  (vagy PHPUnit)
# Would you like to initialize a Git?  → Yes / No (ízlés szerint)
# Which database will your app use?    → MySQL
# Would you like to run migrations?    → No  (majd kézzel, ha az .env kész)

# 3. Belépés a projekt mappájába
cd projekt-neve

# 4. Laravel API telepítése (routes/api.php + API middleware)
php artisan install:api

# 5. Fejlesztői szerver indítása
php artisan serve

.env – Adatbázis beállítása

A .env fájl a projekt gyökérkönyvtárában található, és a környezetfüggő beállításokat tárolja (adatbázis, levelezés, API kulcsok stb.). Ez a fájl nem kerül verziókezelőbe (.gitignore-ban van). A laravel minden .env-ben beállított értéket a env('KULCS') függvénnyel olvas be.

  • DB_DATABASE – az adatbázis neve (létre kell hozni phpMyAdmin-ban vagy terminálban)
  • DB_USERNAME – adatbázis felhasználó (XAMPP-nál: root)
  • DB_PASSWORD – jelszó (XAMPP-nál általában üres)
  • Módosítás után ne felejtsd el futtatni: php artisan config:clear
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=adatbazis_neve
DB_USERNAME=root
DB_PASSWORD=

Hasznos Artisan parancsok

Az artisan a Laravel CLI eszköze. Segítségével futtathatók a migrációk, generálhatók modellek, controllerek, seederek — mindezt a projekt mappájából kell futtatni (cd projekt-neve után).

  • php artisan migrate – a még nem futtatott migrációs fájlok végrehajtása
  • php artisan migrate:fresh – az összes tábla törlése, majd újra migráció (fejlesztés közben hasznos)
  • php artisan migrate:fresh --seed – törlés + migráció + seeder futtatása
  • php artisan make:migration create_TABLA_table – migrációs fájl generálása
  • php artisan make:model Nev – Model osztály generálása
  • php artisan make:controller API/NevController – Controller generálása az API almappába
  • php artisan make:seeder NevSeeder – Seeder osztály generálása
# Migrációk futtatása
php artisan migrate

# Összes tábla törlése és újra migráció
php artisan migrate:fresh

# Összes tábla törlése, újra migráció + seederek futtatása
php artisan migrate:fresh --seed

# Migration fájl létrehozása
php artisan make:migration create_categories_table

# Model létrehozása
php artisan make:model Category

# API Controller létrehozása
php artisan make:controller API/CategoryController

# Seeder létrehozása
php artisan make:seeder ProductSeeder

Migration – táblaszerkezet definiálása

A migrációs fájlok PHP kódban definiálják az adatbázis táblaszerkezetét. A up() metódus hozza létre a táblát, a down() visszavonja (törli). Ez lehetővé teszi, hogy a táblaszerkezet verziókezelt legyen, és a csapat minden tagja egységes adatbázist használjon.

  • $table->id() – AUTO_INCREMENT, BIGINT, elsődleges kulcs (shortcut)
  • $table->string('nev') – VARCHAR(255); $table->text() – TEXT; $table->integer() – INT
  • $table->decimal('ar', 10, 2) – DECIMAL, pénzösszegekhez ideális
  • $table->boolean('aktiv')->default(true) – TINYINT(1), alapértéke igaz
  • ->nullable() – a mező lehet NULL (nem kötelező kitölteni)
  • $table->foreignId('category_id')->constrained()->cascadeOnDelete() – idegen kulcs a categories táblára; ha a kategória törlődik, a termék is törlődik
  • $table->timestamps() – created_at és updated_at oszlopok automatikusan
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();                                         // BIGINT UNSIGNED, AUTO INCREMENT, PK

            // Idegen kulcs – kapcsolt tábla (categories)
            $table->foreignId('category_id')
                  ->constrained()                                 // categories.id alapján
                  ->cascadeOnDelete();                            // ha a kategória törlődik, a termék is törlődik

            // Szöveges típusok
            $table->string('name');                               // VARCHAR(255)
            $table->string('slug', 100)->unique();                // VARCHAR(100), egyedi
            $table->string('sku')->nullable();                    // VARCHAR(255), lehet NULL
            $table->text('description')->nullable();              // TEXT, lehet NULL
            $table->longText('spec_sheet')->nullable();           // LONGTEXT

            // Szám típusok
            $table->integer('stock')->default(0);                 // INT, alapértéke 0
            $table->unsignedInteger('views')->default(0);         // INT UNSIGNED (nem lehet negatív)
            $table->bigInteger('barcode')->nullable();            // BIGINT
            $table->float('weight', 8, 2)->nullable();            // FLOAT
            $table->decimal('price', 10, 2);                      // DECIMAL(10,2) – pénzösszeghez ideal
            $table->decimal('discount_price', 10, 2)->nullable(); // DECIMAL, lehet NULL

            // Logikai
            $table->boolean('is_active')->default(true);          // TINYINT(1)
            $table->boolean('is_featured')->default(false);

            // Dátum/idő típusok
            $table->date('available_from')->nullable();           // DATE
            $table->dateTime('published_at')->nullable();         // DATETIME
            $table->timestamp('deleted_at')->nullable();          // TIMESTAMP (soft delete mintára)

            // JSON
            $table->json('tags')->nullable();                     // JSON oszlop

            $table->timestamps();                                 // created_at + updated_at
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

Model – Eloquent ORM

A Model egy PHP osztály, amely egy adatbázis-táblát reprezentál. Laravel-ben az Eloquent ORM-t (Object-Relational Mapper) használja, amely lehetővé teszi, hogy a tábla sorait PHP objektumokként kezeljük SQL írása nélkül.

  • protected $fillable = [...] – felsorolja a tömeges hozzárendelésnél (Model::create()) engedélyezett mezőket
  • protected $casts = [...] – automatikus típuskonverzió: pl. JSON string ↔ PHP tömb, '0'/'1' ↔ true/false
  • public function category(): BelongsTo – N:1 kapcsolat (egy terméknek egy kategóriája van)
  • public function reviews(): HasMany – 1:N kapcsolat (egy terméknek több véleménye lehet)
  • A kapcsolat neve alapján Laravel automatikusan megkeresi az idegen kulcsot (category_id)
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Product extends Model
{
    // Tömeges hozzárendelés engedélyezett mezői
    protected $fillable = [
        'category_id',
        'name',
        'slug',
        'sku',
        'description',
        'spec_sheet',
        'stock',
        'views',
        'barcode',
        'weight',
        'price',
        'discount_price',
        'is_active',
        'is_featured',
        'available_from',
        'published_at',
        'deleted_at',
        'tags',
    ];

    // Automatikus típuskonverzió (cast)
    protected $casts = [
        'price'          => 'decimal:2',       // string → "1234.56"
        'discount_price' => 'decimal:2',
        'weight'         => 'float',           // float
        'stock'          => 'integer',         // int
        'views'          => 'integer',
        'barcode'        => 'integer',
        'is_active'      => 'boolean',         // 0/1 → true/false
        'is_featured'    => 'boolean',
        'available_from' => 'date',            // Carbon dátum (idő nélkül)
        'published_at'   => 'datetime',        // Carbon dátum + idő
        'deleted_at'     => 'datetime',
        'tags'           => 'array',           // JSON string → PHP tömb
    ];

    // ——— Kapcsolatok ———

    // N:1 – egy termék egy kategóriához tartozik
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class);
    }

    // 1:N – egy termékhez több rendelési sor tartozhat
    public function orderItems(): HasMany
    {
        return $this->hasMany(OrderItem::class);
    }

    // 1:N – egy termékhez több vélemény tartozhat
    public function reviews(): HasMany
    {
        return $this->hasMany(Review::class);
    }
}

Controller – CRUD végpontok

A Controller fogadja a HTTP kéréseket, elvégzi az üzleti logikát (validáció, adatbázis-műveletek), majd JSON választ küld vissza. Az API controller-ben a Laravel Request osztálya tartalmazza a kérés adatait, a response()->json() JSON formátumú választ ad.

  • index() – GET /termékek → összes elem listája
  • show() – GET /termék → egy elem ID alapján
  • store() – POST /termék/create → új elem létrehozása
  • update() – PUT /termék → meglévő elem módosítása
  • destroy() – DELETE /termék → elem törlése
  • $request->validate([...]) – automatikus validáció; ha sikertelen, 422-es hibakódot küld vissza
  • Product::with('category') – a kapcsolt kategória adatait is lekéri (eager loading)
  • response()->json(['data' => $adat], 201) – JSON válasz 201-es HTTP státuszkóddal
<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    // GET /products – összes termék listázása
    public function index(Request $request)
    {
        $query = Product::with('category')->orderByDesc('id');

        // Opcionális szűrés
        if ($request->has('is_active')) {
            $query->where('is_active', (bool) $request->is_active);
        }

        return response()->json([
            'data' => $query->get(),
        ]);
    }

    // GET /product – egy termék id alapján
    public function show(Request $request)
    {
        $validated = $request->validate([
            'id' => ['required', 'integer', 'exists:products,id'],
        ]);

        $product = Product::with('category')->findOrFail($validated['id']);

        return response()->json([
            'data' => $product,
        ]);
    }

    // POST /product/create – új termék létrehozása
    public function store(Request $request)
    {
        $validated = $request->validate([
            'category_id'    => ['required', 'integer', 'exists:categories,id'],
            'name'           => ['required', 'string', 'max:255', 'unique:products,name'],
            'slug'           => ['required', 'string', 'max:100', 'unique:products,slug'],
            'sku'            => ['nullable', 'string', 'max:255'],
            'description'    => ['nullable', 'string'],
            'stock'          => ['required', 'integer', 'min:0'],
            'price'          => ['required', 'numeric', 'min:0'],
            'discount_price' => ['nullable', 'numeric', 'min:0'],
            'is_active'      => ['boolean'],
            'is_featured'    => ['boolean'],
            'available_from' => ['nullable', 'date'],
            'tags'           => ['nullable', 'array'],
        ]);

        $product = Product::create($validated);

        return response()->json([
            'data' => $product,
        ], 201);
    }

    // PUT /product – meglévő termék módosítása
    public function update(Request $request)
    {
        $validated = $request->validate([
            'id'             => ['required', 'integer', 'exists:products,id'],
            'category_id'    => ['required', 'integer', 'exists:categories,id'],
            'name'           => ['required', 'string', 'max:255'],
            'slug'           => ['required', 'string', 'max:100'],
            'sku'            => ['nullable', 'string', 'max:255'],
            'description'    => ['nullable', 'string'],
            'stock'          => ['required', 'integer', 'min:0'],
            'price'          => ['required', 'numeric', 'min:0'],
            'discount_price' => ['nullable', 'numeric', 'min:0'],
            'is_active'      => ['boolean'],
            'is_featured'    => ['boolean'],
            'available_from' => ['nullable', 'date'],
            'tags'           => ['nullable', 'array'],
        ]);

        $product = Product::findOrFail($validated['id']);
        $product->update($validated);

        return response()->json([
            'data' => $product->fresh()->load('category'),
        ]);
    }

    // DELETE /product – termék törlése
    public function destroy(Request $request)
    {
        $validated = $request->validate([
            'id' => ['required', 'integer', 'exists:products,id'],
        ]);

        $product = Product::findOrFail($validated['id']);
        $product->delete();

        return response()->json([
            'message' => 'Deleted',
        ], 204);
    }
}

Seeder – kezdeti adatok betöltése

A Seeder fejlesztési (teszt) adatokat tölt be az adatbázisba. A DatabaseSeeder a belépési pont, amely meghívja az összes többi seedert. Seederek futtatása: php artisan db:seed, vagy migrációval együtt: php artisan migrate:fresh --seed.

  • Model::firstOrCreate(['mezo' => 'ertek'], [...tobbi...]) – ha már létezik, visszaadja; ha nem, létrehozza
  • Model::insert([...]) – több rekordot egyszerre szúr be (gyorsabb, de nem hívja az Eloquent eseményeket)
  • now() – az aktuális időbélyeg (created_at, updated_at mezőkhöz)
  • A DatabaseSeeder-ben a $this->call([...]) listázza a futtatandó seedereket
<?php

namespace Database\Seeders;

use App\Models\Category;
use App\Models\Product;
use Illuminate\Database\Seeder;

class ProductSeeder extends Seeder
{
    public function run(): void
    {
        // Kategória létrehozása (ha még nincs)
        $category = Category::firstOrCreate(
            ['name' => 'Elektronika'],
            ['is_active' => true]
        );

        // Több termék egyszerre
        Product::insert([
            [
                'category_id'    => $category->id,
                'name'           => 'Laptop',
                'slug'           => 'laptop',
                'price'          => 349990.00,
                'stock'          => 10,
                'is_active'      => true,
                'is_featured'    => true,
                'created_at'     => now(),
                'updated_at'     => now(),
            ],
            [
                'category_id'    => $category->id,
                'name'           => 'Okostelefon',
                'slug'           => 'okostelefon',
                'price'          => 189990.00,
                'stock'          => 25,
                'is_active'      => true,
                'is_featured'    => false,
                'created_at'     => now(),
                'updated_at'     => now(),
            ],
        ]);
    }
}

// -----------------------------------------------
// database/seeders/DatabaseSeeder.php
// A DatabaseSeeder hívja meg az összes seedert
// -----------------------------------------------

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            ProductSeeder::class,
            OrderSeeder::class,
        ]);
    }
}

API Routes – routes/api.php

A routes/api.php fájlban definiáljuk az API végpontokat. Az itt megadott útvonalak automatikusan a /api/ előtagot kapják (pl. Route::get('/termekek'...) → http://localhost:8000/api/termekek). Minden route egy HTTP metódushoz (GET, POST, PUT, DELETE) és egy controller metódushoz kapcsolódik.

  • Route::get('/url', [Controller::class, 'metodus']) – GET kérés kezelője
  • Route::post(...) – POST (létrehozás); Route::put(...) – PUT (módosítás); Route::delete(...) – DELETE (törlés)
  • A végpontok tesztelhetők Postman-nel vagy a böngészőből (csak GET)
<?php

use App\Http\Controllers\API\ProductController;
use Illuminate\Support\Facades\Route;

Route::get('/products',         [ProductController::class, 'index']);
Route::get('/product',          [ProductController::class, 'show']);
Route::post('/product/create',  [ProductController::class, 'store']);
Route::put('/product',          [ProductController::class, 'update']);
Route::delete('/product',       [ProductController::class, 'destroy']);

Alapfogalmak

A szoftvertesztelés célja a szoftver hibáinak felderítése, mielőtt az a felhasználókhoz kerül. A tesztelés nem bizonyítja a hibamentességet — csak azt, hogy az adott tesztesetekben a szoftver helyesen működik. Három alapfogalmat érdemes megkülönböztetni:

  • Hiba (Error) – a fejlesztő által elkövetett tévedés (pl. elgépelés, rossz logika)
  • Defektus / Bug – a hibából keletkező hibás kód a szoftverben
  • Meghibásodás (Failure) – a bug által okozott nem várt viselkedés futás közben (amit a felhasználó tapasztal)
Fogalom Ki érzékeli? Példa
Hiba (Error) Fejlesztő < helyett <= feltétel
Defektus (Bug) Tesztelő A program rossz eredményt számol
Meghibásodás (Failure) Felhasználó Az alkalmazás összeomlott, helytelen adatot mutat
Teszteset – Bemeneti adat + elvárt kimeneti eredmény párosa
Regressziós teszt – Korábbi tesztek újrafuttatása, hogy az új kód ne törjön el régit

Tesztelési szintek

A tesztelés különböző szinteken zajlik — az egyetlen metódustól egészen a teljes rendszer felhasználói próbájáig. Minden szintnek más a fókusza, más végzi, és más eszközöket használ.

Szint Mit tesztel? Ki végzi? Példa
Egységteszt (Unit test) Egyetlen metódus / osztály önmagában Fejlesztő Osszead(2, 3) == 5
Integrációs teszt Két vagy több komponens együttműködése Fejlesztő / tesztelő Controller + adatbázis réteg
Rendszerteszt A teljes rendszer funkcionálisan Tesztelő Bejelentkezés → kosár → fizetés folyamat
Átvételi teszt (UAT) Az ügyfél/felhasználó igényei szerint Megrendelő / végfelhasználó Ügyfél teszteli az átadás előtt

Fekete doboz vs fehér doboz tesztelés

A tesztelési módszereket aszerint csoportosítják, hogy a tesztelő ismeri-e a szoftver belső működését (forráskódját). A fekete doboz (black-box) esetén nem ismeri, csak a be- és kimeneteket vizsgálja. A fehér doboz (white-box) esetén a forráskód alapján tervezi a teszteseteket.

Fekete doboz (Black-box) Fehér doboz (White-box)
Forráskód ismert? Nem Igen
Alapja Specifikáció / követelmény Belső logika, kódszerkezet
Ki végzi? Tesztelő (a fejlesztőtől függetlenül) Fejlesztő vagy kódismerő tesztelő
Módszerek Ekvivalencia-partíció, határérték elemzés Utasítás lefedettség, ág lefedettség
Előnye Felhasználói szempontú, független a kódtól Belső hibák (elérhetetlen kód) is megtalálhatók

C# Unit teszt – MSTest projekt létrehozása

Az MSTest a Microsoft által fejlesztett unit teszt keretrendszer C#-hoz, amely Visual Studio-ba van beépítve. Egy unit teszt projektben a tesztosztályokat [TestClass], az egyes tesztmetódusokat [TestMethod] attribútummal jelöljük. A teszteket a Visual Studio Test Explorer ablakából futtathatjuk.

  • Új projekt: File → Add → New Project → MSTest Test Project (.NET)
  • Adj referenciát a tesztelendő projekthez: jobb klikk a tesztprojekten → Add → Project Reference
  • Tesztek futtatása: Test → Run All Tests (vagy Ctrl+R, A)
# Parancssorból (opcionális)
dotnet new mstest -n SajatTesztek
dotnet add SajatTesztek reference SajatAlkalmazas

MSTest – tesztosztály felépítése

Minden tesztmetódus három részből áll — ezt AAA-mintának (Arrange-Act-Assert) nevezzük. Ez a szerkezet jól olvasható és karbantartható teszteket eredményez.

  • Arrange – előkészítés: objektumok létrehozása, bemenetek megadása
  • Act – végrehajtás: a tesztelendő metódus meghívása
  • Assert – ellenőrzés: az eredmény összehasonlítása az elvárttal
  • A tesztmetódus neve legyen leíró: Metodus_Feltétel_ElvártEredmény (pl. Osszead_KetPozitivSzam_HelyesEredmeny)
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]                         // Ez egy tesztosztály
public class SzamolasokTesztek
{
    [TestMethod]                    // Ez egy tesztmetódus
    public void Osszead_KetPozitivSzam_HelyesEredmeny()
    {
        // Arrange – előkészítés
        var szamolo = new Szamolasok();

        // Act – végrehajtás
        int eredmeny = szamolo.Osszead(3, 5);

        // Assert – ellenőrzés
        Assert.AreEqual(8, eredmeny);
    }

    [TestMethod]
    public void Oszt_NullvalOsztas_KiveteltDob()
    {
        var szamolo = new Szamolasok();

        Assert.ThrowsException<DivideByZeroException>(
            () => szamolo.Oszt(10, 0)
        );
    }
}

Assert metódusok – ellenőrzési lehetőségek

Az Assert osztály statikus metódusai a tesztben az elvárás ellenőrzésére szolgálnak. Ha az ellenőrzés sikertelen, a tesztmetódus azonnal elbukik és hibaüzenetet ad. A metódusok első paramétere általában az elvárt érték, a második a tényleges.

// Egyenlőség (elvárt, tényleges)
Assert.AreEqual(8, eredmeny);
Assert.AreEqual("Kovács", nev);

// Nem egyenlők
Assert.AreNotEqual(0, eredmeny);

// Boolean vizsgálat
Assert.IsTrue(lista.Count > 0);
Assert.IsFalse(szoveg.Contains("hiba"));

// Null vizsgálat
Assert.IsNull(ertek);
Assert.IsNotNull(lista);

// Típusvizsgálat
Assert.IsInstanceOfType(obj, typeof(string));

// Kivétel vizsgálat – azt ellenőrzi, hogy a metódus valóban kivételt dob-e
Assert.ThrowsException<ArgumentException>(() => Metodus(-1));

// Gyűjtemény vizsgálat
CollectionAssert.AreEqual(elvart, tenyleges);
CollectionAssert.Contains(lista, "elem");

Teljes unit teszt példa – Bankszámla osztály

Az alábbi példa bemutatja az AAA-minta alkalmazását egy valós osztályon. A Bankszamla osztály minden nyilvános metódusához legalább egy-egy teszteset tartozik — beleértve a hibás bemenetet is (negatív összeg, fedezethiány).

// --- Tesztelendő osztály ---
public class Bankszamla
{
    public double Egyenleg { get; private set; }

    public Bankszamla(double kezdoEgyenleg)
    {
        if (kezdoEgyenleg < 0)
            throw new ArgumentException("Az egyenleg nem lehet negatív!");
        Egyenleg = kezdoEgyenleg;
    }

    public void Befizet(double osszeg)
    {
        if (osszeg <= 0) throw new ArgumentException("Az összeg pozitív kell legyen!");
        Egyenleg += osszeg;
    }

    public void Kivesz(double osszeg)
    {
        if (osszeg <= 0) throw new ArgumentException("Az összeg pozitív kell legyen!");
        if (osszeg > Egyenleg) throw new InvalidOperationException("Nincs elég fedezet!");
        Egyenleg -= osszeg;
    }
}

// --- Tesztosztály ---
[TestClass]
public class BankszamlaTesztek
{
    [TestMethod]
    public void Befizet_ErvenyesOsszeg_NovekszikAzEgyenleg()
    {
        var szamla = new Bankszamla(1000);  // Arrange
        szamla.Befizet(500);                // Act
        Assert.AreEqual(1500, szamla.Egyenleg); // Assert
    }

    [TestMethod]
    public void Kivesz_ErvenyesOsszeg_CsokkentAzEgyenleg()
    {
        var szamla = new Bankszamla(1000);
        szamla.Kivesz(300);
        Assert.AreEqual(700, szamla.Egyenleg);
    }

    [TestMethod]
    public void Kivesz_TulNagyOsszeg_KiveteltDob()
    {
        var szamla = new Bankszamla(500);
        Assert.ThrowsException<InvalidOperationException>(() => szamla.Kivesz(1000));
    }

    [TestMethod]
    public void Konstruktor_NegativEgyenleg_KiveteltDob()
    {
        Assert.ThrowsException<ArgumentException>(() => new Bankszamla(-100));
    }
}

1. Projekt létrehozása

Új Vue.js projekt létrehozása a hivatalos eszközzel (create-vue), amely a Vite-ra épül.

# Új projekt inicializálása
npm create vue@latest

# Függőségek telepítése
cd my-vue-project
npm install

# Fejlesztői szerver indítása
npm run dev

Projekt létrehozása – React + Vite A Vite egy modern build tool, ami villámgyors fejlesztői szervert biztosít. Az npm create vite parancs interaktívan végigvezet a projekt felállításán. 1. Új Vite projekt létrehozása npm-mel

npm create vite@latest projekt-neve

# vagy yarn / pnpm-mel:
yarn create vite projekt-neve
pnpm create vite projekt-neve

# --- Telepítés közben felteendő kérdések ---
# Select a framework:        → React
# Select a variant:          → JavaScript  (vagy TypeScript)
# Package name:              → projekt-neve

# 2. Belépés a projekt mappájába
cd projekt-neve

# 3. Függőségek telepítése
npm install

# 4. Hasznos kiegészítő csomagok (általában szükségesek)
npm install react-router-dom        # útvonalkezeléshez
npm install axios                    # HTTP kérésekhez
npm install @tanstack/react-query    # adat-fetching és cache
npm install react-hook-form yup @hookform/resolvers  # form + validáció

# 5. Fejlesztői szerver indítása (alapból http://localhost:5173)
npm run dev

# 6. Production build készítése a dist/ mappába
npm run build

# 7. Build előnézet (kipróbálás éles módban)
npm run preview

2. Környezeti változók (.env)

A Vite alapú projektekben a környezeti változóknak VITE_ előtaggal kell kezdődniük, hogy elérhetőek legyenek a böngészőben.

// .env fájl tartalma
VITE_API_BASE_URL=https://api.example.com

// Elérés a Vue kódban
const apiUrl = import.meta.env.VITE_API_BASE_URL;
console.log('API URL:', apiUrl);

.env – Környezeti változók beállítása Vite-ban A Vite csak a VITE_ előtaggal kezdődő változókat teszi elérhetővé a kliens kódban, biztonsági okokból. A .env fájl a projekt gyökerébe kerül és NEM kerül commit alá (.gitignore). ---------------- .env (alap, mindig betöltődik) ----------------

VITE_API_URL=http://localhost:8080/api
VITE_APP_NAME=Termékek
VITE_DEFAULT_LANG=hu

# ---------------- .env.development (csak fejlesztés alatt) ----------------
VITE_API_URL=http://localhost:8080/api
VITE_DEBUG=true

# ---------------- .env.production (build-eléskor) ----------------
VITE_API_URL=https://api.peldaweb.hu
VITE_DEBUG=false

# ---------------- Használat React kódban ----------------
# A változókat az import.meta.env objektumon keresztül érjük el
# Példa: const API_URL = import.meta.env.VITE_API_URL;
#         console.log(import.meta.env.VITE_APP_NAME);

3. Hasznos NPM parancsok

A leggyakrabban használt parancsok csomagkezeléshez és projektépítéshez.

# Csomag telepítése
npm install axios

# Fejlesztői (dev) csomag telepítése
npm install -D tailwindcss

# Éles build elkészítése
npm run build

Hasznos npm / Vite parancsok React projekthez Ezek a leggyakoribb parancsok fejlesztés, build és csomagkezelés során. ---------------- Fejlesztői szerver ----------------

npm run dev                 # Indítás (alapból :5173, hot reload)
npm run dev -- --port 3000  # Egyedi port
npm run dev -- --host       # LAN-on is elérhető legyen

# ---------------- Build / preview ----------------
npm run build               # Production build a dist/ mappába
npm run preview             # Build kipróbálása lokálisan

# ---------------- Csomagkezelés ----------------
npm install                 # Összes függőség telepítése (package.json alapján)
npm install <csomag>        # Új csomag hozzáadása dependencies-hez
npm install -D <csomag>     # Dev függőség (pl. eslint, prettier)
npm uninstall <csomag>      # Csomag eltávolítása
npm update                  # Csomagok frissítése

# ---------------- Kód minőség ----------------
npm run lint                # ESLint ellenőrzés
npm run format              # Prettier formázás (ha be van állítva)

# ---------------- Komponens létrehozás (kézzel, nincs CLI generátor) ----------------
# A komponenseket a src/components/ mappába hozzuk létre, .jsx kiterjesztéssel
# Pl: src/components/ProductCard.jsx

4. Komponens és Props

Vue-ban a komponenseket Single File Component (.vue) formátumban írjuk. A `defineProps` makróval vehetjük át a szülőtől kapott adatokat.

<script setup>
const props = defineProps({
  title: String,
  isActive: Boolean
});
</script>

<template>
  <div class="card">
    <h2>{{ title }}</h2>
    <p v-if="isActive">Aktív komponens</p>
  </div>
</template>

Komponens és props – a React legfontosabb építőeleme A funkcionális komponens egy függvény, ami JSX-et ad vissza. A props objektumon keresztül adunk át adatot a szülőtől a gyerek komponensnek. A destrukturálás teszi olvashatóbbá a propsok kibontását. src/components/ProductCard.jsx

function ProductCard({ name, price, stock, isFeatured }) {
    return (
        <div className="card">
            <h3>{name}</h3>
            <p>Ár: {price} Ft</p>
            <p>Készlet: {stock} db</p>
            {isFeatured && <span className="badge">Kiemelt</span>}
            {stock === 0 && <span className="out-of-stock">Elfogyott</span>}
        </div>
    );
}

export default ProductCard;

// ---------------- Használat (App.jsx) ----------------
import ProductCard from './components/ProductCard';

function App() {
    const termek = {
        name: 'Laptop',
        price: 349990,
        stock: 10,
        isFeatured: true
    };

    return (
        <div>
            {/* propok átadása egyenként */}
            <ProductCard name="Telefon" price={189990} stock={25} isFeatured={false} />

            {/* spread operátorral az egész objektum kibontása */}
            <ProductCard {...termek} />
        </div>
    );
}

export default App;

5. Állapotkezelés (ref)

Reaktív változók létrehozása a Composition API `ref()` függvényével.

<script setup>
import { ref } from 'vue';

const count = ref(0);

const increment = () => {
  count.value++;
};
</script>

<template>
  <button @click="increment">
    Kattintások száma: {{ count }}
  </button>
</template>

useState Hook – State kezelés funkcionális komponensben A useState egy értéket és egy setter függvényt ad vissza tömbként. Amikor a setter-t hívjuk, a komponens újra renderelődik az új értékkel. Soha ne mutáljuk közvetlenül a state-et – mindig a setterrel frissítsünk!

import { useState } from 'react';

function Szamlalo() {
    // [aktualis ertek, set fuggveny] = useState(kezdoertek)
    const [count, setCount] = useState(0);
    const [showText, setShowText] = useState(false);
    const [name, setName] = useState('');

    return (
        <div>
            <h2>Számláló: {count}</h2>

            <button onClick={() => setCount(count + 1)}>+1</button>
            <button onClick={() => setCount(count - 1)}>-1</button>
            <button onClick={() => setCount(0)}>Reset</button>

            {/* boolean toggle */}
            <button onClick={() => setShowText(!showText)}>
                {showText ? 'Elrejt' : 'Mutat'}
            </button>

            {/* feltételes renderelés state alapján */}
            {showText && <input
                type="text"
                value={name}
                onChange={(e) => setName(e.target.value)}
                placeholder="Neved"
            />}

            {name && <p>Szia, {name}!</p>}
        </div>
    );
}

export default Szamlalo;

6. Lista kezelés

Tömbök bejárása a `v-for` direktívával. Fontos a `:key` megadása minden elemhez!

<script setup>
import { ref } from 'vue';

const items = ref([
  { id: 1, name: 'Alma' },
  { id: 2, name: 'Körte' }
]);
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

Lista kezelése (CRUD) – immutable state műveletek Az állapotot mindig új tömbbel frissítjük: spread operátor, filter, map. A key prop fontos a listáknál: egyedi azonosító kell minden elemhez. Controlled input: az input értéke a state-ből jön (value + onChange).

import { useState } from 'react';

function ShoppingList() {
    const [newItem, setNewItem] = useState('');
    const [shoppingList, setShoppingList] = useState([]);
    const [id, setId] = useState(1);

    // CREATE – új elem hozzáadása (spread operátorral új tömb)
    const addItem = () => {
        if (newItem.trim() === '') return;
        const item = { id: id, name: newItem, bought: false };
        setShoppingList([...shoppingList, item]);
        setId(id + 1);
        setNewItem('');
    };

    // DELETE – elem eltávolítása filterrel
    const deleteItem = (itemId) => {
        setShoppingList(shoppingList.filter(item => item.id !== itemId));
    };

    // UPDATE – egy elem módosítása map-pel + objektum spreaddel
    const toggleBought = (itemId) => {
        setShoppingList(shoppingList.map(item =>
            item.id === itemId ? { ...item, bought: !item.bought } : item
        ));
    };

    return (
        <div>
            <h2>Bevásárlólista</h2>
            <input
                type="text"
                value={newItem}
                onChange={(e) => setNewItem(e.target.value)}
                placeholder="Új elem"
            />
            <button onClick={addItem}>Hozzáad</button>

            {shoppingList.length === 0 ? (
                <p>Üres a lista</p>
            ) : (
                <ul>
                    {shoppingList.map(item => (
                        <li key={item.id} style={{
                            textDecoration: item.bought ? 'line-through' : 'none'
                        }}>
                            <span onClick={() => toggleBought(item.id)}>
                                {item.name}
                            </span>
                            <button onClick={() => deleteItem(item.id)}>Töröl</button>
                        </li>
                    ))}
                </ul>
            )}
        </div>
    );
}

export default ShoppingList;

7. Életciklus (onMounted)

Az `onMounted` horog (hook) akkor fut le, amikor a komponens megjelent a DOM-ban.

<script setup>
import { ref, onMounted } from 'vue';

const data = ref(null);

onMounted(async () => {
  console.log('A komponens betöltődött!');
  // Itt érdemes API hívásokat indítani
});
</script>

useEffect Hook – mellékhatások kezelése (lifecycle) Az üres dependency array [] = csak mount-kor fut le egyszer. A return-ben adott függvény = cleanup, unmount előtt fut le. Dependency array: ha változik benne valami, újrafut az effect.

import { useState, useEffect } from 'react';

function TimerPelda() {
    const [seconds, setSeconds] = useState(0);
    const [text, setText] = useState('');

    // Csak mount-kor fut le egyszer (üres array)
    useEffect(() => {
        console.log('Komponens mountolva');

        // setInterval időzítő indítása
        const interval = setInterval(() => {
            setSeconds(prev => prev + 1);
        }, 1000);

        // Cleanup: unmount-kor lefut, leállítja az időzítőt
        return () => {
            console.log('Komponens unmountolva, timer leállítva');
            clearInterval(interval);
        };
    }, []);

    // Minden text változásra lefut
    useEffect(() => {
        console.log('A text megváltozott:', text);
        document.title = text || 'React app';

        return () => {
            console.log('Cleanup a text változás előtt');
        };
    }, [text]);

    return (
        <div>
            <h2>Eltelt: {seconds} másodperc</h2>
            <input
                type="text"
                value={text}
                onChange={(e) => setText(e.target.value)}
                placeholder="Írj valamit..."
            />
        </div>
    );
}

export default TimerPelda;

8. Vue Router

Többoldalas élmény (SPA) biztosítása a Vue Router segítségével.

import { createRouter, createWebHistory } from 'vue-router';
import HomeView from './views/HomeView.vue';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: HomeView },
    { path: '/about', component: () => import('./views/AboutView.vue') }
  ]
});

export default router;

React Router – útvonalkezelés és navigáció több oldal között A BrowserRouter wrappeli az alkalmazást, a Routes csoportosítja az útvonalakat. A Link komponens kattintásra navigál (NEM tag, mert nem tölti újra az oldalt). A wildcard /* a 404-hez használható. ---------------- src/App.jsx ----------------

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import Profile from './pages/Profile';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';

function App() {
    return (
        <BrowserRouter>
            <nav>
                {/* Link – belső navigáció, nem tölti újra az oldalt */}
                <Link to="/">Főoldal</Link>
                <Link to="/profile">Profil</Link>
                <Link to="/contact">Kapcsolat</Link>
            </nav>

            <Routes>
                <Route path="/"        element={<Home />} />
                <Route path="/profile" element={<Profile />} />
                <Route path="/contact" element={<Contact />} />
                <Route path="/*"       element={<NotFound />} />
            </Routes>
        </BrowserRouter>
    );
}

export default App;

// ---------------- src/pages/Home.jsx ----------------
function Home() {
    return <h1>Üdv a főoldalon!</h1>;
}
export default Home;

// ---------------- Programozott navigáció useNavigate-tel ----------------
import { useNavigate } from 'react-router-dom';

function LoginGomb() {
    const navigate = useNavigate();

    const bejelentkez = () => {
        // ... auth logika ...
        navigate('/profile'); // átirányítás
    };

    return <button onClick={bejelentkez}>Bejelentkezés</button>;
}

// ---------------- URL paraméterek (dinamikus route) ----------------
// <Route path="/product/:id" element={<ProductDetail />} />
//
// import { useParams } from 'react-router-dom';
// function ProductDetail() {
//     const { id } = useParams();
//     return <h1>Termék: {id}</h1>;
// }

9. Form kezelés és validáció

Kétirányú adatkötés a `v-model` segítségével, valamint egyszerű form beküldés és validáció.

<script setup>
import { ref } from 'vue';

const form = ref({ username: '' });
const error = ref('');

const submitForm = () => {
  if (form.value.username.length < 3) {
    error.value = 'A név túl rövid!';
    return;
  }
  error.value = '';
  console.log('Küldés:', form.value);
};
</script>

<template>
  <form @submit.prevent="submitForm">
    <input v-model="form.username" type="text" />
    <p v-if="error" style="color: red">{{ error }}</p>
    <button type="submit">Küldés</button>
  </form>
</template>

React Hook Form + Yup – form kezelés validációval A useForm hook regisztrálja az inputokat és kezeli a submit-et. A yup egy schema-alapú validációs könyvtár, a yupResolver kapcsolja össze őket. Az errors objektumon keresztül jelenítjük meg a hibákat. Telepítés: npm install react-hook-form yup @hookform/resolvers

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

// Validációs schema – minden mezőre szabály
const schema = yup.object({
    nev: yup.string()
        .required('A név kötelező')
        .min(3, 'Legalább 3 karakter'),
    email: yup.string()
        .required('Az email kötelező')
        .email('Érvénytelen email cím'),
    kor: yup.number()
        .typeError('Csak szám lehet')
        .positive('Pozitív számot adj meg')
        .integer('Egész szám legyen')
        .required('A kor kötelező'),
    jelszo: yup.string()
        .required('A jelszó kötelező')
        .min(6, 'Legalább 6 karakter'),
    jelszoMegerositese: yup.string()
        .oneOf([yup.ref('jelszo')], 'A két jelszó nem egyezik')
        .required('Erősítsd meg a jelszót'),
    szerepkor: yup.string()
        .oneOf(['user', 'admin'], 'Válassz egy szerepkört')
        .required(),
}).required();

function RegisztracioForm() {
    const {
        register,
        handleSubmit,
        formState: { errors, isSubmitting }
    } = useForm({
        resolver: yupResolver(schema)
    });

    const onSubmit = (adatok) => {
        console.log('Validált adatok:', adatok);
        // Ide jön az API hívás, pl. axios.post(...)
    };

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <div>
                <label>Név:</label>
                <input {...register('nev')} />
                {errors.nev && <span style={{ color: 'red' }}>{errors.nev.message}</span>}
            </div>

            <div>
                <label>Email:</label>
                <input type="email" {...register('email')} />
                {errors.email && <span style={{ color: 'red' }}>{errors.email.message}</span>}
            </div>

            <div>
                <label>Kor:</label>
                <input type="number" {...register('kor')} />
                {errors.kor && <span style={{ color: 'red' }}>{errors.kor.message}</span>}
            </div>

            <div>
                <label>Jelszó:</label>
                <input type="password" {...register('jelszo')} />
                {errors.jelszo && <span style={{ color: 'red' }}>{errors.jelszo.message}</span>}
            </div>

            <div>
                <label>Jelszó megerősítése:</label>
                <input type="password" {...register('jelszoMegerositese')} />
                {errors.jelszoMegerositese && (
                    <span style={{ color: 'red' }}>{errors.jelszoMegerositese.message}</span>
                )}
            </div>

            <div>
                <label>Szerepkör:</label>
                <select {...register('szerepkor')}>
                    <option value="">-- válassz --</option>
                    <option value="user">Felhasználó</option>
                    <option value="admin">Admin</option>
                </select>
                {errors.szerepkor && <span style={{ color: 'red' }}>{errors.szerepkor.message}</span>}
            </div>

            <button type="submit" disabled={isSubmitting}>
                {isSubmitting ? 'Küldés...' : 'Regisztráció'}
            </button>
        </form>
    );
}

export default RegisztracioForm;

10. API hívás (Axios)

Adatok lekérése a háttérrendszerből axios segítségével az `onMounted` fázisban.

<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';

const users = ref([]);

onMounted(async () => {
  try {
    const response = await axios.get('https://api.example.com/users');
    users.value = response.data;
  } catch (error) {
    console.error('Hiba történt:', error);
  }
});
</script>

Axios + TanStack React Query – API hívások és cache kezelés Az axios egy HTTP kliens, a React Query pedig kezeli a betöltést, errort, cache-t. A useQuery automatikusan újrahív, cache-el és reagál a hálózat változására. A useMutation a CREATE/UPDATE/DELETE műveletekhez való. Telepítés: npm install axios @tanstack/react-query ---------------- src/main.jsx (provider beállítás) ----------------

import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';

const queryClient = new QueryClient({
    defaultOptions: {
        queries: {
            staleTime: 1000 * 60 * 5,  // 5 percig friss az adat
            retry: 2,                  // hibás kérés újrapróbálás 2x
        }
    }
});

ReactDOM.createRoot(document.getElementById('root')).render(
    <QueryClientProvider client={queryClient}>
        <App />
    </QueryClientProvider>
);

// ---------------- GET kérés – useQuery (lista lekérése) ----------------
import axios from 'axios';
import { useQuery } from '@tanstack/react-query';

const API_URL = import.meta.env.VITE_API_URL;

function ProductList() {
    const { data, isLoading, error, refetch } = useQuery({
        queryKey: ['products'],
        queryFn: () => axios.get(`${API_URL}/products`, {
            auth: { username: 'admin', password: 'titok' }
        }).then(res => res.data)
    });

    if (isLoading) return <p>Betöltés...</p>;
    if (error) return <p>Hiba: {error.message}</p>;

    return (
        <div>
            <button onClick={() => refetch()}>Frissítés</button>
            <ul>
                {data.map(p => (
                    <li key={p.id}>{p.name} – {p.price} Ft</li>
                ))}
            </ul>
        </div>
    );
}

// ---------------- POST kérés – useMutation (új termék) ----------------
import { useMutation, useQueryClient } from '@tanstack/react-query';

function AddProduct() {
    const queryClient = useQueryClient();

    const mutation = useMutation({
        mutationFn: (newProduct) => axios.post(`${API_URL}/products`, newProduct),
        onSuccess: () => {
            // Sikeres mentés után invalidáljuk a listát, hogy újratöltsön
            queryClient.invalidateQueries({ queryKey: ['products'] });
        }
    });

    const handleSubmit = () => {
        mutation.mutate({ name: 'Új termék', price: 12990 });
    };

    return (
        <div>
            <button onClick={handleSubmit} disabled={mutation.isPending}>
                {mutation.isPending ? 'Mentés...' : 'Hozzáad'}
            </button>
            {mutation.isSuccess && <p>Sikeresen mentve!</p>}
            {mutation.isError && <p>Hiba: {mutation.error.message}</p>}
        </div>
    );
}

// ---------------- Axios interceptor – globális token beillesztése ----------------
axios.interceptors.request.use((config) => {
    const token = localStorage.getItem('token');
    if (token) {
        config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
});

export { ProductList, AddProduct };

Mezők törlése – Clear gomb logikája

A törlés gomb megnyomásakor először ellenőrizni kell, hogy van-e egyáltalán mit törölni. Ha a mezők már üresek, hibaüzenetet mutatunk. Ha van tartalom, megerősítő dialógot kérünk a felhasználótól, és csak Igen esetén törlünk.

  • label1.Text / textBox1.Text – az adott vezérlő aktuális szöveges értéke
  • MessageBox.Show(...) – modális üzenetablak megjelenítése
  • MessageBoxButtons.YesNo – Igen/Nem gombok megjelenítése
  • DialogResult.Yes – ha a felhasználó Igen-t választott
  • textBox1.Clear() – textBox tartalmának törlése
  • label1.Text = "" – label szövegének törlése
Az ellenőrzés sorrendje: előbb üres-e? → igen: hibaüzenet. Nem üres: megerősítés → törlés. Ez a minta szinte minden WFA feladatban megjelenik.
string ures  = label1.Text;
string ures4 = textBox1.Text;

if (ures == "" && ures4 == "")
{
    MessageBox.Show("Már üres a mező.", "Hiba", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
    DialogResult diag2 = MessageBox.Show("Biztosan törlöd?", "Törlés?",
        MessageBoxButtons.YesNo, MessageBoxIcon.Question);
    if (diag2 == DialogResult.Yes)
    {
        textBox1.Clear();
        label1.Text = "";
    }
}

A mezők törlése JavaFX-ben az fx:id-vel összekötött kontrollerek segítségével történik. A gomb eseménykezelőjét az onAction attribútummal definiáljuk. Az interakciókhoz (hibaüzenet, megerősítés) az Alert osztályt használjuk.

  • @FXML – jelzi, hogy a mező/metódus az FXML fájlból érkezik
  • label.getText() / textField.getText() – értékek kiolvasása
  • Alert(AlertType.ERROR) – hibaüzenet ablak
  • Alert(AlertType.CONFIRMATION) – megerősítő kérdés
  • ButtonType.YES / NO – a válaszlehetőségek típusai
A JavaFX Alert modális ablak, a showAndWait() metódussal megállítja a futást a válaszig.
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;

import java.util.Optional;

public class FoController {

    // Scene Builderben fx:id-vel kapcsolódik a komponens a kontrollerhez
    @FXML private Label label1;
    @FXML private TextField textBox1;

    @FXML
private void torolKattintas(ActionEvent event) {
        String ures  = label1.getText();
        String ures4 = textBox1.getText();

        if (ures.isEmpty() && ures4.isEmpty()) {
            Alert hiba = new Alert(AlertType.ERROR);
            hiba.setTitle("Hiba");
            hiba.setHeaderText(null);
            hiba.setContentText("Már üres a mező.");
            hiba.showAndWait();
    } else {
            Alert kerdes = new Alert(AlertType.CONFIRMATION,
                    "Biztosan törlöd?", ButtonType.YES, ButtonType.NO);
            kerdes.setTitle("Törlés?");
            kerdes.setHeaderText(null);

            Optional<ButtonType> valasz = kerdes.showAndWait();
            if (valasz.isPresent() && valasz.get() == ButtonType.YES) {
            textBox1.clear();
            label1.setText("");
            }
        }
    }
}

Kilépési dialóg

A kilépés előtt megerősítést kérünk a felhasználótól. A MessageBox.Show() visszaad egy DialogResult értéket, amellyel megállapítható, melyik gombot nyomta meg a felhasználó. Ha Igen-t választott, az ablakot a Close() metódussal zárjuk be.

  • DialogResult diag = MessageBox.Show(...) – eltárolja a felhasználó választását
  • MessageBoxButtons.YesNo – Igen és Nem gombokat jelenít meg
  • MessageBoxIcon.Question – kérdőjel ikon az ablakban
  • DialogResult.Yes – az Igen gomb eredménye
  • Close() – az aktuális Form bezárása
DialogResult diag = MessageBox.Show("Biztosan kilépsz?", "Kilépsz?",
    MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (diag == DialogResult.Yes)
{
    Close();
}

Kilépés előtt JavaFX-ben is megerősítést kérünk az Alert osztály segítségével. A program leállítása a Platform.exit() hívással, vagy az aktuális ablak (Stage) bezárásával történhet.

  • AlertType.CONFIRMATION – kérdőjeles ablak
  • kerdes.showAndWait() – vár a gombnyomásra
  • Platform.exit() – a teljes alkalmazás leállítása
  • stage.close() – csak az ablak bezárása
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.stage.Stage;

import java.util.Optional;

public class KilepesController {

    @FXML
private void kilepesKattintas(ActionEvent event) {
        Alert kerdes = new Alert(AlertType.CONFIRMATION,
                "Biztosan kilépsz?", ButtonType.YES, ButtonType.NO);
        kerdes.setTitle("Kilépsz?");
        kerdes.setHeaderText(null);

        Optional<ButtonType> valasz = kerdes.showAndWait();
        if (valasz.isPresent() && valasz.get() == ButtonType.YES) {
            // 1. lehetőség – csak ezt az ablakot zárjuk
            Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
            stage.close();

            // 2. lehetőség – egész alkalmazás leállítása
            // Platform.exit();
        }
    }
}

Statisztika Dictionary-vel – eredmény Label-ben

A konzolos Dictionary-statisztikához képest a WFA-ban az eredményt nem a konzolra, hanem egy Label vezérlőbe írjuk. A += operátorral szövegeket fűzünk egymás után, a "\n" karakterrel sortörést szúrunk be.

  • label1.Text += "\n" + ... – új sort fűz a label szövegéhez
  • A ciklus indexe i = 1-ről indul, ha az első sor fejléc (fejlécet kihagyjuk)
  • stat.Count – az egyedi értékek (kulcsok) száma
Dictionary<string, int> stat = new Dictionary<string, int>();
for (int i = 1; i < szamok.Length; i++)
{
    if (stat.ContainsKey(szamok[i]))
    {
        stat[szamok[i]]++;
    }
    else
    {
        stat.Add(szamok[i], 1);
    }
}

foreach (var x in stat)
{
    label1.Text += "\n" + (x.Key + "-----" + x.Value + " db");
}
label1.Text += "\nÖsszes db: " + stat.Count;

A statisztika eredményét JavaFX-ben egy Label-ben jelenítjük meg. Mivel a Label szövege string, a gyűjtéshez érdemes StringBuilder-t használni a hatékonyság érdekében.

  • StringBuilder sb = new StringBuilder() – dinamikus szövegépítés
  • sb.append("\n") – új sor hozzáadása
  • label.setText(sb.toString()) – a kész szöveg beállítása
import javafx.fxml.FXML;
import javafx.scene.control.Label;

import java.util.HashMap;
import java.util.Map;

public class StatisztikaController {

    @FXML private Label label1;

    private String[] szamok;

    @FXML
private void szamolKattintas() {
    Map<String, Integer> stat = new HashMap<>();
        for (int i = 1; i < szamok.length; i++) {
            if (stat.containsKey(szamok[i])) {
                stat.put(szamok[i], stat.get(szamok[i]) + 1);
            } else {
                stat.put(szamok[i], 1);
            }
        }

        StringBuilder sb = new StringBuilder(label1.getText());
        for (Map.Entry<String, Integer> x : stat.entrySet()) {
            sb.append("\n").append(x.getKey()).append("-----")
                    .append(x.getValue()).append(" db");
    }
        sb.append("\nÖsszes db: ").append(stat.size());
    label1.setText(sb.toString());
    }
}

Statisztika fájlba írása – StreamWriter

A StreamWriter szöveges fájlba írja az adatokat. Fontos, hogy a fájlt try-catch blokkban kezeljük (a fájl lehet foglalt vagy nem írható), és mindig .Close()-zal zárjuk le. A lezárásnak a catch blokkon kívül kell lennie.

  • new StreamWriter("fajl.txt") – megnyitja írásra; ha nem létezik, létrehozza; ha létezik, felülírja
  • sw.WriteLine(szoveg) – sort ír be (sortöréssel a végén)
  • sw.Close() – lezárja és menti a fájlt (mindig szükséges!)
  • MessageBox.Show(..., MessageBoxIcon.Information) – informáló üzenetablak
Ha feltételes írást szeretnél (pl. csak az "F" típusúakat nem), a foreach belsejébe if (x.Key != "F") feltételt rakj.
StreamWriter sw = new StreamWriter("statisztika.txt");
try
{
    foreach (var x in stat)
    {
        if (x.Key != "F")
        {
            sw.WriteLine(x.Key + " --- " + x.Value + " db");
        }
    }
    MessageBox.Show("Sikeres írás!", "Siker", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception)
{
    MessageBox.Show("Hiba a fájl írásakor!", "Hiba", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
sw.Close();

A Java 7-ben bevezetett try-with-resources szerkezet automatikusan lezárja a fájlt, így nincs szükség manuális close() hívásra. Hiba esetén (pl. írásvédett fájl) IOException-t kapunk el.

  • try (BufferedWriter sw = ...) – az erőforrás automatikus lezárása
  • sw.write(szoveg) – adat kiírása
  • sw.newLine() – rendszerspecifikus sortörés beszúrása
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;

public class StatisztikaIroController {

    private Map<String, Integer> stat;

    @FXML
    private void mentesKattintas() {
        try (BufferedWriter sw = new BufferedWriter(new FileWriter("statisztika.txt"))) {
            for (Map.Entry<String, Integer> x : stat.entrySet()) {
                if (!x.getKey().equals("F")) {
                    sw.write(x.getKey() + " --- " + x.getValue() + " db");
        sw.newLine();
    }
            }
            Alert siker = new Alert(AlertType.INFORMATION);
            siker.setTitle("Siker");
            siker.setHeaderText(null);
            siker.setContentText("Sikeres írás!");
            siker.showAndWait();
} catch (IOException e) {
            Alert hiba = new Alert(AlertType.ERROR);
            hiba.setTitle("Hiba");
            hiba.setHeaderText(null);
            hiba.setContentText("Hiba a fájl írásakor!");
            hiba.showAndWait();
        }
    }
}

Fájlbeolvasás – Olvas() metódus WFA-ban

WFA-ban a fájlbeolvasást érdemes külön private void Olvas() metódusba szervezni, amelyet a Form konstruktorából hívunk meg (InitializeComponent() után). Így az adatok az ablak megnyitásakor automatikusan betöltődnek, és az összes többi metódus már hozzáfér az adatokhoz.

  • private string[,] adatok; – osztályszintű mező (minden metódusban elérhető)
  • private int hossz; – sorok száma, szintén osztályszintű
  • A konstruktorban: Olvas(); – az adatok azonnal betöltődnek
  • foreach (var sor in adat) – soronként iterál a beolvasott tömbön
  • splitSor[i] – az adott sor i-edik mezője
private string[,] adatok;
private int hossz;

// A konstruktorban hívjuk meg:
// public Form1() { InitializeComponent(); Olvas(); }

private void Olvas()
{
    string[] adat = File.ReadAllLines("nobel.csv");
    hossz = adat.Length;
    adatok = new string[hossz, 4];
    int n = 0;
    foreach (var sor in adat)
    {
        string[] splitSor = sor.Split(';');
        for (int i = 0; i < 4; i++)
        {
            adatok[n, i] = splitSor[i];
        }
        n++;
    }
}

JavaFX-ben a kontroller betöltődése után az initialize() metódus fut le automatikusan. Ide érdemes tenni a kezdeti fájlbeolvasást, hogy az adatok már rendelkezésre álljanak a felhasználói interakciók előtt.

  • Initializable interfész – a régebbi, explicit módszer
  • @FXML private void initialize() – a modern, rövidebb módszer
  • Osztályszintű mezőkben tároljuk az adatokat a későbbi eléréshez
A fájlbeolvasást célszerű try-catch blokkba tenni, mert a fájl hiánya esetén az alkalmazás nem indulna el megfelelően.
import javafx.fxml.FXML;
import javafx.fxml.Initializable;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.ResourceBundle;

public class NobelController implements Initializable {

    private String[][] adatok;
    private int hossz;

    // Az initialize() automatikusan lefut a kontroller létrehozásakor,
    // miután az @FXML mezők be vannak injektálva.
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        olvas();
    }

    // Vagy ha nem implementáljuk az Initializable-t, csak ezt a metódust:
    // @FXML
    // private void initialize() {
    //     olvas();
    // }

    private void olvas() {
        try {
            List<String> adat = Files.readAllLines(Paths.get("nobel.csv"));
            hossz = adat.size();
            adatok = new String[hossz][4];
            int n = 0;
            for (String sor : adat) {
                String[] splitSor = sor.split(";");
                for (int i = 0; i < 4; i++) {
                    adatok[n][i] = splitSor[i];
                }
                n++;
            }
        } catch (IOException e) {
            System.err.println("Hiba a fájl olvasásakor: " + e.getMessage());
        }
    }
}

Irányítószámok – teljes WFA feladat

Összetett WFA feladat: irányítószám adatbázis kezelése. Tartalmazza az összes korábban tanult elemet: Olvas() metódus, keresés tömbben, Dictionary statisztika, ListBox feltöltése, fájlba írás, törlés és kilépési dialóg. Jó összefoglaló vizsgafeladat-sablon.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        Olvas();
    }

    private string[,] adatok;
    private int hossz;

    private void Olvas()
    {
        string[] adat = File.ReadAllLines("irsz.csv");
        hossz = adat.Length;
        adatok = new string[hossz, 3];
        int n = 0;
        foreach (var sor in adat)
        {
            string[] splitSor = sor.Split(';');
            for (int i = 0; i < 3; i++)
                adatok[n, i] = splitSor[i];
            n++;
        }
    }

    // 1. gomb: darabszám
    private void button1_Click(object sender, EventArgs e)
    {
        textBox1.Text = (hossz - 1).ToString();
    }

    // 2. gomb: legnagyobb irányítószám
    private void button2_Click(object sender, EventArgs e)
    {
        int max = 0;
        for (int i = 1; i < hossz; i++)
            if (int.Parse(adatok[i, 0]) > max)
                max = int.Parse(adatok[i, 0]);
        textBox2.Text = max.ToString();
    }

    // 3. gomb: megye statisztika Dictionary-vel
    private void button3_Click(object sender, EventArgs e)
    {
        Dictionary<string, int> stat = new Dictionary<string, int>();
        for (int i = 1; i < hossz; i++)
        {
            if (stat.ContainsKey(adatok[i, 2]))
                stat[adatok[i, 2]]++;
            else
                stat.Add(adatok[i, 2], 1);
        }
        if (stat.ContainsKey(""))
        {
            int value = stat[""];
            stat.Remove("");
            stat["Budapest"] = value;
        }
        foreach (var x in stat)
            listBox1.Items.Add(x.Key + ": " + x.Value + " db");
        button3.Enabled = false;
    }

    // 4. gomb: fájlba írás
    private void button4_Click(object sender, EventArgs e)
    {
        StreamWriter sw = new StreamWriter("megye.txt");
        if (listBox1.Items.Count != 0)
        {
            for (int i = 0; i < listBox1.Items.Count; i++)
                sw.WriteLine(listBox1.Items[i]);
            MessageBox.Show("Sikeres írás.", "Siker", MessageBoxButtons.OK, MessageBoxIcon.Information);
            listBox1.Items.Clear();
        }
        sw.Close();
    }

    // 5. gomb: törlés
    private void button5_Click(object sender, EventArgs e)
    {
        if (textBox1.Text == "" && textBox2.Text == "" && textBox3.Text == "" && listBox1.Items.Count == 0)
        {
            MessageBox.Show("Már üres a mező.", "Hiba", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        else
        {
            DialogResult diag = MessageBox.Show("Biztosan törlöd?", "Törlés?",
                MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (diag == DialogResult.Yes)
            {
                textBox1.Clear(); textBox2.Clear(); textBox3.Clear();
                listBox1.Items.Clear();
                button3.Enabled = true;
            }
        }
    }

    // 6. gomb: kilépés
    private void button6_Click(object sender, EventArgs e)
    {
        DialogResult diag = MessageBox.Show("Biztosan kilépsz?", "Kilépsz?",
            MessageBoxButtons.YesNo, MessageBoxIcon.Question);
        if (diag == DialogResult.Yes)
            Close();
    }

    // 7. gomb: keresés irányítószám alapján
    private void button7_Click(object sender, EventArgs e)
    {
        if (textBox3.Text == "")
        {
            MessageBox.Show("Nem adott meg irányítószámot!", "Hiba",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        bool talalt = false;
        for (int i = 1; i < hossz; i++)
        {
            if (adatok[i, 0] == textBox3.Text)
            {
                MessageBox.Show("Az irányítószám itt található: " + adatok[i, 1],
                    "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
                textBox3.Text = "";
                talalt = true;
                break;
            }
        }
        if (!talalt)
            MessageBox.Show("Nem található ilyen irányítószám!", "Hiba",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

Irányítószámok – teljes JavaFX feladat (Scene Builder + FXML) Minden gombhoz külön onAction handler, az adatokat 2D tömbben tároljuk. A ListView a WFA ListBox megfelelője, a TextField a TextBox-é. Az fx:id-k alapján a Scene Builder generálja a kapcsolatokat a kontrollerhez. ===== iranyitoszamok.fxml (Scene Builderben szerkeszthető) =====

/*
<VBox xmlns:fx="http://javafx.com/fxml"
      fx:controller="hu.pelda.IranyitoszamokController"
      spacing="10" alignment="CENTER">
    <TextField fx:id="textBox1" promptText="Darabszám" />
    <TextField fx:id="textBox2" promptText="Legnagyobb" />
    <TextField fx:id="textBox3" promptText="Keresett irsz" />
    <ListView  fx:id="listBox1" prefHeight="200" />
    <Button fx:id="button1" text="Darabszám"     onAction="#button1Click" />
    <Button fx:id="button2" text="Legnagyobb"    onAction="#button2Click" />
    <Button fx:id="button3" text="Megye stat"    onAction="#button3Click" />
    <Button fx:id="button4" text="Fájlba ír"     onAction="#button4Click" />
    <Button fx:id="button5" text="Töröl"         onAction="#button5Click" />
    <Button fx:id="button6" text="Kilép"         onAction="#button6Click" />
    <Button fx:id="button7" text="Keres"         onAction="#button7Click" />
</VBox>
*/

// ===== IranyitoszamokController.java =====
package hu.pelda;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.stage.Stage;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;

public class IranyitoszamokController implements Initializable {

    @FXML private TextField textBox1, textBox2, textBox3;
    @FXML private ListView<String> listBox1;
    @FXML private Button button3;

    private String[][] adatok;
    private int hossz;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        olvas();
    }

    private void olvas() {
        try {
            List<String> adat = Files.readAllLines(Paths.get("irsz.csv"));
            hossz = adat.size();
            adatok = new String[hossz][3];
            int n = 0;
            for (String sor : adat) {
                String[] splitSor = sor.split(";");
                for (int i = 0; i < 3; i++) {
                    adatok[n][i] = splitSor[i];
                }
                n++;
            }
        } catch (IOException e) {
            hibaUzenet("Nem sikerült beolvasni az irsz.csv fájlt!");
        }
    }

    // 1. gomb: darabszám
    @FXML private void button1Click() {
        textBox1.setText(String.valueOf(hossz - 1));
    }

    // 2. gomb: legnagyobb irányítószám
    @FXML private void button2Click() {
        int max = 0;
        for (int i = 1; i < hossz; i++) {
            if (Integer.parseInt(adatok[i][0]) > max) {
                max = Integer.parseInt(adatok[i][0]);
            }
        }
        textBox2.setText(String.valueOf(max));
    }

    // 3. gomb: megye statisztika
    @FXML private void button3Click() {
        Map<String, Integer> stat = new HashMap<>();
        for (int i = 1; i < hossz; i++) {
            stat.merge(adatok[i][2], 1, Integer::sum);
        }
        if (stat.containsKey("")) {
            int value = stat.get("");
            stat.remove("");
            stat.put("Budapest", value);
        }
        for (Map.Entry<String, Integer> x : stat.entrySet()) {
            listBox1.getItems().add(x.getKey() + ": " + x.getValue() + " db");
        }
        button3.setDisable(true);
    }

    // 4. gomb: fájlba írás
    @FXML private void button4Click() {
        if (listBox1.getItems().isEmpty()) return;

        try (BufferedWriter sw = new BufferedWriter(new FileWriter("megye.txt"))) {
            for (String item : listBox1.getItems()) {
                sw.write(item);
                sw.newLine();
            }
            infoUzenet("Sikeres írás.");
            listBox1.getItems().clear();
        } catch (IOException e) {
            hibaUzenet("Nem sikerült a fájlba írás!");
        }
    }

    // 5. gomb: törlés
    @FXML private void button5Click() {
        if (textBox1.getText().isEmpty() && textBox2.getText().isEmpty()
                && textBox3.getText().isEmpty() && listBox1.getItems().isEmpty()) {
            hibaUzenet("Már üres a mező.");
            return;
        }

        Alert kerdes = new Alert(AlertType.CONFIRMATION, "Biztosan törlöd?",
                ButtonType.YES, ButtonType.NO);
        kerdes.setTitle("Törlés?");
        kerdes.setHeaderText(null);

        Optional<ButtonType> valasz = kerdes.showAndWait();
        if (valasz.isPresent() && valasz.get() == ButtonType.YES) {
            textBox1.clear();
            textBox2.clear();
            textBox3.clear();
            listBox1.getItems().clear();
            button3.setDisable(false);
        }
    }

    // 6. gomb: kilépés
    @FXML private void button6Click(ActionEvent event) {
        Alert kerdes = new Alert(AlertType.CONFIRMATION, "Biztosan kilépsz?",
                ButtonType.YES, ButtonType.NO);
        kerdes.setTitle("Kilépsz?");
        kerdes.setHeaderText(null);

        Optional<ButtonType> valasz = kerdes.showAndWait();
        if (valasz.isPresent() && valasz.get() == ButtonType.YES) {
            Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
            stage.close();
        }
    }

    // 7. gomb: keresés irányítószám alapján
    @FXML private void button7Click() {
        if (textBox3.getText().isEmpty()) {
            hibaUzenet("Nem adott meg irányítószámot!");
            return;
        }
        for (int i = 1; i < hossz; i++) {
            if (adatok[i][0].equals(textBox3.getText())) {
                infoUzenet("Az irányítószám itt található: " + adatok[i][1]);
                textBox3.clear();
                return;
            }
        }
        hibaUzenet("Nem található ilyen irányítószám!");
    }

    // ----- segédmetódusok -----
    private void hibaUzenet(String szoveg) {
        Alert a = new Alert(AlertType.ERROR);
        a.setTitle("Hiba"); a.setHeaderText(null); a.setContentText(szoveg);
        a.showAndWait();
    }
    private void infoUzenet(String szoveg) {
        Alert a = new Alert(AlertType.INFORMATION);
        a.setTitle("Info"); a.setHeaderText(null); a.setContentText(szoveg);
        a.showAndWait();
    }
}

MessageBox – dialógablak

A MessageBox.Show() modális (blokkoló) üzenetablakot jelenít meg. WPF-ben a MessageBoxResult tárolja a felhasználó választását — ez eltér a WFA DialogResult-jától. A metódus paraméterei: üzenetszöveg, ablakcím, gombok, ikon.

  • MessageBoxButton.OK – csak OK gomb; MessageBoxButton.YesNo – Igen/Nem gombok
  • MessageBoxImage.Question – kérdőjel ikon; MessageBoxImage.Warning – figyelmeztető ikon
  • MessageBoxResult.Yes – ha a felhasználó Igen-t nyomott
  • Close() – az ablak bezárása
// Kilépési megerősítés
MessageBoxResult result = MessageBox.Show(
    "Biztosan kilépsz?",
    "Kilépés",
    MessageBoxButton.YesNo,
    MessageBoxImage.Question);

if (result == MessageBoxResult.Yes)
{
    Close();
}

Alert – dialógablak JavaFX-ben (WPF MessageBox megfelelője) Az AlertType lehet: NONE, INFORMATION, WARNING, CONFIRMATION, ERROR. A showAndWait() blokkolja a UI-t, amíg a felhasználó nem válaszol. Az Optional a választ adja vissza (vagy üres, ha bezárta).

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.stage.Stage;

import java.util.Optional;

public class AlertController {

    @FXML
    private void kilepesKattintas(ActionEvent event) {
        Alert kerdes = new Alert(
                AlertType.CONFIRMATION,
                "Biztosan kilépsz?",
                ButtonType.YES, ButtonType.NO);
        kerdes.setTitle("Kilépés");
        kerdes.setHeaderText(null);

        Optional<ButtonType> result = kerdes.showAndWait();
        if (result.isPresent() && result.get() == ButtonType.YES) {
            Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
            stage.close();
        }
    }

    // További Alert típusok:
    private void peldaInfo() {
        Alert a = new Alert(AlertType.INFORMATION, "Sikeres mentés!");
        a.setHeaderText(null); a.showAndWait();
    }

    private void peldaFigyelmeztetes() {
        Alert a = new Alert(AlertType.WARNING, "A művelet lassú lehet.");
        a.setHeaderText(null); a.showAndWait();
    }

    private void peldaHiba() {
        Alert a = new Alert(AlertType.ERROR, "Nem sikerült a kapcsolódás.");
        a.setHeaderText(null); a.showAndWait();
    }
}

Fájl megnyitása – OpenFileDialog

Az OpenFileDialog egy rendszerdialógot nyit meg, amelyen a felhasználó fájlt választhat. A Filter tulajdonsággal meghatározhatjuk, milyen típusú fájlok jelenjenek meg. A dialóg csak akkor ad vissza true-t, ha a felhasználó ténylegesen választott fájlt (nem nyomott Mégset).

  • using Microsoft.Win32; – szükséges névtér WPF-ben
  • Filter – fájltípus szűrő: "Leírás (*.ext)|*.ext" formátum
  • dialog.ShowDialog() == true – csak akkor fut le a blokk, ha fájlt választottak
  • dialog.FileName – a kiválasztott fájl teljes elérési útja
  • File.ReadAllText() – a fájl teljes tartalmát egy stringben adja vissza
using Microsoft.Win32;

OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Szöveges fájlok (*.txt)|*.txt|Minden fájl (*.*)|*.*";
dialog.Title  = "Fájl megnyitása";

if (dialog.ShowDialog() == true)
{
    string tartalom = File.ReadAllText(dialog.FileName);
    TartTxtBox.Text = tartalom;
}

A WPF-es OpenFileDialog JavaFX megfelelője a FileChooser. Segítségével a felhasználó tallózhat a fájlok között, mi pedig szűrőket (extension filters) adhatunk meg a típusok korlátozására.

  • FileChooser dialog = new FileChooser() – a dialógus példányosítása
  • getExtensionFilters().add(...) – fájltípus szűrők hozzáadása
  • showOpenDialog(stage) – az ablak megjelenítése; null-t ad vissza, ha megszakították
  • Files.readString(path) – modern Java (11+) módszer a teljes fájl beolvasására
A showOpenDialog-nak át kell adni az aktuális Stage-et (ablakot) szülőként, hogy modális legyen.
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.TextArea;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.event.ActionEvent;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class FajlMegnyitController {

    @FXML private TextArea tartTxtBox;

    @FXML
private void megnyitKattintas(ActionEvent event) {
    FileChooser dialog = new FileChooser();
    dialog.setTitle("Fájl megnyitása");

        // Szűrők (Filter) hozzáadása – mindkét forma megengedett:
        dialog.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("Szöveges fájlok (*.txt)", "*.txt"),
                new FileChooser.ExtensionFilter("Minden fájl (*.*)", "*.*")
        );

        // A dialógusnak Stage-et kell adni szülőként
    Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
    File file = dialog.showOpenDialog(stage);

    if (file != null) {
        try {
            String tartalom = Files.readString(file.toPath());
            tartTxtBox.setText(tartalom);
        } catch (IOException e) {
                System.err.println("Hiba az olvasáskor: " + e.getMessage());
            }
        }
    }
}

Fájl mentése – SaveFileDialog

A SaveFileDialog a mentés dialóg — a felhasználó megadhatja, hova és milyen névvel szeretné menteni a fájlt. A FileName tulajdonsággal alapértelmezett fájlnevet adhatunk meg. A tényleges fájlba írást a File.WriteAllText() végzi.

  • dialog.FileName = "eredmeny" – alapértelmezett fájlnév (kiterjesztés nélkül)
  • File.WriteAllText(elerlesiUt, tartalom) – az egész tartalom egyszeri írása; ha létezik a fájl, felülírja
using Microsoft.Win32;

SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter   = "Szöveges fájlok (*.txt)|*.txt|Minden fájl (*.*)|*.*";
dialog.Title    = "Fájl mentése";
dialog.FileName = "eredmeny"; // alapértelmezett fájlnév

if (dialog.ShowDialog() == true)
{
    File.WriteAllText(dialog.FileName, TartTxtBox.Text);
    MessageBox.Show("Fájl sikeresen mentve!", "Siker",
        MessageBoxButton.OK, MessageBoxImage.Information);
}

A mentés JavaFX-ben is a FileChooser-rel történik, de a showSaveDialog() metódust használjuk. Ez lehetőséget ad a fájlnév megadására és a mentési hely kiválasztására.

  • setInitialFileName("nev.txt") – alapértelmezett név beállítása
  • showSaveDialog(stage) – mentési ablak megnyitása
  • Files.writeString(path, content) – modern Java (11+) módszer a fájl kiírására
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.TextArea;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class FajlMentesController {

    @FXML private TextArea tartTxtBox;

    @FXML
private void mentesKattintas(ActionEvent event) {
    FileChooser dialog = new FileChooser();
    dialog.setTitle("Fájl mentése");
        dialog.setInitialFileName("eredmeny.txt"); // alapértelmezett fájlnév

        dialog.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("Szöveges fájlok (*.txt)", "*.txt"),
                new FileChooser.ExtensionFilter("Minden fájl (*.*)", "*.*")
        );

    Stage stage = (Stage) ((Node) event.getSource()).getScene().getWindow();
    File file = dialog.showSaveDialog(stage);

    if (file != null) {
        try {
            Files.writeString(file.toPath(), tartTxtBox.getText());
                Alert siker = new Alert(AlertType.INFORMATION, "Fájl sikeresen mentve!");
                siker.setTitle("Siker");
                siker.setHeaderText(null);
                siker.showAndWait();
        } catch (IOException e) {
                Alert hiba = new Alert(AlertType.ERROR, "Hiba a mentéskor: " + e.getMessage());
                hiba.setHeaderText(null);
                hiba.showAndWait();
            }
        }
    }
}

XAML – Grid elrendezés

A Grid a WPF legalapvetőbb elrendezési vezérlője: sor-oszlop rácsot definiál. A vezérlők a Grid.Row és Grid.Column csatolt tulajdonságokkal helyezhetők el a cellákban. A méret lehet rögzített (px), automatikus (Auto) vagy arányos (*).

  • Height="Auto" – a sor a tartalom magassága szerint méretezi magát
  • Height="*" – a maradék helyet foglalja el (mint flex: 1)
  • Grid.Row="0", Grid.Column="1" – a cella pozíciója (0-tól indexelve)
  • Grid.ColumnSpan="2" – az elem áthidal 2 oszlopot (colspan)
  • Margin="5" – külső margó minden irányban 5 px
<!-- 2 soros, 2 oszlopos Grid -->
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>   <!-- tartalom szerint -->
        <RowDefinition Height="*"/>       <!-- maradék hely -->
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="200"/>   <!-- fix szélesség -->
        <ColumnDefinition Width="*"/>     <!-- maradék -->
    </Grid.ColumnDefinitions>

    <Label  Grid.Row="0" Grid.Column="0" Content="Név:"/>
    <TextBox Grid.Row="0" Grid.Column="1" x:Name="nevTxt" Margin="5"/>

    <!-- ColumnSpan: átnyúlik mindkét oszlopra -->
    <Button Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
            Content="Mentés" Click="MentesBtn_Click" Margin="5"/>
</Grid>

JavaFX-ben a WPF Grid megfelelője a GridPane. Ez is sorokba és oszlopokba rendezi a vezérlőket. A Scene Builder segítségével ezeket vizuálisan is könnyen beállíthatjuk.

  • GridPane.rowIndex és columnIndex – az elem pozíciója a rácsban
  • hgap és vgap – a cellák közötti távolság (pixelben)
  • padding – a rács belső margója
  • ColumnConstraints – oszlopszélességek és nyújtási szabályok definiálása
A USE_COMPUTED_SIZE érték felel meg a WPF-es Auto méretezésnek.
<!-- FXML – GridPane elrendezés (WPF Grid megfelelője) -->
<!-- Scene Builderben drag-and-drop módban szerkeszthető. -->
<!-- A row/columnIndex 0-tól indul. A USE_COMPUTED_SIZE = "Auto" megfelelője. -->
<!-- A GridPane.columnSpan="2" XML attribútum jelenti, hogy egy elem két oszlopra nyúljon át. -->

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>

<GridPane xmlns="http://javafx.com/javafx"
          xmlns:fx="http://javafx.com/fxml"
          fx:controller="hu.pelda.GridController"
          hgap="10" vgap="10">

    <padding>
        <Insets top="10" right="10" bottom="10" left="10"/>
    </padding>

    <!-- Sorok – Auto = tartalom szerint, "*" megfelelője a Vgrow="ALWAYS" -->
    <rowConstraints>
        <RowConstraints minHeight="-Infinity" prefHeight="30"/>
        <RowConstraints vgrow="ALWAYS"/>
    </rowConstraints>

    <!-- Oszlopok – fix és kitöltő -->
    <columnConstraints>
        <ColumnConstraints prefWidth="200" minWidth="200"/>
        <ColumnConstraints hgrow="ALWAYS"/>
    </columnConstraints>
    
    <!-- Komponensek – row/column index alapján -->
    <Label   text="Név:"               GridPane.rowIndex="0" GridPane.columnIndex="0"/>
    <TextField fx:id="nevTxt"          GridPane.rowIndex="0" GridPane.columnIndex="1"/>
    
    <!-- ColumnSpan: két oszlopra nyúlik át -->
    <Button  text="Mentés"
             onAction="#mentesKattintas"
             GridPane.rowIndex="1"
             GridPane.columnIndex="0"
             GridPane.columnSpan="2"/>
</GridPane>

<!-- ===== Hozzá tartozó kontroller ===== -->
<!--
package hu.pelda;
import javafx.fxml.FXML;
import javafx.scene.control.TextField;

public class GridController {
    @FXML private TextField nevTxt;

    @FXML
    private void mentesKattintas() {
        System.out.println("Mentés: " + nevTxt.getText());
    }
}
-->

ListBox adatkötés – ObservableCollection

Az adatkötés (data binding) WPF-ben összekapcsolja a UI vezérlőt egy C# tulajdonsággal — amikor az adat változik, a UI automatikusan frissül. Az ObservableCollection<T> speciálisan erre való: minden módosításkor (Add, Remove) értesíti a ListBox-ot, hogy frissüljön.

  • ObservableCollection<T> – dinamikus lista, amelynek változásait a UI automatikusan követi
  • DataContext = this – az ablak önmagát adja meg adatforrásként; ezután az XAML Binding-ok az ablak property-ire hivatkoznak
  • ItemsSource="{Binding Nevek}" – XAML adatkötés a Nevek property-hez
  • listBox.SelectedItem – a kiválasztott elem; is Tanulo kivalasztott – típusellenőrzés és típuskonverzió egyszerre
// C# kód (MainWindow.xaml.cs)
using System.Collections.ObjectModel;

public partial class MainWindow : Window
{
    public ObservableCollection<string> Nevek { get; set; }

    public MainWindow()
    {
        InitializeComponent();
        Nevek = new ObservableCollection<string> { "Kovács Béla", "Nagy Anna" };
        DataContext = this; // XAML Binding-ok innen olvasnak
    }

    private void HozzaadBtn_Click(object sender, RoutedEventArgs e)
    {
        if (!string.IsNullOrWhiteSpace(nevTxt.Text))
        {
            Nevek.Add(nevTxt.Text); // UI automatikusan frissül
            nevTxt.Clear();
        }
    }

    private void TorlesBtn_Click(object sender, RoutedEventArgs e)
    {
        if (nevekList.SelectedItem != null)
            Nevek.Remove((string)nevekList.SelectedItem);
    }
}

// XAML kód (MainWindow.xaml)
/*
<ListBox x:Name="nevekList"
         ItemsSource="{Binding Nevek}"
         Height="200" Margin="5"/>
<TextBox x:Name="nevTxt" Margin="5"/>
<Button Content="Hozzáad" Click="HozzaadBtn_Click" Margin="5"/>
<Button Content="Töröl"   Click="TorlesBtn_Click"  Margin="5"/>
*/

Az ObservableList JavaFX-ben pontosan azt a szerepet tölti be, mint az ObservableCollection WPF-ben: értesíti a UI-t a változásokról, így a ListView automatikusan frissül.

  • FXCollections.observableArrayList() – az élő lista létrehozása
  • listView.setItems(lista) – a lista összekötése a UI-val
  • lista.add(elem) – a UI azonnal megjeleníti az új elemet
  • listView.getSelectionModel().getSelectedItem() – a kiválasztott elem lekérése
/*
<VBox xmlns:fx="http://javafx.com/fxml" spacing="5"
      fx:controller="hu.pelda.NevekController">
    <ListView fx:id="nevekList" prefHeight="200"/>
    <TextField fx:id="nevTxt"/>
    <Button text="Hozzáad" onAction="#hozzaadKattintas"/>
    <Button text="Töröl"   onAction="#torlesKattintas"/>
</VBox>
*/

// ===== NevekController.java =====
package hu.pelda;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;

import java.net.URL;
import java.util.ResourceBundle;

public class NevekController implements Initializable {

    @FXML private ListView<String> nevekList;
    @FXML private TextField nevTxt;

    // ObservableList – mintha egy "élő" lista lenne, ami értesíti a UI-t
    private ObservableList<String> nevek;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        nevek = FXCollections.observableArrayList("Kovács Béla", "Nagy Anna");
        // Az ItemsSource binding helyett egyszerűen setItems()
        nevekList.setItems(nevek);
    }

    @FXML
    private void hozzaadKattintas() {
        String szoveg = nevTxt.getText();
        if (szoveg != null && !szoveg.isBlank()) {
            nevek.add(szoveg); // a ListView automatikusan frissül
            nevTxt.clear();
        }
    }

    @FXML
    private void torlesKattintas() {
        String kivalasztott = nevekList.getSelectionModel().getSelectedItem();
        if (kivalasztott != null) {
            nevek.remove(kivalasztott);
        }
    }
}

DataGrid – objektumok táblázatos megjelenítése

A DataGrid táblázatos formában jeleníti meg az objektumok gyűjteményét. Az AutoGenerateColumns="False" beállítással magunk határozhatjuk meg, mely oszlopok jelenjenek meg és milyen felirattal — ezt érdemes mindig alkalmazni.

  • AutoGenerateColumns="False" – kikapcsolja az automatikus oszlopgenerálást
  • DataGridTextColumn Header="Felirat" Binding="{Binding Nev}" – egy szöveges oszlop, amely a Nev property-t jeleníti meg
  • Width="*" – az oszlop a maradék szélességet foglalja el
  • IsReadOnly="True" – csak olvasható táblázat (a felhasználó nem szerkesztheti)
  • SelectionChanged esemény – soron kattintáskor hívódik meg
// Model osztály
public class Tanulo
{
    public string Nev     { get; set; }
    public string Osztaly { get; set; }
    public double Atlag   { get; set; }
}

// MainWindow.xaml.cs
public ObservableCollection<Tanulo> Tanulok { get; set; }

public MainWindow()
{
    InitializeComponent();
    Tanulok = new ObservableCollection<Tanulo>
    {
        new Tanulo { Nev = "Kovács Béla", Osztaly = "13D", Atlag = 4.2 },
        new Tanulo { Nev = "Nagy Anna",   Osztaly = "13D", Atlag = 4.8 },
    };
    DataContext = this;
}

private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (tanulokGrid.SelectedItem is Tanulo kivalasztott)
        MessageBox.Show($"Kiválasztva: {kivalasztott.Nev}");
}

// XAML
/*
<DataGrid x:Name="tanulokGrid"
          ItemsSource="{Binding Tanulok}"
          AutoGenerateColumns="False"
          IsReadOnly="True"
          SelectionChanged="DataGrid_SelectionChanged" Margin="5">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Név"     Binding="{Binding Nev}"     Width="*"/>
        <DataGridTextColumn Header="Osztály" Binding="{Binding Osztaly}" Width="100"/>
        <DataGridTextColumn Header="Átlag"   Binding="{Binding Atlag}"   Width="80"/>
    </DataGrid.Columns>
</DataGrid>
*/

A WPF-es DataGrid megfelelője JavaFX-ben a TableView. A táblázat oszlopait a PropertyValueFactory segítségével köthetjük az objektumok getter metódusaihoz.

  • TableView<T> – a táblázat vezérlő, ahol T a megjelenítendő osztály
  • TableColumn<T, Tipus> – egy oszlop definiálása
  • PropertyValueFactory("nev") – a getNev() getterhez köti az oszlopot
  • getSelectionModel().selectedItemProperty() – a kiválasztott elem változásának figyelése
Fontos, hogy a modell osztályban (pl. Tanulo) legyenek publikus getterek, különben a táblázat üres marad!
package hu.pelda;

public class Tanulo {
    private String nev;
    private String osztaly;
    private double atlag;

    public Tanulo(String nev, String osztaly, double atlag) {
        this.nev = nev;
        this.osztaly = osztaly;
        this.atlag = atlag;
    }

    // Getterek – a PropertyValueFactory ezeken keresztül olvassa az értékeket
    public String getNev()      { return nev; }
    public String getOsztaly()  { return osztaly; }
    public double getAtlag()    { return atlag; }
}

// ===== tanulok.fxml =====
/*
<TableView fx:id="tanulokGrid"
           xmlns:fx="http://javafx.com/fxml"
           fx:controller="hu.pelda.TanulokController"
           editable="false">
    <columns>
        <TableColumn text="Név"     fx:id="nevCol"     prefWidth="200"/>
        <TableColumn text="Osztály" fx:id="osztalyCol" prefWidth="100"/>
        <TableColumn text="Átlag"   fx:id="atlagCol"   prefWidth="80"/>
    </columns>
</TableView>
*/

// ===== TanulokController.java =====
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

import java.net.URL;
import java.util.ResourceBundle;

public class TanulokController implements Initializable {

    @FXML private TableView<Tanulo> tanulokGrid;
    @FXML private TableColumn<Tanulo, String> nevCol;
    @FXML private TableColumn<Tanulo, String> osztalyCol;
    @FXML private TableColumn<Tanulo, Double> atlagCol;

    private ObservableList<Tanulo> tanulok;

    @Override
public void initialize(URL location, ResourceBundle resources) {
        // A "nev" string a getNev() metódus nevére utal (camelCase nélkül a "get")
    nevCol.setCellValueFactory(new PropertyValueFactory<>("nev"));
        osztalyCol.setCellValueFactory(new PropertyValueFactory<>("osztaly"));
        atlagCol.setCellValueFactory(new PropertyValueFactory<>("atlag"));

        tanulok = FXCollections.observableArrayList(
        new Tanulo("Kovács Béla", "13D", 4.2),
                new Tanulo("Nagy Anna",   "13D", 4.8)
    );
    tanulokGrid.setItems(tanulok);

        // Selection listener – mint a SelectionChanged WPF-ben
        tanulokGrid.getSelectionModel().selectedItemProperty()
                .addListener((obs, regi, kivalasztott) -> {
                    if (kivalasztott != null) {
                        Alert info = new Alert(AlertType.INFORMATION,
                                "Kiválasztva: " + kivalasztott.getNev());
                        info.setHeaderText(null);
                        info.showAndWait();
                    }
    });
    }
}

MySQL csatlakozás – NuGet csomag telepítése

Az Entity Framework Core (EF Core) egy ORM (Object-Relational Mapper) keretrendszer: lehetővé teszi, hogy C# osztályokon keresztül dolgozzunk az adatbázissal SQL írása nélkül. MySQL adatbázishoz a Pomelo gyártó ingyenes provider-csomagját kell telepíteni.

  • Telepítés: Tools → NuGet Package Manager → Package Manager Console, majd az alábbi parancs
  • A csomag tartalmazza az EF Core alap csomagot is, nem kell külön telepíteni
# Package Manager Console-ban futtatandó
Install-Package Pomelo.EntityFrameworkCore.MySql

A NuGet helyett Java-ban leggyakrabban a Maven csomagkezelőt használjuk. A függőségeket a pom.xml fájlban kell definiálni, az IDE pedig automatikusan letölti azokat.

  • pom.xml – a projekt központi konfigurációs fájlja
  • <dependency> – egy külső könyvtár (pl. MySQL driver) hozzáadása
  • mvn install – függőségek letöltése és a projekt fordítása
<!-- MySQL kapcsolat – Maven dependency a pom.xml-be -->
<!-- A NuGet csomagkezelő helyett Java-ban a Maven (vagy Gradle) használjuk. -->
<!-- A pom.xml a projekt függőségeit írja le, az IDE letölti és csatolja őket. -->

<!-- ===== pom.xml részlet (a <dependencies> blokkba) ===== -->

<dependencies>
    <!-- MySQL JDBC driver – natív kapcsolat -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.4.0</version>
    </dependency>

    <!-- VAGY Hibernate ORM (Entity Framework helyett) -->
    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>6.5.2.Final</version>
    </dependency>

    <!-- VAGY Spring Data JPA (Spring Boot projekthez) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
</dependencies>

<!--
    ===== Parancssoros megfelelő (NuGet helyett) =====
    mvn install               # függőségek letöltése + projekt fordítás
    mvn clean package         # tiszta build, jar generálás
    mvn dependency:tree       # függőség fa kiíratása

    Gradle alternatíva (build.gradle):
    implementation 'com.mysql:mysql-connector-j:8.4.0'
-->

DbContext és Model – EF Core + MySQL

Az EF Core két kulcsfontosságú fogalma a Model és a DbContext. A Model egy C# osztály, amely egy adatbázis-táblát reprezentál — minden property egy oszlopnak felel meg. A DbContext az adatbázis-kapcsolatot kezeli és a lekérdezéseket közvetíti.

  • public int Id { get; set; } – az Id nevű property-t az EF Core automatikusan elsődleges kulcsnak veszi
  • DbSet<Termek> Termekek – a Termekek tábla C# reprezentációja; ezen futnak a lekérdezések
  • OnConfiguring() – a kapcsolati karakterlánc megadásának helye
  • options.UseMySql() – MySQL provider beállítása
using Microsoft.EntityFrameworkCore;

// Model – egy adatbázis sor C# megfelelője
public class Termek
{
    public int     Id  { get; set; }   // EF Core automatikusan PK-nak veszi
    public string  Nev { get; set; }
    public decimal Ar  { get; set; }
}

// DbContext – adatbázis kapcsolat és táblák kezelője
public class AppDbContext : DbContext
{
    public DbSet<Termek> Termekek { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseMySql(
            "Server=localhost;Database=adatbazis;User=root;Password=;",
            ServerVersion.AutoDetect("Server=localhost;Database=adatbazis;User=root;Password=;")
        );
    }
}

A Java világában az Entity Framework megfelelője a JPA (Jakarta Persistence API), amelynek legnépszerűbb megvalósítása a Hibernate. A Spring Boot-tal kombinálva rendkívül egyszerűvé válik az adatbázis-kezelés.

  • @Entity – jelzi, hogy az osztály egy adatbázis-táblát képvisel
  • @Id + @GeneratedValue – az elsődleges kulcs definiálása
  • JpaRepository<T, ID> – interfész, amely automatikusan biztosítja az alapvető műveleteket
  • application.properties – itt adjuk meg a kapcsolati adatokat (URL, user, password)
package hu.pelda.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.math.BigDecimal;

@Entity
@Table(name = "termekek")
public class Termek {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // auto increment
    private Integer id;

    @Column(nullable = false, length = 100)
    private String nev;

    @Column(nullable = false, precision = 10, scale = 2)
    private BigDecimal ar;

    public Termek() { }

    public Termek(String nev, BigDecimal ar) {
        this.nev = nev;
        this.ar = ar;
    }

    public Integer getId()       { return id; }
    public String getNev()       { return nev; }
    public void setNev(String nev) { this.nev = nev; }
    public BigDecimal getAr()    { return ar; }
    public void setAr(BigDecimal ar) { this.ar = ar; }
}

// ===== TermekRepository.java – Spring Data interface =====
package hu.pelda.repository;

import hu.pelda.entity.Termek;
import org.springframework.data.jpa.repository.JpaRepository;

import java.math.BigDecimal;
import java.util.List;

public interface TermekRepository extends JpaRepository<Termek, Integer> {
    // A Spring Data automatikusan implementál minden CRUD metódust:
    //   findAll(), findById(), save(), deleteById(), count(), stb.

    // Egyedi metódus – a Spring a név alapján generálja a SQL-t
    List<Termek> findByArGreaterThan(BigDecimal hatar);
    List<Termek> findByNev(String nev);
}

// ===== application.properties (mint a connection string) =====
/*
spring.datasource.url=jdbc:mysql://localhost:3306/adatbazis
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
*/

CRUD műveletek – EF Core

A CRUD (Create, Read, Update, Delete) a négy alapvető adatbázis-művelet. EF Core-ral ezeket C# kóddal végezzük, az SQL utasításokat az EF Core generálja automatikusan a háttérben. A SaveChanges() hívás menti el ténylegesen az adatbázisba a változtatásokat.

  • db.Termekek.Add(obj) – új rekord hozzáadása (Create)
  • db.Termekek.ToList() – összes rekord lekérdezése (Read)
  • db.Termekek.Where(t => t.Ar > 500) – szűrt lekérdezés (LINQ)
  • db.Termekek.First(t => t.Nev == "Alma") – az első illő rekord; kivételt dob, ha nincs
  • elso.Ar = 349; db.SaveChanges() – módosítás mentése (Update)
  • db.Termekek.Remove(obj); db.SaveChanges() – törlés (Delete)
  • using var db = new AppDbContext() – automatikusan lezárja a kapcsolatot a blokk végén
using var db = new AppDbContext();

// CREATE – új rekord
db.Termekek.Add(new Termek { Nev = "Alma", Ar = 299 });
db.SaveChanges();

// READ – összes rekord
var termekek = db.Termekek.ToList();
foreach (var t in termekek)
    Console.WriteLine($"{t.Id}: {t.Nev} – {t.Ar} Ft");

// READ – szűrve (LINQ)
var draga = db.Termekek.Where(t => t.Ar > 500).ToList();

// UPDATE – módosítás
var elso = db.Termekek.First(t => t.Nev == "Alma");
elso.Ar = 349;
db.SaveChanges();

// DELETE – törlés
var torlendo = db.Termekek.First(t => t.Id == 1);
db.Termekek.Remove(torlendo);
db.SaveChanges();

CRUD műveletek JPA Repository-val (EF Core CRUD megfelelője) A JpaRepository minden alapvető műveletet tartalmaz: save, findAll, findById, delete. Spring kontextusban a repository @Autowired vagy konstruktor-injekcióval kerül be. A változások mentése automatikus a save()/delete() hívásnál (nincs külön SaveChanges).

import hu.pelda.entity.Termek;
import hu.pelda.repository.TermekRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

@Service
public class TermekService {

    private final TermekRepository repo;

    public TermekService(TermekRepository repo) {
        this.repo = repo;
    }

    // CREATE – új rekord
    public Termek hozzaad() {
        Termek uj = new Termek("Alma", new BigDecimal("299"));
        return repo.save(uj); // INSERT INTO termekek ...
    }

    // READ – összes rekord
    public void osszesKiir() {
        List<Termek> termekek = repo.findAll();
        for (Termek t : termekek) {
            System.out.println(t.getId() + ": " + t.getNev() + " – " + t.getAr() + " Ft");
        }
    }

    // READ – szűrve (egyedi metódussal a repository-ban)
    public List<Termek> dragaTermekek() {
        return repo.findByArGreaterThan(new BigDecimal("500"));
    }

    // READ – ID alapján
    public Optional<Termek> egyTermek(Integer id) {
        return repo.findById(id);
    }

    // UPDATE – módosítás
    public void modosit() {
        List<Termek> almak = repo.findByNev("Alma");
        if (!almak.isEmpty()) {
            Termek elso = almak.get(0);
            elso.setAr(new BigDecimal("349"));
            repo.save(elso); // UPDATE termekek SET ... WHERE id = ?
        }
    }

    // DELETE – törlés
    public void torles(Integer id) {
        repo.deleteById(id); // DELETE FROM termekek WHERE id = ?
    }

    // ===== Tisztán JPA EntityManager-rel (Spring Data nélkül) =====
    /*
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("perzisztencia-egyseg");
    EntityManager em = emf.createEntityManager();

    em.getTransaction().begin();
    em.persist(new Termek("Alma", new BigDecimal("299")));   // CREATE
    em.getTransaction().commit();

    List<Termek> osszes = em.createQuery("SELECT t FROM Termek t", Termek.class)
                            .getResultList();                // READ

    em.close();
    emf.close();
    */
}

Spring Initializr (start.spring.io)

A legegyszerűbb módja egy új Spring Boot projekt kezdésének. Állítsd be a függőségeket, és töltsd le a ZIP-et.

Új Spring Boot projekt létrehozása

A Spring Boot projektek alapja a Maven (vagy Gradle) build tool és a pom.xml konfiguráció. A leggyorsabb kezdés a Spring Initializr használata.

  • Spring Web – REST API-khoz és beágyazott Tomcat szerverhez
  • Spring Data JPA – adatbázis-kezeléshez (Hibernate)
  • MySQL Driver – a MySQL kapcsolathoz
  • Validation – bemeneti adatok ellenőrzéséhez
// ===== pom.xml (Maven) – Spring Boot Starter dependencies =====
/*
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version>
    </parent>

    <groupId>hu.pelda</groupId>
    <artifactId>termek-api</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>21</java.version>
    </properties>

<dependencies>
        <!-- Web (REST controller, embedded Tomcat) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

        <!-- Spring Data JPA + Hibernate -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

        <!-- MySQL / MariaDB driver -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Validáció (jakarta validation) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

        <!-- Tesztelés -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
*/

// ===== TermekApiApplication.java – belépési pont =====
package hu.pelda;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TermekApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(TermekApiApplication.class, args);
    }
}

// ===== Hasznos parancsok =====
/*
mvn spring-boot:run        # alkalmazás indítása fejlesztésre
mvn clean package          # JAR fájl építése a target/ mappába
java -jar target/termek-api-1.0.0.jar   # JAR futtatása

# Mappa szerkezet javasolt:
src/main/java/hu/pelda/
    TermekApiApplication.java
    controller/    – REST végpontok
    service/       – business logika
    repository/    – Spring Data interfészek
    entity/        – JPA entitások (adatbázis táblák)
    dto/           – DTO osztályok
    exception/     – egyedi kivételek + handler
src/main/resources/
    application.properties
*/

Konfiguráció (application.properties)

A Spring Boot alkalmazás viselkedését az application.properties (vagy application.yml) fájlban szabályozhatjuk. Itt adjuk meg az adatbázis elérését, a portot és egyéb beállításokat.

  • server.port – a port, amin a szerver figyel (alapértelmezett: 8080)
  • spring.datasource.url – az adatbázis kapcsolati karakterlánca
  • spring.jpa.hibernate.ddl-auto=update – automatikus táblafrissítés a modell alapján
  • spring.jpa.show-sql=true – a generált SQL lekérdezések naplózása
# application.properties – Spring Boot konfiguráció
# A src/main/resources/application.properties fájlba kerül.
# Itt állítható az adatbázis kapcsolat, port, log szintek, Hibernate viselkedés.
# YAML alternatíva is van (application.yml), ugyanazt teszi, csak más szintaxissal.

# ---------------- Szerver ----------------
server.port=8080
server.servlet.context-path=/api
# server.error.include-message=always   # hibaüzenet a JSON válaszban

# ---------------- Adatbázis kapcsolat ----------------
spring.datasource.url=jdbc:mysql://localhost:3306/termek_db?useSSL=false&serverTimezone=Europe/Budapest
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# ---------------- Connection pool (HikariCP, ez az alapértelmezett) ----------------
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.connection-timeout=20000

# ---------------- JPA / Hibernate ----------------
# ddl-auto: none / validate / update / create / create-drop
#   update = a séma automatikusan szinkronban a entitásokkal (fejlesztéshez)
#   none   = semmit ne csináljon (production)
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.open-in-view=false

# ---------------- Log szintek ----------------
logging.level.root=INFO
logging.level.hu.pelda=DEBUG
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE

# ---------------- Profilok (különböző environmentek) ----------------
# Indítás profilonként:
#   mvn spring-boot:run -Dspring-boot.run.profiles=dev
#   java -jar app.jar --spring.profiles.active=prod
spring.profiles.active=dev

# ---------------- Egyedi értékek (saját konfig) ----------------
app.felhasznalo.alapertelmezett-szerepkor=USER
app.cache.lejarat-perc=15
# Használat kódból:
#   @Value("${app.cache.lejarat-perc}") private int lejaratPerc;

REST Controller – HTTP végpontok kezelése

A vezérlő réteg (Controller) felelős a HTTP kérések fogadásáért és a válaszok küldéséért. A Spring automatikusan JSON formátumba alakítja a visszatérő objektumokat.

  • @RestController – jelzi, hogy az osztály egy REST API vezérlő
  • @RequestMapping – az útvonal (endpoint) alapja
  • @GetMapping / @PostMapping – HTTP metódusok specifikálása
  • @RequestBody – a kliens által küldött adatok (JSON) bekötése
package hu.pelda.controller;

import hu.pelda.entity.Termek;
import hu.pelda.service.TermekService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/products")
public class TermekController {

    private final TermekService service;

    // Konstruktor injekció (preferált) – nem kell @Autowired Spring 4.3+ óta
    public TermekController(TermekService service) {
        this.service = service;
    }

    // GET /products – összes termék listázása
    @GetMapping
    public List<Termek> osszes() {
        return service.osszes();
    }

    // GET /products/5 – egy termék id alapján
    @GetMapping("/{id}")
    public Termek egy(@PathVariable Integer id) {
        return service.egy(id); // ha nincs, ResourceNotFoundException-t dob
    }

    // GET /products/search?nev=Alma
    @GetMapping("/search")
    public List<Termek> kereses(@RequestParam String nev) {
        return service.keresesNevAlapjan(nev);
    }

    // POST /products – új termék létrehozása
    @PostMapping
    public ResponseEntity<Termek> uj(@Valid @RequestBody Termek termek) {
        Termek mentett = service.ment(termek);
        return ResponseEntity.status(HttpStatus.CREATED).body(mentett);
    }

    // PUT /products/5 – meglévő termék módosítása
    @PutMapping("/{id}")
    public Termek modosit(@PathVariable Integer id,
                          @Valid @RequestBody Termek termek) {
        return service.modosit(id, termek);
    }

    // DELETE /products/5 – termék törlése
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> torles(@PathVariable Integer id) {
        service.torles(id);
        return ResponseEntity.noContent().build(); // 204 No Content
    }
}

Service réteg – business logika elválasztása

A szolgáltatási réteg (Service) tartalmazza az üzleti logikát. Feladata a kontrollerek és a repository-k közötti közvetítés, valamint a tranzakciók kezelése.

  • @Service – jelzi, hogy az osztály egy szolgáltatás komponens
  • @Transactional – biztosítja az adatbázis-műveletek atomiságát
  • Felelősség: adatok validálása, számítások elvégzése, hibák kezelése
package hu.pelda.service;

import hu.pelda.entity.Termek;
import hu.pelda.exception.ResourceNotFoundException;
import hu.pelda.repository.TermekRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;

@Service
@Transactional // alapból írható tranzakció minden public metódusra
public class TermekService {

    private final TermekRepository repo;

    public TermekService(TermekRepository repo) {
        this.repo = repo;
    }

    // Read-only optimalizáció – Hibernate gyorsabb dirty check nélkül
    @Transactional(readOnly = true)
    public List<Termek> osszes() {
        return repo.findAll();
    }

    @Transactional(readOnly = true)
    public Termek egy(Integer id) {
        return repo.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException(
                        "Nincs ilyen termék: id=" + id));
    }

    @Transactional(readOnly = true)
    public List<Termek> keresesNevAlapjan(String nev) {
        return repo.findByNevContainingIgnoreCase(nev);
    }

    public Termek ment(Termek termek) {
        // Itt lehet üzleti validáció: pl. "ne lehessen negatív ár"
        if (termek.getAr().compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("Az ár nem lehet negatív");
        }
        return repo.save(termek);
    }

    public Termek modosit(Integer id, Termek ujAdatok) {
        Termek meglevo = egy(id); // dob exception-t ha nincs
        meglevo.setNev(ujAdatok.getNev());
        meglevo.setAr(ujAdatok.getAr());
        return repo.save(meglevo);
    }

    public void torles(Integer id) {
        if (!repo.existsById(id)) {
            throw new ResourceNotFoundException("Nincs ilyen termék: id=" + id);
        }
        repo.deleteById(id);
    }
}

Spring Data JPA Repository – automatikus CRUD interfészen keresztül

A Spring Data JPA segítségével az adatbázis-műveletek (CRUD) elvégzéséhez elegendő egy interfészt definiálni. A megvalósítást a Spring automatikusan generálja.

  • JpaRepository<T, ID> – az alapműveleteket biztosító ősosztály
  • findBy[Mezőnév] – automatikus lekérdezés generálás a metódus neve alapján
  • @Query – egyedi JPQL vagy SQL lekérdezések megadása
package hu.pelda.repository;

import hu.pelda.entity.Termek;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

@Repository
public interface TermekRepository extends JpaRepository<Termek, Integer> {

    // ===== Metódusnév-alapú generálás =====
    // SELECT * FROM termek WHERE nev = ?
    List<Termek> findByNev(String nev);

    // ILIKE keresés – SELECT * FROM termek WHERE LOWER(nev) LIKE LOWER('%alma%')
    List<Termek> findByNevContainingIgnoreCase(String reszlet);

    // SELECT * FROM termek WHERE ar > ? ORDER BY ar DESC
    List<Termek> findByArGreaterThanOrderByArDesc(BigDecimal hatar);

    // SELECT * FROM termek WHERE ar BETWEEN ? AND ?
    List<Termek> findByArBetween(BigDecimal min, BigDecimal max);

    // Optional – ha lehet null
    Optional<Termek> findFirstByNevOrderByArAsc(String nev);

    // Számolás
    long countByArGreaterThan(BigDecimal hatar);

    // Létezik-e
    boolean existsByNev(String nev);

    // ===== Egyedi @Query (JPQL) =====
    @Query("SELECT t FROM Termek t WHERE t.ar < :max ORDER BY t.nev")
    List<Termek> olcsobbakAtNal(@Param("max") BigDecimal max);

    // Aggregáló lekérdezés
    @Query("SELECT AVG(t.ar) FROM Termek t")
    BigDecimal atlagAr();

    // ===== Natív SQL =====
    @Query(value = "SELECT * FROM termek WHERE keszlet > 0 AND aktiv = true",
           nativeQuery = true)
    List<Termek> aktivKeszleten();
}

Layered (rétegezett) architektúra – tiszta felelősség-szétválasztás

A modern webalkalmazások tiszta felelősség-szétválasztáson alapulnak. Ez biztosítja a karbantarthatóságot, a tesztelhetőséget és a kód átláthatóságát.

  • Controller: HTTP végpontok, validáció, JSON átalakítás
  • Service: Üzleti logika, tranzakciókezelés, összetett műveletek
  • Repository: Közvetlen adatbázis-műveletek (Data Access Layer)
  • Entity / DTO: Az adatok formátuma a különböző rétegek között
/*
        ┌─────────────────────────────────────────┐
        │   HTTP kliens (Postman, frontend, stb.) │
        └─────────────────────────────────────────┘
                          │  JSON
                          ▼
        ┌─────────────────────────────────────────┐
        │   Controller (@RestController)          │   – HTTP-specifikus
        │   - @RequestBody, @PathVariable         │     - validáció
        │   - HTTP státusz kódok                  │     - JSON ↔ objektum
        └─────────────────────────────────────────┘
                          │  Termek objektum
                          ▼
        ┌─────────────────────────────────────────┐
        │   Service (@Service)                    │   – üzleti logika
        │   - validáció, számítás                 │     - tranzakciók
        │   - több repo összehangolása            │     - kivételdobás
        └─────────────────────────────────────────┘
                          │  Termek objektum
                          ▼
        ┌─────────────────────────────────────────┐
        │   Repository (extends JpaRepository)    │   – adatbázis hozzáférés
        │   - findByXxx, save, delete             │     - JPQL / SQL
        └─────────────────────────────────────────┘
                          │  SQL
                          ▼
        ┌─────────────────────────────────────────┐
        │   Adatbázis (MySQL / MariaDB)           │
        └─────────────────────────────────────────┘
*/

// ===== Rövid teljes példa az összes réteggel =====

// 1. Entity (entity/Termek.java)
@Entity
@Table(name = "termek")
public class Termek {
    @Id @GeneratedValue private Integer id;
    private String nev;
    private BigDecimal ar;
    // getterek / setterek
}

// 2. Repository (repository/TermekRepository.java)
@Repository
public interface TermekRepository extends JpaRepository<Termek, Integer> {
    List<Termek> findByNev(String nev);
}

// 3. Service (service/TermekService.java)
@Service
public class TermekService {
    private final TermekRepository repo;

    public TermekService(TermekRepository repo) { this.repo = repo; }

    public List<Termek> osszes() {
        return repo.findAll();
    }

    public Termek ment(Termek termek) {
        return repo.save(termek);
    }
}

// 4. Controller (controller/TermekController.java)
@RestController
@RequestMapping("/products")
public class TermekController {
    private final TermekService service;

    public TermekController(TermekService service) { this.service = service; }

    @GetMapping
    public List<Termek> osszes() {
        return service.osszes();
    }

    @PostMapping
    public Termek uj(@RequestBody Termek termek) {
        return service.ment(termek);
    }
}

// ===== Miért így? =====
// - Cserélhetőség: ha lecseréled a repository-t (pl. MongoDB-re), csak a repo változik
// - Tesztelhetőség: a service mockolt repo-val tesztelhető DB nélkül
// - Tisztaság: minden réteg csak egy dolgot csinál
// - Tranzakció kezelés: a @Transactional a service szinten van, NEM a controlleren

DTO pattern – adatátviteli objektum a kliens és szerver között

A DTO (Data Transfer Object) használata elengedhetetlen a belső adatmodell (Entity) és a külső felé (API) kommunikált adatok szétválasztásához.

  • Biztonság: nem szivárognak ki érzékeny adatok (pl. jelszó, belső azonosítók)
  • Szelektálás: csak a szükséges mezők küldése/fogadása
  • Stabilitás: az adatbázis sémájának változása nem töri össze automatikusan az API-t
  • Konverzió: könnyen megvalósítható MapStruct-al vagy manuális mapperekkel
// ===== Entity (DB-orientált) =====
@Entity
@Table(name = "felhasznalo")
public class Felhasznalo {
    @Id @GeneratedValue private Integer id;
    private String nev;
    private String email;
    private String jelszo;        // EZT SOHA NE adjuk vissza JSON-ban!
    private LocalDateTime letrehozas;

    @ManyToOne private Csoport csoport;  // lazy kapcsolat
    // getterek / setterek
}

// ===== DTO – kimenő (response) =====
public class FelhasznaloDto {
    private Integer id;
    private String nev;
    private String email;
    private String csoportNev;    // lapított érték a kapcsolatból

    public FelhasznaloDto(Integer id, String nev, String email, String csoportNev) {
        this.id = id; this.nev = nev; this.email = email; this.csoportNev = csoportNev;
    }
    // getterek
}

// ===== DTO – bejövő (request) – csak amit a kliens küldhet =====
public class CreateFelhasznaloRequest {
    @NotBlank
    @Size(min = 3, max = 100)
    private String nev;

    @Email
    @NotBlank
    private String email;

    @NotBlank
    @Size(min = 6)
    private String jelszo;

    private Integer csoportId;
    // getterek / setterek
}

// ===== Mapper – konverzió Entity ↔ DTO =====
@Component
public class FelhasznaloMapper {

    public FelhasznaloDto toDto(Felhasznalo entity) {
        String csoportNev = entity.getCsoport() != null
                ? entity.getCsoport().getNev() : null;
        return new FelhasznaloDto(
                entity.getId(),
                entity.getNev(),
                entity.getEmail(),
                csoportNev
        );
    }

    public Felhasznalo toEntity(CreateFelhasznaloRequest req, Csoport csoport) {
        Felhasznalo f = new Felhasznalo();
        f.setNev(req.getNev());
        f.setEmail(req.getEmail());
        f.setJelszo(req.getJelszo()); // a service még be is hash-eli
        f.setCsoport(csoport);
        f.setLetrehozas(LocalDateTime.now());
        return f;
    }
}

// ===== Service – DTO-val dolgozik a kifelé, Entity-vel a DB felé =====
@Service
public class FelhasznaloService {

    private final FelhasznaloRepository repo;
    private final CsoportRepository csoportRepo;
    private final FelhasznaloMapper mapper;

    public FelhasznaloService(FelhasznaloRepository repo,
                              CsoportRepository csoportRepo,
                              FelhasznaloMapper mapper) {
        this.repo = repo; this.csoportRepo = csoportRepo; this.mapper = mapper;
    }

    public List<FelhasznaloDto> osszes() {
        return repo.findAll().stream()
                .map(mapper::toDto)
                .toList();
    }

    public FelhasznaloDto letrehoz(CreateFelhasznaloRequest req) {
        Csoport csoport = csoportRepo.findById(req.getCsoportId())
                .orElseThrow(() -> new ResourceNotFoundException("Csoport nincs"));
        Felhasznalo entity = mapper.toEntity(req, csoport);
        Felhasznalo mentett = repo.save(entity);
        return mapper.toDto(mentett);
    }
}

// ===== Controller – csak DTO-kat lát, soha nem entityt =====
@RestController
@RequestMapping("/users")
public class FelhasznaloController {
    private final FelhasznaloService service;
    public FelhasznaloController(FelhasznaloService service) { this.service = service; }

    @GetMapping
    public List<FelhasznaloDto> osszes() {
        return service.osszes();
    }

    @PostMapping
    public ResponseEntity<FelhasznaloDto> uj(@Valid @RequestBody CreateFelhasznaloRequest req) {
        return ResponseEntity.status(HttpStatus.CREATED).body(service.letrehoz(req));
    }
}

Globális hibakezelés – @ControllerAdvice + @ExceptionHandler

A Spring Boot lehetővé teszi a hibák központi kezelését a @ControllerAdvice használatával. Így nem kell minden metódusban try-catch blokkokat alkalmazni.

  • @ExceptionHandler – specifikus kivételek elkapása
  • @ResponseStatus – a hiba esetén küldött HTTP státuszkód
  • Egységes válaszformátum minden hiba esetén
// ===== Custom exception – ha nincs entitás az adott ID-val =====
package hu.pelda.exception;

public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

// ===== ApiError – egységes hibaválasz formátum (DTO) =====
package hu.pelda.exception;

import java.time.LocalDateTime;
import java.util.List;

public class ApiError {
    private LocalDateTime timestamp;
    private int status;
    private String error;
    private String message;
    private String path;
    private List<String> reszletek;

    public ApiError(int status, String error, String message, String path) {
        this.timestamp = LocalDateTime.now();
        this.status = status;
        this.error = error;
        this.message = message;
        this.path = path;
    }

    public ApiError(int status, String error, String message, String path,
                    List<String> reszletek) {
        this(status, error, message, path);
        this.reszletek = reszletek;
    }
    // getterek
}

// ===== GlobalExceptionHandler – minden hibát itt kezelünk =====
package hu.pelda.exception;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.util.List;

@ControllerAdvice
public class GlobalExceptionHandler {

    // 404 – nincs ilyen erőforrás
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ApiError> notFound(ResourceNotFoundException ex,
                                             HttpServletRequest req) {
        ApiError hiba = new ApiError(
                HttpStatus.NOT_FOUND.value(),
                "Not Found",
                ex.getMessage(),
                req.getRequestURI()
        );
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(hiba);
    }

    // 400 – validációs hiba (@Valid + @RequestBody mezőkre)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiError> validation(MethodArgumentNotValidException ex,
                                               HttpServletRequest req) {
        List<String> reszletek = ex.getBindingResult().getFieldErrors().stream()
                .map(fe -> fe.getField() + ": " + fe.getDefaultMessage())
                .toList();

        ApiError hiba = new ApiError(
                HttpStatus.BAD_REQUEST.value(),
                "Validation Failed",
                "Hibás bemenet",
                req.getRequestURI(),
                reszletek
        );
        return ResponseEntity.badRequest().body(hiba);
    }

    // 400 – üzleti szabály megsértése (saját dobás)
    @ExceptionHandler(IllegalArgumentException.class)
    public ResponseEntity<ApiError> illegalArg(IllegalArgumentException ex,
                                               HttpServletRequest req) {
        ApiError hiba = new ApiError(
                HttpStatus.BAD_REQUEST.value(),
                "Bad Request",
                ex.getMessage(),
                req.getRequestURI()
        );
        return ResponseEntity.badRequest().body(hiba);
    }

    // 500 – minden más kezeletlen hiba (utolsó "catch-all")
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiError> general(Exception ex, HttpServletRequest req) {
        ApiError hiba = new ApiError(
                HttpStatus.INTERNAL_SERVER_ERROR.value(),
                "Internal Server Error",
                "Váratlan hiba történt",
                req.getRequestURI()
        );
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(hiba);
    }
}

// ===== Példa hibadobásra a service-ben =====
/*
public Termek egy(Integer id) {
    return repo.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Termék nincs: " + id));
    }
*/

// ===== Mit ad vissza a kliensnek: =====
/*
HTTP/1.1 404 Not Found
{
    "timestamp": "2026-05-06T10:23:45",
    "status": 404,
    "error": "Not Found",
    "message": "Termék nincs: 9999",
    "path": "/products/9999"
}
*/

Hibernate alapok – pom.xml, hibernate.cfg.xml, HibernateUtil

A Hibernate egy népszerű ORM (Object-Relational Mapping) keretrendszer, amely lehetővé teszi, hogy Java objektumokat mentsünk adatbázisba anélkül, hogy manuálisan SQL-t írnánk.

  • hibernate.cfg.xml – központi konfigurációs fájl (kapcsolati adatok)
  • SessionFactory – a sessionök létrehozásáért felelős objektum (alkalmazásonként egy)
  • Session – egyetlen adatbázis-művelet vagy tranzakció egysége
  • Dialektus – meghatározza, hogy melyik adatbázis-kezelőhöz (pl. MySQL) generáljon kódot
// ===== pom.xml – Hibernate dependencies =====
/*
<dependencies>
    <!-- Hibernate ORM – a jakarta API-val (Java EE 9+) -->
    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>6.5.2.Final</version>
    </dependency>

    <!-- MySQL JDBC driver -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.4.0</version>
    </dependency>
</dependencies>
*/

// ===== hibernate.cfg.xml – src/main/resources/ =====
/*
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <!-- Adatbázis kapcsolat -->
        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/iskola</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password"></property>

        <!-- Dialektus – melyik DB nyelvét használja a Hibernate -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

        <!-- Fejlesztési segédlet -->
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>

        <!-- Séma kezelés: validate / update / create / create-drop -->
        <property name="hibernate.hbm2ddl.auto">update</property>

        <!-- Pool méret -->
        <property name="hibernate.connection.pool_size">10</property>

        <!-- Mapped class-ok – minden entitást fel kell sorolni -->
        <mapping class="hu.pelda.entity.Tanulo"/>
        <mapping class="hu.pelda.entity.Osztaly"/>
    </session-factory>
</hibernate-configuration>
*/

// ===== HibernateUtil.java – singleton SessionFactory =====
package hu.pelda.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

    private static SessionFactory sessionFactory;

    static {
        try {
            sessionFactory = new Configuration()
                    .configure("hibernate.cfg.xml") // a resources/ alól
                    .buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("SessionFactory hiba: " + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public static void shutdown() {
        if (sessionFactory != null) {
            sessionFactory.close();
        }
    }

    private HibernateUtil() {} // ne lehessen példányosítani
}

// ===== Használat (bárhol) =====
/*
import org.hibernate.Session;

try (Session session = HibernateUtil.getSessionFactory().openSession()) {
    // ... DB műveletek ...
} // Session automatikusan zárul

// Alkalmazás végén:
HibernateUtil.shutdown();
*/

Entity osztály – Java objektum ↔ adatbázis tábla

Az Entity egy olyan Java osztály, amelynek példányait adatbázisban tároljuk. Minden mező egy oszlopnak felel meg a táblában.

  • @Entity – jelzi, hogy az osztály egy entitás
  • @Table – megadja az adatbázis-tábla nevét
  • @Id – az elsődleges kulcs mező
  • @GeneratedValue – automatikus azonosító generálás (pl. Auto Increment)
  • @Column – oszlop-specifikus beállítások (név, nullable, length)
package hu.pelda.entity;

import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Entity
@Table(name = "tanulok")
public class Tanulo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // MySQL AUTO_INCREMENT
    @Column(name = "id")
    private Integer id;

    @Column(name = "nev", nullable = false, length = 100)
    private String nev;

    // Ha az oszlop neve = mező neve, a @Column elhagyható
    @Column(nullable = false, length = 10)
    private String osztaly;

    @Column(name = "szuletesi_datum")
    private LocalDate szuletesi;

    @Column(name = "atlag", precision = 4, scale = 2) // DECIMAL(4,2)
    private BigDecimal atlag;

    @Column(nullable = false)
    private Boolean aktiv = true;

    // Enum tárolása stringként
    @Enumerated(EnumType.STRING)
    @Column(length = 20)
    private Statusz statusz = Statusz.AKTIV;

    @Column(name = "letrehozas")
    private LocalDateTime letrehozas;

    @Transient // EZT a mezőt NE mentsd DB-be (számolt érték, csak memóriában)
    private int eletkor;

    // Lifecycle callback – a persist előtt fut le
    @PrePersist
    public void preInsert() {
        if (letrehozas == null) {
            letrehozas = LocalDateTime.now();
        }
    }

    public Tanulo() { } // KÖTELEZŐ paraméter nélküli konstruktor

    public Tanulo(String nev, String osztaly, LocalDate szuletesi, BigDecimal atlag) {
        this.nev = nev;
        this.osztaly = osztaly;
        this.szuletesi = szuletesi;
        this.atlag = atlag;
    }

    // ===== Getterek / setterek (kötelezőek a Hibernate-hez) =====
    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

    public String getNev() { return nev; }
    public void setNev(String nev) { this.nev = nev; }

    public String getOsztaly() { return osztaly; }
    public void setOsztaly(String osztaly) { this.osztaly = osztaly; }

    public LocalDate getSzuletesi() { return szuletesi; }
    public void setSzuletesi(LocalDate szuletesi) { this.szuletesi = szuletesi; }

    public BigDecimal getAtlag() { return atlag; }
    public void setAtlag(BigDecimal atlag) { this.atlag = atlag; }

    public Boolean getAktiv() { return aktiv; }
    public void setAktiv(Boolean aktiv) { this.aktiv = aktiv; }

    public Statusz getStatusz() { return statusz; }
    public void setStatusz(Statusz statusz) { this.statusz = statusz; }

    @Override
    public String toString() {
        return id + " - " + nev + " (" + osztaly + "): " + atlag;
    }
}

// ===== Enum külön fájlba =====
package hu.pelda.entity;

public enum Statusz {
    AKTIV, INAKTIV, FELFUGGESZTETT
}

CRUD műveletek tisztán Hibernate-tel (Spring nélkül)

A Hibernate segítségével az adatbázis-műveletek objektumorientált módon végezhetők el a Session objektumon keresztül.

  • session.persist(entity) – új rekord mentése
  • session.get(Class, id) – lekérés azonosító alapján
  • session.merge(entity) – meglévő rekord frissítése
  • session.remove(entity) – rekord törlése
package hu.pelda.dao;

import hu.pelda.entity.Tanulo;
import hu.pelda.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;

import java.util.List;

public class TanuloDao {

    // ===== CREATE – új sor beszúrása =====
    public void mentes(Tanulo tanulo) {
        Transaction tx = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            tx = session.beginTransaction();
            session.persist(tanulo); // INSERT INTO tanulok ...
    tx.commit();
        } catch (Exception e) {
            if (tx != null) tx.rollback();
            throw e;
        }
    }

    // ===== READ – id alapján egy rekord =====
    public Tanulo egy(Integer id) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.get(Tanulo.class, id); // SELECT * FROM tanulok WHERE id = ?
            // ha nincs ilyen id: null-t ad vissza (a session.find ugyanaz)
        }
    }

    // ===== READ – összes =====
    public List<Tanulo> osszes() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery("FROM Tanulo", Tanulo.class).getResultList();
        }
    }

    // ===== UPDATE – módosítás =====
    public void modositas(Tanulo tanulo) {
        Transaction tx = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            tx = session.beginTransaction();
            session.merge(tanulo); // UPDATE ... WHERE id = ?
            tx.commit();
        } catch (Exception e) {
            if (tx != null) tx.rollback();
            throw e;
        }
    }

    // ===== DELETE – törlés =====
    public void torles(Integer id) {
        Transaction tx = null;
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            tx = session.beginTransaction();
            Tanulo t = session.get(Tanulo.class, id);
            if (t != null) {
                session.remove(t); // DELETE FROM tanulok WHERE id = ?
            }
            tx.commit();
        } catch (Exception e) {
            if (tx != null) tx.rollback();
            throw e;
        }
    }
}

// ===== Példa használat =====
/*
public class Main {
    public static void main(String[] args) {
        TanuloDao dao = new TanuloDao();

        // CREATE
        Tanulo uj = new Tanulo("Kovács Béla", "13D", LocalDate.of(2006, 3, 15), new BigDecimal("4.20"));
        dao.mentes(uj);
        System.out.println("Mentve, id = " + uj.getId());

        // READ
        Tanulo betoltott = dao.egy(uj.getId());
        System.out.println(betoltott);

        // UPDATE
        betoltott.setAtlag(new BigDecimal("4.50"));
        dao.modositas(betoltott);

        // READ ALL
        for (Tanulo t : dao.osszes()) {
            System.out.println(t);
        }

        // DELETE
        dao.torles(betoltott.getId());

        HibernateUtil.shutdown();
    }
}
*/

// ===== Entity állapotok (érdemes tudni) =====
/*
- transient: új objektum, még nincs DB-ben (new Tanulo())
- persistent: a Session ismeri, változások auto követve (persist után)
- detached: már nincs aktív Session (close után)
- removed: remove()-mal törlésre jelölve
*/

Kapcsolatok (relációk) – @OneToMany, @ManyToOne, @OneToOne, @ManyToMany

Az ORM lényege, hogy a táblák közti idegen kulcsokat objektum-referenciaként láthatjuk.
A "tulajdonos" (owning) oldal az, amelyik az idegen kulcsot tartalmazza, általában a @ManyToOne.
A "fordított" (inverse) oldal mappedBy-jal hivatkozik a tulajdonosra.
FetchType.LAZY = csak akkor tölt, ha kéred (default 1:N-nél); EAGER = mindig betölt.

import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

// ===== 1:N – Egy ország, sok város =====

@Entity
@Table(name = "country")
public class Country {

    @Id @GeneratedValue
    private Integer id;
    private String nev;

    // Egy országhoz több város tartozik – fordított oldal
    // mappedBy = a City osztály "country" mezője a tulajdonos
    @OneToMany(mappedBy = "country",
               cascade = CascadeType.ALL,    // ha mentem az országot, a városok is mentődnek
               orphanRemoval = true,          // ha kiveszek egy várost a listából, törlődik DB-ből
               fetch = FetchType.LAZY)
    private List<City> cities = new ArrayList<>();

    // Convenience metódusok – mindkét oldalt szinkronizálja
    public void addCity(City c) {
        cities.add(c);
        c.setCountry(this);
    }
    public void removeCity(City c) {
        cities.remove(c);
        c.setCountry(null);
    }
    // getterek / setterek
}

// ===== N:1 – Sok város egy országhoz tartozik =====

@Entity
@Table(name = "city")
public class City {

    @Id @GeneratedValue
    private Integer id;
    private String nev;
    private Integer lakossag;

    // Tulajdonos oldal – ez tartalmazza az idegen kulcsot (country_id oszlop)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "country_id", nullable = false)
    private Country country;
    // getterek / setterek
}

// ===== 1:1 – Egy felhasznalo egy profilhoz tartozik =====

@Entity
@Table(name = "felhasznalo")
public class Felhasznalo {
    @Id @GeneratedValue private Integer id;
    private String email;

    // Tulajdonos oldal – itt van a profil_id
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "profil_id", referencedColumnName = "id")
    private Profil profil;
}

@Entity
@Table(name = "profil")
public class Profil {
    @Id @GeneratedValue private Integer id;
    private String tlefonszam;

    @OneToOne(mappedBy = "profil")
    private Felhasznalo felhasznalo;
}

// ===== N:M – Sok diák, sok kurzus (köztes tábla automatikus) =====

@Entity
@Table(name = "diak")
public class Diak {
    @Id @GeneratedValue private Integer id;
    private String nev;

    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
    @JoinTable(
        name = "diak_kurzus",                                  // köztes tábla neve
        joinColumns = @JoinColumn(name = "diak_id"),           // saját FK
        inverseJoinColumns = @JoinColumn(name = "kurzus_id")   // másik oldal FK
    )
    private Set<Kurzus> kurzusok = new HashSet<>();
}

@Entity
@Table(name = "kurzus")
public class Kurzus {
    @Id @GeneratedValue private Integer id;
    private String cim;

    @ManyToMany(mappedBy = "kurzusok")  // fordított oldal
    private Set<Diak> diakok = new HashSet<>();
}

// ===== Cascade típusok =====
/*
- PERSIST: ha persist-eled a szülőt, a gyerekek is persist-elődnek
- MERGE: ha merge-eled a szülőt, a gyerekek is
- REMOVE: ha törlöd a szülőt, a gyerekek is törlődnek
- REFRESH: szülő frissítésénél a gyerekek is
- DETACH: szülő detach-jánál a gyerekek is
- ALL: mind az összes
*/

// ===== Lazy loading – figyelmeztetés =====
/*
A lazy kapcsolatok csak nyitott Session-ön belül érhetők el!
Ha a Session már zárva van és próbálod elérni a kapcsolt entitást:
    LazyInitializationException

Megoldás: vagy nyitott Sessionön belül érd el, vagy használj JOIN FETCH-t a HQL-ben:
    SELECT c FROM Country c LEFT JOIN FETCH c.cities WHERE c.id = :id
*/

HQL (Hibernate Query Language) – objektum-orientált lekérdezések

A HQL (Hibernate Query Language) az SQL objektumorientált megfelelője. Nem táblákkal és oszlopokkal, hanem osztályokkal és mezőkkel dolgozik.

  • Paraméterezett lekérdezések a biztonság érdekében (SQL Injection elleni védelem)
  • FROM EntityName – az összes entitás lekérése
  • WHERE, ORDER BY, GROUP BY – a szokásos SQL funkciók támogatottak
package hu.pelda.dao;

import hu.pelda.entity.Tanulo;
import hu.pelda.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.query.Query;

import java.math.BigDecimal;
import java.util.List;

public class TanuloHqlDao {

    // ===== Egyszerű SELECT – összes =====
    public List<Tanulo> osszes() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            Query<Tanulo> q = session.createQuery("FROM Tanulo", Tanulo.class);
            return q.getResultList();
        }
    }

    // ===== WHERE – nevezett paraméter (preferált) =====
    public List<Tanulo> osztalyAlapjan(String osztaly) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery(
                    "FROM Tanulo t WHERE t.osztaly = :osztaly", Tanulo.class)
                    .setParameter("osztaly", osztaly)
                    .getResultList();
        }
    }

    // ===== Csak egy találat – uniqueResult / getSingleResult =====
    public Tanulo nevAlapjan(String nev) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery(
                    "FROM Tanulo t WHERE t.nev = :nev", Tanulo.class)
                    .setParameter("nev", nev)
                    .uniqueResult(); // null, ha nincs; exception, ha több
        }
    }

    // ===== Több feltétel + ORDER BY =====
    public List<Tanulo> jokTanulok(BigDecimal hatar) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery(
                    "FROM Tanulo t WHERE t.atlag >= :hatar AND t.aktiv = true " +
                    "ORDER BY t.atlag DESC, t.nev ASC", Tanulo.class)
                    .setParameter("hatar", hatar)
                    .getResultList();
        }
    }

    // ===== LIMIT + OFFSET (paginálás) =====
    public List<Tanulo> oldalankent(int oldal, int meret) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery("FROM Tanulo ORDER BY id", Tanulo.class)
                    .setFirstResult(oldal * meret) // OFFSET
                    .setMaxResults(meret)          // LIMIT
                    .getResultList();
        }
    }

    // ===== Csak bizonyos mezők (projection – Object[] tömb) =====
    public List<Object[]> nevEsAtlag() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery(
                    "SELECT t.nev, t.atlag FROM Tanulo t", Object[].class)
                    .getResultList();
            // for (Object[] sor : eredmeny) { String nev = (String) sor[0]; ... }
        }
    }

    // ===== Aggregáló függvények =====
    public Long darab() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery("SELECT COUNT(t) FROM Tanulo t", Long.class)
                    .uniqueResult();
        }
    }

    public BigDecimal atlagAtlaga() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery(
                    "SELECT AVG(t.atlag) FROM Tanulo t", BigDecimal.class)
                    .uniqueResult();
        }
    }

    // ===== GROUP BY – osztályonkénti létszám =====
    public List<Object[]> osztalyonkentiLetszam() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createQuery(
                    "SELECT t.osztaly, COUNT(t) FROM Tanulo t " +
                    "GROUP BY t.osztaly ORDER BY COUNT(t) DESC", Object[].class)
                    .getResultList();
        }
    }

    // ===== JOIN – kapcsolt entitásokkal (egy ország városai) =====
    /*
    "SELECT c FROM Country c JOIN c.cities ct WHERE ct.lakossag > 100000"
    */

    // ===== JOIN FETCH – N+1 probléma elkerülése =====
    /*
    Probléma: a country listához ha lazy a cities, minden country lekérdezésnél
    újabb külön query fut le a cities-re. JOIN FETCH-csel egy query-vel betölti.

    "SELECT DISTINCT c FROM Country c LEFT JOIN FETCH c.cities"
    */

    // ===== UPDATE / DELETE bulk =====
    public int inaktiv(BigDecimal hatar) {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            session.beginTransaction();
            int erintett = session.createMutationQuery(
                    "UPDATE Tanulo t SET t.aktiv = false WHERE t.atlag < :h")
                    .setParameter("h", hatar)
                    .executeUpdate();
            session.getTransaction().commit();
            return erintett;
        }
    }

    // ===== Natív SQL – ha tényleg kell tábla-szintű query =====
    public List<Tanulo> nativeQuery() {
        try (Session session = HibernateUtil.getSessionFactory().openSession()) {
            return session.createNativeQuery(
                    "SELECT * FROM tanulok WHERE atlag > 4.0", Tanulo.class)
                    .getResultList();
        }
    }
}

JDBC kapcsolódás – tisztán Java, ORM nélkül

A JDBC a Java alacsony szintű DB API-ja. A DriverManager.getConnection() nyit kapcsolatot,
a PreparedStatement biztonságos, paraméterezett SQL-t fut le (NEM SQL injection-érzékeny!),
a ResultSet adja vissza az eredménysorokat. A try-with-resources mindent automatikusan zár.
Mindig PreparedStatement-et használj, NE Statement-et string konkatenációval!

package hu.pelda.jdbc;

import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class JdbcPelda {

    private static final String URL  = "jdbc:mysql://localhost:3306/iskola";
    private static final String USER = "root";
    private static final String PASS = "";

    // ===== Kapcsolódás =====
    public static Connection kapcsolat() throws SQLException {
        return DriverManager.getConnection(URL, USER, PASS);
    }

    // ===== INSERT – új sor + visszaadott auto-increment ID =====
    public int beszuras(String nev, String osztaly, BigDecimal atlag) throws SQLException {
        String sql = "INSERT INTO tanulok (nev, osztaly, atlag) VALUES (?, ?, ?)";

        try (Connection conn = kapcsolat();
             PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {

            ps.setString(1, nev);
            ps.setString(2, osztaly);
            ps.setBigDecimal(3, atlag);

            ps.executeUpdate();

            try (ResultSet keys = ps.getGeneratedKeys()) {
                if (keys.next()) {
                    return keys.getInt(1); // a generált id
                }
            }
        }
        return -1;
    }

    // ===== SELECT – egy sor =====
    public Tanulo egyAlapjanId(int id) throws SQLException {
        String sql = "SELECT id, nev, osztaly, atlag FROM tanulok WHERE id = ?";

        try (Connection conn = kapcsolat();
             PreparedStatement ps = conn.prepareStatement(sql)) {

            ps.setInt(1, id);

            try (ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    Tanulo t = new Tanulo();
                    t.id      = rs.getInt("id");
                    t.nev     = rs.getString("nev");
                    t.osztaly = rs.getString("osztaly");
                    t.atlag   = rs.getBigDecimal("atlag");
                    return t;
                }
            }
        }
        return null;
    }

    // ===== SELECT – több sor =====
    public List<Tanulo> osztalyAlapjan(String osztaly) throws SQLException {
        String sql = "SELECT id, nev, osztaly, atlag FROM tanulok " +
                     "WHERE osztaly = ? ORDER BY atlag DESC";

        List<Tanulo> eredmeny = new ArrayList<>();

        try (Connection conn = kapcsolat();
             PreparedStatement ps = conn.prepareStatement(sql)) {

            ps.setString(1, osztaly);

            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    Tanulo t = new Tanulo();
                    t.id      = rs.getInt("id");
                    t.nev     = rs.getString("nev");
                    t.osztaly = rs.getString("osztaly");
                    t.atlag   = rs.getBigDecimal("atlag");
                    eredmeny.add(t);
                }
            }
        }
        return eredmeny;
    }

    // ===== UPDATE =====
    public int modositas(int id, BigDecimal ujAtlag) throws SQLException {
        String sql = "UPDATE tanulok SET atlag = ? WHERE id = ?";

        try (Connection conn = kapcsolat();
             PreparedStatement ps = conn.prepareStatement(sql)) {

            ps.setBigDecimal(1, ujAtlag);
            ps.setInt(2, id);

            return ps.executeUpdate(); // érintett sorok száma
        }
    }

    // ===== DELETE =====
    public int torles(int id) throws SQLException {
        String sql = "DELETE FROM tanulok WHERE id = ?";

        try (Connection conn = kapcsolat();
             PreparedStatement ps = conn.prepareStatement(sql)) {

            ps.setInt(1, id);
            return ps.executeUpdate();
        }
    }

    // ===== Tranzakció – több művelet egy egységként =====
    public void atutalas(int kuldoId, int fogadoId, BigDecimal osszeg) throws SQLException {
        try (Connection conn = kapcsolat()) {
            conn.setAutoCommit(false); // KÉZI tranzakció kezelés

            try (PreparedStatement ki = conn.prepareStatement(
                    "UPDATE szamla SET egyenleg = egyenleg - ? WHERE id = ?");
                 PreparedStatement be = conn.prepareStatement(
                    "UPDATE szamla SET egyenleg = egyenleg + ? WHERE id = ?")) {

                ki.setBigDecimal(1, osszeg);
                ki.setInt(2, kuldoId);
                ki.executeUpdate();

                be.setBigDecimal(1, osszeg);
                be.setInt(2, fogadoId);
                be.executeUpdate();

                conn.commit(); // mindkettő sikerült
            } catch (SQLException e) {
                conn.rollback(); // hibánál semmi nem maradjon meg
                throw e;
            }
        }
    }

    // POJO – belső használatra
    static class Tanulo {
        int id;
        String nev;
        String osztaly;
        BigDecimal atlag;
    }
}

Spring Datasource

# Spring Boot DataSource konfiguráció + HikariCP connection pool
# A Spring Boot a HikariCP-t használja alapértelmezett connection pool-nak (a leggyorsabb).
# Az application.properties-be írt értékekből automatikusan példányosítja a DataSource-t,
# nem kell külön @Bean-t írni hozzá. Több adatbázishoz kell @ConfigurationProperties-zel egyedi config.

# ===== application.properties – egy adatbázis (tipikus) =====

# Adatbázis URL és driver
spring.datasource.url=jdbc:mysql://localhost:3306/termek_db?useSSL=false&serverTimezone=Europe/Budapest
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# HikariCP connection pool finomhangolás
spring.datasource.hikari.pool-name=TermekHikariPool
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=20000
spring.datasource.hikari.validation-timeout=5000

# JPA / Hibernate beállítások
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

# ===== Egyedi DataSource kód (ha kézzel kell létrehozni, pl. több DB) =====
# Ehhez a fájlhoz Java részlet:

/*
package hu.pelda.config;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    // Elsődleges DB
    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource fooDataSource() {
        return new HikariDataSource();
    }

    // Másodlagos DB (pl. egy archiv DB)
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.archiv")
    public DataSource archivDataSource() {
        return new HikariDataSource();
    }
}

# Hozzá tartozó application.properties:
#   spring.datasource.archiv.url=jdbc:mysql://...
#   spring.datasource.archiv.username=...
#   spring.datasource.archiv.password=...
*/

# ===== Profilonkénti DB beállítás =====
# application-dev.properties:
#   spring.datasource.url=jdbc:mysql://localhost:3306/dev_db
#   spring.jpa.hibernate.ddl-auto=update

# application-prod.properties:
#   spring.datasource.url=jdbc:mysql://prod-szerver:3306/prod_db
#   spring.jpa.hibernate.ddl-auto=validate
#   spring.jpa.show-sql=false

# Indítás profilra:
#   mvn spring-boot:run -Dspring-boot.run.profiles=prod
#   java -jar app.jar --spring.profiles.active=prod

# ===== Health check végpont =====
# Spring Boot Actuator-ral:
#   pom.xml: spring-boot-starter-actuator
#   GET /actuator/health  -> { "status": "UP", "components": { "db": ... } }

Tiszta JPA (Spring nélkül) – persistence.xml + EntityManager

A persistence.xml konfigurálja a "perzisztencia egységet" (persistence unit),
ami nagyjából a Hibernate SessionFactory megfelelője. Az EntityManagerFactory ebből készül.
Az EntityManager a Session megfelelője – CRUD műveletekre + JPQL query-kre való.
Spring projektnél ezt nem írod kézzel, de ha tisztán Jakarta Persistence-t használsz, kell.

// ===== pom.xml dependencies =====
/*
<dependencies>
    <!-- Jakarta Persistence API -->
    <dependency>
        <groupId>jakarta.persistence</groupId>
        <artifactId>jakarta.persistence-api</artifactId>
        <version>3.2.0</version>
    </dependency>

    <!-- Hibernate mint JPA implementáció -->
    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>6.5.2.Final</version>
    </dependency>

    <!-- MySQL driver -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.4.0</version>
    </dependency>
</dependencies>
*/

// ===== src/main/resources/META-INF/persistence.xml =====
/*
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence
                 https://jakarta.ee/xml/ns/persistence/persistence_3_2.xsd"
             version="3.2">

    <persistence-unit name="iskolaPU" transaction-type="RESOURCE_LOCAL">

        <!-- JPA implementáció (Hibernate) -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!-- Mapped osztályok – minden entitást fel kell sorolni -->
        <class>hu.pelda.entity.Tanulo</class>
        <class>hu.pelda.entity.Osztaly</class>

        <properties>
            <!-- DB kapcsolat -->
            <property name="jakarta.persistence.jdbc.driver"   value="com.mysql.cj.jdbc.Driver"/>
            <property name="jakarta.persistence.jdbc.url"      value="jdbc:mysql://localhost:3306/iskola"/>
            <property name="jakarta.persistence.jdbc.user"     value="root"/>
            <property name="jakarta.persistence.jdbc.password" value=""/>

            <!-- Hibernate-specifikus tulajdonságok -->
            <property name="hibernate.dialect"        value="org.hibernate.dialect.MySQLDialect"/>
            <property name="hibernate.show_sql"       value="true"/>
            <property name="hibernate.format_sql"     value="true"/>
            <property name="hibernate.hbm2ddl.auto"   value="update"/>
            <property name="hibernate.connection.pool_size" value="10"/>
        </properties>
    </persistence-unit>
</persistence>
*/

// ===== JpaUtil.java – EntityManagerFactory singleton =====
package hu.pelda.util;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public class JpaUtil {

    private static final EntityManagerFactory emf =
            Persistence.createEntityManagerFactory("iskolaPU");

    public static EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

    public static void close() {
        emf.close();
    }

    private JpaUtil() {}
}

// ===== EntityManager használata – CRUD =====
package hu.pelda.dao;

import hu.pelda.entity.Tanulo;
import hu.pelda.util.JpaUtil;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityTransaction;

import java.util.List;

public class TanuloJpaDao {

    public void mentes(Tanulo tanulo) {
        EntityManager em = JpaUtil.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        try {
            tx.begin();
            em.persist(tanulo); // INSERT
            tx.commit();
        } catch (Exception e) {
            if (tx.isActive()) tx.rollback();
            throw e;
        } finally {
            em.close();
        }
    }

    public Tanulo egy(Integer id) {
        EntityManager em = JpaUtil.getEntityManager();
        try {
            return em.find(Tanulo.class, id); // SELECT id alapján
        } finally {
            em.close();
        }
    }

    public List<Tanulo> osszes() {
        EntityManager em = JpaUtil.getEntityManager();
        try {
            return em.createQuery("FROM Tanulo", Tanulo.class).getResultList();
        } finally {
            em.close();
        }
    }

    public Tanulo modositas(Tanulo tanulo) {
        EntityManager em = JpaUtil.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        try {
            tx.begin();
            Tanulo merged = em.merge(tanulo); // UPDATE (vagy INSERT detached objektumnál)
            tx.commit();
            return merged;
        } finally {
            em.close();
        }
    }

    public void torles(Integer id) {
        EntityManager em = JpaUtil.getEntityManager();
        EntityTransaction tx = em.getTransaction();
        try {
            tx.begin();
            Tanulo t = em.find(Tanulo.class, id);
            if (t != null) em.remove(t); // DELETE
            tx.commit();
        } finally {
            em.close();
        }
    }
}

// ===== JPA vs Hibernate API – fő különbségek =====
/*
- jakarta.persistence.* (JPA, szabvány)  vs  org.hibernate.* (Hibernate-specifikus)
- EntityManager                          vs  Session
- EntityManagerFactory                   vs  SessionFactory
- em.persist(), em.find(), em.merge()    vs  session.persist(), session.get(), session.merge()
- em.createQuery(JPQL)                   vs  session.createQuery(HQL)
- persistence.xml                        vs  hibernate.cfg.xml

A Hibernate megvalósítja a JPA-t, de van extra funkciója is.
JPA = szabvány, Hibernate = implementáció.
*/

Java Stream API – funkcionális adatfeldolgozás

A Stream egy "sorozat" elemen végzett műveletek láncolata: szűrés, transzformáció, gyűjtés.
Két típusú művelet: KÖZBENSŐ (intermediate, lazy: filter, map, sorted) – csak Stream-et adnak vissza,
és VÉGSŐ (terminal: collect, forEach, count, reduce) – ezek indítják el ténylegesen a feldolgozást.
A Stream NEM módosítja az eredeti gyűjteményt – mindig új eredményt ad.

import java.util.*;
import java.util.stream.*;

public class StreamPelda {

    public static void main(String[] args) {

        // ===== filter – szűrés feltétel alapján =====
        List<Integer> szamok = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> parosak = szamok.stream()
                .filter(n -> n % 2 == 0)
                .toList(); // [2, 4, 6, 8, 10]

        // ===== map – minden elem transzformálása =====
        List<Integer> negyzetek = szamok.stream()
                .map(n -> n * n)
                .toList(); // [1, 4, 9, 16, ...]

        List<String> szovegek = szamok.stream()
                .map(n -> "Szám: " + n)
                .toList();

        // ===== filter + map láncolva =====
        List<Integer> parosNegyzet = szamok.stream()
                .filter(n -> n % 2 == 0)
                .map(n -> n * n)
                .toList(); // [4, 16, 36, 64, 100]

        // ===== sorted, distinct, limit, skip =====
        List<Integer> rendezett = szamok.stream()
                .distinct()                     // ismétlődések kiszűrése
                .sorted(Comparator.reverseOrder())
                .limit(3)                       // első N elem
                .skip(1)                        // első elem átugrása
                .toList();

        // ===== count, sum, average – aggregáció =====
        long parosakSzama = szamok.stream()
                .filter(n -> n % 2 == 0)
                .count(); // 5

        int osszeg = szamok.stream()
                .mapToInt(Integer::intValue)
                .sum(); // 55

        OptionalDouble atlag = szamok.stream()
                .mapToInt(Integer::intValue)
                .average();

        // ===== reduce – egyedi aggregáció =====
        int szorzat = szamok.stream()
                .reduce(1, (a, b) -> a * b); // 1*2*3*...*10 = 3628800

        Optional<Integer> max = szamok.stream().max(Comparator.naturalOrder());
        Optional<Integer> min = szamok.stream().min(Comparator.naturalOrder());

        // ===== anyMatch, allMatch, noneMatch =====
        boolean vanParos     = szamok.stream().anyMatch(n -> n % 2 == 0); // true
        boolean mindPozitiv  = szamok.stream().allMatch(n -> n > 0);       // true
        boolean nincsNegativ = szamok.stream().noneMatch(n -> n < 0);      // true

        // ===== forEach – terminál művelet =====
        szamok.stream()
                .filter(n -> n > 5)
                .forEach(System.out::println);

        // ===== Object Stream – User példa =====
        record User(String nev, int kor, String varos) { }

        List<User> users = List.of(
                new User("Anna", 25, "Budapest"),
                new User("Béla", 30, "Debrecen"),
                new User("Cili", 22, "Budapest"),
                new User("Dóra", 40, "Szeged")
        );

        // 25 év fölöttiek nevei nagybetűsen
        List<String> idosebbek = users.stream()
                .filter(u -> u.kor() > 25)
                .map(User::nev)
                .map(String::toUpperCase)
                .sorted()
                .toList(); // [BÉLA, DÓRA]

        // ===== Collectors.joining – stringgé összefűzés =====
        String nevek = users.stream()
                .map(User::nev)
                .collect(Collectors.joining(", ", "[", "]"));
        // [Anna, Béla, Cili, Dóra]

        // ===== Collectors.groupingBy – csoportosítás =====
        Map<String, List<User>> varosonkent = users.stream()
                .collect(Collectors.groupingBy(User::varos));
        // { "Budapest" = [Anna, Cili], "Debrecen" = [Béla], "Szeged" = [Dóra] }

        // ===== groupingBy + counting =====
        Map<String, Long> varosonkentiSzam = users.stream()
                .collect(Collectors.groupingBy(User::varos, Collectors.counting()));
        // { "Budapest" = 2, "Debrecen" = 1, "Szeged" = 1 }

        // ===== groupingBy + averaging =====
        Map<String, Double> varosonkentiAtlagKor = users.stream()
                .collect(Collectors.groupingBy(
                        User::varos,
                        Collectors.averagingInt(User::kor)
                ));

        // ===== partitioningBy – kettéosztás boolean alapján =====
        Map<Boolean, List<User>> felosztva = users.stream()
                .collect(Collectors.partitioningBy(u -> u.kor() >= 30));
        // { false = [Anna, Cili], true = [Béla, Dóra] }

        // ===== Collectors.toMap =====
        Map<String, Integer> nevKor = users.stream()
                .collect(Collectors.toMap(User::nev, User::kor));

        // ===== flatMap – beágyazott lista lapítása =====
        List<List<Integer>> beagyazott = List.of(
                List.of(1, 2, 3),
                List.of(4, 5),
                List.of(6, 7, 8, 9)
        );
        List<Integer> lapitott = beagyazott.stream()
                .flatMap(List::stream)
                .toList(); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

        // ===== peek – debugging eszköz (közbenső, NEM terminal) =====
        List<Integer> peekPelda = szamok.stream()
                .filter(n -> n > 5)
                .peek(n -> System.out.println("átengedve: " + n))
                .map(n -> n * 10)
                .peek(n -> System.out.println("transzformálva: " + n))
                .toList();

        // ===== IntStream.range – számtartomány =====
        int sum1to100 = IntStream.rangeClosed(1, 100).sum(); // 5050

        IntStream.range(0, 10)
                .filter(i -> i % 2 == 0)
                .forEach(System.out::println); // 0, 2, 4, 6, 8

        // ===== Stream.of, Stream.generate, Stream.iterate =====
        Stream.of("a", "b", "c").forEach(System.out::println);

        Stream.iterate(1, n -> n * 2)
                .limit(10)
                .forEach(System.out::println); // 1, 2, 4, 8, ..., 512
    }
}

Hasznos Dokumentációs Linkek

Általános, Webes alapok

  • W3Schools – HTML, CSS, JS, SQL puskázás
  • MDN Web Docs – Hivatalos JS, HTML, CSS dokumentáció
  • GeeksforGeeks – Algoritmusok, adatszerkezetek

C# és .NET

  • Programozási Tételek (C#) PDF
  • Microsoft Learn (C#) – Hivatalos útmutató
  • WPF Dokumentáció
  • Windows Forms (WFA)

Java és Spring Boot

  • Java Hivatalos Dokumentáció (Oracle)
  • JavaFX (OpenJFX)
  • Spring Boot Reference
  • Baeldung – Legjobb Java / Spring Boot leírások

PHP és Laravel

  • PHP Reference (PHP.net)
  • Laravel Docs – Eloquent, Routing

Frontend és UI

  • Vue.js Hivatalos Docs
  • Bootstrap 5 – Grid, komponensek

Frontend és UI

  • React Hivatalos Docs
  • Bootstrap 5 – Grid, komponensek

Adatbázis

  • SQL Jegyzet PDF
  • MySQL Reference Manual
  • Adatbázis - kisbaba2012.atw.hu

Magyar nyelvű felkészítő oldalak

  • Infojegyzet.hu
  • Informatikai Ismeretek (nhely.hu)