Refactoring POJO puro Pattern di registrazione attiva con pattern di deposito, senza framework ORM o DI per motivi pedagogici

4

Ho insegnato un corso anni fa, e poi ho usato alcuni esercizi JDBC usando quello che ora conosco come Active Record Pattern.

Mi piacerebbe modernizzare l'esercizio modificando il pattern Active Record in modo che le classi non facciano la loro persistenza. Ma non vorrei usare alcuna struttura di persistenza o ORM per scopi didattici.

Una classe di esempio è Vehicle:

  • Riceve una connessione e un numero di targa nel costruttore.
  • Nel costruttore interroga il database e popola i membri della classe e un membro booleano existInDB è impostato su true.
  • Se la piastra non viene trovata nel DB, existInDB rimane falsa.
  • Puoi cambiare lo stato dell'oggetto con i setter.
  • Se chiami save() , inserisce nel database se existInDB è falso o aggiorna una riga nel database se existInDB è falso.
  • Dopo il refactoring la classe non sarebbe un semplice oggetto di trasferimento dati perché ha un metodo doSomeBusinessThing() che fa qualcosa di business.
  • Tutte le altre classi come Maker, Owner, usano anche il pattern Active Record, ma possiamo lasciarle da sole per ora.

Questo non è un codice di produzione. È materiale del corso Java SE. Non voglio introdurre framework per le persone che stanno imparando la lingua e / oi principi OOP per la prima volta. Un requisito della mia domanda è che deve essere basato su POJO. Forse usando un modello di design ma senza framework esterni o ORM.

Vorrei leggere i tuoi consigli su come eseguire un refactoring di questo tipo.

La classe del veicolo:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Vehicle {

    private String plate;
    private Model model;
    private Owner owner;
    private String color;
    private String status;
    private int year;
    private boolean existInDB= false;
    private Connection conn;
    private ResultSet rs;

    public Vehicle(Connection con_,String plate_) throws SQLException {
        String sql=
        "select \n"+ 
        "       v.plate, \n" +
        "       v.model_id, \n"+
        "       v.owner_id, \n"+
        "       v.color, \n"+
        "       v.status, \n"+
        "       v.year \n"+
        "from \n"+
        "     vehicle v \n"+
        "where \n"+
        "     v.plate = ? \n";

        PreparedStatement ps = con_.prepareStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
        ps.setString(1, plate_);
        rs = ps.executeQuery();

        plate = plate_;
        conn = con_;

        if (rs.next()){         
            try {
                model = new Model(conn,rs.getString("model_id"));
            } catch (UnknownMakerException e) {
                e.printStackTrace();
            } catch (UnknownModelException e) {
                e.printStackTrace();
            }
            try {
                owner = new Owner(conn,rs.getInt("owner_id"));
            } catch (UnknownOwnerException e) {
                e.printStackTrace();
            }
            color = rs.getString("color");
            status = rs.getString("status");
            year = rs.getInt("year");
            existInDB = true;   
        }

    }

    public void save() throws SQLException{
        if (existsInDB()){
            rs.absolute(1);
            rs.updateString("plate",plate);
            rs.updateString("model_id", model.getID());
            rs.updateInt("owner_id", owner.getID());
            rs.updateString("color", color);
            rs.updateString("status", status);
            rs.updateInt("year", year);
            rs.updateRow();             
        } else {
            rs.moveToInsertRow();
            rs.updateString("plate", plate);
            rs.updateString("model_id", model.getID());
            rs.updateInt("owner_id", owner.getID());
            rs.updateString("color", color);
            rs.updateString("status", status);
            rs.updateInt("year", year);
            rs.insertRow();     
        }


    }

    public String getPlate() {
        return plate;
    }

    public Model getModel() {
        return model;
    }

    public Owner getOwner() {
        return owner;
    }

    public String getColor() {
        return color;
    }

    public String getStatus() {
        return status;
    }

    public int getYear() {
        return year;
    }

    public boolean existsInDB() {
        return existInDB;
    }

    public void setPlate(String plate) {
        this.plate = plate;
    }

    public void setModel(String  modelID_) throws SQLException, UnknownModelException {
        try {
            this.model = new Model(conn,modelID_);
        } catch (UnknownMakerException e) {
            e.printStackTrace();
        }
    }

    public void setOwner(int ownerID) throws SQLException, UnknownOwnerException {
        this.owner = new Owner(conn,ownerID);
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public void setYear(int year) {
        this.year = year;
    }

    protected void finalize() throws Throwable,  SQLException
    {
        try {
            PreparedStatement stmt = (PreparedStatement) rs.getStatement();
            rs.close();
            stmt.close();
            stmt = null;
        } finally {
            super.finalize();
        }
    }   

    public String getDescription(){
        return
        this.getModel().getMaker().getName() + " " +
        this.getModel().getName() + " " +
        this.getColor() + " " +
        this.getYear();

    }

    public String toString(){
        return
        "\nVEHICLE" +
        "\nPlate: " +this.getPlate() + 
        "\nModel ID: " +this.getModel().getID() + 
        "\nOwner ID: " +this.getOwner().getID() +
        "\nColor: " +this.getColor() +
        "\nStatus: " +this.getStatus() +
        "\nYear: " +this.getYear();
    }

    public void doSomeBusinessThing(){
        /* some important business logic just to clarify 
         * that this is not a simple data transfer object
         */

    }

}
    
posta Tulains Córdova 19.08.2016 - 14:24
fonte

2 risposte

2

Lasciando il più possibile il tuo codice originale, rifatterò la classe Vehicle in modo che sia costruita utilizzando una nuova interfaccia Importer e salvata utilizzando una nuova interfaccia Exporter.

public class Vehicle {

    // fields omitted

    public Vehicle(Importer importer) {
        plate = importer.plate();
        model = importer.model();
        owner = importer.owner();
        color = importer.color();
        status = importer.status();
        year = importer.year();
    }

    public interface Importer {
        String plate();
        Model model();
        Owner owner();
        String color();
        String status();
        int year();
    }

    public void save(Exporter exporter) {
        exporter.plateIs(plate)
                .modelIs(model)
                .ownerIs(owner)
                .colorIs(color)
                .statusIs(status)
                .yearIs(year)
                .export();
    }

    public interface Exporter {
        Exporter plateIs(String plate);
        Exporter modelIs(Model model);
        Exporter ownerIs(Owner owner);
        Exporter colorIs(String color);
        Exporter statusIs(String status);
        Exporter yearIs(int year);
        void export();
    }

    // rest of class unchanged
}

A questo punto la classe non fa più la propria persistenza e possiamo scrivere importatori ed esportatori per varie tecnologie di persistenza. Ora scrivo un SqlVehicleImporter (utilizzando il codice SQL originale) ...

public class SqlVehicleImporter implements Vehicle.Importer {

    private String plate;
    private Model model;
    private Owner owner;
    private String color;
    private String status;
    private int year;

    public SqlVehicleImporter(Connection con_, String plate_) throws SQLException {
        String sql=
                "select \n"+
                        "       v.plate, \n" +
                        "       v.model_id, \n"+
                        "       v.owner_id, \n"+
                        "       v.color, \n"+
                        "       v.status, \n"+
                        "       v.year \n"+
                        "from \n"+
                        "     vehicle v \n"+
                        "where \n"+
                        "     v.plate = ? \n";

        PreparedStatement ps = con_.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
        ps.setString(1, plate_);
        ResultSet rs;
        rs = ps.executeQuery();

        plate = plate_;

        if (rs.next()){
            try {
                model = new Model(con_,rs.getString("model_id"));
            } catch (UnknownMakerException e) {
                e.printStackTrace();
            } catch (UnknownModelException e) {
                e.printStackTrace();
            }
            try {
                owner = new Owner(con_,rs.getInt("owner_id"));
            } catch (UnknownOwnerException e) {
                e.printStackTrace();
            }
            color = rs.getString("color");
            status = rs.getString("status");
            year = rs.getInt("year");
        }

        rs.close();
        ps.close();
    }

    // interface implementation omitted
}

... e un SqlVehicleExporter.

public class SqlVehicleExporter implements Vehicle.Exporter {

    private final Connection connection;

    // fields omitted

    public SqlVehicleExporter(Connection connection) {
        this.connection = connection;
    }

    public Vehicle.Exporter plateIs(String plate) {
        this.plate = plate;
        return this;
    }

    // repetitive interface methods omitted

    public void export() {
        String sql=
                "select \n"+
                        "       v.plate, \n" +
                        "       v.model_id, \n"+
                        "       v.owner_id, \n"+
                        "       v.color, \n"+
                        "       v.status, \n"+
                        "       v.year \n"+
                        "from \n"+
                        "     vehicle v \n"+
                        "where \n"+
                        "     v.plate = ? \n";

        try {
            PreparedStatement ps = connection.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
            ps.setString(1, plate);
            ResultSet rs = ps.executeQuery();
            boolean existsInDB = rs.next();

            if (existsInDB) {
                rs.absolute(1);
                rs.updateString("plate", plate);
                rs.updateString("model_id", model.getID());
                rs.updateInt("owner_id", owner.getID());
                rs.updateString("color", color);
                rs.updateString("status", status);
                rs.updateInt("year", year);
                rs.updateRow();
            } else {
                rs.moveToInsertRow();
                rs.updateString("plate", plate);
                rs.updateString("model_id", model.getID());
                rs.updateInt("owner_id", owner.getID());
                rs.updateString("color", color);
                rs.updateString("status", status);
                rs.updateInt("year", year);
                rs.insertRow();
            }
        } catch(SQLException error) {
            error.printStackTrace();
        }
    }
}

A questo punto esiste un codice comune tra l'importatore e l'esportatore, quindi potrebbe essere una buona idea combinarli.

    
risposta data 02.09.2016 - 14:51
fonte
2

Ecco il mio approccio. Basato su questo presupposto:

I would like to modernize the exercise by changing the Active Record Pattern so classes don't do their own persistence

I would not like to use any persistence or ORM framework for teaching purposes.

I don't want to introduce frameworks to people who is learning the language and/or OOP principles for the first time. One requirement of my question is that it must be POJO-based. Maybe using a design pattern but no external frameworks or ORM

Separazione delle preoccupazioni

Sembra evidente che @Tulains vorrebbe insegnare che in OOP, la separazione delle preoccupazioni è importante. Questo pattern design farà riflettere su "chi è responsabile per ogni compito". Ci condurrà a codificare più componenti (in effetti), ma sarà più semplice da capire.

1. POJO Prima preoccupazione Il POJO stesso. Come POJO significa (Plain Old Java Object) è solo un semplice contenitore di dati, che, nel nostro caso, ha anche alcune funzionalità come doSomeBusinessThing . È importante sottolineare qui agli studenti che dovrebbero rendersi conto di come a Vehicle non interessa più come-quando Model e Onwer sono stati ottenuti o persi ... Non è un problema: -). È un interesse

di un altro
public class Vehicle {

   private String plate;
   private Model model;
   private Owner owner;
   private String color;
   private String status;
   private int year;
   private boolean existInDB;

   public Vehicle(String plate){
      this.plate = plate;
      existInDB = false;
   }

   //Define as many constructs you need here... We only need one atm

   //Getters and setters here...
   ...
   //getDescriptions and toString methods here
   ...

   public void doSomeBusinessThing(){
   }
}

NOTA : credo che alcune persone discutano sul motivo per cui ho mantenuto existInDB invece di implementare un metodo DAO specifico per tale scopo. Lascerò quel refactoring a @Tulains. Può introdurre quali cambiamenti sono necessari e le sue implicazioni (per ogni inserto o aggiornamento, deve essere eseguita un'altra query per verificare se il veicolo esiste o no ...)

2. DAO Secondo preoccupazione , l'accesso al database. Tutto ciò che riguarda l'accesso al database è buono per mantenerlo semplice e riutilizzabile. Qui affrontiamo il primo numero. PreparedStatements sono diversi per ogni POJO (select, insert e updates) quindi creerò un DAO per Entity. Scriverò solo uno di loro. I 3 DAO sono molto simili. Una volta implementato, @Tulains, hai una situazione eccellente per introdurre ereditarietà attraverso classi astratte.

Potrei anche fare DAO per restituire Veicolo , ma poi VehicleDAO sarebbe costretto ad avere accesso ai dati Model e Onwer. Devo essere conseguente al mio progetto. Quindi consentirò ad altri componenti di gestire i dati di Model e Owner.

public class VehicleDAO {
      private DataSource ds;
      private static final String SELECT_VEHICLE_STMNT = "select ...";
      private static final String INSERT_VEHICLE_STMNT = "insert ...";
      private static final String UPDATE_VEHICLE_STMNT = "update ...";
      public VehicleDAO (DataSource ds){
         this.ds = ds;
      }

      public Map<String,Object> find(Stirng plate) throws VehicleNotExistException, SQLException{
          // ... PreparedStatement here
          // ... dump rs data into a Map
          // ... if rs.next() fails. Then throw VehicleNotExistException
          // ... rs.close();
          //I do return a Map because I want they know that the responsable of RS is the DAO. It is also responsable of to close it
          return map;
      }

      public void saveOrUpdate(Vehicle vehicle) throws SQLException {
         if(vehicle.existsInDB()){
             //PreparedStatement for upate here
         }else{
             //PreparedStatement for insert here
         }             
         // I have decided to use preparedStatements insted of ResulSet API like it was in the original code. I have been thinking on myBatis which works with prepared statements all the way            
      }

      private Connection getConnection(){
          //creates a new connection or recover an existing one.
      }
}

3. App

Nella mia precedente risposta, il componente successivo era una sorta di "Servizio" che si trovava da qualche parte in un livello aziendale. A causa dello scopo di questo post è di introdurre Java per i principianti, ho rimosso tale componente perché ci sono troppi concetti impliciti di design dietro e potrebbe portare gli studenti alla confusione. Quindi ho trasformato quel servizio in una classe principale. L'obiettivo è spiegare le basi di java e jdbc.

public class MyApp {

      private static VehicleDAO vDao;
      private static ModelDAO mDao;
      private static OwnerDAO oDao;

      public static void main(String[] arg){
            bootstrap();
            demoRead();
            demoSaveOrUpdate();
            shutdown();
      }

      public static void demoSaveOrUpdate() throws Exception{
            Map<String, Object> vehicleMap = vDao.find("plate");
            Vehicle vehicle = new Vehicle(vehicleMap.get("plate"));

            if(vehicleMap.get("model_ID") != null){
                Map<String, Object> modelMap = mDao.find(vehicleMap.get("model_ID"));
                Model model = new Model(modelMap.get("id"));
                model.set...
                //and so on..
                vehicle.setModel(model);
            }

            if(vehicleMap.get("owner_ID") != null){
                Map<String, Object> ownerMap = oDao.find(vehicleMap.get("owner_ID"));
                Owner owner = new Owner(ownerMap.get("id"));
                owner.set...
                //and so on..
                vehicle.setOwner(owner);
            }

            model.setYear(modelMap.get("year"));
            //and so on...

            //Final result
            System.out.println("Vehicle description: " + vehicle.getDescription());                

      }

      public static void demoSaveOrUpdate() throws Exception{
           Vehicle vehicle = new Vehicle("plate");
           vehicle.setModel(new Model());
           vehicle.getModel().setName("ModelA");
           //and so on...

           vehicle.setOwner(new Owner());
           vehicle.getOwner().setName("OwnerName");
           //and so on...

           vehicle.setColor("color");
           vehicle.setYear(2016);
           //and so on...

           //First we persist model and owner to garantee 
           //data integrity at DB
           mDao.saveOrUpdate(vehicle.getModel());               
           oDao.saveOrUpdate(vehicle.getOwner());
           //once Owner and Model are persisted, we can persist Vehicle
           vDao.saveOrUpdate(vehicle);               

      }

      private static shutdown() throws Exception {
           //set DataSource to null
           //set daos to null
      }

      private static void bootstrap() throws Exception {
           //Initialize DataSource
           //Initialize daos   
      }

  }    

Commenti

  • existInDB: Non mi piace questo attributo, ma ho spiegato perché l'ho tenuto.

  • DAO generico vs DAO specifico: un DAO "per governare tutti" è possibile utilizzando i moderni ORM, ma in questo caso dobbiamo digitare la frase SQL per ogni entità. Ho deciso di mantenere separati tutti gli accessi ai dati. ModelDAO e OnwerDAO sono molto simili a VehicleDAO .

  • L'orribile Mappa : immagino che gli occhi di qualcuno stiano sanguinando a causa di questo punto. Ma c'è una ragione. Non sono riuscito a restituire Veicolo (ho già spiegato le mie ragioni) e non sono riuscito a restituire ResultSet perché tutto ciò che è realizzato su DB (include l'API JDBC e il suo utilizzo) è la responsabilità di DAO. Non mi piace vedere elementi JDBC distribuiti in tutto il codice. È molto più facile spostare una mappa terribile di un ResultSet (IMO) perché le implicazioni dietro a un ResultSet.

risposta data 31.08.2016 - 12:39
fonte

Leggi altre domande sui tag