I taught a course years ago, and then I used some JDBC exercises using what I now know is the Active Record Pattern. I would like to modernize the exercise by changing the Active Record Pattern so classes don't do their own persistence. But I would not like to use any persistence or ORM framework for teaching purposes.A sample class is Vehicle:It receives a Connection and plate number in the constructor.In the constructor it queries the database and populates the class members and a boolean member existInDB is set to true.If the plate is not found in the DB, existInDB remains false.You can change the state of the object with setters.If you call save() it either inserts in the database if existInDB is false or updates a row in the database if existInDB is false.After refactoring the class would not be a simple data transfer object because it has a doSomeBusinessThing() method that does some business thing.All other classes like Maker, Owner, also use the Active Record Pattern, but we can leave them alone for now.This is not production code. It's Java SE course material. I don't want to introduce frameworks to people who are 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.I would like to read your advice on how to do such a refactoring. The vehicle class: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         */    }}
Pure POJO refactoring Active Record Pattern using Repository Pattern, no ORM or DI frameworks for pedagogical reasons
java;design patterns;refactoring;repository
Leaving as much of your original code as possible, I would refactor the Vehicle class so that it is constructed using a new Importer interface and saved using a new Exporter interface.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}At this point the class no longer does its own persistence and we can write importers and exporters for various persistence technologies.I would now write a SqlVehicleImporter (using your original SQL code)...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}...and a 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();        }    }}At this point there is code that is common between the importer and exporter so it might be a good idea to combine them.