Java saját szoftver készítése 9. rész – MVC és DAO az Áfa modulon keresztül

Ú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.

fomenu.jpg

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:

ujafa.jpg

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:

hibasafa.jpg

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:

fomenu.jpg

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:

afamodify.jpg

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

employeemodify.jpg

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:

fomenu.jpg
afamodify.jpg
afamodositasa.jpg

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.