Úgy látom, hogy korábban nem beszéltem a rendeléseket rögzítő szoftverem elvi felépítéséről, a különféle kérések kezeléséről, az adatok kiszolgálásáról. Itt jön képbe az MVC és a DAO, most ezeken keresztül mutatom be, hogy működik a programom.
Ha megnézzük a StudiCore Java SE tanfolyamának részleteit, ott az Adatbázisok + Java SE haladó eszközök kurzus 35. fejezete ez: JDBC II. – A DAO minta használata az adatbáziskezelésben. A DAO lényege, hogy a különféle adatbázisműveleteket DAO osztályokba tesszük, és objektumokat adunk át nekik, minden adattáblát önálló DAO osztály kezel.
Gondolom az előnye világos, ahelyett, hogy véletlenszerűen írogatnánk a sokféle forrásfájlba az adatbázis hívásokat, egy vagy több DAO osztályba tesszük ezeket, így módosításkor mindent könnyen megtalálunk, és nem lesz keveredés.
Az MVC pedig a szoftvertervezésben használatos szerkezeti minta, amely szétválasztja az adatok kezelését és a felhasználói felületet. Így anélkül lehet az adatokkal foglalkozó részt módosítgatni, hogy a felhasználók bármit is észre vennének belőle. Ezt az ötletet a mentorom adta még a tervezés korai szakaszában, és ennem megfelelően terveztem meg a programot.
DAO a gyakorlatban
A rendeléseket kezelő szoftverem többféle menüponttal rendelkezik, ahogy a képen is látható, a vállalkozás menüpontban a vállalkozásainkat adhatom hozzá, módosíthatom vagy törölhetem, a vevő menüpontban a vevőket, a megrendelésnél a megrendeléseket, a munkatársaknál a munkatársakat, az áfánál pedig az áfa típusokat.

Most az áfa segítségével mutatom be a szoftver tervét. A program elindulása utáni főképernyő a CompanyMain osztály, ennek feladata kirajzolni a JFrame alapot, és rátenni a menüt. Lesz még más is a kezdő képernyőn, de erről majd később.
Áfa esetében a VatController osztály látja el fő feladatokat, hozzá fordul mindenki más.
A VatDetailsDialog egy olyan űrlap, melyben fel tudom venni az áfa adatokat, vagy módosításkor ebben írhatom át a már adatbázisban szereplő dolgokat.
Az adatbázisműveleteket a VatDAO osztály végzi el, és ott van még a Vat osztály, mely áfa objektumok készítésére való.
Új Áfa kulcs hozzáadása
Hogyan is működik az új Áfa kulcs felvétele?
A CompanyMain Áfa menüpontjából kiválasztom az Új Áfa kulcs menüpontot. Erre a menüpontra van felfűzve egy newVatItemActionPerformed metódus, a menüre való kattintáskor ez fut le.
private void newVatItemActionPerformed(java.awt.event.ActionEvent evt) {
VatController controller = new VatController();
try
controller.addNewVat();
} catch (SQLException ex) {
}
}
Ez létrehoz egy VatController-t, és meghívja az addNewVat() metódusát. Ezen kívül nem tud mást, azt sem tudja a CompanyMain, hogy milyen adatbázis van a rendszer mögött, milyen táblákkal, ő csak szól a VatControllernek, hogy adjon hozzá egy új áfát.
A VatController konstruktorában helyeztem el egy hívást, ahol létrehozok egy VatDAO típusú objektumot:
vatRepo = new VatDAO();
Az addNewVat() metódus feldob egy VatDetailsDialog ablakot, ahol fel tudom vinni az új Áfa adatait.
public void addNewVat() throws SQLException {
VatDetailsDialog vatDetails = new VatDetailsDialog(frame, modal);
vatDetails.setVisible(true);
vatRepo.save(new Vat(vatDetails.getName(), vatDetails.getValue(), vatDetails.isDomestic()));
}
Így néz ki az új Áfa felvitele ablak:

A VatController által elküldött Vat objektumot fogadja a VatDAO osztály save metódusa:
public void save(Vat vat) throws SQLException {
if (vat.getVatId() == null) {
insert(vat);
} else {
update(vat);
}
}
Ez megnézi, hogy van-e ID (azonosító) beállítva Vat objektumnak. Ha nincs, akkor új Áfa, és majd az adatbázis automatikusan ad neki egy azonosítót. Ha viszont van ID-je, akkor ez már egy adatbázisból kiolvasott áfatípus, amit nem most hozok létre, hanem csak módosítom az adatait.
Az én esetemben ezt a Vat objektumot adtam át:
new Vat(vatDetails.getName(), vatDetails.getValue(), vatDetails.isDomestic());
Ahogy látszik, itt nem volt ID, ezért a VatDAO save metódusa átriányít az insert metódushoz, és annak továbbítja is ezt a Vat objektumot.
A VatDAO osztály konstruktora már tartalmaz egy csomó PreparedStatement-et:
this.insert = conn.prepareStatement("INSERT INTO vat (abbr, value, domestic) VALUES (?, ?, ?)");
this.update = conn.prepareStatement("UPDATE vat SET abbr = ?, value = ?, domestic = ? WHERE vat_id = ?");
this.findAll = conn.prepareStatement("SELECT * from vat");
this.findById = conn.prepareStatement("SELECT * FROM vat WHERE vat_id = ?");
this.delete = conn.prepareStatement("DELETE FROM VAT WHERE vat_id = ?");
Ahogy látszik, ezek már konkrét SQL műveletek, itt jön elő a DAO-elv, a VatDAO osztály kezeli a vat nevű adattáblát az adatbázisban.
Szerepel még ott egy conn, ami a private Connection conn; akar lenni. Csináltam egy külön DatabaseConnector osztályt, aminek az a feladata, hogy tárolja az adatbázis hosztját, portját, felhasználónevét, jelszavát, és akármi is változik az adatbázisba történő belépéskor, azt ebben az egyetlen egy osztályban kell csak átírni. A sokféle DAO osztály már csak a Connection-t kapja meg, nem lát rá a konkrét adatbázis belépési adatokra.
Tehát megkaptuk a DatabaseConnector-tól a Connection-t, és a VatDAO konstruktorában csináltunk egy csomó PreparadStatement-et. Nekünk az új áfa hozzáadásához az insert kell:
this.insert = conn.prepareStatement("INSERT INTO vat (abbr, value, domestic) VALUES (?, ?, ?)");
azaz, szúrd be a vat adattáblába az abbr, value és domestic oszlopokba a következő értékeket: ?, ?, ?.
Hogy mi ez a három kérdőjel, arról a VatDAO osztály insert metódusa gondoskodik:
public void insert(Vat vat) throws SQLException {
this.insert.setString(1, vat.getAbbr());
this.insert.setDouble(2, vat.getValue());
this.insert.setBoolean(3, vat.isDomestic());
this.insert.executeUpdate();
}
azaz, megkapja az áfa Vat objektumunkat a kontrolleről, és a VatDao save metódusától, attól lekérdezi az új áfa rövidítését, értékét, és hogy belföldi-e, majd ezeket a PreparadStatement segítségével végrehajtja az adatbázison.
És kész, az új Áfa bekerül az adatbázisba. Az esetleges kivételeket le kell kezelni, de ez már csak a dolog szépségét fokozza, az alapfeladat ez lenne.
Na most felmerül a kérdés, hogy mi történik, ha a felhasználó süti, hogy az áfa értékénél mondjuk szöveget ad meg? A Java dob egy hibát, és leáll. De ha mindent jól csinálunk, akkor sem értesülünk arról, hogy az új áfa bekerült az adatbázisba.
Erre vezettem be a VatDetailsDialog űrlapnál egy fieldCheck metódust:
private boolean fieldCheck() {
boolean back = true;
String errorMessage = "A következő hibákat találtam a kitöltés közben: \n";
if (nameField.getText().equals("")) {
back = false;
errorMessage += "- A név mező nem lehet üres.\n";
}
if (valueField.getText().equals("")) {
back = false;
errorMessage += "- Az érték mező nem lehet üres.\n";
} else {
try {
Double.parseDouble(valueField.getText());
} catch (NumberFormatException ex) {
back = false;
errorMessage += "- A érték mező csak számokat tartalmazhat.\n";
}
}
this.errorMessage = errorMessage;
return back;
}
Ez még koránt sem teljes, most azt vizsgálom, hogy minden mező ki lett-e töltve, és az értéknél csak számok vannak-e. Ahogy a múltkori bejegyzésemnél írtam, ez a NumberFormatException kivétel dobása valószínűleg nem túl elegáns, regex kifejezéssel szebben lehetne ellenőrizni, de ez majd egy következő feladat lesz.
A lényeg, hogy ez a fieldCheck metódus igaz értékkel tér vissza, ha jók az új áfa űrlapba írt adatok, és hamis értékkel, ha valami nem oké.
A VatDetailsDialog-on két gomb van, a Küldés és a Mégsem. A Mégsem gombra fűzött eseményt figyelő metódus egyszerű:
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {
this.dispose();
}
azaz, a Mégsem gomb megnyomására bezárja az új Áfa hozzáadása ablakot, nem történik semmi. Pontosabban visszatérünk a CompanyMain ablakához.
A Küldés gomb ennél bonyolultabb:
private void sendButtonActionPerformed(java.awt.event.ActionEvent evt) {
if (!fieldCheck()) {
JOptionPane.showMessageDialog(new javax.swing.JDialog(), errorMessage, "Hiba a kitöltés során!", JOptionPane.ERROR_MESSAGE);
} else {
JOptionPane.showMessageDialog(new javax.swing.JDialog(), "Az adatok sikeresen elmentésre kerültek.");
this.dispose();
}
}
azaz, ha a fieldCheck metódus szerint valami gond van, akkor feldobunk egy hibaablakot, amiben megmutatjuk a hibákat:

Ezt le lehet okézni, de ismét visszajutunk az új Áfa ablakunkhoz. Azt vagy elvetjük a Mégsem gombbal, vagy javítjuk a hibákat, míg a fieldCheck mindent rendben nem talál. Ekkor még feldobok egy megerősítést, hogy az adatokat sikeresen elmentettem az adatbázisba, és csakis ekkor engedem bezárni a VatDetailsDialog ablakot a dispose() paranccsal.
Na most jelenleg nem világos, hogy a fieldcheck() és a hibaüzenetek dobása maradhat-e a VatDetailsDialog-ban, vagy ezt is a VatControllernek kell intéznie. Nekem ez a megoldás tetszik, a mentorom majd megmondja, hogy a programtervezési és -írási konvenciók szerint hogyan szokták a programozók.
Áfa kulcs módosítása vagy törlése
Az áfa kulcs módosítására vagy törlése is a CompanyMain osztályból indul, az ÁFA kulcs módosítása/törlése menüponttal:

Erre a menüpontra a következő metódus van felfűzve:
private void editDeleteVatItemActionPerformed(java.awt.event.ActionEvent evt) {
VatController controller = new VatController();
try {
controller.modifyDeleteVat();
} catch (SQLException ex) {
System.out.println("Hiba: " + ex.getMessage());
}
}
Na most ebben a metódusban és az új áfa metódusában is egy VatController objektumot hozok létre, esetlegesen a VatController controller létrehozása betehető a CompanyMain konstruktorába, vagy egy makeControllers() metódusba, amit a konstruktor hív meg, és akkor itt már csak használom ezt a közös objektumot. Ilyen kérdések még tisztázásra várnak, a szoftver finomítása során lehet ezekkel majd foglalkozni.
Ami most a lényeg, szólunk a VatController-nek, hogy a modifyDeleteVat() metódussal induljon el az Áfa törlése vagy módosítása.
Ez a metódus most így néz ki:
public void modifyDeleteVat() throws SQLException {
List<Vat> vatList = vatRepo.findAll();
ModifyDeleteDialog modifyDelete = new ModifyDeleteDialog(new javax.swing.JFrame(), true, vatList);
modifyDelete.setVisible(true);
if (modifyDelete.getSelectedEvent() != null) {
if (modifyDelete.getSelectedEvent().equals("delete")) {
deleteVat(Integer.valueOf(modifyDelete.getSelectedId()));
vatList = vatRepo.findAll();
modifyDelete = new ModifyDeleteDialog(new javax.swing.JFrame(), true, vatList);
modifyDelete.setVisible(true);
} else if (modifyDelete.getSelectedEvent().equals("modify")) {
modifyVat(Integer.valueOf(modifyDelete.getSelectedId()));
vatList = vatRepo.findAll();
modifyDelete = new ModifyDeleteDialog(new javax.swing.JFrame(), true, vatList);
modifyDelete.setVisible(true);
}
}
}
A VatDAO-tól kérek egy olyan listát, ami tartalmazza az összes áfa Vat objektumot. Ez a vatRepo.findAll();
Ezután meghívom a ModifyDeleteDialog osztályt, ezt azért készítettem, hogy az összes menüpont módosítását vagy törlését elvégezze. Áfa esetében ez ugrik fel:

Munkatársak módosításánál meg amúgy így nézne ki:

Ahogy látszik, ez egy univerzális ablak, ami kap egy listát, ez esetben az áfákat, majd megmutatja őket egy táblázatban. Ezután választhatok, hogy a kijelölt elemet törölni vagy módosítani szeretném.
A ModifyDeleteDialog generikus osztály lett, de ezt majd egy következő bejegyzésben mutatom be, mert most nem erről a témáról van szó.
Akkor ugye ott tartunk, hogy a ModifyDeleteDialog konstruktora megkapta az áfák listáját a VatController-től:
ModifyDeleteDialog modifyDelete = new ModifyDeleteDialog(new javax.swing.JFrame(), true, vatList);
majd láthatóvá is teszem ezt az ablakot:
modifyDelete.setVisible(true);
Ezután a felhasználó kijelöli azt az áfát, amivel foglalkozni szeretne, és megnyomja vagy a Szerkesztés, vagy a Törlés gombot. Van ott egy Mégse gomb is, az csak bezárja az ablakot, és visszatérünk vele a CompanyMain főablakhoz.
Nézzük a két érdekesebb gombot:
private void editButtonActionPerformed(java.awt.event.ActionEvent evt) {
selectedEvent = "modify";
this.dispose();
}
private void deleteButtonActionPerformed(java.awt.event.ActionEvent evt) {
selectedEvent = "delete";
this.dispose();
}
Annyi történik, hogy a Szerkesztés gomb megnyomása után egy String selectedEvent változóba a „modify” érték kerül, a Törlés gomb megnyomásakor pedig a „delete” érték. Ezután a dispose() utasítással bezárom a ModifyDeleteDialog ablakot.
Ezután visszatérünk a VatControllerhez:
if (modifyDelete.getSelectedEvent() != null) {
if (modifyDelete.getSelectedEvent().equals("delete")) {
deleteVat(Integer.valueOf(modifyDelete.getSelectedId()));
vatList = vatRepo.findAll();
modifyDelete = new ModifyDeleteDialog(new javax.swing.JFrame(), true, vatList);
modifyDelete.setVisible(true);
} else if (modifyDelete.getSelectedEvent().equals("modify")) {
modifyVat(Integer.valueOf(modifyDelete.getSelectedId()));
vatList = vatRepo.findAll();
modifyDelete = new ModifyDeleteDialog(new javax.swing.JFrame(), true, vatList);
modifyDelete.setVisible(true);
}
}
a ModifyDeleteDialog-tól a getSelectedEvent metódussal kérdezem le, hogy delete vagy modify volt a String-be elmentett érték ennek megfelelően pedig meghívom a VatControler deleteVat vagy a modifyVat metódusát. A táblázatban kiválasztott áfa azonosítóját mindkét esetben a ModifyDeleteDialog mondja el, a getSelectedId() metódusán keresztül. Erről majd bővebben akkor lesz szó, mikor bemutatom ezt az osztályt is.
Először nézzük a törlést:
deleteVat(Integer.valueOf(modifyDelete.getSelectedId()));
Egy azonosítót adunk át a VatController deleteVat metódusának, ami így néz ki:
public void deleteVat(int id) throws SQLException {
vatRepo.delete(id);
}
Ez pedig nem csinál mást, mint a VatDAO delete metódusának passzoja tovább az azonosítót.
Ugye a VatDAO törlési PreparadStatement-je ez volt:
this.delete = conn.prepareStatement("DELETE FROM vat WHERE vat_id = ?");
a delete metódus pedig a kérdőjel számára átadja az id-t:
public void delete(int id) throws SQLException {
this.delete.setInt(1, id);
this.delete.executeUpdate();
}
Ezzel már az SQL utasítás kiadható, töröld a vat adattáblából azt a rekordot, ahol az id a megkapott azonosítóval egyenlő.
És akkor a módosítás:
A VatController modifyVat metódusa:
public void modifyVat(int id) throws SQLException {
Vat vat = vatRepo.findById(id);
if (vat == null) {
System.out.println("Hiba: a ModifyDialog üres VAT-ot ad át a VatDetailsDialognak");
} else {
VatDetailsDialog vatDetails = new VatDetailsDialog(frame, modal, vat );
vatDetails.setVisible(true);
vatRepo.save(new Vat(vatDetails.getId(), vatDetails.getName(), vatDetails.getValue(), vatDetails.isDomestic()));
}
}
azaz, feldobunk egy VatDetailsDialog ablakot, de ezúttal át is adunk neki egy konkrét áfa Vat objektumot, ez pedig betölti az adott áfa adatait az űrlapba. Képeken ez így néz ki:



A VatDetailsDialog ezután ugyanúgy működik, mint az új áfa hozzáadásakor már mutattam. A küldés gomb hatására bezárul, a VatController pedig meghívja a VatDAO save metódusát. A különbség annyi, hogy mivel most így hívja meg:
vatRepo.save(new Vat(vatDetails.getId(), vatDetails.getName(), vatDetails.getValue(), vatDetails.isDomestic()));
ezért ott lesz az ID is az Vat objektumban, és így a Save nem az INSERT SQL utasítást futtatja le, hanem az UPDATE-t. A PreparedStatement:
this.update = conn.prepareStatement("UPDATE vat SET abbr = ?, value = ?, domestic = ? WHERE vat_id = ?");
és a VatDAO update metódusa:
public void update(Vat vat) throws SQLException {
this.update.setString(1, vat.getAbbr());
this.update.setDouble(2, vat.getValue());
this.update.setBoolean(3, vat.isDomestic());
this.update.setInt(4, vat.getVatId());
this.update.executeUpdate();
}
tehát frissítsd a vat adattáblában az elvenezést az első paraméterre, az értéket a másodikra, a belföldiséget a harmadikra, ahol az áfa azonosítója a negyedik paraméterrel egyenlő.
Úgy gondoltam, hogy legyen szó törlésről vagy módosításról, előfordulhat, hogy én még további áfa kulcsokat is szeretnék módosítani. Ehhez ne kelljen már a főmenüben bogarászni, ha már úgyis mutatom az áfák listáját a ModifyDeleteDialog ablakban, akkor maradjon is aktív a módosítás vagy törlés után.
Így a VatController modifyDeleteVat metódusa a törlés vagy módosítás után ezt csinálja:
vatList = vatRepo.findAll(); modifyDelete = new ModifyDeleteDialog(new javax.swing.JFrame(), true, vatList); modifyDelete.setVisible(true);
A VatDAO segítségével lekéri a frissített áfalistát, majd feldobja újra az áfalista táblázatát, de most már a módosítás/törlés utáni értékekkel. Itt, ha törlök/módosítok, akkor megint frissít és feldobja megint, ha nem akarok már semmit csinálni, akkor a Mégse gombbal visszatérek a CompanyMain főablakba.
Videós bemutató
Készítettem egy gyors videót is, hogyan működik most a szoftver. Sok gyermekbetegsége van még, de a lényeg látható. Hozzáadok egy új áfa kulcsot, módosítok, törlök. Valamilyen hiba folyamán előfordul néha, hogy a gombra kattintást nem észleli a szoftver, ilyenkor bezárja a dialógusablakot, ez kétszer is megjelent. Ezt majd kinyomozom, mert ez nem programozási hiba lesz, hanem valami olyan ok, ami vagy a Swinggel, vagy a Netbeans-szel vagy az együttműködésükkel jár.
Egy személyes észrevétel még a végére, tavaly nyáron annyit tudtam a Java-ról, hogy van feltétel, meg for ciklus. Ma meg saját nyilvántartó szoftvert készítek magamnak, és ami a legjobb, hogy nagyon élvezem is. Nem gondoltam volna, hogy idáig eljutok. A részleteket ti is elolvashatjátok a StudiCore kategória bejegyzéseit böngészve.

Vélemény, hozzászólás?