私は何年も前にコースを教えた後、現在知っているアクティブレコードパターンを使用して、いくつかのJDBC演習を行いました。
クラスが独自の永続化を行わないように、Active Recordパターンを変更して、演習を最新化したいと思います。しかし、私は教育目的で永続化またはORMフレームワークを使用したくありません。
サンプルクラスはVehicleです。
existInDB
をtrueに設定します。existInDB
はfalseのままです。save()
を呼び出すと、existInDB
がfalseの場合はデータベースに挿入されるか、existInDB
がfalseの場合はデータベースの行が更新されます。doSomeBusinessThing()
メソッドがあるため、クラスは単純なデータ転送オブジェクトにはなりません。これは製品コードではありません。これはJava SEコース教材です。この言語を初めて学んでいる人や、OOPの原則)にフレームワークを紹介したくありません。1つ私の質問の要件は、POJOベースである必要があるということです。多分デザインパターンを使用していて、外部フレームワークやORMを使用していない可能性があります。
このようなリファクタリングを行う方法についてのアドバイスを読みたいと思います。
車両クラス:
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
*/
}
}
元のコードをできるだけ残して、Vehicleクラスをリファクタリングして、新しいImporterインターフェイスを使用して構築し、新しい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
}
この時点で、クラスは独自の永続化を行わなくなり、さまざまな永続化テクノロジーのインポーターとエクスポーターを作成できます。 SqlVehicleImporterを作成します(元のSQLコードを使用)...
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
}
...そして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();
}
}
}
この時点で、インポーターとエクスポーターの間で共通のコードがあるので、それらを組み合わせるとよいでしょう。
ここに私のアプローチ。この前提に基づいて:
クラスが独自の永続化を行わないように、Active Recordパターンを変更することで、演習を近代化したいと思います
教育目的で永続化またはORMフレームワークを使用したくありません。
言語やOOP=原則を初めて学習する人にフレームワークを導入したくありません。私の質問の要件の1つは、POJOベースでなければならないということです。設計パターンはあるが外部フレームワークやORMはない
@TulainsがOOPでは懸念の分離が重要であることを教えたいと思っていることは明らかです。このパターン設計は、「各タスクの責任者」を考えさせます。 (実際に)より多くのコンポーネントをコーディングすることになりますが、それらはよりシンプルで理解しやすくなります。
1。POJO最初の懸念。 POJO自体。 POJOが(Plain Old Java Object))を意味するように、これは単なるデータコンテナーであり、この場合、doSomeBusinessThingのようないくつかの機能もあります。ここで学生に、車両がどのようにModelとOnwerがいつどのように気にならないかを理解する必要があることを学生に指摘することが重要です取得または永続化...それはその問題ではありません:-)。他人の懸念
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(){
}
}
[〜#〜] note [〜#〜]:特定のDAOを実装するのではなく、なぜexistInDBを保持したのかについて一部の人々が私に主張するでしょうそのような目的のための方法。そのリファクタリングは@Tulainsに任せます。彼は必要な変更とその影響を紹介できます(すべての挿入または更新について、車両が存在するかどうかを確認するために別のクエリを実行する必要があります...)
2。DAO秒懸念、データベースアクセス。データベースへのアクセスに関連するすべてのものは、シンプルで再利用可能な状態に保つのに適しています。ここで最初の問題に直面します。 PreparedStatementsはPOJO(選択、挿入、更新)ごとに異なるため、エンティティごとに1つのDAOを作成します。そのうちの1つをコーディングします。 3つのDAOは非常によく似ています。 @Tulainsを実装すると、抽象クラスを通じて継承を導入するための優れた状況になります。
DAOがVehicleを返すようにすることもできますが、その場合、VehicleDAOはModelおよびOnwerデータへのアクセスを強制されます。私は自分のデザインに結果を出さなければなりません。したがって、他のコンポーネントがモデルと所有者のデータを処理できるようにします。
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.
}
}
。アプリ
私の以前の回答では、次のコンポーネントはビジネスレイヤーのどこかに配置される一種の「サービス」でした。この投稿の目的は、Javaを初心者に紹介することです。設計の暗黙の概念が多すぎて、学生を混乱させる可能性があるため、このようなコンポーネントを削除しました。そのサービスをメインクラスに組み込みます。目標は、Javaおよび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
}
}
コメント
existInDB:この属性は好きではありませんが、保持した理由を説明しました。
一般的なDAOと特定のDAO:最新のORMを使用して「すべてを統治する」DAOを1つ実行できますが、この場合、エンティティごとにSQL文を入力する必要があります。すべてのデータアクセスを分離しておくことにしました。 ModelDAOとOnwerDAOはVehicleDAOと非常によく似ています。
ひどいMap:この点のために誰かの目が出血していると思います。しかし、理由があります。 Vehicle(理由はすでに説明しました)を返すことができませんでした、そしてResultSetを返すことができませんでした。 JDBC APIとその使用方法)はDAOの責任です。コードの周りにJDBC要素が散在しているのを見たくありません。 ResultSetの背後にある影響のため、ResultSet(IMO)よりも恐ろしいマップを移動する方がはるかに優れています。