Le modèle du domaine est constitué par des javabeans simples appelés parfois POJOS (Plain Old Java Objects).
La mécanique d'accès aux données est réalisée par des classes DAO (Data Access Objects). Il y a une classe Dao par Pojo.
L'affichage utilise des JSP et des scriptlets simples. Deux JSP par POJO: une liste et un formulaire.
La dynamique applicative ainsi que l'exécution des règles de gestion est assurée par des servlets, comportant chacune, dans cet exemple, moins de cent lignes de code. (Une Servlet par POJO).
Le code le plus fastidieux à écrire est le code des DAO.
Nous montrerons ensuite que dans cette maquette, il sera possible de remplacer les DAO codés à la main par des classes utilisant le moteur de persistance Hibernate, sans modification du restant de l'application.
La base de données sous-jacente est Mysql.
Il n'est apporté aucun soin au design graphique, ce n'est pas le but de cette maquette. Par contre, l'usabilité doit être soignée (marquage de la ligne courante, navigation intuitive et ergonomique).

Conditions à respecter strictement:

  • Pas de SQL dans les POJOS.
  • Les POJOS n'ont aucune dépendance vers les DAOS.
  • Pas de SQL ni de règles métier dans les JSP, uniquement de l'affichage.
  • Pas de règles métier dans les DAO, uniquement de l'accès aux données.
  • Les seules règles métier dans les POJOS concernent les attributs calculés (exemple: prix total = prix unitaire * quantité).
  • Pas de triggers ni de procédures stockées dans la base de données.
  • Pas de html dans les servlets, ni de SQL.
  • Les règles de gestion sont codées dans les servlets ainsi que dans les POJOS.
  • Les JSP ne sont jamais appelées par le client (à l'exception de la page d'accueil).
  • Pas de code Javascript dans les JSP (par la suite, on pourra introduire des contrôles sur les étendues de valeurs ou sur des champs obligatoires dans les formulaires; les règles de gestion doivent toujours figurer à minima sur le serveur)

Toutes les requêtes du client attaquent les servlets (CONTROLEUR), qui effectuent l'accès aux données, la mise à jour du MODELE (les POJOS), et l'actualisation de la VUE (les JSP).

Les beans métier (pojos)

La base de données (modèle relationnel)

La couche Ihm

Fragments de code de DAO (Data Access Object)

Le script de création de la base de données

CREATE DATABASE `mystore2`;

use mystore2;

CREATE TABLE `customers` (

 `ID` int(11) NOT NULL auto_increment,
 `NAME` varchar(50) NOT NULL default '',
 `FOR_NAME` varchar(50) NOT NULL default '',
 `ADDRESS` varchar(50) NOT NULL default '',
 `TOWN` varchar(50) NOT NULL default '',
 `COUNTRY` varchar(50) NOT NULL default '',
 `TOTAL_PRICE` double NOT NULL default '0',
 PRIMARY KEY  (`ID`)

) TYPE=InnoDB AUTO_INCREMENT=3;

INSERT INTO `customers` VALUES (1, 'Dupont', 'Jules', 'rue des lilas','Nimes','France', 3600.54); INSERT INTO `customers` VALUES (2, 'Durand', 'Alfred', 'rue des roses','Montpellier','France', 1200.48);

CREATE TABLE `products` (

 `ID` int(11) NOT NULL auto_increment,
 `NAME` varchar(50) NOT NULL default '',
 `PRICE` double NOT NULL default '0',
 `AMOUNT` int(11) NOT NULL default '0',
 PRIMARY KEY  (`ID`)

) TYPE=InnoDB AUTO_INCREMENT=29 ;

INSERT INTO `products` VALUES (1, 'Foo', 3.6, 100); INSERT INTO `products` VALUES (2, 'Bar', 6.84, 100); INSERT INTO `products` VALUES (4, 'Baz', 2.232, 100); INSERT INTO `products` VALUES (9, 'Shme', 5.256, 100); INSERT INTO `products` VALUES (12, 'Hukarz', 5.2, 100); INSERT INTO `products` VALUES (14, 'Qux', 5.256, 100); INSERT INTO `products` VALUES (20, 'Xyzzy', 5.256, 100); INSERT INTO `products` VALUES (21, 'Gazonc', 46.44, 100); INSERT INTO `products` VALUES (22, 'Schmurz', 173.376, 100); INSERT INTO `products` VALUES (23, 'Bing', 173.376, 100); INSERT INTO `products` VALUES (24, 'Ding', 29.376, 100); INSERT INTO `products` VALUES (26, 'Patati', 2.88, 100); INSERT INTO `products` VALUES (27, 'Patata', 100, 100); INSERT INTO `products` VALUES (28, 'Nyoronyoro', 29.34, 100);

CREATE TABLE `orders` (

 `ID` int(11) NOT NULL auto_increment,
 `CUSTOMER_ID` int(11) NOT NULL default '0',
 `ORDER_DATE` timestamp(14) NOT NULL,
 `PRICE_TOTAL` double NOT NULL default '0',
 PRIMARY KEY  (`ID`)

) TYPE=InnoDB AUTO_INCREMENT=21 ;

INSERT INTO `orders` VALUES (1, 1, '20050921000000', 22); INSERT INTO `orders` VALUES (5, 1, '20060921000000', 81.752); INSERT INTO `orders` VALUES (6, 1, '20050921000000', 1206.788); INSERT INTO `orders` VALUES (7, 1, '20050921000000', 2167.012); INSERT INTO `orders` VALUES (9, 1, '20050928000000', 1567.2); INSERT INTO `orders` VALUES (10, 1, '20050928000000', 86.55); INSERT INTO `orders` VALUES (15, 2, '20051117000000', 120.168); INSERT INTO `orders` VALUES (17, 2, '20051118000000', 14.04); INSERT INTO `orders` VALUES (18, 2, '20051205000000', 0); INSERT INTO `orders` VALUES (19, 2, '20051205000000', 0); INSERT INTO `orders` VALUES (20, 2, '20051206000000', 0);

CREATE TABLE `order_items` (

 `ID` int(11) NOT NULL auto_increment,
 `ORDER_ID` int(11) NOT NULL default '0',
 `PRODUCT_ID` int(11) NOT NULL default '0',
 `AMOUNT` int(11) NOT NULL default '0',
 `PRICE` double NOT NULL default '0',
 PRIMARY KEY  (`ID`)

) TYPE=InnoDB AUTO_INCREMENT=64 ;

INSERT INTO `order_items` VALUES (1, 1, 4, 5, 7.75); INSERT INTO `order_items` VALUES (2, 1, 2, 3, 14.25); INSERT INTO `order_items` VALUES (9, 5, 21, 3, 139.32); INSERT INTO `order_items` VALUES (10, 5, 24, 5, 232.2); INSERT INTO `order_items` VALUES (11, 6, 12, 10, 52); INSERT INTO `order_items` VALUES (14, 7, 4, 5, 7.75); INSERT INTO `order_items` VALUES (15, 7, 27, 10, 464.4); INSERT INTO `order_items` VALUES (16, 7, 2, 3, 14.25); INSERT INTO `order_items` VALUES (19, 9, 23, 12, 1444.8); INSERT INTO `order_items` VALUES (20, 9, 24, 6, 122.4); INSERT INTO `order_items` VALUES (21, 10, 4, 5, 7.75); INSERT INTO `order_items` VALUES (22, 10, 21, 2, 64.5); INSERT INTO `order_items` VALUES (23, 10, 2, 3, 14.25); INSERT INTO `order_items` VALUES (39, 6, 2, 3, 20.52); INSERT INTO `order_items` VALUES (43, 15, 2, 8, 54.72); INSERT INTO `order_items` VALUES (47, 15, 14, 8, 42.048); INSERT INTO `order_items` VALUES (48, 15, 2, 1, 6.84); INSERT INTO `order_items` VALUES (55, 17, 2, 0, 0); INSERT INTO `order_items` VALUES (56, 5, 24, 2, 0); INSERT INTO `order_items` VALUES (57, 20, 4, 2, 0); INSERT INTO `order_items` VALUES (58, 6, 14, 10, 52.56); INSERT INTO `order_items` VALUES (59, 6, 24, 1, 29.376); INSERT INTO `order_items` VALUES (60, 15, 26, 1, 0); INSERT INTO `order_items` VALUES (61, 15, 2, 2, 0); INSERT INTO `order_items` VALUES (62, 6, 26, 5, 12);

Utilitaires d'accès à la base de données

 /*
 * JdbcDatabase.java
 *
 * 
 */
package generic;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;



import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * 
 * @author fpfister
 */
public abstract class JdbcDatabase {

	private static Log log = LogFactory.getLog(JdbcDatabase.class);
	private Connection testConnection = null;
	private String driver = "com.mysql.jdbc.Driver";
	private String server = "jdbc:mysql://localhost:3306/";
	private String database = "foo";
	private String user = "bar";
	private String pw = "bouze";
	private static JdbcDatabase instance;

	protected ArrayList items = new ArrayList();

	public Connection getConnection() throws Exception {
		try {
			Class.forName(driver).newInstance();
			Connection connection = DriverManager.getConnection(server + database, user, pw);
			return connection;
		} catch (Exception e) {
			log.error("unable to open connection");
			throw new Exception(e.getMessage());
		}
	}

	protected void openTestConnection() throws Exception {
		try {
			Class.forName(driver).newInstance();
			testConnection = DriverManager.getConnection(server + database, user, pw);
		} catch (Exception e) {
			log.error("unable to open connection");
			throw new Exception(e.getMessage());
		}
	}

	protected ResultSet getSampleResultSet() throws Exception {
		ResultSet rs = null;
		String request = getSampleRequestString();
		openTestConnection();
		try {
			Statement st = testConnection.createStatement();
			rs = st.executeQuery(request);
		} catch (SQLException e) {
			log.error("unable to get data");
			throw new Exception(e.getMessage());
		}
		return rs;
	}

	public ResultSet executeQuery(Connection connection, String request) throws Exception {
		ResultSet rs = null;
		try {
			Statement st = connection.createStatement();
			rs = st.executeQuery(request);
		} catch (SQLException e) {
			log.error("unable to get data");
			throw new Exception(e.getMessage());
		}
		return rs;
	}

	public void update(Connection connection, String request) throws Exception {
		try {
			Statement st = connection.createStatement();
			st.executeUpdate(request);
		} catch (SQLException e) {
			log.error("unable to update data");
			throw new Exception(e.getMessage());
		}
	}

	public int insert(Connection connection, String request) throws Exception {
		int key = -1;
		try {
			Statement st = connection.createStatement();
			st.executeUpdate(request, Statement.RETURN_GENERATED_KEYS);
			ResultSet k = st.getGeneratedKeys();
			if (k.next())
				key = k.getInt(1);
			return key;
		} catch (SQLException e) {
			log.error("unable to update data");
			throw new Exception(e.getMessage());
		}
	}

	protected void closeConnection() {
		try {
			if (testConnection != null && !testConnection.isClosed()) {
				testConnection.close();
			}
		} catch (Exception e) {

		}
	}

	protected void createTable() {
		String request = "DROP TABLE ....\n ";
		try {
			openTestConnection();
			Statement st = testConnection.createStatement();
			ResultSet rs = st.executeQuery(request);
		} catch (Exception e) {

		}
	}

	protected boolean insertRows() {
		String request = "INSERT INTO ....";

		try {
			openTestConnection();
			Statement st = testConnection.createStatement();
			st.executeQuery(request);
		} catch (Exception e) {
			return false;
		}
		return true;
	}

	public JdbcDatabase() {
		super();
	}

	protected abstract String getSampleRequestString();

	public void setDatabase(String database) {
		this.database = database;
	}

	public void setDriver(String driver) {
		this.driver = driver;
	}

	public void setPw(String pw) {
		this.pw = pw;
	}

	public void setServer(String server) {
		this.server = server;
	}

	public void setUser(String user) {
		this.user = user;
	}

}


package app.dao.jdbc;

import generic.JdbcDatabase;

import java.sql.ResultSet;

public class MyStoreDatabase extends JdbcDatabase{
	private static MyStoreDatabase instance;

    /**
     * Singleton access method
     * @return MyStoreDatabase single instance
     * @throws Exception 
     */
	
    public static MyStoreDatabase getInstance() throws Exception {
        if (instance == null) {
            instance = new MyStoreDatabase();
            instance.setDriver("com.mysql.jdbc.Driver");
            instance.setServer("jdbc:mysql://localhost:3306/");
            instance.setDatabase("mystore2");
            instance.setUser("root"); 
            instance.setPw("");
            instance.openTestConnection();
			try {
				instance.getSampleResultSet().next();
			} catch (Exception e) {
				e.printStackTrace();
				throw(e);
			} finally {
				instance.closeConnection();
			}
        }
        return instance;
    }	
    
	
	protected String getSampleRequestString() {
		return "select * from orders";
	}    
    
	public static void main(String args) {
		try {
			MyStoreDatabase cnx = getInstance();
			cnx.openTestConnection();
			ResultSet rs = cnx.getSampleResultSet();
			while (rs.next()) {
				String id = rs.getString("ID");
				String date = rs.getString("ORDER_DATE");
				System.out.println("ROW = " + id + " " + date);
			}
			cnx.closeConnection();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

}

classe DAO pour Customer

package app.dao.jdbc;

import generic.BaseDao;
import generic.BaseObject;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import app.Order;
import app.Customer;
public class CustomerDao implements BaseDao {

	private static final Log log = LogFactory.getLog(CustomerDao.class);

	private static MyStoreDatabase database;

	private static CustomerDao instance;

	private CustomerDao() {

	}

	public static synchronized CustomerDao getInstance() {
		if (instance == null) {
			try {
				database = MyStoreDatabase.getInstance();
				instance = new CustomerDao();
			} catch (Exception e) {
				e.printStackTrace();
				throw new RuntimeException(e);
			}
		}
		return instance;
	}

	public void save(Order instance) { 
		log.error("todo");
	}

	public void save(BaseObject instance) {
		save((Order) instance);
	}

	private Customer getInstance(ResultSet rs) throws NumberFormatException, SQLException{
		Customer customer = new Customer();
		customer.setId(new Integer(rs.getString("ID")));
		customer.setAddress(rs.getString("ADDRESS"));
		customer.setCountry(rs.getString("COUNTRY"));
		customer.setForName(rs.getString("FOR_NAME"));
		customer.setName(rs.getString("NAME"));
		customer.setTown(rs.getString("TOWN"));
		customer.setTotalPrice(rs.getDouble("TOTAL_PRICE"));
		List orders = OrderDao.getInstance().findByParent(customer);
		Iterator iterator = orders.iterator();
		while (iterator.hasNext()) {
			Order element = (Order) iterator.next();
			customer.addOrder(element);	
		}
		return customer;
	}
	
	
	public Customer findById(Integer id) {
		if (id==null)
			 return null;
		try {
			String query = "select * from Customers where ID=" + id.intValue();
			Customer customer = null;//new Customer();
			Connection c = database.getConnection();
			ResultSet rs = database.executeQuery(c, query);
			if (rs.next())
				customer=getInstance(rs);
			c.close();
			return customer;
		} catch (Exception re) {
			log.error("findById failed", re);
			throw new RuntimeException(re);
		}
	}

	public List findByParentId(String id) {
		String filter = "";  // no parent foreign key
		String query = "select * from Customers " + filter +" order by ID";
		log.debug(query);
		List result = new ArrayList();
		try {
			Connection c = database.getConnection();
			ResultSet rs = database.executeQuery(c, query);
			while (rs.next()) {
				Customer Customer = getInstance(rs);
				result.add(Customer);
			}
			c.close();
		} catch (Exception re) {
			log.error("find by ParentId failed", re);
			throw new RuntimeException(re);
		}
		return result;
	}

	public BaseObject findById(String id) {
		if (id == null || id.equals(""))
			return null;
		try {
			return findById(new Integer(id));
		} catch (Exception re) {
			log.error("findById failed", re);
			throw new RuntimeException(re);
		}
	}	
	
	public void deleteById(String id) { //tester la récupération des erreurs
		try {
			Connection c = database.getConnection();
			database.update(c, "delete from Customers where ID=" + id);
			c.close();
		} catch (Exception re) {
			log.error("delete failed", re);
			throw new RuntimeException(re);
		}
	}

}

Classe métier

package app;

import generic.BaseObject;

import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;


public class Order extends BaseObject implements Serializable {

	private static final long serialVersionUID = -4176967988091913835L;

	private Integer id;
	
	private Customer customer;

	private Date date;

	private double priceTotal;


	private Set orderItems = new HashSet();


	public void setOrderItems(Set set) {
		orderItems = set;
	}


	public Set getOrderItems() {
		return orderItems;
	}

	public Order() {
		this.date = new Date();
	}



	public void addOrderItem(OrderItem orderItem) {
		orderItem.setOrder(this);
		this.priceTotal = this.priceTotal + orderItem.getProduct().getPrice()
				* orderItem.getAmount();
		this.orderItems.add(orderItem);
	}

	public void removeOrderItem(OrderItem orderItem) {
		orderItem.setOrder(null);
		orderItems.remove(orderItem);
	}


	public Date getDate() {
		return date;
	}


	public Integer getId() {
		return id;
	}


	public double getPriceTotal() {
		return priceTotal;
	}


	public void setDate(Date date) {
		this.date = date;
	}


	public void setId(Integer id) {
		this.id = id;
	}


	public void setPriceTotal(double d) {
		priceTotal = d;
	}

	public String orderItemstoString() {
		Iterator i = orderItems.iterator();
		String o = "";
		while (i.hasNext()) {
			OrderItem item = (OrderItem) i.next();
			o = o + item.getAmount() + " " + item.getProduct().getName() + "-";
		}
		return o;
	}

	public String toString() {
		String s = "Order id=" + id + "\n        priceTotal=" + priceTotal
				+ "\n        date=" + date + "\n";
		s = s + "Order items:\n";
		Iterator i = orderItems.iterator();
		String o = "";
		while (i.hasNext()) {
			OrderItem item = (OrderItem) i.next();
			o = o + "  " + item + "\n";
		}
		return s + o;
	}

	public String asString() {
		return  id +"  "+ date.toString()+ " "+priceTotal;
	}

	public Customer getCustomer() {
		return customer;
	}

	public void setCustomer(Customer customer) {
		this.customer = customer;
	}

}