这里我们的需求分析比较简单,我们想要做一个网站的注册登录功能,这时候我们就会去想数据库需要哪些表呢?表里需要哪些字段呢?这里我们设计的表略简单,目的就是为了方便新手入门理解。注册登录一个表足以,我们创建一个名为web的数据库,然后新建一个用户user表,表里面的字段和内容如下:
username | password |
---|---|
xxxf | 123456 |
xxxxf | 123456 |
方大大 | 123456 |
测试员 | 123456 |
这里,我也给出创建数据库和表的SQL语句:
CREATE DATABASE web;
USE web;
CREATE TABLE USER(
username VARCHAR(20) NOT NULL PRIMARY KEY COMMENT '账号',
PASSWORD VARCHAR(20) NOT NULL COMMENT '密码'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO USER(username,PASSWORD) VALUES ('xxxf','123456');
INSERT INTO USER(username,PASSWORD) VALUES ('xxxxf','123456');
INSERT INTO USER(username,PASSWORD) VALUES ('方大大','123456');
INSERT INTO USER(username,PASSWORD) VALUES ('测试员','123456');
这里讲一下我们的业务逻辑,注册时会检查数据库有无此账号名,若有注册失败,若无则注册成功;登录时,检查数据库账号密码是否正确,账号或密码错误都会登录失败,并且前端会给返回提示。需求分析就这么多,还是蛮easy吧!
包结构
这里简单解释一下src下每个包的作用:
(1) POJO(Plain Ordinary Java Object)简单的Java对象,实际就是普通JavaBeans,是为了避免和EJB混淆所创造的简称。在POJO层我们创建的类,属性用private修饰,目的是为了封装属性,然后用getter&setter方法去获得或修改属性值。
(2) DAO(Data Access Object)是一个数据访问接口,数据访问:顾名思义就是与数据库打交道。夹在业务逻辑与数据库资源中间。dao我们放了很多方法接口,在mapper层使用xml配置实现SQL语句的填充,这是使用mybatis的一贯做法。
(3)Service层(biz):业务层 控制业务
Service层主要负责业务模块的逻辑应用设计。和DAO层一样都是先设计接口,再创建要实现的类,然后在配置文件中进行配置其实现的关联。接下来就可以在service层调用接口进行业务逻辑应用的处理。
封装Service层的业务逻辑有利于业务逻辑的独立性和重复利用性。
(4)ServiceImpl:业务层的实现层
service层只写了接口方法,并没有去真正去实现方法,在impl层我们会把具体实现方法写出来
(5)Controller层: 控制层 控制业务逻辑
Controller层负责具体的业务模块流程的控制,controller层主要调用Service层里面的接口控制具体的业务流程,控制的配置也需要在配置文件中进行。
(6)config层:配置层
一般来说,配置层在xml中实现也可,这里我用了java代码来实现SSM初始化配置,有三个类需要我们去关注,WebAPPInitialzer.java(配置类),RootConfig.java(Spring IoC上下文配置)、WebConfig.java(配置DispatchServlet上下文)
(7)mybatis层:使用xml实现mybatis配置
这里我使用的是全注解方式完成项目
首先我们需要做的是,把项目所需的jar包导入到你项目的WebContent/WEB-INF/lib或者是WebRoot/WEB-INF/lib下,这里我们的所依赖的JAR包有很多个,所以为了方便大家找到所需要的资源我将项目放到了github上,你只要访问我的Git仓库(戳这里),在WebContent/WEB-INF/lib下复制所有的jar包到你本地代码同样的目录下,再buildpath一下就可以使用啦!顺便提一下,这里面有很多jar包该项目并没有用到,为什么不删除呢?答案是:我比较懒,不想花时间去挑选JAR包。如果你用maven去构建项目,只需要在pom.xml把所需要的jar包信息配置上也可以达到相同的效果。
1.通过继承AbstractAnnotationConfigDispatcherServletInitializer(这个类名字巨长,反正我是记不住)去配置其他内容,因此首先来配置WebAPPInitialzer,如代码所示:
package com.xxxxf.config;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletRegistration.Dynamic;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
*WebAPPInitialzer.java配置类
*/
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// Spring IoC环境配置
@Override
protected Class>[] getRootConfigClasses() {
// 配置Spring IoC资源
return new Class>[] { RootConfig.class };
}
// DispatcherServlet环境配置
@Override
protected Class>[] getServletConfigClasses() {
// 加载Java配置类
return new Class>[] { WebConfig.class };
}
// DispatchServlet拦截请求配置
@Override
protected String[] getServletMappings() {
return new String[] { "*.do" };//拦截所有以.do结尾的请求
}
/**
* @param dynamic
* Servlet上传文件配置.
*/
@Override
protected void customizeRegistration(Dynamic dynamic) {
// 配置上传文件路径
String filepath = "e:/mvc/uploads";
// 5MB
Long singleMax = (long) (5 * Math.pow(2, 20));
// 10MB
Long totalMax = (long) (10 * Math.pow(2, 20));
// 设置上传文件配置
dynamic.setMultipartConfig(new MultipartConfigElement(filepath, singleMax, totalMax, 0));
}
}
2.上面代码重写的三个方法就可以配置Web工程中的Spring IoC资源和DispatchServlet的配置内容,然后是配置Spring IoC容器,配置类RootConfig,代码如下:
package com.xxxxf.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
//定义Spring 扫描的包
@ComponentScan(value= "com.*",
includeFilters= {@Filter(type = FilterType.ANNOTATION, value ={Service.class})})
//使用事务驱动管理器
@EnableTransactionManagement
//实现接口TransactionManagementConfigurer,这样可以配置注解驱动事务
public class RootConfig implements TransactionManagementConfigurer {
private DataSource dataSource = null;
/**
* 配置数据库.
* @return 数据连接池
*/
@Bean(name = "dataSource")
public DataSource initDataSource() {
if (dataSource != null) {
return dataSource;
}
Properties props = new Properties();
props.setProperty("driverClassName", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/web?useUnicode=true&characterEncoding=UTF-8");
props.setProperty("username", "root");
props.setProperty("password", "123456");
props.setProperty("maxActive", "200");
props.setProperty("maxIdle", "20");
props.setProperty("maxWait", "30000");
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("连接成功----------");
return dataSource;
}
/***
* 配置SqlSessionFactoryBean
* @return SqlSessionFactoryBean
*/
@Bean(name="sqlSessionFactory")
public SqlSessionFactoryBean initSqlSessionFactory() {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(initDataSource());
//配置MyBatis配置文件
Resource resource = new ClassPathResource("mybatis/mybatis-config.xml");
sqlSessionFactory.setConfigLocation(resource);
return sqlSessionFactory;
}
/***
* 通过自动扫描,发现MyBatis Mapper接口
* @return Mapper扫描器
*/
@Bean
public MapperScannerConfigurer initMapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.*");
msc.setSqlSessionFactoryBeanName("sqlSessionFactory");
msc.setAnnotationClass(Repository.class);
return msc;
}
/**
* 实现接口方法,注册注解事务,当@Transactional 使用的时候产生数据库事务
*/
@Override
@Bean(name="annotationDrivenTransactionManager")
public PlatformTransactionManager annotationDrivenTransactionManager() {
DataSourceTransactionManager transactionManager =
new DataSourceTransactionManager();
transactionManager.setDataSource(initDataSource());
return transactionManager;
}
}
3.有了Spring IoC容器后,还需要配置DispatchServlet上下文,完成这个任务的便是WebConfig这个类,代码如下:
package com.xxxxf.config;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
@Configuration
//定义Spring MVC扫描的包
@ComponentScan(value="com.*",
includeFilters= {@Filter(type = FilterType.ANNOTATION, value = Controller.class)})
//启动Spring MVC配置
@EnableWebMvc
public class WebConfig extends AsyncConfigurerSupport {
/***
* 通过注解 @Bean 初始化视图解析器
* @return ViewResolver 视图解析器
*/
@Bean(name="internalResourceViewResolver")
public ViewResolver initViewResolver() {
InternalResourceViewResolver viewResolver =new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
/**
* 初始化RequestMappingHandlerAdapter,并加载Http的Json转换器
* @return RequestMappingHandlerAdapter 对象
*/
@Bean(name="requestMappingHandlerAdapter")
public HandlerAdapter initRequestMappingHandlerAdapter() {
//创建RequestMappingHandlerAdapter适配器
RequestMappingHandlerAdapter rmhd = new RequestMappingHandlerAdapter();
//HTTP JSON转换器
MappingJackson2HttpMessageConverter jsonConverter
= new MappingJackson2HttpMessageConverter();
//MappingJackson2HttpMessageConverter接收JSON类型消息的转换
MediaType mediaType = MediaType.APPLICATION_JSON_UTF8;
List mediaTypes = new ArrayList();
mediaTypes.add(mediaType);
//加入转换器的支持类型
jsonConverter.setSupportedMediaTypes(mediaTypes);
//往适配器加入json转换器
rmhd.getMessageConverters().add(jsonConverter);
return rmhd;
}
/**
* 异步任务处理
* */
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(200);
taskExecutor.initialize();
return taskExecutor;
}
}
4.通过上面三个类就搭建好了Spring MVC和Spring开发环境,但是没有对MyBatis进行配置,使用/mybatis/mabatis-config.xml进行配置,源码也很简单:
"1.0" encoding="UTF-8" ?>
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
"com/xxxxf/mapper/User.xml"/>
</mappers>
>
1.pojo层的User类,属性对应数据库表的每个字段,并且我们使用驼峰规则命名属性(这里很重要,字段userName和UserName在封装时差别很大,会映射不到mapper层的xml上),我们一贯的做法会让pojo序列化,这里虽然没有并发的情况,但是这是一种良好的做法。代码如下:
package com.xxxxf.pojo;
import java.io.Serializable;
/**
*@author xxxxf
*2018年7月10日
*/
public class User implements Serializable{
private static final long serialVersionUID = 89448952;
private String userName;
private String passWord;
/**
* getter && setter方法
*/
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;
}
/**
* toString方法
*/
@Override
public String toString() {
return "user [userName=" + userName + ", passWord=" + passWord + "]";
}
}
2.dao层中的UserDao类,这里实现我们业务和数据库直接交互的很多接口,@Repository的注解目的是持久化数据层。代码如下:
package com.xxxxf.dao;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import com.xxxxf.pojo.User;
/**
*@author xxxxf
*2018年7月10日
*/
@Repository
public interface UserDao {
/**
* 查询用户信息
*/
public String getUser(@Param("userName") String userName,@Param("passWord") String passWord);
/**
* 增加用户
*/
public int addUser(User user);
/**
* 按主键查询用户
*/
public String getUserByUserName(String userName);
}
编写完dao层以后我们需要在mapper层编写User.xml实现DAO层接口方法,它相当于Dao的实现层(你可以这么理解,但是本质上有很大区别的),在User.xml我们是写了很多SQL语句与MySQL数据库进行交互,至于怎么去写SQL,举个例子:
select语句的返回值是不确定的,insert,update语句的返回值总是int(自己去思考是为什么?),所以Mybatis封装了这两种SQL语句的resultType,程序员就不用去写,这几种SQL语句参数类型也是根据方法里面的参数而决定的,两个或两个以上参数就需要用到@Param注解,MyBatis的语法我就不多赘述,有地方不理解或不清楚的自行百度,user.xml具体实现代码如下:
"1.0" encoding="UTF-8"?>
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
"com.xxxxf.dao.UserDao">
>
3.service层中的UserService类存放几个和用户相关的接口,这是是对用户而言的业务,这里有一种思想就是:用户要干嘛?我们程序员要去帮助用户实现什么接口?代码如下:
package com.xxxxf.service;
import com.xxxxf.pojo.User;
/**
*@author xxxxf
*2018年7月10日
*/
public interface UserService {
/**
* 用户注册
*/
public void regist(User user);
/**
* 用户登录
*/
public String login(String userName,String passWord);
/**
* 检测用户是否存在
*/
public String checkUser(String userName);
}
4.serviceImpl层中UserServiceImpl是UserService的实现类,@Service注解告诉Spring这是个业务层,UserService中只声明了几个接口,我们并没有去真正去实现,但是有人想为什么不直接写个实现类,反而要加一个Service层呢?这个问题,这要归结到代码复用性和维护性问题,如果你直接写一个实现,当你增加新的功能时就要在一大堆代码中是添加修改,这是非常耗时的操作,所以接口和实现分离有利于维护,代码如下:
package com.xxxxf.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.xxxxf.dao.UserDao;
import com.xxxxf.pojo.User;
import com.xxxxf.service.UserService;
/**
*@author xxxxf
*2018年7月10日
*/
@Service
public class UserServiceImpl implements UserService{
//依赖注入
@Autowired
private UserDao userDao=null;
@Override
//读写提交
@Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public void regist(User user) {
// TODO Auto-generated method stub
userDao.addUser(user);
}
@Override
//读写提交
@Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public String login(String userName, String passWord) {
// TODO Auto-generated method stub
//userDao.getUser(userName, passWord);
return userDao.getUser(userName, passWord);
}
@Override
//读写提交
@Transactional(isolation=Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public String checkUser(String userName) {
// TODO Auto-generated method stub
return userDao.getUserByUserName(userName);
}
}
5.控制层Controller
使用@Controller注解,然后使用@AutoWired导入service层,因为service中的方法是我们使用到的,controller通过接收前端传过来的参数进行业务操作,在返回一个指定的路径或者数据表。RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。@responseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据,需要注意的呢,在使用此注解之后不会再走试图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。上面讲的这些都是Spring MVC知识,如果你看不懂请百度Spring MVC流程控制,基础知识我就不多废话。这里我们的控制器类叫UserController.java,里面有两个方法,一个叫注册,另一个叫登录,具体代码如下:
package com.xxxxf.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codehaus.jackson.map.util.JSONPObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.portlet.bind.annotation.ResourceMapping;
import org.springframework.web.servlet.ModelAndView;
import com.sun.java.swing.plaf.windows.resources.windows;
import com.xxxxf.dao.UserDao;
import com.xxxxf.pojo.User;
import com.xxxxf.service.UserService;
/**
*@author xxxxf
*2018年7月10日
*/
@Controller
public class UserController {
//依赖注入
@Autowired
private UserService userService=null;
/**
* 注册功能实现
* */
@RequestMapping(value="/register.do")
@ResponseBody
public ModelAndView regist(User user,Model model,HttpServletRequest request,HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");//必须写在第一位,因为采用这种方式去读取数据,否则数据会出错。
//设置这样方式去读。这样中文就能够读取出来了,但是需要注意。表单的发送方式必须是method='post'
response.setContentType("text/html;charset=utf-8");//设置传过去的页面显示的编码
System.out.println("#################################################");
System.out.println("用户注册:"+"账号:"+user.getUserName()+" "+"密码:"+user.getPassWord());
String userName=user.getUserName();
String result=userService.checkUser(userName);
if (result==null) {
userService.regist(user);
PrintWriter out = response.getWriter();
out.print("");
//model.addAttribute("msg","注册成功");
//return new ModelAndView("redirect:/login.jsp");
}else {
PrintWriter out = response.getWriter();
out.print("");
//model.addAttribute("msg","账号存在,请重新注册");
//return new ModelAndView("redirect:/register.jsp");
}
return null;
}
/**
* 登录功能实现
* */
@RequestMapping(value="/login.do")
@ResponseBody
public ModelAndView login( @RequestBody User user,HttpServletRequest request,HttpServletResponse response) {
/*Model model,HttpServletRequest request,HttpServletResponse response*/
System.out.println("***********************************");
String username=user.getUserName();
//request.getParameter(username);
String password=user.getPassWord();
System.out.println("用户登录:"+"账号:"+username+" "+"密码:"+password);
String result=userService.login(username, password);
System.out.println("从数据查询结果:"+result);//查询账号密码都正确,登录成功;账号或密码错误,result=null;
if (result != null) {
try {
response.getWriter().write("true");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
try {
response.getWriter().write("false");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//model.addAttribute("msg", "登陆成功");
return null;
}
}
在Controller里面有一些业务流程控制,业务思想是需要你去仔细思考,比如:
String userName=user.getUserName();
String result=userService.checkUser(userName);
if (result==null) {
userService.regist(user);
PrintWriter out = response.getWriter();
out.print("");
}else {
PrintWriter out = response.getWriter();
out.print("");
}
这里为什么对result这个变量进行空判断,那你就得看看result是什么?奥,原来啊,reslut这个变量调用了checkUser(String userName)这个方法,这个方法去数据库查询是否有这个username是否存在,如果不存在就写进数据库里面,注册成功,如果有则前端就会提示注册失败。登录方法也用到了类似的思想,需要读者自己去揣摩思考。
对于前端页面和功能的实现,我是借助于layui和jQuery这个前台框架,实际上我是个后台人员前端并不是我的强项。小伙伴在跟着这篇博客学习的时候,我会把我的整个项目的源码放到github上,源码戳这里,项目的WebContent目录下几个页面就是我们项目所需的页面(PS:如果写得不好请不要吐槽我,尽力了),当然前端页面与后台数据交互用到了JSON,还实现了AJAX技术,在页面代码里需要大家去揣摩,我就不多BB了。网页中文乱码想必是家常便饭,包括请求数据到后台乱码,前台页面展示乱码等等,这里我也解决好了,你自己去看我的项目源码,我是怎么去解决的,如果是你你会怎么去解决呢?
写完一个项目,一定要去总结,这个项目什么地方我花费了很多时间,怎么去解决如何去解决,怎么样解决又快又好?我个人觉得思考问题本身比代码设计阶段更重要,如果没有思维过程,自己写得东西就像是套一个代码模板,下次别人换一个需求你可能就写不出来了,这也是很多刚入门的新手需要注意的地方,也是从一个码农到一个合格程序员必经之路!当然,新手刚开始只需要去多看别人写的,多去查一些资料,自己再去仿写出来就行了,久而久之你一定会有很多收获。