day14 JDBC

Author:相忠良
Email: ugoood@163.com
起始於:May 29, 2018
最後更新日期:June 6, 2018javascript

聲明:本筆記依據傳智播客方立勳老師 Java Web 的授課視頻內容記錄而成,中間加入了本身的理解。本筆記目的是強化本身學習所用。如有疏漏或不當之處,請在評論區指出。謝謝。
涉及的圖片,文檔寫完後,一次性更新。html

day14 JDBC

1. JDBC 入門

JDBC(Java Data Base Connectivity) 是 java 數據庫鏈接,主要有接口組成。
JDBC的做用:能夠經過java程序操做數據庫。java

1

組成JDBC的2個包:mysql

  • java.sql
  • javax.sql
    這2個包就是JDBC,就是接口。

除導入JDBC外,還需導入相應數據庫JDBC接口的實現,即數據庫驅動。咱們用的是mysql-connector-java-5.0.8-bin.jar針對mysql的數據庫驅動。程序員

作JDBC試驗前的準備工做:
user.sql文件,代碼以下:web

create database day14 character set utf8 collate utf8_general_ci;

use day14;

create table users(
    id int primary key,
    name varchar(40),
    password varchar(40),
    email varchar(60),
    birthday date
);

insert into users(id,name,password,email,birthday) values(1,'zs','123456','zs@sina.com','1980-12-04');
insert into users(id,name,password,email,birthday) values(2,'lisi','123456','lisi@sina.com','1981-12-04');
insert into users(id,name,password,email,birthday) values(3,'wangwu','123456','wangwu@sina.com','1979-12-04');

而後創建普通java工程day14,並創建lib文件夾,將mysql-connector-java-5.0.8-bin.jar複製到lib,並把該包變爲奶瓶。spring

創建Demo1.java,操做 day14 數據庫的 users 表,代碼以下:sql

package cn.wk.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo1 {
    public static void main(String[] args) throws SQLException {

      /*
      jdbc:mysql 表示協議
      //localchost:3306 本機3306端口,是mysql服務器端口
      day14 要鏈接的數據庫
      */
        String url = "jdbc:mysql://localhost:3306/day14";
        String username = "root";
        String password = "root";

        // 1. 加載驅動
        DriverManager.registerDriver(new com.mysql.jdbc.Driver());

        // 2. 獲取鏈接
        Connection conn = DriverManager.getConnection(url, username, password);

        // 3. 獲取向數據庫發sql語句的statement對象
        Statement st = conn.createStatement();

        // 4. 向數據庫發sql,獲取數據庫返回的結果集
        ResultSet rs = st.executeQuery("select * from users");

        // 5. 從結果集中獲取數據
        while (rs.next()) {
            System.out.print("id = " + rs.getObject("id") + "  ");
            System.out.print("name = " + rs.getObject("name") + "  ");
            System.out.print("password = " + rs.getObject("password") + "  ");
            System.out.print("email = " + rs.getObject("email") + "  ");
            System.out.print("birthday = " + rs.getObject("birthday") + "  ");
            System.out.println();
        }

        // 6. 釋放資源(釋放連接)
        rs.close();
        st.close();
        conn.close();
    }
}

2. jdbc程序步驟詳解1

  1. 加載驅動的細節;
  2. 獲取鏈接的細節。
    以下:

2

3

依據上面所說,修改了上節的代碼,代碼片斷以下:數據庫

// localhost簡寫,?後接各類參數,不管mysql用何種字符集,均推薦字符顯式設定
String url = "jdbc:mysql:///day14"
    + "?user=root&password=root"
    + "&useUnicode=true&characterEncoding=UTF-8";

Class.forName("com.mysql.jdbc.Driver"); // <---- 加載類,同時註冊
Connection conn = DriverManager.getConnection(url);

3. jdbc程序步驟詳解2

Connection對象:
Jdbc程序中的Connection,它用於表明數據庫的連接,Collection是數據庫編程中最重要的一個對象,客戶端與數據庫全部交互都是經過connection對象完成的,這個對象的經常使用方法:apache

  • createStatement():建立向數據庫發送sql的statement對象。
  • prepareStatement(sql) :建立向數據庫發送預編譯sql的PrepareSatement對象。
  • prepareCall(sql):建立執行存儲過程的callableStatement對象。
  • setAutoCommit(boolean autoCommit):設置事務是否自動提交。
  • commit() :在連接上提交事務。
  • rollback() :在此連接上回滾事務。

Statement對象:
Statement對象用於向數據庫發送SQL語句, Statement對象經常使用方法:

  • executeQuery(String sql) :用於向數據發送查詢語句。
  • executeUpdate(String sql):用於向數據庫發送insert、update或delete語句
  • execute(String sql):用於向數據庫發送任意sql語句,通常不用。
  • addBatch(String sql) :把多條sql語句放到一個批處理中。
  • executeBatch():向數據庫發送一批sql語句執行。

ResultSet對象:
ResultSet用於表明Sql語句的執行結果。
下圖表達了,mysql 表中字段類型與 ResultSet 方法對照表:
4

最後必定要釋放 conn,由於mysql的連接資源不多很寶貴,釋放技巧以下:
資源釋放代碼放入finally裏。

模板代碼 以下:

package cn.wk.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import cn.wk.domain.User;

public class Demo2 {
    public static void main(String[] args) throws SQLException,
            ClassNotFoundException {

        String url = "jdbc:mysql:///day14";
        String username = "root";
        String password = "root";

        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;

        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(url, username, password);
            st = conn.createStatement(); // throw
            rs = st.executeQuery("select * from users");
            while (rs.next()) {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setPassword(rs.getString("password"));
                user.setEmail(rs.getString("email"));
                user.setBirthday(rs.getDate("birthday"));
            }
        } finally {
            if (rs != null) {
                try {
                    rs.close(); // throw new
                } catch (Exception e) {
                    e.printStackTrace();
                }
                rs = null;
            }
            if (st != null) {
                try {
                    st.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                st = null;
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

順便建了個User類,作爲javabean對象,代碼以下:

package cn.wk.domain;

import java.util.Date;

public class User {
    private int id;
    private String name;
    private String password;
    private String email;
    private Date birthday;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}

4. 使用 jdbc 完成 crud

案例:

  1. Demo3.java實現crud方法;
  2. db.properties資源文件,達到靈活切換數據庫驅動、url、username 和 password;
  3. cn.wk.utils.JdbcUtils工具類,實現驅動加載、創建連接和釋放連接這種公共代碼。

Demo3.java,以下:

package cn.wk.demo;

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

import org.junit.Test;

import cn.wk.domain.User;
import cn.wk.utils.JdbcUtils;

public class Demo3 {

    @Test
    public void insert() throws SQLException {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "insert into users(id,name,password,email,birthday)"
                    + "values (4,'eee','123','ee@sina.com','1980-11-11')";
            int num = st.executeUpdate(sql);
            if (num > 0) {
                System.out.println("插入成功!!!");
            }
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Test
    public void update() throws SQLException {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "update users set name = 'fff' where id = '4'";
            int num = st.executeUpdate(sql);
            if (num > 0) {
                System.out.println("更新成功!!!");
            }

        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Test
    public void delete() throws SQLException {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "delete from users where id='4'";
            int num = st.executeUpdate(sql);
            if (num > 0) {
                System.out.println("刪除成功!!!");
            }
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Test
    public void find() throws SQLException {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "select * from users where id='2'";
            rs = st.executeQuery(sql);
            if (rs != null) {
                System.out.println("查詢成功!!!");
            }
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Test
    public void getAll() throws SQLException {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "select id,name,password,email,birthday from users";
            rs = st.executeQuery(sql);
            List list = new ArrayList();

            while (rs.next()) {
                User user = new User();
                user.setId(rs.getInt("id"));
                user.setName(rs.getString("name"));
                user.setPassword(rs.getString("password"));
                user.setEmail(rs.getString("email"));
                user.setBirthday(rs.getDate("birthday"));
                list.add(user);
            }

            System.out.println(list);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }
}

db.properties資源文件:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day14
username=root
password=root

cn.wk.utils.JdbcUtils工具類:

package cn.wk.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JdbcUtils {

    private static Properties config = new Properties();

    static {
        try {
            // 讀配置文件 db.properties
            config.load(JdbcUtils.class.getClassLoader().getResourceAsStream(
                    "db.properties"));
            Class.forName(config.getProperty("driver"));
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e); // 異常轉換成錯誤
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(config.getProperty("url"),
                config.getProperty("username"), config.getProperty("password"));
    }

    public static void release(Connection conn, Statement st, ResultSet rs) {
        // 模板代碼
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if (st != null) {
            try {
                st.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            st = null;
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

5. 切換到 oracle 完成 crud

更換db.properties文件信息爲 oracle 的,幾乎不用改程序,就能夠用oracle作crud。
爲保證程序有移植性,即想作到不依賴具體的數據庫管理系統,那麼,Connection 等對象就用 sun 公司的接口,而不能用具體數據庫的相應對象!如:咱們導入的是java.sql.Connection

6. 用 jdbc 改造用戶模塊

準備工做:
複製 day09_user 工程,命名爲 day14_user,並點擊該工程屬性,選 web 選項, 將 Web Context-root 改成/day09_user

UserDaoJdbcImpl代碼以下:

package cn.wk.dao.impl;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

import cn.wk.dao.UserDao;
import cn.wk.domain.User;
import cn.wk.utils.JdbcUtils;

public class UserDaoJdbcImpl implements UserDao {

    @Override
    public void add(User user) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement(); // conn.preparedment() 用佔位符替換位置
            String sql = "insert into users(id,username,password,email,birthday,nickname)"
                    + "values('"
                    + user.getId()
                    + "','"
                    + user.getUsername()
                    + "','"
                    + user.getPassword()
                    + "','"
                    + user.getEmail()
                    + "','"
                    + user.getBirthday().toLocaleString()
                    + "','"
                    + user.getNickname() + "')";
            int num = st.executeUpdate(sql);
            if (num < 1) {
                throw new RuntimeException("註冊用戶失敗!!!");
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Override
    public User find(String username, String password) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "select * from users where username='" + username
                    + "' and password='" + password + "'";
            rs = st.executeQuery(sql);
            if (rs.next()) {
                User user = new User();
                user.setBirthday(rs.getDate("birthday"));
                user.setEmail(rs.getString("email"));
                user.setId(rs.getString("id"));
                user.setNickname(rs.getString("nickname"));
                user.setPassword(rs.getString("password"));
                user.setUsername(rs.getString("username"));
                return user;
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Override
    public boolean find(String username) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "select * from users where username='" + username
                    + "'";
            rs = st.executeQuery(sql);
            if (rs.next()) {
                return true;
            }
            return false;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }
}

其餘的不寫了,好累。(2018年5月30日 23:00 於 濰坊科技學院軟件學院 315 辦公室)

7. dao 工廠和預防 sql 注入

爲使 service 層的 UserDao 與 實際的實現解耦,需專門創建 dao工廠類,由該工廠讀取dao.properties這個配置文件,實現調用具體的 UserDao 實現,如調用UserDaoJdbcImplUserDaoXmlImpl等其餘實現,以下圖所示:
5

因爲有可能有不少工廠,故創建cn.wk.factory包來存放工廠類。此時,咱們在該包內創建DaoFactory類,以下:

package cn.wk.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class DaoFactory {

    private Properties daoConfig = new Properties();

    // 工廠是單例的,即全部的dao只由一個工廠來生產
    // new 對象同時,加載配置文件
    private DaoFactory() {
        InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream(
                "dao.properties");
        try {
            daoConfig.load(in);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    private static DaoFactory instance = new DaoFactory();
    public static DaoFactory getInstance(){
        return instance;
    }

    // 未來可能產生多個 不一樣類型的 dao,故此處用泛型

    //                  若給   UserDao.class,本方法就產生UserDao的dao
    //                      DepartmentDao.class 同上
    // 你給我一個接口類型, 我給你返回一個接口實現
    // 爲避免調用者強轉,此處應該用泛型

    // 泛型的學習,重點:
    // Class<T> : 人家傳進來什麼類型, T就表明什麼類型
    // <T> T : <T> 是類型聲明, T 是返回的類型
    public <T> T createDao(Class<T> clazz){

        // 1. 獲得傳進來的接口名稱
        // clazz.getName()  //cn.wk.dao.UserDao
        String name = clazz.getSimpleName();
        String className = daoConfig.getProperty(name);
        try {
            // 加載類,併產生實例
            T dao = (T)Class.forName(className).newInstance();
            return dao;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

還需建dao.properties配置文件,以下:

UserDao=cn.wk.dao.impl.UserDaoJdbcImpl

修改BusinessServiceImpl以下:

// private UserDao dao = new UserDaoJdbcImpl(); // 可用工廠模式 或 spring 解耦,之後解耦

// 利用工廠,使得業務類,無代碼依賴於具體實現,已徹底解耦
private UserDao dao = DaoFactory.getInstance().createDao(UserDao.class);

通過改造,系統結構很是好,好靈活,以下圖:
6

關於異常:
應該每一層都自定義異常,以便拋出異常時,能快速定位是哪層出現異常。
對待異常的態度,方立勳遵循了 Spring 做者的理念,即:
你是否但願上層處理本異常,若但願,則拋出 checked 異常,不然拋出 unchecked 異常。有時,咱們就想對上層造麻煩,擔憂上層忘記處理本層某異常,那就不用猶豫,拋 checked 異常便可!

7.1 preparedStatement 對象

statement 和 preparedStatement 的區別:

  1. preparedStatement 是 statement 的崽;
  2. preparedStatement 能夠防止sql注入問題;
  3. preparedStatement 會對sql語句進行預編譯,以減輕數據庫服務器的壓力。

preparedStatement 使用的代碼片斷,通常就用 preparedStatement 而不用 statement,以下:

String url = "jdbc:mysql:///day14";
String username = "root";
String password = "root";

Connection conn = null;
preparedStatement st = null;
ResultSet rs = null;

Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url, username, password);
String sql = "select * from users where username=?"; // ?佔位符
st = conn.preparedStatement(sql);
st.setString(1, username); // 填補?號

rs = st.executeQuery();
if(rs.next()){
  rs.close();
  st.close();
  conn.close();
  return true;
}
rs.close();
st.close();
conn.close();
return false;

8. jdbc 實現客戶關係管理案例

要實現一個客戶關係管理系統,客戶信息 customer 表以下:

字段名 類型
id varchar(40)
name varchar(20)
gender varchar(4)
birthday date
cellphone varchar(20)
email varchar(40)
preference varchar(100)
type varchar(40)
description varchar(255)
1.搭建環境
    1.1 導開發包
        mysql驅動
        beanUtils
        log4j開發
        jstl開發包

    1.2 建立組織程序的包
        cn.wk.domain
        cn.wk.dao
        cn.wk.dao.impl
        cn.wk.service
        cn.wk.service.impl
        cn.wk.web.controller
        cn.wk.web.UI
        cn.wk.utils
        cn.wk.exception
        junit.test

        WEB-INF/jsp

    1.3 爲應用建立相應庫和表
        create database day14_customer character set utf8 collate utf8_general_ci;
        use day14_customer;
        create table customer
        (
            id varchar(40) primary key,
            name varchar(40) not null,
            gender varchar(4) not null,
            birthday date,
            cellphone varchar(20),
            email varchar(40),
            preference varchar(255),
            type varchar(100) not null,
            description varchar(255)
        );

2.建實體

3.寫dao

4.寫service

5.寫web

根據應用建立數據庫及表,根據表建立 javabean,以下:

package cn.wk.domain;

import java.util.Date;

public class Customer {
    private String id;
    private String name;
    private String gender;
    private Date birthday;
    private String cellphone;
    private String email;
    private String preference;
    private String type;
    private String description;

    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String getCellphone() {
        return cellphone;
    }
    public void setCellphone(String cellphone) {
        this.cellphone = cellphone;
    }
    public String getPreference() {
        return preference;
    }
    public void setPreference(String preference) {
        this.preference = preference;
    }
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

要鏈接數據庫,則建立連數據庫的工具類,這算是一種模板代碼,cn.wk.utils.JdbcUtils,以下:

package cn.wk.utils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JdbcUtils {

    private static Properties config = new Properties();

    static {
        try {
            // 讀配置文件 db.properties
            config.load(JdbcUtils.class.getClassLoader().getResourceAsStream(
                    "db.properties"));
            Class.forName(config.getProperty("driver"));
        } catch (Exception e) {
            throw new ExceptionInInitializerError(e); // 異常轉換成錯誤
        }
    }

    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(config.getProperty("url"),
                config.getProperty("username"), config.getProperty("password"));
    }

    public static void release(Connection conn, Statement st, ResultSet rs) {
        // 模板代碼
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if (st != null) {
            try {
                st.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            st = null;
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

連數據庫時,需用到配置文件db.properties,它在 src 目錄下,代碼以下:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day14_customer
username=root
password=root

再建立操做數據庫進行 crud 的實現類cn.wk.dao.impl.CustomerDaoImpl,以下:

package cn.wk.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import cn.wk.dao.CustomerDao;
import cn.wk.domain.Customer;
import cn.wk.exception.DaoException;
import cn.wk.utils.JdbcUtils;

public class CustomerDaoImpl implements CustomerDao {

    @Override
    public void add(Customer c) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            String sql = "insert into customer (id,name,gender,birthday,cellphone,email,preference,type,description) values (?,?,?,?,?,?,?,?,?)";
            st = conn.prepareStatement(sql);
            st.setString(1, c.getId());
            st.setString(2, c.getName());
            st.setString(3, c.getGender());
            st.setDate(4, new java.sql.Date(c.getBirthday().getTime()));
            st.setString(5, c.getCellphone());
            st.setString(6, c.getEmail());
            st.setString(7, c.getPreference());
            st.setString(8, c.getType());
            st.setString(9, c.getDescription());

            st.executeUpdate();

        } catch (Exception e) {
            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Override
    public void update(Customer c) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            String sql = "udate customer set name=?,gender=?,birthday=?,cellphone=?,email=?,preference=?,type=?,description=? where id=?";
            st = conn.prepareStatement(sql);
            st.setString(1, c.getName());
            st.setString(2, c.getGender());
            st.setDate(3, new java.sql.Date(c.getBirthday().getTime()));
            st.setString(4, c.getCellphone());
            st.setString(5, c.getEmail());
            st.setString(6, c.getPreference());
            st.setString(7, c.getType());
            st.setString(8, c.getDescription());
            st.setString(9, c.getId());
            st.executeUpdate();
        } catch (Exception e) {
            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Override
    public void delete(String id) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            String sql = "delete from customer where id=?";
            st = conn.prepareStatement(sql);
            st.setString(1, id);
            st.executeUpdate();
        } catch (Exception e) {
            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

    @Override
    public Customer find(String id) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            String sql = "select * from customer where id=?";
            st = conn.prepareStatement(sql);
            st.setString(1, id);
            rs = st.executeQuery();
            if (rs.next()) {
                Customer c = new Customer();
                c.setBirthday(rs.getDate("birthday"));
                c.setCellphone(rs.getString("cellphone"));
                c.setDescription(rs.getString("description"));
                c.setEmail(rs.getString("email"));
                c.setGender(rs.getString("gender"));
                c.setId(rs.getString("id"));
                c.setName(rs.getString("name"));
                c.setPreference(rs.getString("preference"));
                c.setType(rs.getString("type"));
                return c;
            }
        } catch (Exception e) {
            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
        return null;
    }

    @Override
    public List<Customer> getAll() {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            String sql = "select * from customer";
            st = conn.prepareStatement(sql);
            rs = st.executeQuery();

            List<Customer> list = new ArrayList<Customer>();
            while (rs.next()) {
                Customer c = new Customer();
                c.setBirthday(rs.getDate("birthday"));
                c.setCellphone(rs.getString("cellphone"));
                c.setDescription(rs.getString("description"));
                c.setEmail(rs.getString("email"));
                c.setGender(rs.getString("gender"));
                c.setId(rs.getString("id"));
                c.setName(rs.getString("name"));
                c.setPreference(rs.getString("preference"));
                c.setType(rs.getString("type"));
                list.add(c);
            }
            return list;
        } catch (Exception e) {
            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }
}

再對操做數據庫的實現類cn.wk.dao.impl.CustomerDaoImpl抽取成接口cn.wk.dao.CustomerDao,以下:

package cn.wk.dao;
import java.util.List;
import cn.wk.domain.Customer;

public interface CustomerDao {
    public abstract void add(Customer c);
    public abstract void update(Customer c);
    public abstract void delete(String id);
    public abstract Customer find(String id);
    public abstract List<Customer> getAll();
}

這時,注意到每層都應創建本身的異常類,以方便未來發生異常時,方便程序員瞭解究竟是哪層發生異常。故創建cn.wk.exception.DaoException,並聲明該異常類爲RuntimeException,同時繼承父類的全部方法,以下:

package cn.wk.exception;
public class DaoException extends RuntimeException {
    public DaoException() {}
    public DaoException(String message) {super(message);}
    public DaoException(Throwable cause) {super(cause);}
    public DaoException(String message, Throwable cause) {super(message, cause);}
    public DaoException(String message, Throwable cause,
            boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

接着,想到本工程的業務僅僅是對客戶信息進行 crud,業務邏輯超級簡單。因此,咱們能夠當即創建業務層實現cn.wk.service.impl.BusinessServiceImpl,以下(注意:若業務邏輯很複雜,本層代碼也會很複雜):

package cn.wk.service.impl;

import java.util.List;

import cn.wk.dao.CustomerDao;
import cn.wk.dao.impl.CustomerDaoImpl;
import cn.wk.domain.Customer;
import cn.wk.service.BusinessService;

// 薄薄的業務層
public class BusinessServiceImpl implements BusinessService {

    private CustomerDao dao = new CustomerDaoImpl();

    @Override
    public void addCustomer(Customer c){
        dao.add(c);
    }

    @Override
    public void updateCustomer(Customer c){
        dao.update(c);
    }

    @Override
    public void deleteCustomer(String id){
        dao.delete(id);
    }

    @Override
    public Customer findCustomer(String id){
        return dao.find(id);
    }

    @Override
    public List<Customer> getAllCustormer(){
        return dao.getAll();
    }
}

而後當即進行業務層實現的抽取,造成接口cn.wk.service.BusinessService

package cn.wk.service;
import java.util.List;
import cn.wk.domain.Customer;
public interface BusinessService {
    public abstract void addCustomer(Customer c);
    public abstract void updateCustomer(Customer c);
    public abstract void deleteCustomer(String id);
    public abstract Customer findCustomer(String id);
    public abstract List<Customer> getAllCustormer();
}

接下來,和 web 相關,也就是和用戶相關的層的代碼會較複雜,要當心了。
首先考慮首頁 index.jsp,本例用了分幀技術,涉及了 index.jsp 和 head.jsp,想達到以下效果:
7

index.jsp 以下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>首頁</title>
  </head>

  <frameset rows="20%,*">
    <frame name="head" src="${pageContext.request.contextPath}/head.jsp">
    <frame name="main">
  </frameset>
</html>

head.jsp 以下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>head</title>
  </head>

  <body style="text-align:center;">
    <h1>XXX客戶關係管理系統</h1>
    <br>   

    <a href="${pageContext.request.contextPath}/servlet/AddCustomerServlet" target="main">添加客戶</a>
    <a href="${pageContext.request.contextPath}/servlet/ListCustomerServlet" target="main">查看客戶</a>
  </body>
</html>

注意到 head.jsp 中的2個超連接,結果顯示在 main 那個幀裏。
根據上述的2個超連接,分別編寫那2個控制器servlet程序。
先看添加客戶按鈕的超連接cn.wk.web.controller.AddCustomerServlet,當客戶點擊「添加客戶」按鈕時,發出的是 get 請求, 由AddCustomerServlet的 doGet 方法處理,該方法又把請求轉發給/WEB-INF/jsp/addcustomer.jsp去顯示錶單,用戶填寫完表單後,再用action="${pageContext.request.contextPath}/servlet/AddCustomerServlet" method="post"使用 post 請求又鏈接回cn.wk.web.controller.AddCustomerServlet這個servlet控制器,該控制器用 doPost 方法接受請求並處理(就是把客戶填寫的表單封裝成一個 Customer 對象,並轉交給業務層處理,業務層將該對象轉交給dao,由dao負責將對 Customer 對象存入數據庫),其中涉及到 message 的信息傳送,以表達「添加成功或失敗」信息,並回顯給客戶。

cn.wk.web.controller.AddCustomerServlet以下:

package cn.wk.web.controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.wk.domain.Customer;
import cn.wk.service.BusinessService;
import cn.wk.service.impl.BusinessServiceImpl;
import cn.wk.utils.Globals;
import cn.wk.utils.WebUtils;

public class AddCustomerServlet extends HttpServlet {

    // 給用戶提供一個添加界面
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        // 信息帶給jsp
        req.setAttribute("genders", Globals.genders);
        req.setAttribute("preferences", Globals.preferences);
        req.setAttribute("types", Globals.types);

        // 視圖
        req.getRequestDispatcher("/WEB-INF/jsp/addcustomer.jsp").forward(req,
                resp);
    }

    // 處理用戶的添加請求
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {

        try {
            req.setCharacterEncoding("UTF-8");
            // 表單校驗

            // 把表單數據封裝到 customer 對象中 , 用工具類 WebUtils
            Customer c = WebUtils.request2Bean(req, Customer.class);
            c.setId(WebUtils.generateID());

            BusinessService service = new BusinessServiceImpl();
            service.addCustomer(c);
            req.setAttribute("message", "添加成功!!");

        } catch (Exception e) {
            e.printStackTrace();
            req.setAttribute("message", "添加失敗!!");
        }
        req.getRequestDispatcher("/message.jsp").forward(req, resp);
    }
}

控制層的cn.wk.web.controller.AddCustomerServlet還引入了cn.wk.utils.Globals這個工具類,該類目的是不想把genders, preferences, types寫死,經過修改該類,達到直接控制那3個屬性的目的,代碼以下:

package cn.wk.utils;

public class Globals {
    public static String genders[] = { "男", "女", "人妖" };
    public static String preferences[] = { "唱歌", "跳舞", "桑拿", "打麻將", "看鳳姐",
            "夜生活", "xxx" };
    public static String types[] = { "vip客戶", "重點客戶", "普通客戶" };
}

/WEB-INF/jsp/addcustomer.jsp以下(涉及一個顯示日期的js控件/WEB-INF/js/ShowCalendar.js):
該代碼涉及了一個makepre()的 js 函數,功能是把客戶的多個preference組合成一個字符串,並弄出一個 隱式輸入項 送入要提交的表單中。makepre()函數涉及了 javascript 的 DOM 編程,挺有意思的。

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>添加用戶的視圖</title>
    <script type="text/javascript" src="${pageContext.request.contextPath }/js/ShowCalendar.js"></script>
    <script type="text/javascript">
        function makepre(){
            var pres = document.getElementsByName("pre");
            var preference = "";
            for(var i = 0; i < pres.length; i++){
                var input = pres[i];
                if(input.checked==true){
                    preference = preference + input.value + ",";
                }
            }
            // 組裝字符串   跳舞,打麻將,看鳳姐
            preference = preference.substr(0, preference.length - 1);

            var form = document.getElementById("form");
            var input = document.createElement("input");
            input.type = "hidden";
            input.name = "preference";
            input.value = preference;

            form.appendChild(input);
            return true;
        }
    </script>
</head>

<body style="text-align:center;">
    <br />
    <form id="form"
        action="${pageContext.request.contextPath}/servlet/AddCustomerServlet"
        method="post" onsubmit="return makepre()">
        <!-- js代碼,當按submit按鈕時,就調用makepre()方法 -->

        <table border="1" width="30%">
            <tr>
                <td>客戶姓名</td>
                <td><input type="text" name="name"></td>
            </tr>

            <tr>
                <td>性別</td>
                <td><c:forEach var="gender" items="${genders}">
                        <input type="radio" name="gender" value="${gender}"> ${gender}
                </c:forEach></td>
            </tr>

            <tr>
                <td>生日</td>
                <td><input type="text" name="birthday"
                    onClick="showCalendar(this.id)" id="birthday"></td>
            </tr>

            <tr>
                <td>手機</td>
                <td><input type="text" name="cellphone"></td>
            </tr>

            <tr>
                <td>郵箱</td>
                <td><input type="text" name="email"></td>
            </tr>

            <tr>
                <td>愛好</td>
                <td><c:forEach var="p" items="${preferences}">
                        <input type="checkbox" name="pre" value="${p}">${p}
                </c:forEach></td>
            </tr>

            <tr>
                <td>客戶類型</td>
                <td><c:forEach var="t" items="${types}">
                        <input type="radio" name="type" value="${t}">${t}
                </c:forEach></td>
            </tr>

            <tr>
                <td>客戶備註</td>
                <td><textarea rows="5" cols="100" name="description"></textarea>
                </td>
            </tr>

            <tr>
                <td><input type="reset" value="重置"></td>
                <td><input type="submit" value="添加客戶"></td>
            </tr>
        </table>
    </form>
</body>
</html>

/message.jsp以下:

我也把那個顯示日期的 js 控件/WEB-INF/js/ShowCalendar.js代碼貼出來:

// 日期選擇
// By Ziyue(http://www.web-v.com/)
// 使用方法:
// <script type="text/javascript" src="${pageContext.request.contextPath }/js/ShowCalendar.js"></script>
// <input name="birthday" type="text" id="birthday" title="點擊選擇" onClick="showCalendar(this.id)">

var today;
document
        .writeln("<div id='Calendar' style='position:absolute; z-index:1; visibility: hidden; filter:\"progid:DXImageTransform.Microsoft.Shadow(direction=135,color=#999999,strength=3)\"'></div>");

function getDays(month, year) {
    var daysInMonth = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
    // 下面的這段代碼是判斷當前是不是閏年的
    if (1 == month)
        return ((0 == year % 4) && (0 != (year % 100))) || (0 == year % 400) ? 29
                : 28;
    else
        return daysInMonth[month];
}

function getToday() {
    // 獲得今天的年,月,日
    this.now = new Date();
    this.year = this.now.getFullYear();
    this.month = this.now.getMonth();
    this.day = this.now.getDate();
}

function getStringDay(str) {
    // 獲得輸入框的年,月,日
    var str = str.split("-");

    this.now = new Date(parseFloat(str[0]), parseFloat(str[1]) - 1,
            parseFloat(str[2]));
    this.year = this.now.getFullYear();
    this.month = this.now.getMonth();
    this.day = this.now.getDate();
}

function newCalendar() {
    var parseYear = parseInt(document.all.Year.options[document.all.Year.selectedIndex].value);
    var newCal = new Date(parseYear, document.all.Month.selectedIndex, 1);
    var day = -1;
    var startDay = newCal.getDay();
    var daily = 0;

    if ((today.year == newCal.getFullYear())
            && (today.month == newCal.getMonth()))
        day = today.day;

    var tableCal = document.all.calendar;
    var intDaysInMonth = getDays(newCal.getMonth(), newCal.getFullYear());

    for (var intWeek = 1; intWeek < tableCal.rows.length; intWeek++)
        for (var intDay = 0; intDay < tableCal.rows[intWeek].cells.length; intDay++) {
            var cell = tableCal.rows[intWeek].cells[intDay];
            if ((intDay == startDay) && (0 == daily))
                daily = 1;

            if (day == daily) // 今天,調用今天的Class
            {
                cell.style.background = '#6699CC';
                cell.style.color = '#FFFFFF';
                // cell.style.fontWeight='bold';
            } else if (intDay == 6) // 週六
                cell.style.color = 'green';
            else if (intDay == 0) // 週日
                cell.style.color = 'red';

            if ((daily > 0) && (daily <= intDaysInMonth)) {
                cell.innerText = daily;
                daily++;
            } else
                cell.innerText = "";
        }
}

function GetDate(InputBox) {
    var sDate;
    // 這段代碼處理鼠標點擊的狀況
    if (event.srcElement.tagName == "TD")
        if (event.srcElement.innerText != "") {
            sDate = document.all.Year.value + "-" + document.all.Month.value
                    + "-" + event.srcElement.innerText;
            eval("document.all." + InputBox).value = sDate;
            HiddenCalendar();
        }
}

function HiddenCalendar() {
    // 關閉選擇窗口
    document.all.Calendar.style.visibility = 'hidden';
}

function showCalendar(InputBox) {
    var months = new Array("一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月",
            "九月", "十月", "十一月", "十二月");
    var days = new Array("日", "一", "二", "三", "四", "五", "六");

    var x, y, intLoop, intWeeks, intDays;
    var DivContent;
    var year, month, day;
    var o = eval("document.all." + InputBox);
    var thisyear; // 真正的今年年份

    thisyear = new getToday();
    thisyear = thisyear.year;

    today = o.value;
    if (isDate(today))
        today = new getStringDay(today);
    else
        today = new getToday();

    // 顯示的位置
    x = o.offsetLeft;
    y = o.offsetTop;
    while (o = o.offsetParent) {
        x += o.offsetLeft;
        y += o.offsetTop;
    }
    document.all.Calendar.style.left = x + 2;
    document.all.Calendar.style.top = y + 20;
    document.all.Calendar.style.visibility = "visible";

    // 下面開始輸出日曆表格(border-color:#9DBAF7)
    DivContent = "<table border='0' cellspacing='0' style='border:1px solid #0066FF; background-color:#EDF2FC'>";
    DivContent += "<tr>";
    DivContent += "<td style='border-bottom:1px solid #0066FF; background-color:#C7D8FA'>";

    // 年
    DivContent += "<select name='Year' id='Year' onChange='newCalendar()' style='font-family:Verdana; font-size:12px'>";
    for (intLoop = thisyear - 35; intLoop < (thisyear + 2); intLoop++)
        DivContent += "<option value= " + intLoop + " "
                + (today.year == intLoop ? "Selected" : "") + ">" + intLoop
                + "</option>";
    DivContent += "</select>";

    // 月
    DivContent += "<select name='Month' id='Month' onChange='newCalendar()' style='font-family:Verdana; font-size:12px'>";
    for (intLoop = 0; intLoop < months.length; intLoop++)
        DivContent += "<option value= " + (intLoop + 1) + " "
                + (today.month == intLoop ? "Selected" : "") + ">"
                + months[intLoop] + "</option>";
    DivContent += "</select>";

    DivContent += "</td>";

    DivContent += "<td style='border-bottom:1px solid #0066FF; background-color:#C7D8FA; font-weight:bold; font-family:Wingdings 2,Wingdings,Webdings; font-size:16px; padding-top:2px; color:#4477FF; cursor:hand' align='center' title='關閉' onClick='javascript:HiddenCalendar()'>S</td>";
    DivContent += "</tr>";

    DivContent += "<tr><td align='center' colspan='2'>";
    DivContent += "<table id='calendar' border='0' width='100%'>";

    // 星期
    DivContent += "<tr>";
    for (intLoop = 0; intLoop < days.length; intLoop++)
        DivContent += "<td align='center' style='font-size:12px'>"
                + days[intLoop] + "</td>";
    DivContent += "</tr>";

    // 天
    for (intWeeks = 0; intWeeks < 6; intWeeks++) {
        DivContent += "<tr>";
        for (intDays = 0; intDays < days.length; intDays++)
            DivContent += "<td onClick='GetDate(\""
                    + InputBox
                    + "\")' style='cursor:hand; border-right:1px solid #BBBBBB; border-bottom:1px solid #BBBBBB; color:#215DC6; font-family:Verdana; font-size:12px' align='center'></td>";
        DivContent += "</tr>";
    }
    DivContent += "</table></td></tr></table>";

    document.all.Calendar.innerHTML = DivContent;
    newCalendar();
}

function isDate(dateStr) {
    var datePat = /^(\d{4})(\-)(\d{1,2})(\-)(\d{1,2})$/;
    var matchArray = dateStr.match(datePat);
    if (matchArray == null)
        return false;
    var month = matchArray[3];
    var day = matchArray[5];
    var year = matchArray[1];
    if (month < 1 || month > 12)
        return false;
    if (day < 1 || day > 31)
        return false;
    if ((month == 4 || month == 6 || month == 9 || month == 11) && day == 31)
        return false;
    if (month == 2) {
        var isleap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
        if (day > 29 || (day == 29 && !isleap))
            return false;
    }
    return true;
}

把 request 中表單信息封裝成javabean需創建一個cn.wk.utils.WebUtils工具類。因需把request中的一個 String 類型的參數birthday=1999-01-01轉換成java.utils.Date對象,需註冊一個轉換器,這玩意對我來講很陌生,代碼以下:

package cn.wk.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;

public class WebUtils {
    public static <T> T request2Bean(HttpServletRequest request,
            Class<T> beanClass) {
        try {
            T bean = beanClass.newInstance();

            // 獲得 request 裏全部數據
            Map map = request.getParameterMap();

            // 填充
            // map{name=aa,password=bb,birthday=1999-01-01} 填充到bean
            // bean{name=aa,password=bb,birthday=Date}

            ConvertUtils.register(new Converter() {

                @Override
                // 字符串 轉成 java.util.Date 返回
                public Object convert(Class type, Object value) {
                    if (value == null) {
                        return null;
                    }

                    String str = (String) value;
                    if (str.trim().equals("")) {
                        return null;
                    }

                    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
                    try {
                        return df.parse(str);
                    } catch (ParseException e) {
                        throw new RuntimeException(e);
                    }
                }

            }, Date.class);

            BeanUtils.populate(bean, map);
            return bean;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String generateID(){
        return UUID.randomUUID().toString();        
    }
}

head.jsp 中的另外一個按鈕「查看客戶」,跳轉到一個控制層的servlet上,cn.wk.web.controller.ListCustomerServlet代碼以下:

package cn.wk.web.controller;

import java.io.IOException;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.wk.service.BusinessService;
import cn.wk.service.impl.BusinessServiceImpl;

// 獲得全部客戶顯示
public class ListCustomerServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        try {
            BusinessService service = new BusinessServiceImpl();
            List list = service.getAllCustormer();
            req.setAttribute("list", list);
            req.getRequestDispatcher("/WEB-INF/jsp/listcustomer.jsp").forward(
                    req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            req.setAttribute("message", "查看客戶失敗!!");
            req.getRequestDispatcher("/message.jsp").forward(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doGet(req, resp);
    }
}

經過上面的servlet,除了發生異常時發送「查看客戶失敗!!」的信息到/message.jsp,無異常時將request轉發給/WEB-INF/jsp/listcustomer.jsp,其代碼以下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>列出全部客戶</title>
</head>

<body style="text-align: center;">
    <table frame="border" width="85%">
        <tr>
            <td>客戶姓名</td>
            <td>性別</td>
            <td>生日</td>
            <td>手機</td>
            <td>郵箱</td>
            <td>愛好</td>
            <td>類型</td>
            <td>備註</td>
            <td>操做</td>
        </tr>

        <c:forEach var="c" items="#{requestScope.list}">
            <tr>
                <td>${c.name }</td>
                <td>${c.gender }</td>
                <td>${c.birthday }</td>
                <td>${c.cellphone }</td>
                <td>${c.email }</td>
                <td>${c.preference }</td>
                <td>${c.type }</td>
                <td>${c.description }</td>
                <td>
                    <a href="#">修改</a>
                    <a href="#">刪除</a>
                </td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

上述 jsp 頁面仍會顯示在 index.jsp 中name="main"的分幀裏,緣由是在 head.jsp 中有這樣的定義:

<a href="${pageContext.request.contextPath}/servlet/ListCustomerServlet" target="main">查看客戶</a>

即用戶點擊查看客戶按鈕,跳轉到控制層的ListCustomerServlet這個servlet上,而後request又由該servlet轉發到/WEB-INF/jsp/listcustomer.jsp這個數據展現頁面上,而這個展現頁面仍受 head.jsp 中的target="main"的控制,最終拼接在 index.jsp 頁面指定的位置上。 真夠複雜的了。

相關文章
相關標籤/搜索