IntelliJ IDEA 来自于 JetBrains ,相信很多人都不会感到陌生,其 IED 整体界面风格还是让人很舒服的。下面将演示如何一步一步创建一个简单的 Web 应用的过程。该项目中有简单应用到
Hibernate 和 Servlet 、Tomcat、JSP 等相关内容,如果你还不是特别熟悉这些概念也没关系,这篇文章只是为了方便初学者能够快速了解整体流程。
文中示例项目代码:WebApplicationDemo-Github
本片文章中,你将可以看到如何构建一个拥有简单注册和登录流程的应用。应用大致流程是:用户在 JSP 页面输入并提交表单数据,应用通过 Servlet 获取到请求参数后,使用 Hibernate 进行一些基本的数据库增删改查,处理简单业务逻辑后返回结果内容展示到 JSP 页面。
文章背景大致介绍清楚了,相信你也明白大概要做个什么样的东西。为了更直观地了解,开始之前,我们先来看一眼项目文件结构和实际运行的效果图:
下面是实际运行效果截图,文章最后也有演示内容截图:
1. 下载 IDE
关于开发工具 IDE 的选择因人而异,主要看团队开发环境和个人使用习惯,这里我们就直接选择 JetBrains 的 IntelliJ IDEA 下载了。
2. 搭建运行环境
确认系统已有 JDK 环境:关于安装过程这里就不展开了
搭建 Tomcat 环境:这里推荐之前写的一篇文章:Mac 系统 Tomcat 配置
数据库环境:挑合适的就行了,我这里选熟悉的 MySQL,有需要的直接去 MySQL 官网下载安装就行了,安装完成后记得去系统偏好设置中开启 MySQL 服务。
MySQL 服务使用前建议先改密码,不然会有使用时会有
Access denied for user 'root'@'localhost' (using password: YES)
提示。
# 1.初始化状态无密码,直接按回车进入
mysql -u root -p
# 2.为 root 用户设置密码
SET PASSWORD FOR root = PASSWORD('xxxxxx');
# 3.刷新权限
flush privileges;
# 4.退出
\q
# 5.重新启动
sudo mysql.server restart
这推荐一个数据库可视化操作工具:Sequel Pro,非常简单好用。
你可能还需要一个 MySQL 连接库用于 IDE 中连接数据库,关于这个可以参考我之前的另一篇文章:处理 com.mysql.jdbc.Driver Not Found
3. 创建项目数据库表
登录数据库,查看目前已有数据库
# 登录 mysql
mysql -u root -p
# 查看已有数据库
show databases;
创建新的数据库:
# 创建新的数据库
create database demo_web_app
创建新的数据表:
# 选择数据库
use demo_web_app;
# 创建数据表
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`userName` char(30) CHARACTER SET latin1 NOT NULL DEFAULT '',
`password` char(30) CHARACTER SET latin1 NOT NULL DEFAULT '',
`nickName` char(30) CHARACTER SET latin1 DEFAULT '',
`email` char(30) CHARACTER SET latin1 DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
可视化工具查看
4. 新建项目
这里为了展示创建一个简单的 web 应用,这里直接选 Web Application 和 Hibernate 即可:
勾选 Hibernate 支持
输入项目名称和主 Module 名称
项目创建完成后,我们先简单看看项目文件结构
- lib 包中是 Hibernate 的相关支持库
- src 包是存放项目源码和 Hibernate 配置文件
- web 包是存放 web 页面相关的资源
5. 项目配置
选中 module 后点击右键打开 Open Module Settings, 配置 module 的依赖,项目比较简单我们选择 Hibernate 和 tomcat 依赖即可。
**注意 tomcat 的依赖配置路径,建议选其安装目录下的整个 lib **
配置 Artifacts
下面我们为 module 配置 Artifacts ,这是为了告诉 IDE 选中的 module 要如何进行打包,例如可以是 war exploded、war、jar、ear 等等这种打包形式。某个 module 有了 Artifacts 就可以部署到应用服务器中了。
jar (Java Archive):通常用于聚合大量的Java类文件、相关的元数据和资源(文本、图片等)文件到一个文件,以便分发Java平台应用软件或库;
war (Web application Archive):一种JAR文件,其中包含用来分发的JSP、Java Servlet、Java类、XML文件、标签库、静态网页(HTML和相关文件),以及构成Web应用程序的其他资源;
exploded:在这里你可以理解为展开,不压缩的意思。也就是war、jar等产出物没压缩前的目录结构。建议在开发的时候使用这种模式,便于修改了文件的效果立刻显现出来。
默认情况下,IDEA的 Modules 和 Artifacts 的 output 目录已经设置好了,不需要更改,打成 war 包的时候会自动在 WEB-INF 目录下生成 classes,然后把编译后的文件放进去。
对于项目其他一些配置和作用,大家可以参考这篇文章:理解 IntelliJ IDEA 的项目配置和Web部署
连接 Database,这里我们选择 MySQL 作为项目 Data Source:
我们先前搭建项目基础环境时,已经创建了一个叫 demo_web_app 的数据库,这里直接进行连接即可:
数据库连接成功后,我们我们可以可以在 IDE 中看到直观的看到数据库表内容,里面有我们之前创建的 user 表:
下面将创建项目时自动生成的 hibernate.cfg.xml 进行修改,按照实际数据库连接参数进行配置
hibernate.cfg.xml 修改完成后,我们在 Persistence 选项卡中选中 hibernate.cfg.xml 点击右键,打开 Gennerate Persistence Mapping 进行数据库产生持久化数据映射。
此时 IDE 会自动加载数据库 Database Schema,我们选择一个用于存放映射出来实体类的 Package 路径。
然后 IDE 就会自动生成实体类和对应的 .hbm.xml 文件
项目中 hibernate.cfg.xml 也会自动更新,添加 mapping 语句关联
UserEntity.hbm.xml 文件
这里牵涉到 ORM 的概念,如果你还不熟悉这里,建议可以先结合
Hibernate 的使用过程了解一下。
下面修改运行配置,可以设置运行时启动的浏览器和地址,这里使用默认的即可,注意下图蓝色选中标识的内容,选中之前配置的 Build Artifacts 选项,如果没有出现可以点击提示出现的 fix 按钮自动导入
6. 编码实现
尽可能结合注释内容理解吧,各个知识点就不单独展开说明了
jdbc:mysql://localhost:3306/demo_web_app
com.mysql.jdbc.Driver
thread
root
root
update
true
org.hibernate.dialect.MySQLDialect
package gdut.bai.util;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
/**
* Hibernate 工具类
*
* Session 是由SessionFactory负责创建的,而SessionFactory的实现是线程安全的,
* 多个并发的线程可以同时访问一个 SessionFactory 并从中获取 Session 实例,而Session不是线程安全的。
* Session 中包含了数据库操作相关的状态信息,那么说如果多个线程同时使用一个 Session 实例进行 CRUD,就很有可能导致数据存取的混乱
*/
public class HibernateUtil {
/**
*
*/
private static SessionFactory sessionFactory;
/**
* 使用 ThreadLocal 保存当前业务线程中的 Hibernate Session
*
* ThreadLocal 并不是一个Thread,而是 thread local variable (线程局部变量)。
* ThreadLocal 非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
* 从线程的角度看,就好像每一个线程都完全拥有一个该变量 (Map 类型 key-value 保存)。
* ThreadLocal 这个类本身不是代表线程要访问的变量,这个类的成员变量才是。
* 线程通过 ThreadLocal 的 get 和 set 方法去访问这个变量。
*
*/
private static ThreadLocal threadLocalSession = new ThreadLocal<>();
/**
* Hibernate 配置
*/
private static Configuration configuration = new Configuration();
/**
* 静态代码块
*/
static {
buildSessionFactory();
}
private HibernateUtil() {
}
/**
* 获取 Session
* @return
* @throws HibernateException
*/
public static Session getSession() throws HibernateException {
System.out.println("getSession");
Session session = threadLocalSession.get();
if (session == null || !session.isOpen()) {
if (sessionFactory == null) {
buildSessionFactory();
}
session = (sessionFactory != null) ? sessionFactory.openSession() : null;
threadLocalSession.set(session);
}
return session;
}
/**
* 关闭 Session
* @throws HibernateException
*/
public static void closeSession() throws HibernateException {
Session session = threadLocalSession.get();
threadLocalSession.set(null);
if (session != null) {
session.close();
}
}
/**
* 获取 SessionFactory
* @return SessionFactory
*
* @deprecated
*/
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* 获取 Configuration
* @return Configuration
*
* @deprecated
*/
public static Configuration getConfiguration() {
return configuration;
}
/**
* 构建 SessionFactory
*/
private static void buildSessionFactory() {
System.out.println("start buildSessionFactory.");
try {
// 读取 Hibernate 的配置文件(默认 hibernate.cfg.xml)
if (configuration == null){
configuration = new Configuration();
}
configuration.configure("/hibernate.cfg.xml");
// 创建 ServiceRegistry,通过 StandardServiceRegistryBuilder 构建并设置 Configuration 信息
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.configure() // 可以指定配置文件(默认hibernate.cfg.xml)
.build();
try {
// 创建 SessionFactory
sessionFactory = new MetadataSources(serviceRegistry)
.buildMetadata()
.buildSessionFactory();
}catch (Exception e){
StandardServiceRegistryBuilder.destroy(serviceRegistry);
e.printStackTrace();
}
}catch (Exception e){
System.err.println("Creating SessionFactory Error.");
e.printStackTrace();
}
}
}
package gdut.bai.dao;
import org.hibernate.Session;
/**
* DAO 工厂类
* 单例模式
*
* @author baishixian
*/
public class DAOFactory {
private static volatile DAOFactory mInstance;
private DAOFactory() {
}
public static DAOFactory getInstance(){
if (mInstance == null){
synchronized (DAOFactory.class){
if (mInstance == null){
mInstance = new DAOFactory();
}
}
}
return mInstance;
}
/**
* 获取 UserDao
* @return UserDao
*/
public UserDao getUserDAO(Session session) {
return new UserDaoImpl(session);
}
}
package gdut.bai.dao;
import gdut.bai.entity.UserEntity;
import java.util.List;
/**
* 数据访问对象 DAO
* 作为用户数据的访问接口
*
* @author baishixian
*/
public interface UserDao {
String insertUser(UserEntity user);
String updateUser(UserEntity user);
List queryInfo(String type, Object value);
}
package gdut.bai.dao;
import gdut.bai.entity.UserEntity;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.query.Query;
import javax.swing.*;
import java.util.List;
/**
* 数据访问对象 DAO
* 作为用户数据的访问接口
*
* @author baishixian
*/
public class UserDaoImpl implements UserDao {
private final Session session;
// 提交数据的事务
private Transaction transaction;
public UserDaoImpl(Session session) {
this.session = session;
}
@Override
public String insertUser(UserEntity user) {
String result;
try {
transaction = session.beginTransaction();
session.save(user);
transaction.commit();
result = "用户:" + user.getUserName() + "注册成功!";
} catch (Exception e) {
// showMessage("RegisterInfo error:" + e);
e.printStackTrace();
result = "注册失败:" + e;
}
return result;
}
@Override
public String updateUser(UserEntity user) {
String result;
try {
transaction = session.beginTransaction();
session.update(user);
transaction.commit();
result = "用户:" + user.getUserName() + "信息更新成功!";
} catch (Exception e) {
// showMessage("updateUser error:" + e);
e.printStackTrace();
result = "用户信息失败:" + e;
}
return result;
}
@Override
public List queryInfo(String type, Object value) {
String sql = "from gdut.bai.entity.UserEntity as user where user." + type + "=?";
System.out.println("queryInfo sql " + sql + " value = " + value);
try {
transaction = session.beginTransaction();
Query query = session.createQuery(sql);
query.setParameter(0, value);
List list = query.list();
transaction.commit();
return list;
} catch (Exception e) {
showMessage("queryInfo error:" + e);
e.printStackTrace();
return null;
}
}
private void showMessage(String mess) {
int type = JOptionPane.YES_NO_OPTION;
String title = "提示信息";
JOptionPane.showMessageDialog(null, mess, title, type);
}
}
package gdut.bai.entity;
public class UserEntity {
private int id;
private String userName;
private String password;
private String nickName;
private String email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserEntity that = (UserEntity) o;
if (id != that.id) return false;
if (userName != null ? !userName.equals(that.userName) : that.userName != null) return false;
if (password != null ? !password.equals(that.password) : that.password != null) return false;
if (nickName != null ? !nickName.equals(that.nickName) : that.nickName != null) return false;
if (email != null ? !email.equals(that.email) : that.email != null) return false;
return true;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (userName != null ? userName.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0);
result = 31 * result + (nickName != null ? nickName.hashCode() : 0);
result = 31 * result + (email != null ? email.hashCode() : 0);
return result;
}
}
package gdut.bai.servlet;
import gdut.bai.comment.Constance;
import gdut.bai.dao.DAOFactory;
import gdut.bai.dao.UserDao;
import gdut.bai.entity.UserEntity;
import gdut.bai.util.HibernateUtil;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 交由 post 方法统一处理
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 设置内容类型
response.setContentType("text/html;charset=utf-8");
// 文本输出流
PrintWriter printWriter = response.getWriter();
// 获取请求提交的内容
String username = request.getParameter(Constance.USER_NAME);
String password = request.getParameter(Constance.PASSWORD);
// 获取 UseDAO
UserDao userDao = DAOFactory.getInstance().getUserDAO(HibernateUtil.getSession());
if (username == null || username.length() == 0) {
printWriter.print(getErrorAlertMsg("用户名不能为空"));
} else {
List userList = userDao.queryInfo(Constance.USER_NAME, username);
if (userList != null && !userList.isEmpty()){
for (UserEntity user : userList) {
if (user.getUserName().equals(username)) {
if (user.getPassword().equals(password)) {
printWriter.print("登录成功!");
return;
} else {
printWriter.print(getErrorAlertMsg("密码错误!"));
}
}
}
}
HibernateUtil.closeSession();
printWriter.print(getErrorAlertMsg("用户名错误!"));
}
}
private String getErrorAlertMsg(String msg){
return "";
}
}
package gdut.bai.servlet;
import gdut.bai.comment.Constance;
import gdut.bai.dao.DAOFactory;
import gdut.bai.dao.UserDao;
import gdut.bai.entity.UserEntity;
import gdut.bai.util.HibernateUtil;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
@WebServlet("/RegisterServlet")
public class RegisterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter printWriter = response.getWriter();
String username = request.getParameter(Constance.USER_NAME);
String password = request.getParameter(Constance.PASSWORD);
String confirmPassword = request.getParameter(Constance.CONFIRM_PASSWORD);
UserDao userDao = DAOFactory.getInstance().getUserDAO(HibernateUtil.getSession());
if (username == null || username.length() == 0) {
printWriter.print(getErrorAlertMsg("用户名不能为空!"));
} else {
List userList = userDao.queryInfo(Constance.USER_NAME, username);
if (userList != null && !userList.isEmpty()){
for (UserEntity user : userList) {
if (user.getUserName().equals(username)) {
printWriter.print(getErrorAlertMsg("用户名已存在"));
}
}
}
}
if (password == null || password.length() == 0) {
printWriter.print(getErrorAlertMsg("密码不能为空!"));
} else if (!password.equals(confirmPassword)) {
printWriter.print(getErrorAlertMsg("两次输入的密码不一致!"));
}
// 创建 User 对象
UserEntity user = new UserEntity();
user.setUserName(username);
user.setPassword(password);
// 往数据库插入新用户信息
String result = userDao.insertUser(user);
printWriter.print(result);
HibernateUtil.closeSession();
}
private String getErrorAlertMsg(String msg){
return "";
}
}
package gdut.bai.comment;
/**
* 存放常量
* @author baishixian
*/
public interface Constance {
String USER_NAME = "userName";
String PASSWORD = "password";
String CONFIRM_PASSWORD = "confirmPassword";
}
web/JSP/index.jsp
<%--
Created by IntelliJ IDEA.
User: baishixian
Date: 2017/10/26
Time: 下午3:32
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8"%>
Web Application Demo
Welcome Web Application
Hello World!
web/JSP/login.jsp
<%--
Created by IntelliJ IDEA.
User: baishixian
Date: 2017/10/25
Time: 下午10:31
To change this template use File | Settings | File Templates.
--%>
<%--
Created by IntelliJ IDEA.
User: baishixian
Date: 2017/10/25
Time: 下午10:31
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
My JSP 'index.jsp' starting page
登 录
web/JSP/register.jsp
<%--
Created by IntelliJ IDEA.
User: baishixian
Date: 2017/10/25
Time: 下午10:31
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
My JSP 'index.jsp' starting page
注 册
7. 运行应用
上面运行参数配置后,点击 run 按钮,启动 Tomcat:
Tomcat 运行起来后,会自动打开 localhost:8080
页面
跳转到注册页面,输入用户信息:
点击注册,检测数据库是否已有该用户,没有则新增一条。
注册成功以后的数据库表成功新增一条记录:
进入登录页面,试试刚刚注册的账号能否做登录验证:
登录数据验证成功后,提示登录成功:
测试密码输出的情况,提示一切正常:
Github Project
文中示例项目代码:WebApplicationDemo-Github
参考文章:
理解 IntelliJ IDEA 的项目配置和Web部署
处理 com.mysql.jdbc.Driver Not Found
Mac 系统 Tomcat 配置
Hibernate实现登录注册小例子