首頁 > Java > java教程 > 主體

以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解

巴扎黑
發布: 2017-08-10 15:37:21
原創
2399 人瀏覽過


我們以前是建立代表資料庫的xml檔案來保存使用者資訊的,現在我們已經學習了資料庫相關的知識,所以應把xml換成資料庫,升級成資料庫應用。
我們在複製以前的工程並拷貝時,假設以前的工程名稱是day09_user,現複製一份並拷貝,重新修改工程名為day14_user,此刻將其直接部署在tomcat伺服器上,那麼day14_user這個JavaWeb應用程式映射的虛擬目錄仍然是”/day09_user”,並不是映射成為一個同名的虛擬目錄”/day14_user”,這是一個經常被人忽略的問題,要解決這個問題,可像下面這樣做:
以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解
以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解

升級成資料庫應用程式

  • 匯入資料庫驅動
    以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解

    • ##為應用程式建立對應的函式庫和表格
    • create database day14_user;use day14_user;create table users
      (
          id varchar(40) primary key,
          username varchar(40) not null unique,
          password varchar(40) not null,
          email varchar(100) not null unique,
          birthday date,
          nickname varchar(40) not null);
      登入後複製
    • 1
    • #2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

  • 10以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解

  • #11
    • 在src目錄下建立一個db.properties文件,如下圖所示:
    •  

      在db. properties中編寫MySQL資料庫的連接訊息,程式碼如下所示:
    • driver=com.mysql.jdbc.Driver
      url=jdbc:mysql://localhost:3306/day14_userusername=root
      password=yezi#driver=oracle.jdbc.driver.OracleDriver#url=jdbc:oracle:thin:@localhost:1521:orcl#username=system#password=111
      登入後複製
    • #1
    • ##2
    • 3
    • 4
    • #5
    • ##6

    • #7

    • 8

    • #9

    • 10

    • 11

    • 12

    • #13

    • #14

    • 15

    16

17
以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解
重寫UserDao
現在我們要重寫UserDao介面中所有的方法,在cn.itcast.dao.impl套件中建立一個UserDao介面的實作類別-UserDaoJdbcImpl,如下所示:

由於一開始我們寫的並不是最終的程式碼,所以我們將專注於
public User find(String username, String password)

登入方法。
    我們先使用statement物件重寫
  • public User find(String username, String password)

    方法,程式碼如下:

    @Override
    public User find(String username, String password) {
        Connection conn = null;
        Statement st = null; // PreparedStatement预防SQL注入
        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);
        }
    }1234567891011121314151617181920212223242526272829
    登入後複製
  • 在java中關於對例外的處理真是太mb的煩了,現在總結一下對異常的處理原則。
  • 在學界關於對java異常的處理吵的熱火朝天,主要分為三大門派:

  • #java語言設計者-高司令,他設計出來的java語言有編譯時異常和執行時異常,既然java語言都是他設計出來的,所以他認為編譯時異常也是必須合理存在的。

《Thinking In Java》的作者認為java語言編譯時異常就是垃圾,異常都應轉為執行時期異常拋出。


Spring的作者柔和了以上2人的觀點,他就說-你這個有程式有異常,你拿到這個異常,怎麼做呢?就看上一層程式能不能處理?如果不能處理,就轉為運行時異常拋出去,如果能處理,就轉為編譯時異常直接往上拋出去。

以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解
所以,最好我們應該採用Spring的作者的觀點,那我們的處理方法就是:

你這個有程式有異常,你拿到這個異常,怎麼做呢?就看異常你希不希望上一層程式處理?如果你不希望上一層程式處理,免得給上一層程式帶來麻煩,就轉為運行時異常拋出,如果你希望上一層程式處理,就轉為編譯時異常直接往上拋出去

在實際開發中,最好每一個層都寫一個自訂異常,例如在Dao層(資料存取層)自訂異常類別-DaoException。 在cn.itcast.exception套件中建立例外類別DaoException,如下:
DaoException類別程式碼如下:

public class DaoException extends RuntimeException {    public DaoException() {        // TODO Auto-generated constructor stub
    }    public DaoException(String message) {        super(message);        // TODO Auto-generated constructor stub
    }    public DaoException(Throwable cause) {        super(cause);        // TODO Auto-generated constructor stub
    }    public DaoException(String message, Throwable cause) {        super(message, cause);        // TODO Auto-generated constructor stub
    }    public DaoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {        super(message, cause, enableSuppression, writableStackTrace);        // TODO Auto-generated constructor stub
    }

}123456789101112131415161718192021222324252627
登入後複製
###我們要自訂一個Dao異常拋出去,為什麼呢?自訂一個Dao異常丟出去最大的好處就是我把這個異常拋出去,人家收到這個異常,一看到這個異常的類名,就能知道到底是哪一層出問題了,他就可以快速定位到這一層來找問題。 ###最好每一層都要有一個自訂異常###。 ###接著修改UserDaoJdbcImpl類別中的###public User find(String username, String password)###方法為:###
@Override
public User find(String username, String password) {
    Connection conn = null;
    Statement st = null; // PreparedStatement预防SQL注入
    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) {
        // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛        /*
         * 自定义一个Dao异常抛出去,为什么呢?
         * 自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名,
         * 就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。
         */
        throw new DaoException(e);
    } finally {
        JdbcUtils.release(conn, st, rs);
    }
}1234567891011121314151617181920212223242526272829303132333435
登入後複製

实现service层和dao层的解耦

我们以前在开发service层(service层对web层提供所有的业务服务)时,编写UserService接口的具体实现类——UserServiceImpl时,业务逻辑层和数据访问层是紧密联系在一起的,所以业务逻辑层和数据访问层要解耦(希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式),要解耦,有两种方法:

  • 工厂模式

  • Spring

现在我们重点关注工厂模式。这时我们要定义一个Dao工厂,新建一个cn.itcast.factory包,在包中创建一个Dao工厂——DaoFactory,如下所示:
以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解
工厂一般要设计成单例的,为什么呢?
答:工厂设计成单例的,工厂的对象在内存中只有一个,目的是希望所有的Dao都由一个工厂来生产。假设不把工厂设计成单例的,将来Dao由不同的工厂来生产,你觉得所有的Dao由一个工厂来生产好,还是由不同的工厂来生产好?答案显然是由一个工厂来生产好,将来维护起来好维护。
我们先编写DaoFactory类的代码为:

public class DaoFactory {    private Properties daoConfig = new Properties();    private DaoFactory() {}    private static DaoFactory instance = new DaoFactory();    public static DaoFactory getInstance() {        return instance;
    }    //                      UserDao.class(接口类型)    //                      DepartmentDao.class(接口类型)       public <T> T createDao(Class<T> clazz) {        // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao
        String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao

        DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
        ......
    }
}123456789101112131415161718192021
登入後複製

以上的代码好吗?显然这样做并不好,每次别人调用createDao方法,都会去读一下配置文件dao.properties,但此配置文件在整个系统里面只要读取一次就好,没必要老去读,即以下这行代码只需运行一次。

DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");1
登入後複製

那么这行代码可以放到静态代码块里。
现在将以上代码修改为:

public class DaoFactory {    static {
        DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
    }    private Properties daoConfig = new Properties();    private DaoFactory() {}    private static DaoFactory instance = new DaoFactory();    public static DaoFactory getInstance() {        return instance;
    }    //                      UserDao.class(接口类型)    //                      DepartmentDao.class(接口类型)       public <T> T createDao(Class<T> clazz) {        // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao
        String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao        // DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
        ......
    }
}12345678910111213141516171819202122232425
登入後複製

以上这样做并没有不好,但是对于现在而言还有一种做法,DaoFactory这个类被设计成单例的,所以其构造函数仅执行一次,所以可放到DaoFactory这个类的构造函数里面。所以最后完整的代码如下:

public class DaoFactory {    /*
    static {
        DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
    }
    */    private Properties daoConfig = new Properties();    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;
    }    //                      UserDao.class(接口类型)    //                      DepartmentDao.class(接口类型)       public <T> T createDao(Class<T> clazz) {        // clazz.getName(); // 返回UserDao接口的完整名称:cn.itcast.dao.UserDao
        String name = clazz.getSimpleName(); // 返回UserDao接口的简单名称:UserDao        // DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");

        String className = daoConfig.getProperty(name);        try {
            T dao = (T) Class.forName(className).newInstance(); // 创建实例对象            return dao;
        } catch (Exception e) {            throw new RuntimeException(e);
        }
    }
}123456789101112131415161718192021222324252627282930313233343536373839
登入後複製

然后在src目录下创建一个dao.properties文件,如下图所示:
以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解
在db.properties中内容如下所示:

UserDao=cn.itcast.dao.impl.UserDaoJdbcImpl1
登入後複製

实现service层和dao层的解耦,UserService接口的具体实现类——UserServiceImpl只须修改为:

// 对web层提供所有的业务服务public class BusinessServiceImpl {    /*
     * 业务逻辑层和数据访问层要解耦——希望底层Dao层代码换了,业务逻辑层的代码一行都不改,这时要用到工厂设计模式
     * 要解耦,有两张方法:
     * 1. 工厂模式
     * 2. spring
     */    // private UserDao dao = new UserDaoJdbcImpl();    private UserDao dao = DaoFactory.getInstance().createDao(UserDao.class);    // 对web层提供注册服务    public void register(User user) throws UserExistException {        // 先判断当前要注册的用户是否存在        boolean b = dao.find(user.getUsername());        if(b) {            /*
             * service层是由web层来调用的,
             * 发现当前要注册的用户已存在,要提醒给web层,web层给用户一个友好提示
             * 希望web层一定要处理,处理之后给用户一个友好提示,所以抛一个编译时异常,
             * 抛运行时异常是不行的,因为web层可处理可不处理
             */            throw new UserExistException(); // 发现要注册的用户已存在,则给web层抛一个编译时异常,提醒web层处理这个异常,给用户一个友好提示。
        } else {
            user.setPassword(ServiceUtils.md5(user.getPassword()));
            dao.add(user);
        }
    }    // 对web层提供登录服务    public User login(String username, String password) { // aaa 123
        password = ServiceUtils.md5(password); // 要把密码md5一把再找        return dao.find(username, password);
    }
}123456789101112131415161718192021222324252627282930313233343536
登入後複製

防范sql注入攻击

SQL注入是用户利用某些系统没有对输入数据进行充分的检查,从而进行恶意破坏的行为。
例如,statement存在sql注入攻击问题,假如登录用户名采用&#39; or 1=1 or name=&#39;
以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解

对于防范SQL注入,可以采用PreparedStatement取代Statement。

PreparedStatement对象介绍

PreperedStatement是Statement的孩子,它的实例对象可以通过调用Connection.preparedStatement()方法获得,相对于Statement对象而言:

  • PreperedStatement可以避免SQL注入的问题。

  • Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。PreparedStatement可对SQL进行预编译,从而提高数据库的执行效率。

  • 并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换,简化sql语句的编写。

使用PreparedStatement改写UserDaoJdbcImpl类的public User find(String username, String password)方法。

@Override
public User find(String username, String password) {
    Connection conn = null;
    PreparedStatement st = null; // PreparedStatement预防SQL注入
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();
        String sql = "select * from users where username=? and password=?";        st = conn.prepareStatement(sql); // 预编译这条sql语句        st.setString(1, username);        st.setString(2, password);

        rs = st.executeQuery();
        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 DaoException(e);
    } finally {
        JdbcUtils.release(conn, st, rs);
    }
}12345678910111213141516171819202122232425262728293031
登入後複製

到此为止,我们就可以完整地写出UserDaoJdbcImpl类了,其完整代码如下:

public class UserDaoJdbcImpl implements UserDao {    @Override    public void add(User user) {
        Connection conn = null;        // Statement st = null;
        PreparedStatement st = null;
        ResultSet rs = null;        try {
            conn = JdbcUtils.getConnection();
            String sql = "insert into users(id,username,password,email,birthday,nickname) values(?,?,?,?,?,?)";
            st = conn.prepareStatement(sql);
            st.setString(1, user.getId());
            st.setString(2, user.getUsername());
            st.setString(3, user.getPassword());
            st.setString(4, user.getEmail());
            st.setDate(5, new java.sql.Date(user.getBirthday().getTime()));
            st.setString(6, user.getNickname());            // st = conn.createStatement();            // String sql = "insert into users(id,username,password,email,birthday,nickname) values(&#39;"+user.getId()+"&#39;,&#39;"+user.getUsername()+"&#39;,&#39;"+user.getPassword()+"&#39;,&#39;"+user.getEmail()+"&#39;,&#39;"+user.getBirthday().toLocaleString()+"&#39;,&#39;"+user.getNickname()+"&#39;)";            // int num = st.executeUpdate(sql);            int num = st.executeUpdate();            if(num < 1) {                throw new RuntimeException("注册用户失败!!!");
            }
        } catch (Exception e) {            // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛            /*
             * 自定义一个Dao异常抛出去,为什么呢?
             * 自定义一个Dao异常抛出去最大的好处就是我把这个异常抛出去,人家收到这个异常,一看到这个异常的类名,
             * 就能知道到底是哪一层出问题了,他就可以快速定位到这一层来找问题。最好每一层都要有一个自定义异常。
             */            /*
             * gosling    Thinking In Java   Spring
             * Spring作者——你这个有程序有异常,你拿到这个异常,怎么做呢?就看上一层程序能不能处理?
             *            如果不能处理,就转为运行时异常抛出去,如果能处理,就转为编译时异常直接往上抛出去。
             *            
             * 我们的处理方法——你这个有程序有异常,你拿到这个异常,怎么做呢?就看异常你希不希望上一层程序处理?
             *            如果你不希望上一层程序处理,免得给上一层程序带来麻烦,就转为运行时异常抛出去,如果你希望上一层程序处理,就转为编译时异常直接往上抛出去。
             */            // throw new RuntimeException(e);            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }    // username=&#39;or 1=1 or username=&#39;   password=""    @Override    public User find(String username, String password) {
        Connection conn = null;
        PreparedStatement st = null; // PreparedStatement预防SQL注入
        ResultSet rs = null;        try {
            conn = JdbcUtils.getConnection();
            String sql = "select * from users where username=? and password=?";
            st = conn.prepareStatement(sql); // 预编译这条sql语句
            st.setString(1, username);
            st.setString(2, password);

            rs = st.executeQuery();            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 DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }    @Override    public boolean find(String username) {
        Connection conn = null;
        PreparedStatement st = null;
        ResultSet rs = null;        try {
            conn = JdbcUtils.getConnection();
            String sql = "select * from users where username=?";
            st = conn.prepareStatement(sql);
            st.setString(1, username);

            rs = st.executeQuery();            if (rs.next()) {                return true;
            }            return false;
        } catch (Exception e) {            // 异常直接往上面抛,除了给上层带来麻烦,没有任何好处,所以可将异常转型然后往业务层抛            throw new DaoException(e);
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }

}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
登入後複製

面试题:Statement和PreparedStatement的区别。
答:

  1. PreparedStatement是Statement的孩子。

  2. PreparedStatement可以防止sql注入的问题。

  3. PreparedStatement会对sql语句进行预编译,以减轻数据库服务器的压力。

就像xxx.javaxxx.classJVM执行一样,sql语句编译数据库执行
开发完数据访问层,一定要对程序已编写好的部分代码进行测试,做一步,测试一步,以免整个程序完成后由于页面太多或者是代码量太大给查找错误造成更大的负担!所以,在juint.test包下创建了一个UserDaoJdbcTest类。
以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解
UserDaoJdbcTest类的具体代码如下:

public class UserDaoJdbcTest {
    @Test    public void testAdd() {
        User user = new User();
        user.setBirthday(new Date());
        user.setEmail("bb@sina.com");
        user.setId("2142354354");
        user.setNickname("李子");
        user.setUsername("bbbb");
        user.setPassword("123");

        UserDao dao = new UserDaoJdbcImpl();
        dao.add(user);
    }

    @Test    public void testFind() {
        UserDao dao = new UserDaoJdbcImpl();
        User user = dao.find("bbbb", "123"); // 在断点模式Watch
        System.out.println(user);
    }

    @Test    public void testFindByUsername() {
        UserDao dao = new UserDaoJdbcImpl();
        System.out.println(dao.find("bbbb"));
    }
}12345678910111213141516171819202122232425262728
登入後複製

经测试,没发现任何错误。同样也要对业务逻辑层已编写好的部分代码进行测试,在juint.test包下创建了一个ServiceTest类。
以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解
ServiceTest类的具体代码如下:

public class ServiceTest {

    @Test    public void testRegister() {
        User user = new User();
        user.setBirthday(new Date());
        user.setEmail("bb@sina.com");
        user.setId("2142354354");
        user.setNickname("李子");
        user.setUsername("lizi");
        user.setPassword("123");

        BusinessServiceImpl service = new BusinessServiceImpl();        try {
            service.register(user);
            System.out.println("注册成功!!!");
        } catch (UserExistException e) {
            System.out.println("用户已存在");
        }
    }

    @Test    public void testLogin() {
        BusinessServiceImpl service = new BusinessServiceImpl();
        User user = service.login("lizi", "123");
        System.out.println(user);
    }
}12345678910111213141516171819202122232425262728
登入後複製

经测试,没发现任何错误。
到此,对基于Servlet+JSP+JavaBean开发模式的用户登录注册的升级改造圆满完成。


以上是以Servlet+JSP+JavaBean為基礎開發模式實現使用者登入註冊的升級教學課程詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!