一、异步请求与表单校验

1、异步请求与AJAX

(1)同步请求和异步请求

当浏览器向服务器发送请求时,可以选择发送同步请求异步请求。它们的主要区别在于发送请求后是否等待响应返回

同步请求需要等服务器发送响应后才能继续后续操作,这样就会产生一种假死的情况,我们可以使用异步请求来解决这个问题。

同步和异步

(2)页面的整体刷新和局部刷新

整体刷新:整个的页面都会刷新一遍

局部刷新:异步请求的刷新方式,每次操作后逐步的获取页面中不同部分的内容,一点一点的加载,大大提高效率。

局部刷新

(3)AJAX(Asynchronous JavaScript and XML)

向服务器发送异步请求并获取数据,加载局部内容。

AJAX

(4)JSON

JSON是一种轻量级数据交换格式,可以在不同平台之艰难轻松的共享数据,是以键值对的形式传递数据。

优点:可读性强,易于生成,数据量小,易于扩展,平台无关性(支持跨平台)

(5)发送AJAX的请求方式

1、使用原生的XMLHttpRequest对象发送请求。代码量大,相对复杂,几乎不用。

2、使用jQuery发送请求。jQuery已经将原生的XMLHttpRequest对象包装起来,其代码较为简洁易懂,容易上手,曾经被广泛使用,现在已经逐步淘汰。

3、使用Fetch API发送请求。Fetch API是一种新的网络请求API,可替代XMLHttpRequest,并具有更简洁和强大的特性,使用不多。

4、使用Axios发送请求。Axios是一个基于Promise的HTTP客户端,可以在浏览器和Node.js中对URL进行请求,可以使用vue进行结合,使用相对较多。

2、Axios

它是前端 HTTP 库,可以用于浏览器和Node.js,可以用于发送 Ajax 请求和处理服务器响应数据。
可以与Vue搭配使用(但不需要,不能说成需要和Vue搭配使用)

(1)Axios的用法

首先创建实体类:

public class User {
    private Integer id;
    private String username;
    private String nickname;

    此处省略.....
}

然后创建controller:

@Controller
public class HelloController {
    @RequestMapping("/hello") //声明请求的URL地址
    @ResponseBody //声明响应的内容类型 如果方法的返回值类型是一个对象,会自动转换为json格式
    public List<User> hello() {
        List<User> users = new ArrayList<>();
        users.add(new User(1, "刘备", "玄德"));
        users.add(new User(2, "关羽", "云长"));
        users.add(new User(3, "张飞", "翼德"));
        users.add(new User(4, "赵云", "子龙"));
        users.add(new User(5, "黄忠", "汉升"));
        users.add(new User(6, "马超", "孟起"));
        return users;
    }
}

最后写页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>第一个AJAX程序</title>
    <!-- 引入AXIOS资源 -->
    <script src="https://cdn.staticfile.org/axios/1.4.0/axios.min.js"></script>
</head>
<body>
<input type="button" value="点击获取数据" onclick="getData()">
<div id="dataDiv"></div>
<script type="text/javascript">
    function getData() {
        //发送get请求
        //then中的function是一个回调函数,发送ajax请求后,回来执行的函数
        axios.get('http://localhost:8080/hello').then(function (response) {
            //获取响应数据,forEach循环遍历所有数据
            response.data.forEach(function (item) {
                //item表示当前遍历的数据,放在页面的div中
                //首先放之前要得到div,item获取login信息
                document.querySelector("#dataDiv").innerHTML +="username:"+ item.username+"<br>";
            })
        })
    }
</script>
</body>
</html>

(2)Vue整合Axios示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue整合Axios</title>
    <!-- 引入Vue组件 -->
    <script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
    <!-- 引入AXIOS资源 -->
    <script src="https://cdn.staticfile.org/axios/1.4.0/axios.min.js"></script>
</head>
<body>
<div id="app">
    <input type="button" value="点击获取数据" @click="getData()">
    <div>
        <h1>用户列表</h1>
        <ul>
            <li v-for="user in users">{{user.id}} -- {{user.username}} -- {{user.nickname}}</li>
        </ul>
    </div>
</div>

<script type="text/javascript">
    let v = new Vue({
        el: "#app", //声明作用范围
        data: {
            users: []
        },
        methods: {
            //绑定的单击事件
            getData() {
                //发送ajax请求
                axios.get('http://localhost:8080/hello').then(function (resp) {
                    //获取返回的数据 放在data的users中
                    //...表示展开resp.data得到的json数组
                    v.users.push(...resp.data);
                }).catch(function (error) {
                    //出现错误会执行catch部分的回调函数
                    console.log(error);
                })
            }
        }
    })
</script>
</body>
</html>

3、注册案例(续)

(1)案例准备工作

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.15</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

(2)页面发送注册请求

添加的html内容:

<!-- 引入AXIOS资源 -->
<script src="https://cdn.staticfile.org/axios/1.4.0/axios.min.js"></script>
<script>
    let v = new Vue({
        el: '#app',
        data: function () {
            return {
                formData: {
                    username: '',
                    password: '',
                    checkPass: ''
                }
            }
        },
        methods: {
            register() {
                //定义发送的请求参数
                let param = {
                    username: v.formData.username, //得到用户名
                    password: v.formData.password  //得到密码
                };
                //发送post请求,第二个参数表示发送请求传递的参数
                axios.post("http://localhost:8080/user/register", param).then(function (resp) {
                    //打印响应
                    console.log(resp)
                    if (resp.data == "注册成功") {
                        //弹出个成功框
                        v.$message.success(resp.data);
                    }else{
                        v.$message.error(resp.data);
                    }
                });

            }
        }
    })
</script>

4、前端表单校验

在用户填写表单的时候,通过js对输入的代码进行验证,确保数据的有效性、完整性和唯一性,前端对一般用户进行约束。

前端表单校验

Element UI表单校验

使用 rules 属性定义验证规则。

基于Element UI实现前端表单校验的步骤如下:

1、引入Element UI组件:在项目中引入Element UI组件库。

2、创建表单和字段: 在页面中使用 el-form 和 el-form-item 组件创建表单,并定义表单字段。

3、定义验证规则: 在 Vue 组件的 data 中定义表单数据和验证规则。验证规则是一个对象,键为字段名,值为验证规则数组。

4、应用验证规则: 将定义的验证规则应用到 el-form-item 组件的 prop 属性上,与表单字段对应。

5、提交表单: 在表单提交时,调用 el-form 组件的 validate 方法进行表单校验。

rules: {
    //定义前端校验规则
    username: [
        //表示是个必填项 不能为空 如果为空则提示message中的消息
        //trigger: "blur" 光标离开文本框则触发
        {required: true, message: "用户名不能为空", trigger: "blur"},
        {min: 4, max: 20, message: "长度在4到20个字符之间", trigger: "blur"}
    ],
    password: [
        {required: true, message: "密码不能为空", trigger: "blur"},
        {min: 6, max: 20, message: "长度在6到20之间", trigger: "blur"}
    ],
    checkPass: [
        {
            //自定义校验规则 rule表示声明的规则,value表示自己输入的内容
            //callback表示回调函数
            //validator表示自定义的校验规则
            validator: function (rule, value, callback) {
                //内容不能为空
                if (value == "") {
                    callback(new Error("请再次输入密码"));
                } else if (value != v.formData.password) {
                    callback(new Error("两次输入的密码不一致"));
                } else {
                    callback();
                }
            },trigger:"change" //只要内容改变就可以触发
        }
    ]
}

//表单中修改的内容
            <el-form :model="formData" :rules="rules" ref="regForm">
                <h2>注册<small><a href="login.html">登录</a></small></h2>
                <el-form-item prop="username">
                    <el-input type="text"
                              v-model="formData.username"
                              placeholder="请输入用户名"
                              suffix-icon="el-icon-user"></el-input>
                </el-form-item>
                <el-form-item prop="password">
                    <el-input type="password"
                              v-model="formData.password"
                              placeholder="请输入密码"
                              suffix-icon="el-icon-lock"></el-input>
                </el-form-item>
                <el-form-item prop="checkPass">
                    <el-input type="password"
                              v-model="formData.checkPass"
                              placeholder="请再次输入密码"
                              suffix-icon="el-icon-lock"></el-input>
                </el-form-item>
                <el-form-item>
                    <!--让某一个元素位置进行微调时 使用相对定位-->
                    <el-button type="primary" @click="register()">注册</el-button>
                </el-form-item>
            </el-form>
                
                
//修改函数
 methods: {
            register() {
                //通过校验才发送请求
                //访问自定义表单验证规则
                v.$refs.regForm.validate(function (valid) {
                    if (valid) {
                        //验证通过发送请求
                        //定义发送的请求参数
                        let param = {
                            username: v.formData.username, //得到用户名
                            password: v.formData.password  //得到密码
                        };
                        //发送post请求,第二个参数表示发送请求传递的参数
                        axios.post("http://localhost:8080/user/register", param).then(function (resp) {
                            //打印响应
                            console.log(resp)
                            if (resp.data == "注册成功") {
                                //弹出个成功框
                                v.$message.success(resp.data);
                            } else {
                                v.$message.error(resp.data);
                            }
                        });
                    } else {
                        //如果验证不通过,验证失败
                        console.log("表单验证失败");
                    }
                })
            }
        }

如果需要在一开始输入用户名时校验用户名是否存在,需要在规则中添加校验:

添加校验步骤

//js的lambda表达式
validator: (rule, value, callback) => {
    let param = {params: {username: v.formData.username}};
    axios.get("/user/exists", param).then(resp => {
        console.log(resp);
        //处理响应
        if (resp.data == "用户名可用") {
            callback(); //什么也不干
        } else {
            callback(new Error(resp.data)); //返回错误内容
        }
    })
}, trigger: "blur"

校验填写完后根据get请求的地址我们可以得知需要添加新的controller(但我们需要从dao层开始写):

dao层接口:

/**
 * 注册验证用户名是否存在
 *
 * @param username 用户名
 * @return 返回根据用户名查询的行数有几行
 */
int exits(String username);

dao层实现类:

@Override
public int exits(String username) {
    int result = 0;
    try (
            Connection connection = DBUtil.getConnection();
    ) {
        String sql = "SELECT count(*) FROM user WHERE username=?";
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.setString(1, username);
        ResultSet rs = ps.executeQuery();
        if (rs.next()) {
            result = rs.getInt(1);
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return result;
}

service层接口:

/**
 * 注册验证用户名是否存在
 * @param username 用户名
 */
void exits(String username);

service层实现类:

@Override
public void exits(String username) {
    int result = userDao.exits(username);
    if (result > 0) {
        //大于0说明存在
        throw new UserException("用户名已存在");
    }
}

controller层代码:

@RequestMapping("/user/exists")
@ResponseBody
public String exists(String username) {
    try {
        userService.exits(username);
    } catch (UserException e) {
        String message = e.getMessage();
        return message;
    }
    return "用户名可用";
}

二、MyBatis框架基础

1、MyBatis概述

mybatis可以将业务逻辑与数据访问代码分离,是当下应用比较多的技术。

mybatis官方文档:MyBatis中文网

2、MyBatis与JDBC的关系

MyBatis的主要作用是让开发者在编写数据库访问代码时,仅需要关注要执行的SQL语句,参数与SQL的绑定关系以及结果集的处理逻辑,其他部分由MyBatis代为完成。

MyBatis底层使用的仍是JDBC技术。

3、在Spring Boot项目中使用MyBatis框架

通常使用Spring Boot整合MyBatis框架,由Spring Boot来简化相关配置。

在Spring Boot项目中使用MyBatis框架一般需要如下步骤。

在Springboot中使用mybatis步骤

1、引入MyBatis依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.2</version>
</dependency>

2、在Spring Boot中配置MyBatis框架

#注释
#properties是一种map集合 文件中所有内容以键值对的形式存储
#配置数据库的驱动
#☆等号前后不能有空格
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据源
spring.datasource.name=defaultDataSource
#数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/coolsharkhub?useSSL=false&serverTimeZone=Asia/Shanghai
#数据库用户名
spring.datasource.username=root
#数据库密码
spring.datasource.password=root
#开启日志 debug为日志的级别 仅限开发过程中测试使用 项目上线后要删除或者改级别
logging.level.cn.highedu.boot2.mapper=debug

3、编写Mapper接口

  • 使用接口中的抽象方法来声明要执行的数据库操作
@Mapper //声明这是一个持久层接口
public interface UserMapper {
    /**
     * 查询所有用户
     * @return
     */
    @Select("select * from user")
    List<User> listAllUser();

}

单元测试:

@SpringBootTest //声明这是一个springboot测试类
class UserMapperTest {
    //获取spring的组件
    //获取spring实例化的UserMapper接口的实现类对象
    @Autowired //注入被spring实例化的对象
    UserMapper userMapper;

    @Test
    void listAllUser() {
        List<User> users = userMapper.listAllUser();
        //::为函数式引用
        users.forEach(System.out::println);
    }
}

4、编写Mapper.xml映射文件或者在接口中添加注解

  • Mapper.xml文件用于描述Java对象和SQL语句之间的映射关系
  • 简单的SQL语句可以通过在Mapper接口的方法前添加注解来实现

5、用Mapper接口方法调用映射语句

4、值的传递

  • 单值传递
  • 对象传值
  • Map传值(一些开发规范中禁止使用)

单值传递示例:

//#{id}是占位符 id要和方法中的参数名称一致 类似jdbc中的占位符“?”
@Select("select * from user where id=#{id}") 
User getById(Integer id);

//多个值需要传递
//注解param声明这一个sql语句的参数 username要和sql语句中的#{username}保持一致
@Select("select * from user where username=#{username} and password=#{password}")
User getUser(@Param("username") String username,@Param("password") String password);   

//测试
    @Test
    void getById() {
        User user = userMapper.getById(1);
        System.out.println(user);
    }

    @Test
    void getUser() {
        User user = userMapper.getUser("zhangsan", "123456");
        System.out.println(user);
    }

对象传值:

/**
 * 保存一个用户
 */
//#{username}在执行的时候自动调用相应的get方法到user对象中取值
@Insert("insert into user (username,password,role) " +
        "values(#{username},#{password},#{role})")
int insertUser(User user);

//测试
  @Test
    void insertUser() {
        User user = new User(null, "SmallG", "123456", "普通用户", null, null);
        userMapper.insertUser(user);
    }

5、#{}和${}

“#{}”可以防止sql注入攻击的,${}不行

三、练习

1 添加商品用例

请在Homework项目下新建一个Module,该Module为一个Spring Boot项目,命名为ssm_hw02,作为今天的课后作业项目。

在该项目中实现添加商品用例。具体要求:

1、商品需要包含id(整型),title(字符串), price(浮点)和num(整型)共4个属性。

2、数据库表使用ssm_hw01db中的product表即可。

3、ssm_hw02中的包名、类名、静态资源文件名称如图所示

练习1

4、addProduct.html参考效果如图所示:

练习2

技术要求:

1、基于MyBatis框架实现持久层访问。

2、基于Element UI和Vue实现前端页面,数值类型的表单组件及校验方法请查阅官方文档或AI工具。

3、添加完善的前后端表单校验逻辑。

  • 商品名称在2-20个字符之间,且不能添加同名的商品
  • 商品价格不能小于20元
  • 库存数量不能小于5,且不能大于100

实体类:

/**
 * @document:
 * @Author:SmallG
 * @CreateTime:2023/9/4+21:34
 */

public class Product {
    private Integer id;
    @NotBlank(message = "商品名称不能为空")
    @Size(min = 2, max = 20, message = "商品名称长度的范围为2~20")
    private String title;
    @NotNull(message = "商品价格不能为空")
    @Min(value = 20, message = "商品价格不能小于20")
    private Double price;
    @NotNull(message = "库存不能为空")
    @Min(value = 5, message = "库存数量不能小于5")
    @Max(value = 100, message = "库存数量不能大于100")
    private Integer num;

    省略...
}

properties配置文件:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ssm_hw01db
spring.datasource.name=defaultDataSource
spring.datasource.username=root
spring.datasource.password=root
logging.level.cn.highedu.boot2.mapper=debug

mapper类:

@Mapper
public interface ProductMapper {

    @Insert("insert into product (title,price,num) values(#{title},#{price},#{num})")
    int insertProduct(Product product);

    @Select("select * from product where title = #{title}")
    Product getProductByTitle(String title);

}

controller类:

@Controller
@RequestMapping("/product")
public class ProductController {
    @Autowired
    ProductMapper productMapper;

    @GetMapping ("/exists")
    @ResponseBody
    public String usernameExists(String title){
        if (productMapper.getProductByTitle(title)!=null){
            return "商品已存在";
        }
        return "商品标题可用";
    }

    @PostMapping ("/add")
    @ResponseBody
    public String addProduct(@RequestBody @Valid Product product, BindingResult bindingResult){
        //参数验证
        if (bindingResult.hasErrors()){
            return bindingResult.getFieldError().getDefaultMessage();
        }
        //将商品信息插入到数据库中
        int row = productMapper.insertProduct(product);
        if (row == 0){
            return "商品添加失败!";
        }
        return "商品添加成功!";
    }

}

前端页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>添加商品</title>
    <link rel="stylesheet" href="https://cdn.staticfile.org/element-ui/2.15.9/theme-chalk/index.css">
    <style>
        .el-card{
            width: 380px;
        }
    </style>
    <script src="https://cdn.staticfile.org/vue/2.6.14/vue.min.js"></script>
    <script src="https://cdn.staticfile.org/element-ui/2.15.9/index.min.js"></script>
    <script src="https://cdn.staticfile.org/axios/1.4.0/axios.min.js"></script>
</head>
<body>
<el-container id="app">
    <el-main>
        <el-card>
            <el-form :model="formData" :rules="rules" ref="ruleForm" label-width="100px">
                <el-form-item label="商品名称" prop = "title">
                    <el-input type="text" v-model="formData.title"
                              placeholder="请输入商品标题"></el-input>
                </el-form-item>
                <el-form-item label="商品价格" prop = "price">
                    <el-input-number v-model="formData.price"
                                     :precision="2" :step="0.1" :min="20"></el-input-number>
                </el-form-item>
                <el-form-item label="商品数量"  prop = "num">
                    <el-input-number v-model="formData.num" :min="5" :max="100"></el-input-number>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="add()" >添加商品</el-button>
                </el-form-item>
            </el-form>
        </el-card>
    </el-main>
</el-container>

<script>
    let v = new Vue({
        el: '#app',
        data: function () {
            return {
                formData: {
                    title: '',
                    price: '',
                    num: ''
                },
                rules: {
                    title: [
                        { required: true, message: '请输入商品名称', trigger: 'blur' },
                        { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' },
                        { validator: (rule, value, callback) => {
                                // 特别注意get请求的参数格式
                                let param ={params: {"title": this.formData.title}}
                                // 发送请求
                                axios.get("/product/exists", param).then(response=>{
                                    // 处理响应数据
                                    if(response.data === '商品标题可用!'){
                                        callback();
                                    }else{
                                        callback(new Error(response.data))
                                    }
                                })
                            }, trigger: 'blur' }
                    ],
                    price: [
                        { required: true, message: '请输入价格', trigger: 'blur' },
                        { type: 'number', min: 20, message: '价格不能低于20元', trigger: 'blur' }
                    ],
                    num: [
                        { required: true, message: '请输入数量', trigger: 'blur' },
                        { type: 'number', min:5, max:100,  message: '数量必须在5-100之间', trigger: 'blur' }
                    ]
                }
            }
        },
        methods: {
            add() {
                // 通过 this.$refs.ruleForm 访问表单实例
                this.$refs.ruleForm.validate((valid) => {
                    if (valid) { // 表单验证通过,发送请求
                        // 生成请求参数
                        let param = {
                            title: this.formData.title,
                            price: this.formData.price,
                            num: this.formData.num
                        }
                        // 发送请求
                        axios.post("/product/add", param).then(response=>{
                            // 处理响应数据
                            if(response.data === '商品添加成功!'){
                                this.$message.success(response.data)
                            }else{
                                // 添加失败
                                this.$message.error(response.data)
                            }
                        })
                    } else { // 表单验证失败
                        console.log('表演验证失败!');
                    }
                });
            }
        }
    })
</script>
</body>
</html>
最后修改:2023 年 09 月 16 日
如果觉得我的文章对你有用,请随意赞赏