Servlet学习(三)Servlet编程实例-网站登录(修改版-增加数据库-附源码)

来源:互联网 发布:淘宝真正的外贸店 编辑:程序博客网 时间:2024/06/15 22:00
我们为刚才的那个示例Servlet学习(二)Servlet编程实例-网站登录添加上数据库,组成一个较为完整的登录功能。

在开始之前我们得首先了解下一些常用的java术语:

PO(persistant object) 持久对象
在o/r 映射的时候出现的概念,如果没有o/r映射,就没有这个概念存在了.
通常对应数据模型(数据库),本身还有部分业务逻辑的处理.
可以看成是与数据库中的表相映射的java对象.
最简单的PO就是对应数据库中某个表中的一条记录,多个记录可以用PO的集合.
PO中应该不包含任何对数据库的操作.

VO(value object) 值对象
通常用于业务层之间的数据传递,和PO一样也是仅仅包含数据而已.
但应是抽象出的业务对象,可以和表对应,也可以不,这根据业务的需要.
个人觉得同DTO(数据传输对象),在web上传递.

BO(business object) 业务对象
从业务模型的角度看,见UML元件领域模型中的领域对象.
封装业务逻辑的java对象,通过调用DAO方法,结合PO,VO进行业务操作.

POJO(plain ordinary java object) 简单无规则java对象
纯 的传统意义的java对象.就是说在一些Object/Relation Mapping工具中,
能够做到维护数据库表记录的persisent object完全是一个符合Java Bean规范的纯Java对象,没有增加别的属性和方法.
我的理解就是最基本的Java Bean,只有属性字段及setter和getter方法!.

DAO(data access object) 数据访问对象
是sun的一个标准j2ee设计模式,这个模式中有个接口就是DAO,它负责持久层的操作.为业务层提供接口.
此对象用于访问数据库.通常和PO结合使用,
DAO中包含了各种数据库的操作方法.通过它的方法,结合PO对数据库进行相关的操作.
夹在业务逻辑与数据库资源中间.配合VO, 提供数据库的CRUD操作...

DTO (Data Transfer Object)数据传输对象
主要用于远程调用等需要大量传输对象的地方。
DTO一般只有成员变量,成员变量的set方法以及get方法还有构造函数。
DTO不能包含业务逻辑
比如我们一张表有100个字段,那么对应的PO就有100个属性。
但是我们界面上只要显示10个字段,
客户端用WEB service来获取数据,没有必要把整个PO对象传递到客户端,
这时我们就可以用只有这10个属性的DTO来传递结果到客户端,这样也不会暴露服务端表结构.到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO

O/R Mapper 对象/关系 映射
定义好所有的mapping之后,这个O/R Mapper可以帮我们做很多的工作.
通过这些mappings,这个O/R Mapper可以生成所有的关于对象保存,删除,读取的SQL语句,我们不再需要写那么多行的DAL代码了.
实体Model(实体模式)
DAL(数据访问层)
IDAL(接口层)
DALFactory(类工厂)
BLL(业务逻辑层)
BOF    Business Object Framework      业务对象框架
SOA    Service Orient Architecture    面向服务的设计
EMF    Eclipse Model Framework        Eclipse建模框架

各组件之间的关系如图:

在引入实例以前,我们有必要回顾,并进一步了解分层架构。“层”是一种体系结构模式[POSA1],也是被广大软件从业人员用得最为广泛而且最为灵活的模式之一。记得在CSDN上,时常有朋友问到:“分层是什么?为什么要分层?三层架构是不是就是表现层、业务逻辑层和数据访问层?”

到这里,你可能会觉得这些朋友的问题很简单,分层嘛,不就是将具有不同职责的组件分离开来,组成一套层内部高聚合,层与层之间低耦合的软件系统吗?不错!这是分层的目标。但是,我们应该如何分层呢?

领域驱动设计的讨论同样也是建立在层模式的基础上的,但与传统的分层架构相比,它更注重领域架构和技术架构的分离。

传统的三层架构

如上文那位朋友提的问题那样,最简单的分层方式自然就是“表现层、业务逻辑层和数据访问层”,我们可以用下图来表示这个思想:

52017893333

注意图中打虚线的“基础结构层”,从实践的表现上来看,这部分内容可能就是一些帮助类,比如 SQLHelper之类的,也可能是一些工具类,比如TextUtility之类。这些东西可以被其它各层所访问。而基于分层的概念,表现层只能跟业务逻辑层打交道,而业务逻辑层在数据持久化方面的操作,则依赖于数据访问层。表现层对数据访问层的内容一无所知。

从领域驱动的角度看,这种分层的方式有一定的弊端。首先,为各个层面提供服务的“基础结构层”的职责比较紊乱,它可以是纯粹的技术框架,也可以包含或处理一定的业务逻辑,这样一来,业务逻辑层与“基础结构层”之间就会存在依赖关系;其次,这种结构过分地突出了“数据访问”的地位,把“数据访问”与 “业务逻辑”放在了等同的地位,这也难怪很多软件人员一上来就问:“我的数据表该如何设计?”

领域驱动设计的分层

领域驱动设计将软件系统分为四层:基础结构层、领域层、应用层和表现层。与上述的三层相比,数据访问层已经不在了,它被移到基础结构层了。


  • 基础结构层:该层专为其它各层提供技术框架支持。注意,这部分内容不会涉及任何业务知识。众所周知的数据访问的内容,也被放在了该层当中,因为数据的读写是业务无关的
  • 领域层:包含了业务所涉及的领域对象(实体、值对象)、领域服务以及它们之间的关系。这部分内容的具体表现形式就是领域模型(Domain Model)。领域驱动设计提倡富领域模型,即尽量将业务逻辑归属到领域对象上,实在无法归属的部分则以领域服务的形式进行定义。有关领域对象和领域服务的内容,我会在接下来的案例中进行阐述
  • 应用层:该层不包含任何领域逻辑,但它会对任务进行协调,并可以维护应用程序的状态,因此,它更注重流程性的东西。在某些领域驱动设计的实践中,也会将其称为“工作流层”。应用层是领域驱动中最有争议的一个层次,也会有很多人对其职责感到模糊不清。比如,有些国外的开发人员会觉得,既然不包含领域逻辑,那又如何协调工作任务呢?我会在《应用层与实体事件》章节对这些问题进行探讨
  • 表现层:这个好理解,跟三层里的表现层意思差不多,但请注意,“Web服务”虽然是服务,但它是表现层的东西
从上图还可以看到,表现层与应用层之间是通过数据传输对象(DTO)进行交互的,数据传输对象是没有行为的POCO对象,它的目的只是为了对领域对象进行数据封装,实现层与层之间的数据传递。为何不能直接将领域对象用于数据传递?因为领域对象更注重领域,而DTO更注重数据。不仅如此,由于“富领域模型”的特点,这样做会直接将领域对象的行为暴露给表现层。



我们首先在mysql数据库中进行建表插数据操作

主要包含两张表:tbl_user表和tbl_address表
tbl_user表中主要储存用户信息,包括id,用户名,密码,邮箱
tbl_address表中主要储存用户的地址信息,使用user_id作为外键与tbl_user表连接起来。

然后,在表中插入一些测试信息:

CREATE TABLE tbl_user(
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
uname VARCHAR(50) NOT NULL DEFAULT '',
upwd VARCHAR(50) NOT NULL DEFAULT '',
email VARCHAR(50) DEFAULT '',
PRIMARY KEY(id))
ENGINE=INNODB
DEFAULT CHARSET=utf8;

CREATE TABLE tbl_address(
id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
city VARCHAR(20) DEFAULT NULL,
country VARCHAR(20) DEFAULT NULL,
user_id INT(11) UNSIGNED NOT NULL,
PRIMARY KEY(id))
ENGINE=INNODB
DEFAULT CHARSET=utf8;



INSERT INTO tbl_user(id,uname,upwd,email)
VALUES
(1,'xiaoming','123456','xiaoming@qq.com'),
(2,'xiaozhang','123456','xiaozhang@qq.com');

INSERT INTO tbl_address(city,country,user_id)
VALUES('beijing','China',1);

INSERT INTO tbl_address(city,country,user_id)
VALUES('tianjing','China',2);




数据库创建完成之后我们就在项目中创建对应的DTO类

首先,在项目中创建包:entity
在包中创建IdEnity类:IdEntity封装了所有的表中的非业务的主键id

public abstract class IdEnity {
    protected long id;

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

    public long getId() {
        return id;
    }
}


接下来我们创建User类,这个类继承自IdEnity类。
public class User extends IdEnity {
    private String uname;
    private String upwd;
    private String email;

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public String getUpwd() {
        return upwd;
    }

    public void setUpwd(String upwd) {
        this.upwd = upwd;
    }

    public String getEmail() {
        return email;
    }

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

    @Override
    public long getId() {
        return super.getId();
    }

    @Override
    public void setId(long id) {
        super.setId(id);
    }

    @Override
    public String toString() {
        return "User{" +
                "uname='" + uname + '\'' +
                ", upwd='" + upwd + '\'' +
                ", email='" + email + '\'' +
                ", id='"+id+'\''+
                '}';
    }
}


Address类同样需要继承IdEnity

public class Address extends IdEnity{
    private String city;
    private String country;
    private long userId;

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public long getUserId() {
        return userId;
    }

    public void setUserId(long userId) {
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", country='" + country + '\'' +
                ", userId=" + userId +'\'' +
                ", id='"+id+'\''+
                '}';
    }
}



将数据库创建好之后,接下来便是要使我们的项目可以连接到数据库,
因为我们使用的是mysql数据库,所以我们下载mysql对应的jdbc驱动。
下载地址https://dev.mysql.com/downloads/connector/j/
下载好之后将jar包文件添加到项目中去

然后我们便可以开发连接类来获取数据库的连接。
创建一个新包:util,在包中创建ConnectionFactory类

public class ConnectionFactory {
    private static String driver;
    private static String dburl;
    private static String name;
    private static String password;

    //声明ConnectionFactory类型对象
    private static final ConnectionFactory factory=new ConnectionFactory();
    //用于保存数据库连接
    private Connection conn;

    /**
    * 使用静态代码块从属性文件中读取配置信息
    * 静态代码快用于初始化类,用于给类进行赋值
    * 当jvm加载类的时候会执行其中的静态代码块,只会执行一次
    */
    static {
        //定义Properties类
        Properties prop=new Properties();
        try {
            //读取配置文件信息
            //首先获取当前类的类加载器,
            // 然后使用类加载器的getResourceAsStream方法将文件读取到一个输入流中
            InputStream in=ConnectionFactory.class.getClassLoader().
                    getResourceAsStream("dbconfig.properties");
            //使用load方法读取属性列表
            prop.load(in);

        } catch (IOException e) {
            System.out.println("========配置文件读取错误========");
        }
        //将读取到的值赋值给成员变量
        driver=prop.getProperty("driver");
        dburl=prop.getProperty("dburl");
        name=prop.getProperty("name");
        password=prop.getProperty("password");
    }

    /**
    * 构造函数
    */
    private ConnectionFactory(){

    }

    /**
    * 使用单例模式,保证运行期间只有一个ConnectionFactory对象存在
    * @return
    */
    public static ConnectionFactory getInstance(){
        return factory;
    }

    /**
    * 获取数据库连接
    * @return
    */
    public Connection makeConnection(){
        try {
            Class.forName(driver);

            conn= DriverManager.getConnection(dburl,name,password);
        }catch (Exception e){
            e.printStackTrace();
        }
        return conn;
    }
}

在这里我们使用到了配置文件来储存我们的数据库连接信息
在项目src文件夹下创建一个dbconfig.porperties配置文件来存储我们连接数据库的配置参数
配置文件中的信息如下:
driver=com.mysql.jdbc.Driver
dburl=jdbc\:mysql\://localhost\:3306/jsp_db
name=wangxin
password=*******(对应自己用户名的密码)


接下来我们实现DAO类的编写
首先我们新建一个dao包,在包中添加UserDao类
UserDao类主要定义方法的接口,使用面向接口开发来实现这些功能。
public interface UserDao {
    public void save(Connection conn, User user)throws SQLException;
    public void update(Connection conn, long id, User user)throws SQLException;
    public void delete(Connection conn, User user)throws SQLException;
    public ResultSet get(Connection conn, User user)throws SQLException;
}

在dao包中在新建一个impl包,用于存放实现UserDao接口的实现类
子啊impl包中新建UserDaoImpl类实现了UserDao接口,并在类中实现了接口中定义的方法

public class UserDaoImpl implements UserDao{
    /**
    * 添加用户信息
    * @param conn
    * @param user
    * @throws SQLException
    */
    @Override
    public void save(Connection conn, User user) throws SQLException {
        PreparedStatement pstm
                =conn.prepareStatement("INSERT INTO tbl_user(uname,upwd,email) VALUES (?,?,?)");
        pstm.setString(1,user.getUname());
        pstm.setString(2,user.getUpwd());
        pstm.setString(3,user.getEmail());
        pstm.execute();

    }

    /**
    * 更新用户信息
    * @param conn
    * @param id
    * @param user
    * @throws SQLException
    */
    @Override
    public void update(Connection conn, long id, User user) throws SQLException {
        PreparedStatement pstm=
                conn.prepareStatement("UPDATE tbl_user SET uname=?,upwd=?,email=? WHERE id=?");
        pstm.setString(1,user.getUname());
        pstm.setString(2,user.getUpwd());
        pstm.setString(3,user.getEmail());
        pstm.setLong(4,id);
        pstm.execute();
    }

    /**
    * 删除用户信息
    * @param conn
    * @param user
    * @throws SQLException
    */
    @Override
    public void delete(Connection conn, User user) throws SQLException {
        PreparedStatement pstm=
                conn.prepareStatement("DELETE FROM tbl_user WHERE id=?");
        pstm.setLong(1,user.getId());
        pstm.execute();
    }

    /**
    * 获取查询结果
    * @param conn
    * @param user
    * @return
    * @throws SQLException
    */
    @Override
    public ResultSet get(Connection conn, User user) throws SQLException {
        PreparedStatement ps=conn.prepareStatement("SELECT * FROM tbl_user WHERE uname=? AND upwd=?");
        ps.setString(1,user.getUname());
        ps.setString(2,user.getUpwd());
        return ps.executeQuery();
    }
}
在后续的数据操作中我们便可以使用此类中的函数来执行对数据库的数据操作。

接下来我们创建服务类来执行校验用户名和密码的服务操作
首先创建一个service包,在包中创建一个类CheckUserService类
public class CheckUserService {
    private UserDao userDao=new UserDaoImpl();
    public boolean check(User user){
        Connection conn=null;
        try{
            //关闭自动提交
            conn= ConnectionFactory.getInstance().makeConnection();
            conn.setAutoCommit(false);
            //查询是否在数据库中有对应的信息
            ResultSet rs=userDao.get(conn,user);
            if (rs.next()){
                return true;
            }
        }catch (Exception e){
            e.printStackTrace();
            try {
                conn.rollback();
            }catch (Exception e1){
                e1.printStackTrace();
            }
        }
        finally {
            try{
                conn.close();
            }catch (Exception e2){
                e2.printStackTrace();
            }

        }
        return false;
    }
}




接下来我们编写Servlet的操作代码,主要是将jsp页面传递过来的表单数据进行封装为DTO类,并使用CheckServletService来对数据进行检查。

public class CheckServlet extends HttpServlet {
    private CheckUserService checkUserService=new CheckUserService();
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String uname=request.getParameter("uname");
        String upwd=request.getParameter("upwd");
        RequestDispatcher rd=null;
        String forword=null;
        //判断用户名和密码是否为空
        if (uname==null||upwd==null){
            request.setAttribute("msg","用户名或者密码为空");
            rd=request.getRequestDispatcher("/15/error.jsp");
            rd.forward(request,response);
        }else{
            User user=new User();
            user.setUname(uname);
            user.setUpwd(upwd);
            boolean bool=checkUserService.check(user);
            if (bool){
                forword="/15/success.jsp";
            }else{
                request.setAttribute("msg","用户名或者密码输入错误,请重试!");
                forword="/15/error.jsp";
            }
            rd=request.getRequestDispatcher(forword);
            rd.forward(request,response);
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //以后开发在doGet方法中直接调用doPost方法
        doPost(request,response);
    }
}



这样,我们的程序便优化完成,成为了一个具有数据库的登录程序。

项目下载地址:https://github.com/icaruswang/StudyJsp
0 0