Ú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.
Tartalomjegyzék
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.