八,九、 MyBatis 操作数据库 ★ ✔

发布于:2024-07-18 ⋅ 阅读:(145) ⋅ 点赞:(0)

八,九、 MyBatis 操作数据库

本节⽬标

  1. 使⽤MyBatis完成简单的增删改查操作, 参数传递.
  2. 掌握MyBatis的两种写法: 注解 和 XML⽅式
  3. 掌握MyBatis 相关的⽇志配置

前⾔
在应⽤分层学习时, 我们了解到web应⽤程序⼀般分为三层,即:Controller、Service、Dao
之前的案例中,请求流程如下: 浏览器发起请求, 先请求Controller, Controller接收到请求之后, 调⽤Service进⾏业务逻辑处理, Service再调⽤Dao, 但是Dao层的数据是Mock的, 真实的数据应该从数据库中读取.
我们学习MySQL数据库时,已经学习了JDBC来操作数据库, 但是JDBC操作太复杂了.

JDBC 操作⽰例回顾

我们先来回顾⼀下 JDBC 的操作流程:

  1. 创建数据库连接池 DataSource
  2. 通过 DataSource 获取数据库连接 Connection
  3. 编写要执⾏带 ? 占位符的 SQL 语句
  4. 通过 Connection 及 SQL 创建操作命令对象 Statement
  5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值
  6. 使⽤ Statement 执⾏ SQL 语句
  7. 查询操作:返回结果集 ResultSet,更新操作:返回更新的数量
  8. 处理结果集
  9. 释放资源

下⾯的⼀个完整案例,展⽰了通过 JDBC 的 API 向数据库中添加⼀条记录,修改⼀条记录,查询⼀条录的操作。

-- 创建数据库
create database if not exists library default character set utf8mb4;
-- 使⽤数据库
use library;
-- 创建表
create table if not exists soft_bookrack (
 book_name varchar(32) NOT NULL,
 book_author varchar(32) NOT NULL,
 book_isbn varchar(32) NOT NULL primary key
);

以下是 JDBC 操作的具体实现代码:

import java.sql.*;

public class BookshelfManager {
    static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    static final String DB_URL = "jdbc:mysql://localhost:3306/your_database_name";
    static final String USER = "your_username";
    static final String PASS = "your_password";

    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        try {
            Class.forName(JDBC_DRIVER);
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            stmt = conn.createStatement();

            // 添加一条记录
            String insertQuery = "INSERT INTO soft_bookrack (book_name, book_author, book_isbn) VALUES ('Java Programming', 'John Doe', '1234567890')";
            stmt.executeUpdate(insertQuery);

            // 修改一条记录
            String updateQuery = "UPDATE soft_bookrack SET book_author = 'Jane Smith' WHERE book_isbn = '1234567890'";
            stmt.executeUpdate(updateQuery);

            // 查询一条记录
            String selectQuery = "SELECT * FROM soft_bookrack WHERE book_isbn = '1234567890'";
            ResultSet rs = stmt.executeQuery(selectQuery);
            while (rs.next()) {
                String bookName = rs.getString("book_name");
                String bookAuthor = rs.getString("book_author");
                String bookISBN = rs.getString("book_isbn");
                System.out.println("Book Name: " + bookName + ", Author: " + bookAuthor + ", ISBN: " + bookISBN);
            }

            rs.close();
            stmt.close();
            conn.close();
        } catch (SQLException se) {
            se.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (stmt != null) stmt.close();
            } catch (SQLException se2) {
            }
            try {
                if (conn != null) conn.close();
            } catch (SQLException se) {
                se.printStackTrace();
            }
        }
    }
}

从上述代码和操作流程可以看出,对于 JDBC 来说,整个操作⾮常的繁琐,我们不但要拼接每⼀个参数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等,⽽所有的这些操作步骤都需要在每个⽅法中重复书写. 那有没有⼀种⽅法,可以更简单、更⽅便的操作数据库呢?
答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更⽅便、更快速的操作数据库.

1. 什么是MyBatis?

  • MyBatis是⼀款优秀的 持久层 框架,⽤于简化JDBC的开发。
  • MyBatis本是 Apache的⼀个开源项⽬iBatis,2010年这个项⽬由apache迁移到了google code,并且改名为MyBatis 。2013年11⽉迁移到Github。

简单来说 MyBatis 是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库⼯具
接下来,我们就通过⼀个⼊⻔程序,让⼤家感受⼀下通过Mybatis如何来操作数据库

2. MyBatis⼊⻔

Mybatis操作数据库的步骤:

  1. 准备⼯作(创建springboot⼯程、数据库表准备、实体类)
  2. 引⼊Mybatis的相关依赖,配置Mybatis(数据库连接信息)
  3. 编写SQL语句(注解/XML)
  4. 测试

2.1 准备⼯作

2.1.1 创建⼯程

创建springboot⼯程,并导⼊ mybatis的起步依赖、mysql的驱动包

在这里插入图片描述
Mybatis 是⼀个持久层框架, 具体的数据存储和数据操作还是在MySQL中操作的, 所以需要添加MySQL驱动

项⽬⼯程创建完成后,⾃动在pom.xml⽂件中,导⼊Mybatis依赖和MySQL驱动依赖

版本会随着SpringBoot 版本发⽣变化, ⽆需关注

<!--Mybatis 依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.1</version></dependency>
<!--mysql驱动包-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>

2.1.2 数据准备

创建⽤⼾表, 并创建对应的实体类User

-- 创建数据库

DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;
-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (
 `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
 `username` VARCHAR ( 127 ) NOT NULL,
 `password` VARCHAR ( 127 ) NOT NULL,
 `age` TINYINT ( 4 ) NOT NULL,
 `gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-⼥ 0-默认',
 `phone` VARCHAR ( 15 ) DEFAULT NULL,
 `delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now(),
 PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; 
-- 添加⽤⼾信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );

创建对应的实体类 UserInfo

实体类的属性名与表中的字段名⼀⼀对应

import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
	private Integer id;
	private String username;
	private String password;
	private Integer age;
	private Integer gender;
	private String phone;
	private Integer deleteFlag;
	private Date createTime;
	private Date updateTime;
}

2.2 配置数据库连接字符串

Mybatis中要连接数据库,需要数据库相关参数配置

  • MySQL驱动类
  • 登录名
  • 密码
  • 数据库连接字符串

如果是application.yml⽂件, 配置内容如下

# 数据库连接配置
spring:
 datasource:
 url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
 username: root
 password: root
 driver-class-name: com.mysql.cj.jdbc.Driver

注意事项:
如果使⽤ MySQL 是 5.x 之前的使⽤的是"com.mysql.jdbc.Driver",如果是⼤于 5.x 使⽤的
是“com.mysql.cj.jdbc.Driver”.

如果是application.properties⽂件, 配置内容如下:

1 #驱动类名称
2 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3 #数据库连接的url
4 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
5 #连接数据库的⽤⼾名
6 spring.datasource.username=root
7 #连接数据库的密码
8 spring.datasource.password=root

2.3 写持久层代码 ★

在项⽬中, 创建持久层接⼝UserInfoMapper
在这里插入图片描述

import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserInfoMapper {
	//查询所有用户
	@Select("select username, password`, age, gender, phone from userinfo")
	public List<UserInfo> queryAllUser();
}

Mybatis的持久层接⼝规范⼀般都叫 XxxMapper
@Mapper注解:表⽰是MyBatis中的Mapper接⼝
• 程序运⾏时, 框架会⾃动⽣成接⼝的实现类对象(代理对象),并给交Spring的IOC容器管理
• @Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容

2.4 单元测试 ★

在创建出来的SpringBoot⼯程中,在src下的test⽬录下,已经⾃动帮我们创建好了测试类 ,我们可以直接使⽤这个测试类来进⾏测试.

import com.example.demo.mapper.UserInfoMapper;
import com.example.demo.model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class DemoApplicationTests {
	@Autowired
	private UserInfoMapper userInfoMapper;
	@Test
	void contextLoads() {
		List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
		System.out.println(userInfoList);
	}
}

测试类上添加了注解 @SpringBootTest,该测试类在运⾏时,就会⾃动加载Spring的运⾏环境.
我们通过@Autowired这个注解, 注⼊我们要测试的类, 就可以开始进⾏测试了

运⾏结果如下
在这里插入图片描述
返回结果中, 可以看到, 只有SQL语句中查询的列对应的属性才有赋值

使⽤Idea ⾃动⽣成测试类

除此之外, 也可以使⽤Idea⾃动⽣成测试类

  1. 在需要测试的Mapper接⼝中, 右键 -> Generate -> Test
    在这里插入图片描述
    在这里插入图片描述

3. MyBatis的基础操作

上⾯我们学习了Mybatis的查询操作, 接下来我们学习MyBatis的增, 删, 改操作
在学习这些操作之前, 我们先来学习MyBatis⽇志打印

3.1 打印⽇志 ★

在Mybatis当中我们可以借助⽇志, 查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果
在配置⽂件中进⾏配置即可

mybatis:
 configuration: # 配置打印 MyBatis⽇志
 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

在这里插入图片描述

3.2 参数传递 ★

需求: 查找id=4的⽤⼾,对应的SQL就是: select * from userinfo where id=4

@Select("select username, `password`, age, gender, phone from userinfo where  id= 4 ")
UserInfo queryById();

但是这样的话, 只能查找id=4 的数据, 所以SQL语句中的id值不能写成固定数值,需要变为动态的数值
解决⽅案:在queryById⽅法中添加⼀个参数(id),将⽅法中的参数,传给SQL语句使⽤ #{} 的⽅式获取⽅法中的参数

@Select("select username, `password`, age, gender, phone from userinfo where id= #{id} ")
UserInfo queryById(Integer id);

如果mapper接⼝⽅法形参只有⼀个普通类型的参数,#{…} ⾥⾯的属性名可以随便写,如:#{id}、#{value}。建议和参数名保持⼀致

添加测试⽤例

@Test
void queryById() {
 UserInfo userInfo = userInfoMapper.queryById(4);
 System.out.println(userInfo);
}

在这里插入图片描述

也可以通过 @Param , 设置参数的别名, 如果使⽤ @Param 设置别名, #{…}⾥⾯的属性名必须和@Param 设置的⼀样

@Select("select username, `password`, age, gender, phone from userinfo where
id= #{userid} ")
UserInfo queryById(@Param("userid") Integer id);

3.3 增(Insert) ★

SQL 语句:

insert into userinfo (username, `password`, age, gender, phone) values ("zhaoliu","zhaoliu",19,1,"18700001234");

把SQL中的常量替换为动态的参数
Mapper接⼝

@Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);

直接使⽤UserInfo对象的属性名来获取参数
测试代码:

@Test
void insert() {
 UserInfo userInfo = new UserInfo();
 userInfo.setUsername("zhaoliu");
 userInfo.setPassword("zhaoliu");
 userInfo.setGender(2);
 userInfo.setAge(21);
 userInfo.setPhone("18612340005");
 userInfoMapper.insert(userInfo);
}

运⾏后, 观察数据库执⾏结果

如果设置了 @Param 属性, #{…} 需要使⽤ 参数.属性 来获取

@Insert("insert into userinfo (username, `password`, age, gender, phone) values (#{userinfo.username},#{userinfo.password},#{userinfo.age},# {userinfo.gender},#{userinfo.phone})")
Integer insert(@Param("userinfo") UserInfo userInfo);

返回主键 ★

Insert 语句默认返回的是 受影响的⾏数
但有些情况下, 数据插⼊之后, 还需要有后续的关联操作, 需要获取到新插⼊数据的id

⽐如订单系统
当我们下完订单之后, 需要通知物流系统, 库存系统, 结算系统等, 这时候就需要拿到订单ID

如果想要拿到⾃增id, 需要在Mapper接⼝的⽅法上添加⼀个Options的注解

@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username, age, gender, phone) values (#{userinfo.username},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
Integer insert(@Param("userinfo") UserInfo userInfo);
  • useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false.
  • keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)

测试数据:

@Test
void insert() {
 UserInfo userInfo = new UserInfo();
 userInfo.setUsername("zhaoliu");
 userInfo.setPassword("zhaoliu");
 userInfo.setGender(2);
 userInfo.setAge(21);
 userInfo.setPhone("18612340005");
 Integer count = userInfoMapper.insert(userInfo);
 System.out.println("添加数据条数:" +count +", 数据ID:" + userInfo.getId());
}

运⾏结果:
在这里插入图片描述
注意: 设置 useGeneratedKeys=true 之后, ⽅法返回值依然是受影响的⾏数, ⾃增id 会设置在上述 keyProperty 指定的属性中

3.4 删(Delete) ★

SQL 语句

delete from userinfo where id=6

把SQL中的常量替换为动态的参数
Mapper接⼝

 @Delete("delete from userinfo where id = #{id}")
 void delete(Integer id);

3.5 改(Update) ★

SQL 语句:

update userinfo set username="zhaoliu" where id=5

把SQL中的常量替换为动态的参数
Mapper接⼝

@Update("update userinfo set username=#{username} where id=#{id}")
void update(UserInfo userInfo);

3.6 查(Select) ★

我们在上⾯查询时发现, 有⼏个字段是没有赋值的, 只有Java对象属性和数据库字段⼀模⼀样时, 才会进⾏赋值
接下来我们多查询⼀些数据

@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from userinfo")
List<UserInfo> queryAllUser();

查询结果:
在这里插入图片描述
从运⾏结果上可以看到, 我们SQL语句中, 查询了delete_flag, create_time, update_time, 但是这⼏个属性却没有赋值.
MyBatis 会根据⽅法的返回结果进⾏赋值.

⽅法⽤对象 UserInfo接收返回结果, MySQL 查询出来数据为⼀条, 就会⾃动赋值给对象.
⽅法⽤List接收返回结果, MySQL 查询出来数据为⼀条或多条时, 也会⾃动赋值给List.

但如果MySQL 查询返回多条, 但是⽅法使⽤UserInfo接收, MyBatis执⾏就会报错

原因分析:
当⾃动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略⼤⼩写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性

在这里插入图片描述
解决办法:

  1. 起别名
  2. 结果映射
  3. 开启驼峰命名
3.6.1 起别名 ★

在SQL语句中,给列名起别名,保持别名和实体类属性名⼀样

@Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, " + "create_time as createTime, update_time as updateTime from userinfo")
public List<UserInfo> queryAllUser();

SQL语句太⻓时, 使⽤加号 + 进⾏字符串拼接

3.6.2 结果映射 ★
@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time from userinfo")
@Results({
 @Result(column = "delete_flag",property = "deleteFlag"),
 @Result(column = "create_time",property = "createTime"),
 @Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();

如果其他SQL, 也希望可以复⽤这个映射关系, 可以给这个Results定义⼀个名称

@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time from userinfo")
@Results(id = "resultMap",value = {
 @Result(column = "delete_flag",property = "deleteFlag"),
 @Result(column = "create_time",property = "createTime"),
 @Result(column = "update_time",property = "updateTime")
})
List<UserInfo> queryAllUser();
@Select("select id, username, `password`, age, gender, phone, delete_flag, 
create_time, update_time " +
 "from userinfo where id= #{userid} ")
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);

使⽤ id 属性给该 Results 定义别名, 使⽤ @ResultMap 注解来复⽤其他定义的 ResultMap
在这里插入图片描述

3.6.3 开启驼峰命名(推荐) ★

通常数据库列使⽤蛇形命名法进⾏命名(下划线分割各个单词), ⽽ Java 属性⼀般遵循驼峰命名法约定.为了在这两种命名⽅式之间启⽤⾃动映射,需要将 mapUnderscoreToCamelCase 设置为 true。

mybatis:
 configuration:
 map-underscore-to-camel-case: true #配置驼峰⾃动转换

驼峰命名规则: abc_xyz => abcXyz
• 表中字段名:abc_xyz
• 类中属性名:abcXyz

Java 代码不做任何处理

@Select("select id, username, `password`, age, gender, phone, delete_flag as 
deleteFlag, " +
 "create_time as createTime, update_time as updateTime from userinfo")
public List<UserInfo> queryAllUser();

4. MyBatis XML配置⽂件

Mybatis的开发有两种⽅式:

  1. 注解
  2. XML

上⾯学习了注解的⽅式, 接下来我们学习XML的⽅式
使⽤Mybatis的注解⽅式,主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能,建议使⽤XML来配置映射语句,也就是将SQL语句写在XML配置⽂件中.
MyBatis XML的⽅式需要以下两步:

  1. 配置数据库连接字符串和MyBatis
  2. 写持久层代码

4.1 配置连接字符串和MyBatis

此步骤需要进⾏两项设置,数据库连接字符串设置和 MyBatis 的 XML ⽂件配置。
如果是application.yml⽂件, 配置内容如下:

# 数据库连接配置
spring:
 datasource:
 url: jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
 username: root
 password: root
 driver-class-name: com.mysql.cj.jdbc.Driver
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
 mapper-locations: classpath:mapper/**Mapper.xml

4.2 写持久层代码

持久层代码分两部分

  1. ⽅法定义 Interface
  2. ⽅法实现: XXX.xml

在这里插入图片描述

4.2.1 添加 mapper 接⼝

数据持久层的接⼝定义:

import com.example.demo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserInfoXMlMapper {
 List<UserInfo> queryAllUser();
}
4.2.2 添加 UserInfoXMLMapper.xml

数据持久成的实现,MyBatis 的固定 xml 格式
创建UserInfoXMLMapper.xml, 路径参考yml中的配置

在这里插入图片描述
查询所有⽤⼾的具体实现 :

<?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.example.demo.mapper.UserInfoXMlMapper">
 <select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
select username,`password`, age, gender, phone from userinfo
 </select>
</mapper>

以下是对以上标签的说明:

  • < mapper> 标签:需要指定 namespace 属性,表⽰命名空间,值为 mapper 接⼝的全限定名,包括全包名.类名。
  • < select> 查询标签:是⽤来执⾏数据库的查询操作的:
    • id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法。
    • resultType :是返回的数据类型,也就是开头我们定义的实体类.

在这里插入图片描述

4.2.3 单元测试
@SpringBootTest
class UserInfoMapperTest {
	 @Autowired
	 private UserInfoMapper userInfoMapper;
	 @Test
	 void queryAllUser() {
		 List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
		 System.out.println(userInfoList);
	 }
 
}

以下是一个简单的示例,展示了如何在 MyBatis 中编写增删查改对应的 Mapper 接口和 XML 文件:

  1. 编写 Mapper 接口:
public interface UserMapper {
    
    // 查询操作
    User selectUserById(int id);
    
    // 插入操作
    void insertUser(User user);
    
    // 更新操作
    void updateUser(User user);
    
    // 删除操作
    void deleteUser(int id);
}
  1. 编写对应的 XML 文件(假设 namespace 为 “com.example.mapper.UserMapper”):
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    
    <!-- 查询操作 -->
    <select id="selectUserById" parameterType="int" resultType="com.example.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
    
    <!-- 插入操作 -->
    <insert id="insertUser" parameterType="com.example.model.User">
        INSERT INTO users (id, name, age) VALUES (#{id}, #{name}, #{age})
    </insert>
    
    <!-- 更新操作 -->
    <update id="updateUser" parameterType="com.example.model.User">
        UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}
    </update>
    
    <!-- 删除操作 -->
    <delete id="deleteUser" parameterType="int">
        DELETE FROM users WHERE id = #{id}
    </delete>
</mapper>

请根据实际情况调整实体类 User 和数据库表结构,以及对应的 SQL 语句。

xml 结果映射

在这里插入图片描述

5. 其他查询操作

5.1 多表查询

多表查询和单表查询类似, 只是SQL不同⽽已

5.1.1 准备⼯作

上⾯建了⼀张⽤⼾表, 我们再来建⼀张⽂章表, 进⾏多表关联查询.
⽂章表的uid, 对应⽤⼾表的id.

-- 创建⽂章表
DROP TABLE IF EXISTS articleinfo;
CREATE TABLE articleinfo (
 id INT PRIMARY KEY auto_increment,
 title VARCHAR ( 100 ) NOT NULL,
 content TEXT NOT NULL,
 uid INT NOT NULL,
 delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
 create_time DATETIME DEFAULT now(),
 update_time DATETIME DEFAULT now() 
) DEFAULT charset 'utf8mb4';
-- 插⼊测试数据
INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正⽂', 1
);

对应Model:

import lombok.Data;
import java.util.Date;
@Data
public class ArticleInfo {
 private Integer id;
 private String title;
 private String content;
 private Integer uid;
 private Integer deleteFlag;
 private Date createTime;
 private Date updateTime;
}
5.1.2 数据查询

需求: 根据uid查询作者的名称等相关信息
SQL

SELECT
 ta.id,
 ta.title,
 ta.content,
 ta.uid,
 tb.username,
 tb.age,
 tb.gender 
FROM
 articleinfo ta
 LEFT JOIN userinfo tb ON ta.uid = tb.id 
WHERE
 ta.id =1

补充实体类:

@Data
public class ArticleInfo {
 private Integer id;
 private String title;
 private String content;
 private Integer uid;
 private Integer deleteFlag;
 private Date createTime;
 private Date updateTime;
 //⽤⼾相关信息
 private String username;
 private Integer age;
 private Integer gender;
}

接⼝定义:

@Mapper
public interface ArticleInfoMapper {
 @Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " +
 "FROM articleinfo ta LEFT JOIN userinfo tb ON ta.uid = tb.id " +
 "WHERE ta.id = #{id}")
 ArticleInfo queryUserByUid(Integer id);
}

如果名称不⼀致的, 采⽤ResultMap, 或者别名的⽅式解决, 和单表查询⼀样
Mybatis 不分单表还是多表, 主要就是三部分: SQL, 映射关系和实体类
通过映射关系, 把SQL运⾏结果和实体类关联起来.

5.2 #{} 和 ${}【总结 查询参数用#{}】

MyBatis 参数赋值有两种⽅式, 咱们前⾯使⽤了 #{} 进⾏赋值, 接下来我们看下⼆者的区别

5.2.1 #{} 和${} 使⽤
1. 先看Interger类型的参数
@Select("select username, `password`, age, gender, phone from userinfo where id= #{id} ")
UserInfo queryById(Integer id);

观察我们打印的⽇志在这里插入图片描述
发现我们输出的SQL语句

select username, `password`, age, gender, phone from userinfo where id= ?

我们输⼊的参数并没有在后⾯拼接,id的值是使⽤ ? 进⾏占位. 这种SQL 我们称之为预编译SQL
MySQL 课程 JDBC编程使⽤的就是预编译SQL, 此处不再多说

我们把 #{} 改成 ${} 再观察打印的⽇志:

@Select("select username, `password`, age, gender, phone from userinfo where id= ${id} ")
 UserInfo queryById(Integer id);

在这里插入图片描述
可以看到, 这次的参数是直接拼接在SQL语句中了.

2. 接下来我们再看String类型的参数
@Select("select username, `password`, age, gender, phone from userinfo where username = #{name} ")
UserInfo queryByName(String name);

观察我们打印的⽇志, 结果正常返回
在这里插入图片描述我们把 #{} 改成 ${} 再观察打印的⽇志

@Select("select username, `password`, age, gender, phone from userinfo where
username= ${name} ")
UserInfo queryByName(String name);

在这里插入图片描述
可以看到, 这次的参数依然是直接拼接在SQL语句中了, 但是字符串作为参数时, 需要添加引号 ‘’ , 使⽤ ${} 不会拼接引号 ‘’ , 导致程序报错.
修改代码如下:

@Select("select username, `password`, age, gender, phone from userinfo where username= '${name}' ")
UserInfo queryByName(String name);

再次运⾏, 结果正常返回在这里插入图片描述
从上⾯两个例⼦可以看出:

  • #{} 使⽤的是预编译SQL, 通过 ? 占位的⽅式, 提前对SQL进⾏编译, 然后把参数填充到SQL语句中. #{} 会根据参数类型, ⾃动拼接引号 ‘’ .
  • ${} 会直接进⾏字符替换, ⼀起对SQL进⾏编译. 如果参数为字符串, 需要加上引号 ‘’ .
    参数为数字类型时, 也可以加上, 查询结果不变, 但是可能会导致索引失效, 性能下降.
#{} 通过?占位符的形式,填充到sql中,并且自动添加引号
${}直接字符替换,手动添加引号
5.2.2 #{} 和 ${}区别

#{} 和 ${} 的区别就是预编译SQL和即时SQL 的区别.
简单回顾:
当客⼾发送⼀条SQL语句给服务器后, ⼤致流程如下:

  1. 解析语法和语义, 校验SQL语句是否正确
  2. 优化SQL语句, 制定执⾏计划
  3. 执⾏并返回结果⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL)

  1. 性能更⾼
    绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐
    如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要
    经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了.在这里插入图片描述
  2. 更安全(防⽌SQL注⼊)
    SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。
    由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。
    sql 注⼊代码: ’ or 1='1
    先来看看SQL注⼊的例⼦
@Select("select username, `password`, age, gender, phone from userinfo where username= '${name}' ")
List<UserInfo> queryByName(String name);

测试代码:
正常访问情况:

@Test
void queryByName() {
 List<UserInfo> userInfos = userInfoMapper.queryByName("admin");
 System.out.println(userInfos);
}

在这里插入图片描述
SQL注⼊场景:

@Test
void queryByName() {
 List<UserInfo> userInfos = userInfoMapper.queryByName("' or 1='1");
 System.out.println(userInfos);
}

结果依然被正确查询出来了, 其中参数 or被当做了SQL语句的⼀部分
在这里插入图片描述
可以看出来, 查询的数据并不是⾃⼰想要的数据. 所以⽤于查询的字段,尽量使⽤ #{} 预查询的⽅式
SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段, SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀.
如果发⽣在⽤⼾登录的场景中, 密码输⼊为 ’ or 1='1 , 就可能完成登录(不是⼀定会发⽣的场景,
需要看登录代码如何写)
控制层: UserController

5.3 排序功能【关键字 用 $】

从上⾯的例⼦中, 可以得出结论: ${} 会有SQL注⼊的⻛险, 所以我们尽量使⽤#{}完成查询
既然如此, 是不是 就没有存在的必要性了呢 ? 当然不是 . 接下来我们看下 {} 就没有存在的必要性了呢? 当然不是. 接下来我们看下 就没有存在的必要性了呢?当然不是.接下来我们看下{}的使⽤场景
在这里插入图片描述
Mapper实现

@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from userinfo order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
使⽤ ${sort} 可以实现排序查询, ⽽使⽤ #{sort} 就不能实现排序查询了.
注意: 此处 sort 参数为String类型, 但是SQL语句中, 排序规则是不需要加引号 '' 的, 所以此时的${sort} 也不加引号

5.4 like 查询

like 使⽤ #{} 报错

把 #{} 改成 ${} 可以正确查出来, 但是${}存在SQL注⼊的问题, 所以不能直接使⽤ ${}.
解决办法: 使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:
@Select("select id, username, age, gender, phone, delete_flag, create_time, 
update_time " +
 "from userinfo where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);

6. 数据库连接池

数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,
⽽不是再重新建⽴⼀个.
没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQL
语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源
使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾
请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把
Connection归还给连接池.

Hikari : SpringBoot默认使⽤的数据库连接池在这里插入图片描述

7. 总结

7.1 MySQL 开发企业规范

  1. 表名, 字段名使⽤⼩写字⺟或数字, 单词之间以下划线分割. 尽量避免出现数字开头或者两个下划线
    中间只出现数字. 数据库字段名的修改代价很⼤, 所以字段名称需要慎重考虑。
    MySQL 在 Windows 下不区分⼤⼩写, 但在 Linux 下默认是区分⼤⼩写. 因此, 数据库名, 表名, 字
    段名都不允许出现任何⼤写字⺟, 避免节外⽣枝
    正例: aliyun_admin, rdc_config, level3_name
    反例: AliyunAdmin, rdcConfig, level_3_name
  2. 表必备三字段: id, create_time, update_time
    id 必为主键, 类型为 bigint unsigned, 单表时⾃增, 步⻓为 1
    create_time, update_time 的类型均为 datetime 类型, create_time表⽰创建时间,
    update_time表⽰更新时间
    有同等含义的字段即可, 字段名不做强制要求
  3. 在表查询中, 避免使⽤ * 作为查询的字段列表, 标明需要哪些字段(课堂上给⼤家演⽰除外).
    1. 增加查询分析器解析成本
    2. 增减字段容易与 resultMap 配置不⼀致
    3. ⽆⽤字段增加⽹络消耗, 尤其是 text 类型的字段

7.2 #{} 和${} 区别

  1. #{}:预编译处理, ${}:字符直接替换
  2. #{} 可以防⽌SQL注⼊, ${}存在SQL注⼊的⻛险, 查询语句中, 可以使⽤ #{} ,推荐使⽤ #{}
  3. 但是⼀些场景, #{} 不能完成, ⽐如 排序功能, 表名, 字段名作为参数时, 这些情况需要使⽤${}
  4. 模糊查询虽然${}可以完成, 但因为存在SQL注⼊的问题,所以通常使⽤mysql内置函数concat来完成

MyBatis 操作数据库(进阶)

1. 动态SQL

动态 SQL 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接

1.1 if标签

在注册⽤⼾的时候,可能会有这样⼀个问题,如下图所⽰

注册分为两种字段:必填字段和⾮必填字段,那如果在添加⽤⼾的时候有不确定的字段传⼊,程序应该如何实现呢?
这个时候就需要使⽤动态标签 来判断了,⽐如添加的时候性别 gender 为⾮必填字段,具体实现如下:

接⼝定义:

1 Integer insertUserByCondition(UserInfo userInfo);

Mapper.xml实现:


<insert id="insertUserByCondition">
 INSERT INTO userinfo (
 username,
 `password`,
 age,
 <if test="gender != null">
 gender,
 </if>
 phone)
 VALUES (
 #{username},
 #{age},
 <if test="gender != null">
 #{gender},
 </if>
 #{phone})
</insert>

或者使⽤注解⽅式(不推荐)
把上⾯SQL(包括标签), 使⽤ 标签括起来就可以

@Insert("<script>" +
 "INSERT INTO userinfo (username,`password`,age," +
 "<if test='gender!=null'>gender,</if>" +
 "phone)" +
 "VALUES(#{username},#{age}," +
 "<if test='gender!=null'>#{gender},</if>" +
 "#{phone})"+
 "</script>")
Integer insertUserByCondition(UserInfo userInfo);

注意 test 中的 gender,是传⼊对象中的属性,不是数据库字段
Q: 可不可以不进⾏判断, 直接把字段设置为null呢?
A: 不可以, 这种情况下, 如果gender字段有默认值, 就会设置为默认值

1.2 trim标签

之前的插⼊⽤⼾功能,只是有⼀个 gender 字段可能是选填项,如果有多个字段,⼀般考虑使⽤标签结合标签,对多个字段都采取动态⽣成的⽅式。
标签中有如下属性:
• prefix:表⽰整个语句块,以prefix的值作为前缀
• suffix:表⽰整个语句块,以suffix的值作为后缀
• prefixOverrides:表⽰整个语句块要去除掉的前缀
• suffixOverrides:表⽰整个语句块要去除掉的后缀
调整 Mapper.xml 的插⼊语句为:

<insert id="insertUserByCondition">
 INSERT INTO userinfo
 <trim prefix="(" suffix=")" suffixOverrides=",">
 <if test="username !=null">
 username,
 </if>
 <if test="password !=null">
 `password`,
 </if>
 <if test="age != null">
 age,
 </if>
 <if test="gender != null">
 gender,
 </if>
 <if test="phone != null">
 phone,
 </if>
 </trim>
 VALUES
 <trim prefix="(" suffix=")" suffixOverrides=",">
 <if test="username !=null">
 #{username},
 </if>
 <if test="password !=null">
 #{password},
 </if>
 <if test="age != null">
 #{age},
 </if>
 <if test="gender != null">
 #{gender},
 </if>
 <if test="phone != null">
  #{phone}
 </if>
 </trim>
</insert>

或者使⽤注解⽅式(不推荐)

@Insert("<script>" +
 "INSERT INTO userinfo " +
 "<trim prefix='(' suffix=')' suffixOverrides=','>" +
 "<if test='username!=null'>username,</if>" +
 "<if test='password!=null'>password,</if>" +
 "<if test='age!=null'>age,</if>" +
 "<if test='gender!=null'>gender,</if>" +
 "<if test='phone!=null'>phone,</if>" +
 "</trim>" +
 "VALUES " +
 "<trim prefix='(' suffix=')' suffixOverrides=','>" +
 "<if test='username!=null'>#{username},</if>" +
 "<if test='password!=null'>#{password},</if>" +
 "<if test='age!=null'>#{age},</if>" +
 "<if test='gender!=null'>#{gender},</if>" +
 "<if test='phone!=null'>#{phone}</if>" +
 "</trim>"+
 "</script>")
Integer insertUserByCondition(UserInfo userInfo);

在以上 sql 动态解析时,会将第⼀个 部分做如下处理:
• 基于 prefix 配置,开始部分加上 (
• 基于 suffix 配置,结束部分加上 )
• 多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于suffixOverrides 配置去掉最后⼀个 ,
• 注意 中的 username 是传⼊对象的属性

1.3 where标签

看下⾯这个场景, 系统会根据我们的筛选条件, 动态组装where 条件在这里插入图片描述
这种如何实现呢?
接下来我们看代码实现:
需求: 传⼊的⽤⼾对象,根据属性做where条件查询,⽤⼾对象中属性不为 null 的,都为查询条件. 如
username 为 “a”,则查询条件为 where username=“a”
原有SQL

SELECT
 * 
FROM
 userinfo 
WHERE
 age = 18
 AND gender = 1
 AND delete_flag =0

接⼝定义:

 List<UserInfo> queryByCondition();

Mapper.xml实现

<select id="queryByCondition" resultType="com.example.demo.model.UserInfo">
 select id, username, age, gender, phone, delete_flag, create_time, 
update_time
 from userinfo
 <where>
 <if test="age != null">
 and age = #{age}
 </if>
 <if test="gender != null">
 and gender = #{gender}
 </if>
 <if test="deleteFlag != null">
 and delete_flag = #{deleteFlag}
 </if>
 </where>
</select>
<where> 只会在⼦元素有内容的情况下才插⼊where⼦句,⽽且会⾃动去除⼦句的开头的AND或
OR
以上标签也可以使⽤ <trim prefix="where" prefixOverrides="and"> 替换, 但是此种
情况下, 当⼦元素都没有内容时, where关键字也会保留

或者使⽤注解⽅式

@Select("<script>select id, username, age, gender, phone, delete_flag, 
create_time, update_time" +
 " from userinfo" +
 " <where>" +
 " <if test='age != null'> and age = #{age} </if>" +
 " <if test='gender != null'> and gender = #{gender} </if>" +
 " <if test='deleteFlag != null'> and delete_flag = #
{deleteFlag} </if>" +
 " </where>" +
 "</script>")
List<UserInfo> queryByCondition(UserInfo userInfo);

1.4 set标签

需求: 根据传⼊的⽤⼾对象属性来更新⽤⼾数据,可以使⽤标签来指定动态内容.
接⼝定义: 根据传⼊的⽤⼾ id 属性,修改其他不为 null 的属性

Integer updateUserByCondition(UserInfo userInfo);

Mapper.xml

<update id="updateUserByCondition">
 update userinfo
 <set>
 <if test="username != null">
 username = #{username},
 </if>
 <if test="age != null">
 age = #{age},
 </if>
 <if test="deleteFlag != null">
 delete_flag = #{deleteFlag},
 </if>
 </set>
 where id = #{id}
</update>
<set> :动态的在SQL语句中插⼊set关键字,并会删掉额外的逗号. (⽤于update语句中)
以上标签也可以使⽤ <trim prefix="set" suffixOverrides=","> 替换

MyBatis 是一个优秀的持久层框架,它提供了丰富的动态 SQL 标签来构建复杂的 SQL 查询。以下是 MyBatis 中常用的动态 SQL 标签及其对应的 Java Mapper 代码示例,以及注解方式查询的代码示例。

动态 SQL 标签

  1. <if> 标签
  2. <choose> 标签
  3. <when> 标签
  4. <otherwise> 标签
  5. <trim> 标签
  6. <where> 标签
  7. <set> 标签
  8. <foreach> 标签
  9. <bind> 标签

示例代码

1. <if> 标签

XML Mapper:

<select id="findUserById" parameterType="int" resultType="User">
    SELECT * FROM users
    WHERE id = #{id}
    <if test="name != null">
        AND name = #{name}
    </if>
</select>

Java Mapper:

public interface UserMapper {
    User findUserById(@Param("id") int id, @Param("name") String name);
}
2. <choose> 标签

XML Mapper:

<select id="findUser" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <choose>
            <when test="id != null">
                id = #{id}
            </when>
            <when test="name != null">
                name = #{name}
            </when>
            <otherwise>
                age = #{age}
            </otherwise>
        </choose>
    </where>
</select>

Java Mapper:

public interface UserMapper {
    User findUser(@Param("id") Integer id, @Param("name") String name, @Param("age") Integer age);
}
3. <trim> 标签

XML Mapper:

<select id="findUserByConditions" parameterType="map" resultType="User">
    SELECT * FROM users
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="id != null">
            AND id = #{id}
        </if>
        <if test="name != null">
            AND name = #{name}
        </if>
    </trim>
</select>

Java Mapper:

public interface UserMapper {
    User findUserByConditions(@Param("id") Integer id, @Param("name") String name);
}
4. <where> 标签

XML Mapper:

<select id="findUserByConditions" parameterType="map" resultType="User">
    SELECT * FROM users
    <where>
        <if test="id != null">
            AND id = #{id}
        </if>
        <if test="name != null">
            AND name = #{name}
        </if>
    </where>
</select>

Java Mapper:

public interface UserMapper {
    User findUserByConditions(@Param("id") Integer id, @Param("name") String name);
}
5. <set> 标签

XML Mapper:

<update id="updateUser" parameterType="User">
    UPDATE users
    <set>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
    </set>
    WHERE id = #{id}
</update>

Java Mapper:

public interface UserMapper {
    void updateUser(User user);
}
6. <foreach> 标签

XML Mapper:

<select id="findUsersByIds" parameterType="list" resultType="User">
    SELECT * FROM users
    WHERE id IN
    <foreach item="id" collection="list" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

Java Mapper:

public interface UserMapper {
    List<User> findUsersByIds(List<Integer> ids);
}
7. <bind> 标签

XML Mapper:

<select id="findUserByNameLike" parameterType="string" resultType="User">
    <bind name="pattern" value="'%' + name + '%'" />
    SELECT * FROM users
    WHERE name LIKE #{pattern}
</select>

Java Mapper:

public interface UserMapper {
    List<User> findUserByNameLike(String name);
}

注解方式查询

MyBatis 也支持通过注解的方式来进行查询。以下是一些常见的注解方式查询的示例:

1. 基本查询
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User findUserById(int id);
}
2. 动态查询
public interface UserMapper {
    @Select("<script>" +
            "SELECT * FROM users" +
            " <where>" +
            "   <if test='id != null'>AND id = #{id}</if>" +
            "   <if test='name != null'>AND name = #{name}</if>" +
            " </where>" +
            "</script>")
    User findUserByConditions(@Param("id") Integer id, @Param("name") String name);
}
3. 更新操作
public interface UserMapper {
    @Update("<script>" +
            "UPDATE users" +
            " <set>" +
            "   <if test='name != null'>name = #{name},</if>" +
            "   <if test='age != null'>age = #{age},</if>" +
            " </set>" +
            "WHERE id = #{id}" +
            "</script>")
    void updateUser(User user);
}
4. 批量查询
public interface UserMapper {
    @Select("<script>" +
            "SELECT * FROM users WHERE id IN" +
            " <foreach item='id' collection='list' open='(' separator=',' close=')'>" +
            "   #{id}" +
            " </foreach>" +
            "</script>")
    List<User> findUsersByIds(List<Integer> ids);
}

这些示例展示了 MyBatis 中常用的动态 SQL 标签及其对应的 Java Mapper 代码,以及注解方式查询的代码。希望这些示例对你有所帮助!

2. 案例练习

留言板

在这里插入图片描述

前端
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>留言板</title>
    <style>
        .container {
            width: 350px;
            height: 300px;
            margin: 0 auto;
            /* border: 1px black solid; */
            text-align: center;
        }

        .grey {
            color: grey;
        }

        .container .row {
            width: 350px;
            height: 40px;

            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .container .row input {
            width: 260px;
            height: 30px;
        }

        #submit {
            width: 350px;
            height: 40px;
            background-color: orange;
            color: white;
            border: none;
            margin: 10px;
            border-radius: 5px;
            font-size: 20px;
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>留言板</h1>
        <p class="grey">输入后点击提交, 会将信息显示下方空白处</p>
        <div class="row">
            <span>谁:</span> <input type="text" name="" id="from">
        </div>
        <div class="row">
            <span>对谁:</span> <input type="text" name="" id="to">
        </div>
        <div class="row">
            <span>说什么:</span> <input type="text" name="" id="say">
        </div>
        <input type="button" value="提交" id="submit" onclick="submit()">
        <!-- <div>A 对 B 说: hello</div> -->
    </div>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
        //页面加载时, 显示留言信息
        //从后端获取到留言信息, 并显示在页面上
        $.ajax({
            type: "get",
            url: "/message/getList",
            success: function(messages){
                for(var message of messages){
                    var html = "<div>"+message.from+" 对 "+message.to+" 说: "+message.message+"</div>";
                    $(".container").append(html);
                }
            }
        });


        function submit() {
            //1. 获取留言的内容
            var from = $('#from').val();
            var to = $('#to').val();
            var say = $('#say').val();
            if (from == '' || to == '' || say == '') {
                return;
            }
            $.ajax({
                type: "post",
                url: "/message/publish",
                data: {
                    from: from,
                    to: to,
                    message: say
                },
                success: function (result) {
                    if (result == true) {
                        //添加成功
                        //2. 构造节点
                        var divE = "<div>" + from + "对" + to + "说:" + say + "</div>";
                        //3. 把节点添加到页面上
                        $(".container").append(divE);
                        //4. 清空输入框的值
                        $('#from').val("");
                        $('#to').val("");
                        $('#say').val("");
                    }else{
                        alert("发表失败");
                    }
                }
            });
        }

    </script>
</body>

</html>
sql
DROP TABLE IF EXISTS message_info;
 CREATE TABLE `message_info` (
 `id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
 `from` VARCHAR ( 127 ) NOT NULL,
 `to` VARCHAR ( 127 ) NOT NULL,
 `message` VARCHAR ( 256 ) NOT NULL,
 `delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now(),
PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
引⼊MyBatis 和 MySQL驱动依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
配置mybatis
spring:
 datasource:
 url: jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
 username: root
 password: root
 driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
 configuration: # 配置打印 MyBatis⽇志
 map-underscore-to-camel-case: true #配置驼峰⾃动转换
model
@Data
public class MessageInfo {
    private Integer id;
    private String from;
    private String to;
    private String message;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}
mapper
@Mapper
public interface MessageMapper {
    /**
     * 插入留言信息
     * @return
     */
    @Insert("insert into message_info (`from`, `to`, `message`) values (#{from}, #{to}, #{message})")
    Integer insertMessage(MessageInfo messageInfo);
    /**
     * 查询留言信息
     */
    @Select("select * from message_info where delete_flag <> 1")
    List<MessageInfo> queryMessageList();
}

service
@Service
public class MessageService {
    @Autowired
    private MessageMapper messageMapper;

    public Integer insertMessage(MessageInfo messageInfo) {
        return messageMapper.insertMessage(messageInfo);
    }

    public List<MessageInfo> queryList() {
        return messageMapper.queryMessageList();
    }
}

controller
@RequestMapping("/message")
@RestController
public class MessageController {
    @Autowired
    private MessageService messageService;

    private List<MessageInfo> messageInfos = new ArrayList<>();

    @RequestMapping("/publish")
    public boolean publishMessage(MessageInfo messageInfo){
        if (!StringUtils.hasLength(messageInfo.getFrom())
                || !StringUtils.hasLength(messageInfo.getTo())
                || !StringUtils.hasLength(messageInfo.getMessage())){
            return false;
        }
        //暂时存放在内存中
//        messageInfos.add(messageInfo);
        //把数据放在mysql当中
        messageService.insertMessage(messageInfo);
        return true;
    }

    @RequestMapping("/getList")
    public List<MessageInfo> getList(){
        return messageService.queryList();
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到