一、MyBatis使用时数据流动顺序分析
需求:
前端提交表单,请求后台使用MyBatis往数据库里插入一条数据
使用流程:
- 前端传回数据,此时数据传到controller的对应方法里
- controller使用调用service对象对应方法,将数据传递过去
- service实际上是个接口,数据只是通过这个接口,传递到service的接口实现类serviceImp
- serviceImp接收到调用,接到数据,调用本类中的userDAO对象对应方法,将数据传递出去
- userDAO实际上也是个接口,它头上有个Mapper注解,它会通过这个注解,将数据传递给其对应的Mapper
- Mapper.html有很多个,名字也可以多种多样,但是Mapper中有个namespace属性,这个属性定位了其对应的DAO接口的位置,userDAO也是根据这个属性,找到了自己对应的Mapper
- 一个DAO接口中有很多方法,所以也需要其对应的Mapper中有对应的实现,也就是完成该方法的具体实现,如往数据库中增删改查
- Mapper有个id属性,其的值对应了DAO中的各个方法名称,DAO有多个方法,Mapper中也有多个id值
- 找到对应的Mapper之后,根据id找到对应的实现,传递数据给该实现,由该实现按照既定的规则完成对数据库的操作
PS:该数据流动,实际上使用了设计模式中的依赖倒转原则。
附依赖倒转原则:
- 高层模块不应依赖底层模块,二者应该依赖其抽象。
- 抽象不应该依赖细节,细节应该依赖抽象
- 此原则的中心是:面向接口编程
- 此原则的设计理念:相对细节的多变性,抽象的东西要稳定的多;
以抽象为基础搭建的架构,比以细节为基础的架构要稳定的多 - 使用接口或抽象类的目的是制定好规范,而不涉及任何细节,
把展现细节的任务交给他们的实现类去完成
二、具体代码(实现注册功能)
目录结构:
regist.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>regist</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="css/style.css" />
</head>
<body>
<div id="wrap">
<div id="top_content">
<div id="header">
<div id="rightheader">
<p>
2009/11/20
<br />
</p>
</div>
<div id="topheader">
<h1 id="title">
<a href="#">main</a>
</h1>
</div>
<div id="navigation">
</div>
</div>
<div id="content">
<p id="whereami">
</p>
<h1>
注册
</h1>
<form action="login.html" method="post">
<table cellpadding="0" cellspacing="0" border="0"
class="form_table">
<tr>
<!--整个都是异步通讯,不提交表单,所以表单中input的name没有意义-->
<td valign="middle" align="right">
用户名:
</td>
<td valign="middle" align="left">
<!-- 将表单绑定到Vue属性中-->
<input type="text" class="inputgri" v-model="user.username" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
真实姓名:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" v-model="user.realName" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
密码:
</td>
<td valign="middle" align="left">
<input type="password" class="inputgri" v-model="user.password" />
</td>
</tr>
<tr>
<td valign="middle" align="right">
性别:
</td>
<td valign="middle" align="left">
男
<input type="radio" class="inputgri" v-model="user.sex" value="男" checked="checked"/>
女
<input type="radio" class="inputgri" v-model="user.sex" value="女"/>
</td>
</tr>
<tr>
<td valign="middle" align="right">
验证码:
</td>
<td valign="middle" align="left">
<input type="text" class="inputgri" v-model="code" />
</td>
<td>
<!--从Vue中获取url属性-->
<img id="num" :src="url"/>
<!-- 从Vue中调用名为getImage()的方法-->
<a href="javascript:;" @click="getImage">换一张</a>
</td>
</tr>
</table>
<p>
<input type="buttton" @click="register" class="button" value="Submit »" />
</p>
</form>
</div>
</div>
<div id="footer">
<div id="footer_bg">
ABC@126.com
</div>
</div>
</div>
</body>
</html>
<!--vue导入-->
<script src="/ems_vue/js/vue.js"></script>
<!--异步请求导入-->
<script src="/ems_vue/js/axios.min.js"></script>
<script>
<!-- vue实例-->
var app = new Vue({
//挂载作用域
//从register.html中可得知,整个都是由wrap包裹起来
//所以作用域传wrap进来即可
el:"#wrap",
//作用域中获取的数据
data:{
//定义一个属性url,用来存从后端取得的验证码数据
//让页面调用这个属性,即可得到验证码图片
url:"",
//user对象
user:{
sex:"男"
},
//验证码
code:"",
},
methods:{
//用来更换验证码
getImage(){
this.getSrc();
},
//获取验证码,代码复用,便于调用
getSrc(){
var _this = this;
//console.log("xxxx");
//异步请求:请求验证码图片
axios.get("http://localhost:8989/ems_vue/user/getImage?time="+Math.random()).then(res=>{
console.log(res.data);
// 把图片赋给url属性
_this.url = res.data;
});
},
//用来注册用户信息
register(){
//this.code: 相当于地址栏传参,使得UserController能直接得到code
axios.post("http://localhost:8989/ems_vue/user/register?code="+this.code,this.user).then(res=>{
console.log(res.data);
// 给用户一个提示
// true:注册成功,是否跳转页面
// false:注册失败
if (res.data.state){
alert(res.data.msg+"点击确定跳转至登录页面!");
location.href="http://localhost:8989/ems_vue/login.html"
}else{
alert(res.data.msg);
}
})
}
},
//页面创建之前进行处理
//用来在页面创建前,请求获取验证码
created(){
//获取验证码
this.getSrc();
}
})
</script>
UserController:
package com.chen.controller;
import com.chen.entity.User;
import com.chen.service.UserService;
import com.chen.utils.VerifyCodeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author 淡
* @version 1.0
* @description
* @create 2021-02-23 12:44
*/
@Slf4j
@RestController
@CrossOrigin//允许跨域
@RequestMapping("user")
public class UserController {
@Autowired
//其实这个就是个工具对象,通过它调用业务方法
//业务方法写在UserService接口的实现类:UserServiceImpl里
private UserService userService;
/**
* @description 处理用户登录请求
* <br>
* @param
* @return java.util.Map<java.lang.String, java.lang.Object>
* @author 淡
* @since 2021/2/23 21:39
*/
@PostMapping("login")
public Map<String, Object> login(@RequestBody User user){
log.info("当前登陆用户信息:[{}]",user.toString());
Map<String, Object> map = new HashMap<>();
try {
User userDB = userService.login(user);
map.put("state",true);
map.put("msg","登陆成功");
map.put("user",userDB);
}catch (Exception e){
e.printStackTrace();
map.put("state", false);
map.put("msg",e.getMessage());
}
return map;
}
/*
* 用来处理用户注册方法
* 返回一个注册信息到前台
* */
@PostMapping("register")
// 使用@RequestBody的目的是:为了能直接封装对象
// Axios传数据时,是直接以JSON字符串的形式传
// 必须要用@RequestBody,才能自动把其转换成我们要的User对象
// HttpServletRequest: 存储了之前最近一次传给浏览器的验证码信息
public Map<String, Object> register( @RequestBody User user,String code, HttpServletRequest request){
log.info("用户信息:[{}]",user.toString());
log.info("用户输入的验证码:[{}]",code);
Map<String, Object> map = new HashMap<>();
try {
String key = (String) request.getServletContext().getAttribute("code");
if(key.equalsIgnoreCase(code)){
//1、调用业务方法
userService.register(user);
map.put("state",true);
map.put("msg","提示:注册成功!");
}else{
throw new RuntimeException("验证码异常");
}
}catch (Exception e){
e.printStackTrace();
map.put("state",false);
map.put("msg","提示:"+e.getMessage());
}
return map;
}
/**
* @description 生成验证码图片
* <br>
* @param request HttpServletRequest对象
* 代表客户端的请求,
* 当客户端通过HTTP协议访问服务器时,
* HTTP请求头中的所有信息都封装在这个对象中,
* 通过这个对象提供的方法,可以获得客户端请求的所有信息。
* @return java.lang.String
* @author 淡
* @since 2021/2/23 12:47
*/
@GetMapping("getImage")
public String getImageCode(HttpServletRequest request) throws IOException {
//1.使用工具类生成验证码
String code = VerifyCodeUtils.generateVerifyCode(4);
//2.将验证码放入servletContext作用域
request.getServletContext().setAttribute("code",code);
//3.将图片转换成base64
//字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
//将得到的验证码,使用工具类生成验证码图片,并放入到字节数组缓存区
VerifyCodeUtils.outputImage(220,60,byteArrayOutputStream,code);
//使用spring提供的工具类,将字节缓存数组中的验证码图片流转换成Base64的形式
//并返回给浏览器
return "data:image/png;base64," + Base64Utils.encodeToString(byteArrayOutputStream.toByteArray());
}
}
UserService:
package com.chen.service;
import com.chen.entity.User;
public interface UserService {
//用户注册
void register(User user);
//用户登录
User login(User user);
}
UserServiceImp:
package com.chen.service;
import com.chen.dao.UserDAO;
import com.chen.entity.User;
import com.chen.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import java.util.Date;
/**
* @author 淡
* @version 1.0
* @description
* @create 2021-02-23 15:55
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
// 1、这个其实也是一个工具对象,通过它来调用业务方法
// 2、业务方法,其实是写在UserMapper.xml里
// 3、UserDAO通过@Mapper、UserMapper.xml通过namespace的指向,来使两者连系在一起
// 4、UserMapper通过id这个属性,来分别对应UserDAO接口的方法
// 5、实际上,UserMapper是UserDAO里方法的方法体,是方法的实现
// 6、这其实就是设计模式中(依赖倒转原则)的一个体现
// ps:依赖倒转原则:
// 1. 高层模块不应依赖底层模块,二者应该依赖其抽象。
// 2. 抽象不应该依赖细节,细节应该依赖抽象
// 3. 此原则的中心是:面向接口编程
// 4. 此原则的设计理念:相对细节的多变性,抽象的东西要稳定的多;
// 以抽象为基础搭建的架构,比以细节为基础的架构要稳定的多
// 5. 使用接口或抽象类的目的是制定好规范,而不涉及任何细节,
// 把展现细节的任务交给他们的实现类去完成
private UserDAO userDAO;
@Override
public void register(User user){
//0. 根据用户输入的用户名判断用户是否存在
User userDB = userDAO.findByUserName(user.getUsername());
if(userDB == null){
//1. 生成用户状态
user.setStatus("已激活");
//2. 设置用户注册时间
user.setRegisterTime(new Date());
//3. 调用DAO
userDAO.save(user);
}else{
throw new RuntimeException("用户名已存在!");
}
}
@Override
public User login(User user) {
// 1. 根据用户输入的用户名进行查询
User userDB = userDAO.findByUserName(user.getUsername());
if(!ObjectUtils.isEmpty(userDB)){
// 2. 比较密码
if(userDB.getPassword().equals(user.getPassword())){
return userDB;
}else{
throw new RuntimeException("密码错误");
}
}else {
throw new RuntimeException("用户名输入错误");
}
}
}
UserDAO:
package com.chen.dao;
import com.chen.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper //创建DAO对象
public interface UserDAO {
void save(User user);
User findByUserName(String username);
}
UserMapper:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.chen.dao.UserDAO">
<!-- save-->
<!-- useGeneratedKeys="true"把新增加的主键赋值到自己定义的keyProperty(id)中-->
<insert id="save" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (#{id},#{username},#{realName},#{password},#{sex},#{status},#{registerTime})
</insert>
<select id="findByUserName" parameterType="String" resultType="User">
select id, username,realname,password,sex,status,regsterTime
from t_user where username=#{username}
</select>
</mapper>