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 tizedesNOT 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 rekordotDEFAULT érték– alapértelmezett érték, ha nem adjuk megFOREIGN KEY ... REFERENCES– idegen kulcs: egy másik tábla elsődleges kulcsára mutat
-- 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_INCREMENToszlopot (id) nem kell megadni, automatikusan kitöltődik - Több sort egyszerre is be lehet szúrni: a
VALUESutá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 rekordSELECT oszlop1, oszlop2 FROM tabla– csak a megadott oszlopokAS 'Ú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: AnnaIS NULL– üres (nem kitöltött) mező;IS NOT NULL– nem üresIN ('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-sorrendORDER BY oszlop DESC– csökkenő sorrendLIMIT 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ámoljaSUM(oszlop)– numerikus értékek összegeAVG(oszlop)– numerikus értékek átlagaMAX(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ítjaWHERE– 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
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
WHEREné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éseDELETE 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
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 jobbrad-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 visszadocument.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 értelmezelem.style.color = 'red'– inline CSS beállítása (camelCase!)elem.classList.add('aktiv')– CSS osztály hozzáadásaelem.classList.remove('rejtett')– CSS osztály eltávolításaelem.classList.toggle('kiemelt')– váltogatja: ha van, leveszi; ha nincs, hozzáadjainput.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ásaevent.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 (
() => { }) afunction() { }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(nemundefined)
// 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írjalocalStorage.getItem('kulcs')– kiolvas; ha nincs ilyen kulcs,null-t ad visszalocalStorage.removeItem('kulcs')– egy bejegyzés törléselocalStorage.clear()– az összes bejegyzés törlése- Objektum mentéséhez:
JSON.stringify(); visszaolvasáshoz:JSON.parse()
|| [] 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; egyPromise-t ad visszaresponse.json()– a válasz szövegét JSON objektummá alakítja (szintén Promise)async function neve() { }– aszinkron függvény deklarációjaawait– megvárja a Promise teljesülését (csakasyncfüggvényen belül használható)method: 'POST',headers,body– POST kérés paramétereitry/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égreturn-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 ésreturnszükséges- Template literal: backtick karakterek (
`) között; változók:${valtozo} tömb.filter(x => x > 0)– feltételnek megfelelő elemeket adja visszatö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 visszanew StreamReader("fajl.txt")– soronkénti olvasáshoz, manuálisan kell lezárniolvaso.EndOfStream–true, ha elértük a fájl végétolvaso.ReadLine()– következő sort olvassa, majd lép egyetsor.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!
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éntnew BufferedReader(new FileReader(fajl))– soronkénti olvasáshozolvaso.readLine()– beolvassa a következő sort,null-t ad ha végesor.split(";")– a sort darabolja a megadott elválasztó menténtry-with-resources– (a példában manuális close) automatikusan lezárja az erőforrásokat
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-edict[kulcs]++– ha megvan a kulcs, növeli az értékétdict.Add(kulcs, 1)– ha nincs meg, hozzáadja 1-es kezdőértékkelforeach (var x in dict)– kulcs:x.Key, érték:x.Valuedict.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 elemput(kulcs, ertek)– behelyezi vagy felülírja az értéketget(kulcs)– visszaadja a kulcshoz tartozó értéketgetOrDefault(kulcs, alapertelmezett)– visszaadja az értéket, vagy az alapértelmezettet, ha a kulcs nem létezikentrySet()– a szótár bejárásához szükséges (kulcs és érték párok)
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
BufferedWritersegí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őkkelsa[n] = new Adat()– az objektum példányosítása a tömbbenInteger.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 aCompareTo()metódust (alapértelmezett sorrend)CompareTo()– negatív: this kisebb, 0: egyenlők, pozitív: this nagyobbIComparer<T>– külön osztályban definiáljuk aCompare(x, y)metódust (alternatív sorrend)lista.Sort()– azIComparableszerinti rendezéslista.Sort(new OsztalyComparer())– azIComparerszerinti 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, acompareTo()metódust implementáljukComparator<T>– külön osztályban (vagy lambdával), acompare(x, y)metódust írjuk megDouble.compare(a, b)– biztonságos lebegőpontos összehasonlításInteger.compare(a, b)– egész számok összehasonlítása
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éseforeach (var betu in input)– karakterenként iterál a stringenOrderByDescending(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ásinput.toCharArray()– karakterenkénti bejáráshozstream()– a Map bejegyzéseinek folyamattá alakításacomparingByValue().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ő elemetvar v = new Verem<int>()– konkrét típussal példányosítás
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 elList<T> values– generikus lista az elemek tárolásáravalues.get(index)– elem elérése index alapjánVerem<Integer> v = new Verem<>()– példányosítás konkretizált típussal
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 dobhatcatch (TipusException ex) { }– adott típusú kivételt kap el;ex.Messagea hibaüzenetcatch (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!
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ódcatch (ExceptionType e) { }– specifikus hibák elkapásae.getMessage()– a hiba szöveges leírásafinally { }– garantáltan lefutó kód (pl. fájlzárás)throw new MyException()– saját hiba kiváltása manuálisan
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
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ásaoverride 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.
privatemezők – az adatok elrejtése (encapsulation)getNev()éssetNev()– hozzáférés biztosításathis.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
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éselaravel new projekt-neve– új Laravel projekt létrehozásaphp artisan install:api– API végpontok kezeléséhez szükséges fájlok telepítésephp 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ásaphp 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ásaphp artisan make:migration create_TABLA_table– migrációs fájl generálásaphp artisan make:model Nev– Model osztály generálásaphp artisan make:controller API/NevController– Controller generálása azAPIalmappábaphp 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ésupdated_atoszlopok 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őketprotected $casts = [...]– automatikus típuskonverzió: pl. JSON string ↔ PHP tömb,'0'/'1'↔true/falsepublic 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ájashow()– GET /termék → egy elem ID alapjánstore()– POST /termék/create → új elem létrehozásaupdate()– PUT /termék → meglévő elem módosításadestroy()– DELETE /termék → elem törlése$request->validate([...])– automatikus validáció; ha sikertelen, 422-es hibakódot küld visszaProduct::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étrehozzaModel::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_atmező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őjeRoute::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ékeMessageBox.Show(...)– modális üzenetablak megjelenítéseMessageBoxButtons.YesNo– Igen/Nem gombok megjelenítéseDialogResult.Yes– ha a felhasználó Igen-t választotttextBox1.Clear()– textBox tartalmának törléselabel1.Text = ""– label szövegének törlése
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 érkeziklabel.getText()/textField.getText()– értékek kiolvasásaAlert(AlertType.ERROR)– hibaüzenet ablakAlert(AlertType.CONFIRMATION)– megerősítő kérdésButtonType.YES/NO– a válaszlehetőségek típusai
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átMessageBoxButtons.YesNo– Igen és Nem gombokat jelenít megMessageBoxIcon.Question– kérdőjel ikon az ablakbanDialogResult.Yes– az Igen gomb eredményeClose()– 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 ablakkerdes.showAndWait()– vár a gombnyomásraPlatform.exit()– a teljes alkalmazás leállításastage.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éssb.append("\n")– új sor hozzáadásalabel.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írjasw.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
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ásasw.write(szoveg)– adat kiírásasw.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önsplitSor[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.
Initializableinterfé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
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
/*
<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 gombokMessageBoxImage.Question– kérdőjel ikon;MessageBoxImage.Warning– figyelmeztető ikonMessageBoxResult.Yes– ha a felhasználó Igen-t nyomottClose()– 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
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-benFilter– fájltípus szűrő:"Leírás (*.ext)|*.ext"formátumdialog.ShowDialog() == true– csak akkor fut le a blokk, ha fájlt választottakdialog.FileName– a kiválasztott fájl teljes elérési útjaFile.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ásagetExtensionFilters().add(...)– fájltípus szűrők hozzáadásashowOpenDialog(stage)– az ablak megjelenítése; null-t ad vissza, ha megszakítottákFiles.readString(path)– modern Java (11+) módszer a teljes fájl beolvasására
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ásashowSaveDialog(stage)– mentési ablak megnyitásaFiles.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átHeight="*"– a maradék helyet foglalja el (mintflex: 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éscolumnIndex– az elem pozíciója a rácsbanhgapésvgap– a cellák közötti távolság (pixelben)padding– a rács belső margójaColumnConstraints– oszlopszélességek és nyújtási szabályok definiálása
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övetiDataContext = this– az ablak önmagát adja meg adatforrásként; ezután az XAML Binding-ok az ablak property-ire hivatkoznakItemsSource="{Binding Nevek}"– XAML adatkötés aNevekproperty-hezlistBox.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ásalistView.setItems(lista)– a lista összekötése a UI-vallista.add(elem)– a UI azonnal megjeleníti az új elemetlistView.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ástDataGridTextColumn Header="Felirat" Binding="{Binding Nev}"– egy szöveges oszlop, amely aNevproperty-t jeleníti megWidth="*"– az oszlop a maradék szélességet foglalja elIsReadOnly="True"– csak olvasható táblázat (a felhasználó nem szerkesztheti)SelectionChangedesemé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ályTableColumn<T, Tipus>– egy oszlop definiálásaPropertyValueFactory("nev")– agetNev()getterhez köti az oszlopotgetSelectionModel().selectedItemProperty()– a kiválasztott elem változásának figyelése
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ásamvn 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; }– azIdnevű property-t az EF Core automatikusan elsődleges kulcsnak vesziDbSet<Termek> Termekek– aTermekektábla C# reprezentációja; ezen futnak a lekérdezésekOnConfiguring()– a kapcsolati karakterlánc megadásának helyeoptions.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ásaJpaRepository<T, ID>– interfész, amely automatikusan biztosítja az alapvető műveleteketapplication.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 nincselso.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 szerverhezSpring Data JPA– adatbázis-kezeléshez (Hibernate)MySQL Driver– a MySQL kapcsolathozValidation– 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áncaspring.jpa.hibernate.ddl-auto=update– automatikus táblafrissítés a modell alapjánspring.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ályfindBy[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ésesession.get(Class, id)– lekérés azonosító alapjánsession.merge(entity)– meglévő rekord frissítésesession.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éseWHERE,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