第二章 快速入门

第二章 快速入门

第二章 快速入门

本章的主要内容:

  • Maven构建工具介绍
  • 用户登录实例介绍
  • 基于Spring JDBC的持久层实现
  • 基于Spring声明式事务的业务层实现
  • 基于Spring MVC的展现层实现
  • 在IDEA中开发Web应用的过程
  • 运行Web应用

Spring 拥有持久层,业务层和展现层的“原生技术”,分别是Spring JDBC,声明式事务和Spring MVC.

2.1 实例功能介绍


在持久层拥有两个DAO类,分别是UserDao和LoginLogDao,在业务层对应一个业务类UserService,在展现层拥有一个LoginController类和两个JSP页面,分别是登录页面login.jsp和欢迎页面main.jsp。

2.2 环境的准备

  • MySQL 5.x https://dev.mysql.com/downloads/installer/

  • 构建工具MAven

    • Maven是Apache的一个顶级项目,也是一款强大的构建工具,能够帮助用户建立一套有效的自动化构建体系,从清理,编译,测试到生成报告,再到打包和部署,只需按照Maven提供的POM配置好项目模块间的相互依赖关系以及相关的Maven插件,然后输入简单的命令,Maven就能完成那些繁琐的构建任务,可参考《Maven实战》一书。Maven构建的模型如下:

    • Maven基本概念
      • Project: 任意你想构建的事务。Maven 都可以认为他们是工程。这些工程被认定为工程对象模型(Project Object Model,POM),一个工程可以依赖其他工程,也可以包含多个子工程。
      • POM: POM(pom.xml)是Maven的核心文件,他是Maven如何工作的元数据文件,POM文件位于每个工程的根目录中。
      • GroupID: GroupID 是一个工程在全局中的唯一标识符。一般认为他就是工程名。
      • Artifact: 中文名叫“构件”,是工程将要产生或者需要使用的文件,他可以是。jar文件,源文件,二进制文件,.war文件,甚至是.pom文件。每个Artifact都由GroupID和ArtifactId组合的唯一识别。需要被使用的Artifact都要放在仓库中。否则Maven无法找到他们。即项目库构件的名称(例如,它的JAR或WAR文件的名称)。
      • Dependency: 为了能够构建,一个典型的工程会依赖其他的包,这些依赖的包被称为Dependency.Dependency一般是其他工程的Artifact.
      • Plug-in:可以说是一组插件的集合。每个功能都有插件完成的。插件提供goal,并根据POM中找到的元数据去完成工作
      • Repository: 仓库,即放置Artifact的地方,有中央仓库,公共仓库,私有仓库,本地仓库。
    • Maven的安装
      • 下载v3.3.9 http://maven.apache.org/download.cgi
      • 把apache-maven-3.3.9-bin.zip解压到指定的目录

    2.3 创建库表

  • 创建数据库

    CREATE DATABASE sampledb DEFAULT CHARACTER SET utf8;
  • 创建table
CREATE TABLE t_user(user_id INT AUTO_INCREMENT PRIMARY KEY,
                 USER_NAME VARCHAR(30),
                 credits int,
                password varchar(32),
                last_visit datetime,
                last_ip varchar(23),
                )engine=innodb;

t_user 为用户信息表,t_login_log为用户登录日志表,engine=InnoDB指定了表的引擎为InnoDb类型。该类型的表支持事务。mysql默认才用myIsam引擎,该类型不支持事务,仅仅存储数据,优点在于读写速度快。

  • 插入一条数据
    insert into t_user (user_name,password) values('admin','123456');
    commit;

2.4 建立工程

使用Jntelij IDEA建立工程,file->new-> project ->maven

点击Next,进入Maven工程导向,分别填写GroupID,ArticfactId,Version。

2.5 Maven选项

点击IDEA工程的右边Maven Projects选项。弹出Maven项目的管理界面

每个模块包含了两个节点Lifecycle和Dependencies, Lifecycle子节点下面提供了常用的Maven操作命令。如清理,验证,编译,测试,打包,部署等,Dependencies下面列出了当前模块依赖的所有类库。

2.6 类包以及Spring配置文件规划

  • 类包以分层的方式组织,共划分为四个包,分别是dao,domain,service和web。
  • 单元测试的类包和程序的类包对应。但放在不同的文件夹下。

2.7 持久层

持久层负责数据的访问和操作,DAO类被上层的业务类调用。Spring本身支持多种流行的ORM框架。这里使用Spring JDBC作为持久层的技术实现。

2.7.1 建立领域对象

  • 用户领域对象

    用户领域对象可以看成是t_user表的对象映射,每个字段对应一个对象属性,User类主要有三类信息。分别是userName/password,积分credits以及最后一次登录的信息lastIp,lastVisit。代码如下:

package com.smart.domain;

import java.io.Serializable;
import java.util.Date;

public class User implements Serializable{
	private int userId;

	private String userName;

	private String password;

	private int credits;

	private String lastIp;

	private Date lastVisit;

	public String getLastIp() {
		return lastIp;
	}

	public void setLastIp(String lastIp) {
		this.lastIp = lastIp;
	}

	public Date getLastVisit() {
		return lastVisit;
	}

	public void setLastVisit(Date lastVisit) {
		this.lastVisit = lastVisit;
	}

	public int getUserId() {
		return userId;
	}

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

	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 int getCredits() {
		return credits;
	}

	public void setCredits(int credits) {
		this.credits = credits;
	}
}
  • 登录日志领域对象
    用户每次登录成功之后,都会记录一条登录日志,该日志包含3类信息,分别是用户ID,登录IP和登录时间。还有退出时间。代码如下:
package com.smart.domain;
import java.io.Serializable;
import java.util.Date;

public class LoginLog implements Serializable{
	private int loginLogId;

	private int userId;

	private String ip;

	private Date loginDate;

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public Date getLoginDate() {
		return loginDate;
	}

	public void setLoginDate(Date loginDate) {
		this.loginDate = loginDate;
	}

	public int getLoginLogId() {
		return loginLogId;
	}

	public void setLoginLogId(int loginLogId) {
		this.loginLogId = loginLogId;
	}

	public int getUserId() {
		return userId;
	}

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

}

2.7.2 UserDao

package com.smart.dao;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Repository;

import com.smart.domain.User;

@Repository // 通过Spring注解定义一个Dao
public class UserDao {
	private JdbcTemplate jdbcTemplate;

	private  final static String MATCH_COUNT_SQL = " SELECT count(*) FROM t_user  " +
			" WHERE user_name =? and password=? ";
	private  final static String UPDATE_LOGIN_INFO_SQL = " UPDATE t_user SET " +
			" last_visit=?,last_ip=?,credits=?  WHERE user_id =?";

	public int getMatchCount(String userName, String password) {

		return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[]{userName, password}, Integer.class);
	}

	public User findUserByUserName(final String userName) {
		String sqlStr = " SELECT user_id,user_name,credits "
				+ " FROM t_user WHERE user_name =? ";
		final User user = new User();
		jdbcTemplate.query(sqlStr, new Object[] { userName },
				new RowCallbackHandler() {
					public void processRow(ResultSet rs) throws SQLException {
						user.setUserId(rs.getInt("user_id"));
						user.setUserName(userName);
						user.setCredits(rs.getInt("credits"));
					}
				});
		return user;
	}

	public void updateLoginInfo(User user) {
		jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL, new Object[] { user.getLastVisit(),
				user.getLastIp(),user.getCredits(),user.getUserId()});
	}

	@Autowired //自动注入JdbcTemplate的Bean
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}
}

  • GetMatchCount()根据用户名和密码获取匹配的用户数,等用1表示用户密码正确。等于0表示错误。
  • findSerByUserName()根据用户名获取User对象
  • UpdateLoginInfo()更新用户积分,最后登录IP以及最后登录时间。

在DAO中编写SQL语句时,通常将SQL语句写在类静态变量中,这样会使代码更具有可读性,如果sql语句很长,一般会才用多行字符的方式进行构造。在多行写sql语句的时候,由于上下行最终会组成一行完整的sql语句。因而拼接的时候很容易产生错误,比较好的做法是在句前和句尾都添加一个空格。

2.7.2 在Spring中装配DAO

Spring把操作数据库的操作都封装到了JdbcTemplate中了,JdbcTemplate本身需要一个DataSource,这样它就可以根据需要从DataSource中获取或者返回连接,UserDao都提供了一个带@Autowired注解的JdbcTemplate变量。通过Spring的容器上下文自动绑定机制进行Bean的注入。
在src\resources下面建一个叫smart-context.xml的spring配置文件。如下:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/tx 
       http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
    
    <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
    <context:component-scan base-package="com.futuretechx.dao"/>
    <context:component-scan base-package="com.futuretechx.service"/>
    
    <!-- 配置数据源 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close" 
		p:driverClassName="com.mysql.jdbc.Driver"
		p:url="jdbc:mysql://localhost:3306/sampledb"
		p:username="root"
		p:password="123456" />

	<!-- 配置Jdbc模板  -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
		p:dataSource-ref="dataSource" />
		
	<!-- 配置事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
		p:dataSource-ref="dataSource" />
		
	<!-- 通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务 -->
	<aop:config proxy-target-class="true">
		<aop:pointcut id="serviceMethod"
			expression="(execution(* com.sfuturetechx.service..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))" />
		<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
	</aop:config>
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="*" />
		</tx:attributes>
	</tx:advice>
</beans>

  • 我们使用Spring的context:component-scan扫描指定类包下面的所有类,这样的类中定义的Spring 注解才能生效。
  • 我们使用Jakarta的DBCP开源数据源实现方案定义了一个数据源,数据库驱动器类为com.mysql.jdbc.Driver。
  • p:dataSource-ref申的dataSource注入JdbcTemplate中,而这个JdbcTemplate bean 将通过@Autowired自动注入LoginLog和UserDao 的Bean中,可以Spring可以很好的将注释配置和XML配置统一起来。

2.8 业务层

2.8.1 UserService的实现类需要调用DAO层的两个DAO完成业务逻辑操作。

package futuretechx.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import com.smart.dao.LoginLogDao;
import com.smart.dao.UserDao;
import com.smart.domain.LoginLog;
import com.smart.domain.User;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserService {
	private UserDao userDao;
	private LoginLogDao loginLogDao;


	public boolean hasMatchUser(String userName, String password) {
		int matchCount =userDao.getMatchCount(userName, password);
		return matchCount > 0;
	}
	
	public User findUserByUserName(String userName) {
		return userDao.findUserByUserName(userName);
	}

	@Transactional
    public void loginSuccess(User user) {
		user.setCredits( 5 + user.getCredits());
		LoginLog loginLog = new LoginLog();
		loginLog.setUserId(user.getUserId());
		loginLog.setIp(user.getLastIp());
		loginLog.setLoginDate(user.getLastVisit());
        userDao.updateLoginInfo(user);
        loginLogDao.insertLoginLog(loginLog);
	}

	@Autowired
	public void setUserDao(UserDao userDao) {
		this.userDao = userDao;
	}

	@Autowired
	public void setLoginLogDao(LoginLogDao loginLogDao) {
		this.loginLogDao = loginLogDao;
	}
}

2.8.2 在Spring中装配Service

2.9 展现层

业务层和持久层的开发已经完成了,也该提供程序界面的时候了。

2.9.1 配置Spring MVC 框架

首先需要对web.xml 文件进行配置,以便Web容器在启动时能够自动启动Spring容器。代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
   <!-- 从classpath路径下加载Spring配置文件-->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:smart-context.xml</param-value>
	</context-param>
    <!--负责启动Spring容器的监听器,引用上下文参数获取Spring配置文件的地址-->
	<listener>
		<listener-class>
			org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>

	<servlet>
		<servlet-name>smart</servlet-name>
		<servlet-class>
			org.springframework.web.servlet.DispatcherServlet
		</servlet-class>
		<load-on-startup>3</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>smart</servlet-name>
		<url-pattern>*.html</url-pattern>
	</servlet-mapping>
</web-app>

解释下:

  • <context-param>通过web容器上下文参数指定Spring配置文件的地址。classpath关键字指定类路径下加载
  • <listener> 负责启动Spring容器的监听器,引用上下文参数获取Spring配置文件的地址
  • <servlet> 声明了一个Servlet,Spring MVC也拥有一个Spring配置文件,该配置文件的文件名和此处定义的Servlet名有一个契约,即才用<Servlet 名>-servlet.xml的形式。Servlet名为smart,则在WEB-INF目录下必须提供一个名为smart-servlet.xml的Spring MVC配置文件。
  • <servlet-mapping> 对这个Servlet的URL路径进行定义,这里让所有以.html为后缀的URL都能被smart Servlet截获,进而转由Spring MVC框架进行配置。
  • Spring MVC 截获后,首先根据请求的URL查找到目标的处理器控制器,并将请求参数封装“命令”对象一起传送给控制器,然后控制器调用Spring容器中的业务Bean完成业务处理工作并返回结果视图。

2.9.2 处理登录请求

loginController负责处理登录请求,完成登录业务,并根据登录成功与否转向欢迎页面或者失败页面,代码如下:

package com.smart.web;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.smart.domain.User;
import com.smart.service.UserService;

@RestController
public class LoginController{
	private UserService userService;
    
	@RequestMapping(value = "/index.html")
	public String loginPage(){
		return "login";
	}
	
	@RequestMapping(value = "/loginCheck.html")
	public ModelAndView loginCheck(HttpServletRequest request,LoginCommand loginCommand){
		boolean isValidUser =  userService.hasMatchUser(loginCommand.getUserName(),
					                    loginCommand.getPassword());
		if (!isValidUser) {
			return new ModelAndView("login", "error", "用户名或密码错误。");
		} else {
			User user = userService.findUserByUserName(loginCommand
					.getUserName());
			user.setLastIp(request.getLocalAddr());
			user.setLastVisit(new Date());
			userService.loginSuccess(user);
			request.getSession().setAttribute("user", user);
			return new ModelAndView("main");
		}
	}

	@Autowired
	public void setUserService(UserService userService) {
		this.userService = userService;
	}
}

  • @RestController 标注成为一个Spring MVC 的controller.处理HTTP的请求
  • @RequestMapping(value = “/index.html”) 负责处理/index.html的请求

2.9.3 Spring MVC 配置文件

需要在smart-servlet.xml中声明该控制器,扫描web路径,指定Spring MVC的视图解析器。

2.9.4 JSP视图页面

论坛登录模块共包括两个jsp页面,分别是登录页面login.jsp和欢迎页面main.jsp。

  1. login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<html>
	<head>
		<title>小春论坛登录</title>
	</head>
	<body>
		<c:if test="${!empty error}">
	        <font color="red"><c:out value="${error}" /></font>
		</c:if>        
		<form action="<c:url value="loginCheck.html"/>" method="post">
			用户名:
			<input type="text" name="userName">
			<br data-tomark-pass>			密 码:
			<input type="password" name="password">
			<br data-tomark-pass>			<input type="submit" value="登录" />
			<input type="reset" value="重置" />
		</form>
	</body>
</html>
  1. main.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    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>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>小春论坛</title>
</head>
<body>
    ${user.userName},欢迎您进入小春论坛,您当前积分为${user.credits};
</body>
</html>

2.10 运行Web应用

基于Maven工程,运行Web 应用有两种方式:第一种方式是在IDE工具中配置web应用服务器,第二种方式是在pom.xml文件中配置web应用服务器插件。在pom.xml文件中配置jetty应用服务器插件。

FutureTech