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 javax.sql.DataSource;
import java.awt.print.Book;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class JDBC_DEMO {DataSource dataSource;public JDBC_DEMO(DataSource dataSource){dataSource = dataSource;}public void addBook() {Connection connection = null;PreparedStatement stmt = null;try {//获取数据库连接connection = dataSource.getConnection();//创建语句stmt = connection.prepareStatement("insert into soft_bookrack (book_name, book_author, book_isbn) values (?,?,?);");
//参数绑定stmt.setString(1, "Spring in Action");stmt.setString(2, "Craig Walls");stmt.setString(3, "9787115417305");//执⾏语句stmt.execute();
} catch (SQLException e) {e.printStackTrace();}}public void updateBook() {Connection connection = null;PreparedStatement stmt = null;try {//获取数据库连接connection = dataSource.getConnection();//创建语句stmt = connection.prepareStatement("update soft_bookrack set book_author=? where book_isbn=?;");//参数绑定stmt.setString(1, "张卫滨");stmt.setString(2, "9787115417305");//执⾏语句stmt.execute();} catch (SQLException e) {//处理异常信息} finally {//清理资源try {if (stmt != null) {stmt.close();}if (connection != null) {connection.close();}} catch (SQLException e) {
}}}public void queryBook() {Connection connection = null;PreparedStatement stmt = null;ResultSet rs = null;Book book = null;try {//获取数据库连接connection = dataSource.getConnection();//创建语句stmt = connection.prepareStatement(
//                    "select book_name, book_author, book_isbn from
//                    soft_bookrack where book_isbn =?"//);//参数绑定stmt.setString(1, "9787115417305");//执⾏语句rs = stmt.executeQuery();if (rs.next()) {book = new Book();book.setName(rs.getString("book_name"));book.setAuthor(rs.getString("book_author"));book.setIsbn(rs.getString("book_isbn"));}System.out.println(book);} catch (SQLException e) {//处理异常信息} finally {//清理资源try {if (rs != null) {rs.close();}if (stmt != null) {stmt.close();}if (connection != null) {connection.close();}} catch (SQLException e) {
}}}
}
从上述代码和操作流程可以看出,对于 JDBC 来说,整个操作⾮常的繁琐,我们不但要拼接每⼀个参数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接 等,⽽所有的这些操作步骤都需要在每个⽅法中重复书写. 那有没有⼀种⽅法,可以更简单、更⽅便的 操作数据库呢?
答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更⽅便、更快速的操作数据库.

1. 什么是MyBatis?

MyBatis是⼀款优秀的 持久层 框架,⽤于简化JDBC的开发。
MyBatis本是 Apache的⼀个开源项⽬iBatis,2010年这个项⽬由apache迁移到了google code,并且改名为MyBatis 。2013年11⽉迁移到Github。
官⽹: mybatis ‒ MyBatis 3 | 简介
在上⾯我们提到⼀个词:持久层.
持久层:指的就是持久化操作的层, 通常指数据访问层(dao), 是⽤来操作数据库的.
简单来说 MyBatis 是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库⼯具
接下来,我们就通过⼀个⼊⻔程序,让⼤家感受⼀下通过Mybatis如何来操作数据库.

2.MyBatis⼊⻔

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

2.1 准备⼯作

创建springboot⼯程,并导⼊ mybatis的起步依赖、mysql的驱动包.
Mybatis 是⼀个持久层框架, 具体的数据存储和数据操作还是在MySQL中操作的, 所以需要添加
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
create database mybatis_test;
use mybatis_test;创建表[⽤⼾表]
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
实体类的属性名与表中的字段名⼀⼀对应

package com.example.demo;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⽂件, 配置内容如下:  

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

 2.3 写持久层代码

在项⽬中, 创建持久层接⼝UserInfoMapper .
package mapper;import com.example.demo.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⽬录下,已经⾃动帮我们创建好了测试类 ,我们可以 直接使⽤这个测试类来进⾏测试
package com.example.demo;import mapper.UserInfoMapper;
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 SpringMvcApplicationTests {
@AutowiredUserInfoMapper userInfoMapper;@Testvoid contextLoads() {List<UserInfo> userInfoList = userInfoMapper.queryAllUser();System.out.println(userInfoList);}}

测试类上添加了注解 @SpringBootTest,该测试类在运⾏时,就会⾃动加载Spring的运⾏环境.
我们通过@Autowired这个注解, 注⼊我们要测试的类, 就可以开始进⾏测试了
运⾏结果如下:
返回结果中, 可以看到, 只有SQL语句中查询的列对应的属性才有赋值.
使⽤Idea ⾃动⽣成测试类
书写测试代码
package com.example.demo.mapper;import com.example.demo.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;import static org.junit.jupiter.api.Assertions.*;@SpringBootTest
class UserInfoMapperTest {@AutowiredUserInfoMapper userInfoMapper;@Testvoid queryAllUser() {List<UserInfo> userInfoList = userInfoMapper.queryAllUser();System.out.println(userInfoList);}
}

记得加 @SpringBootTest 注解, 加载Spring运⾏环境

3.MyBatis的基础操作

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

3.1 打印⽇志

在Mybatis当中我们可以借助⽇志, 查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果
在配置⽂件中进⾏配置即可.
mybatis:
configuration: # 配置打印 MyBatis ⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

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

# 指定 mybatis 输出⽇志的位置 , 输出控制台
mybatis.configuration.log-impl = org.apache.ibatis.logging.stdout.StdOutImpl

 注意: 后续配置项, 默认只提供⼀种, 请⾃⾏进⾏配置项转换.

 

①: 查询语句
②: 传递参数及类型
③: SQL执⾏结果

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}。建议和参数名保持⼀致
也可以通过 @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对象的属性名来获取参数
测试代码:
 @Testvoid 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);

测试用例:

@Testvoid delete() {userInfoMapper.delete(1);}

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);
@Testvoid update() {UserInfo userInfo = new UserInfo();userInfo.setId(2);userInfo.setUsername("lyj");
userInfoMapper.update(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<UserInfo>接收返回结果, 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

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

# 驱动类名称
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
# 数据库连接的 url
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/mybatis_test?
characterEncoding=utf8&useSSL=false
# 连接数据库的⽤⼾名
spring.datasource.username = root
# 连接数据库的密码
spring.datasource.password = root
# 配置 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 格式:
<?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.UserInfoMapper" >
</ mapper >

 创建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.UserInfoMapper"><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.3 增删改查操作

4.3.1 增(Insert)

UserInfoMapper接⼝:
Integer insertUser (UserInfo userInfo);
UserInfoMapper.xml实现:
< insert id = "insertUser" >
insert into userinfo (username, `password`, age, gender, phone) values (#
{username}, #{password}, #{age},#{gender},#{phone})
</ insert >
如果使⽤@Param设置参数名称的话, 使⽤⽅法和注解类似
UserInfoMapper接⼝:
Integer insertUser ( @Param("userinfo") UserInfo userInfo);
UserInfoMapper.xml实现:
< insert id = "insertUser" >
insert into userinfo (username, `password`, age, gender, phone) values
(#{userinfo.username},#{userinfo.password},#{userinfo.age},#
{userinfo.gender},#{userinfo.phone})
</ insert >
返回⾃增 id
接⼝定义不变, Mapper.xml 实现 设置useGeneratedKeys 和keyProperty属性
< insert id = "insertUser" useGeneratedKeys = "true" keyProperty = "id" >
insert into userinfo (username, `password`, age, gender, phone) values
(#{userinfo.username},#{userinfo.password},#{userinfo.age},#
{userinfo.gender},#{userinfo.phone})
</ insert >

4.3.2 删(Delete)

UserInfoMapper接⼝:
Integer deleteUser (Integer id);
UserInfoMapper.xml实现:
< delete id = "deleteUser" >
delete from userinfo where id = #{id}
</ delete >

4.3.3 改(Update)

UserInfoMapper接⼝:
Integer updateUser (UserInfo userInfo);
UserInfoMapper.xml实现:
< update id = "updateUser" >
update userinfo set username=#{username} where id=#{id}
</ update >

4.3.4 查(Select)

同样的, 使⽤XML 的⽅式进⾏查询, 也存在数据封装的问题
我们把SQL语句进⾏简单修改, 查询更多的字段内容
< select id = "queryAllUser" resultType = "com.example.demo.model.UserInfo" >
select id, username,`password`, age, gender, phone, delete_flag,
create_time, update_time from userinfo
</ select >

结果显⽰: deleteFlag, createTime, updateTime 也没有进⾏赋值.
解决办法和注解类似:
1. 起别名
2. 结果映射
3. 开启驼峰命名
其中1,3的解决办法和注解⼀样,不再多说, 接下来看下xml如果来写结果映射
Mapper.xml
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
<id column="id" property="id"></id><result column="delete_flag" property="deleteFlag"></result><result column="create_time" property="createTime"></result><result column="update_time" property="updateTime"></result>
</resultMap>
<select id="queryAllUser2" resultMap="BaseMap">select id, username,`password`, age, gender, phone, delete_flag,create_time, update_time from userinfo
</select>
开发中使⽤注解还是XML的⽅式?
关于开发中使⽤哪种模式这个问题, 没有明确答案. 仁者⻅仁智者⻅智, 并没有统⼀的标准, 更多是取决于你的团队或者项⽬经理, 项⽬负责⼈

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进⾏编译. 如果参数为字符串, 需要加上引号 '' .

参数为数字类型时, 也可以加上, 查询结果不变, 但是可能会导致索引失效, 性能下降

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编译等,则效率就明显不⾏了.
预编译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注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀. 如果发⽣在⽤⼾登录的场景中,密码输⼊为 需要看登录代码如何写) 控制层:UserController  

' or 1='1 ,就可能完成登录(不是⼀定会发⽣的场景,需要看登录代码如何写)

控制层:UserController

import com.example.demo.model.UserInfo;import com.example.demo.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class UserController {@Autowiredprivate UserService userService;@RequestMapping("/login")public boolean login(String name, String password) {UserInfo userInfo = userService.queryUserByPassword(name, password);if (userInfo != null) {return true;}return false;}}

业务层:UserService 

 import com.example.demo.mapper.UserInfoMapper;import com.example.demo.model.UserInfo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;@Servicepublic class UserService {@Autowiredprivate UserInfoMapper userInfoMapper;public UserInfo queryUserByPassword(String name, String password) {List<UserInfo> userInfos = userInfoMapper.queryUserByPassword(name, 
password);if (userInfos != null && userInfos.size() > 0) {return userInfos.get(0);}return null;}}

数据层:UserInfoMapper

 import com.example.demo.model.UserInfo;import org.apache.ibatis.annotations.*;
import java.util.List;@Mapperpublic interface UserInfoMapper {@Select("select username, `password`, age, gender, phone from userinfo 
where username= '${name}' and password='${password}' ")List<UserInfo> queryUserByPassword(String name, String password);}

启动服务,访问:http://127.0.0.1:8080/login?name=admin&password=admin

程序正常运⾏

接下来访问SQL注⼊的代码: password设置为 ' or 1='1

password设置为 ' or 1='1 http://127.0.0.1:8080/loginname=admin&password=%27%20or%201=%271 

5.3 排序功能

从上⾯的例⼦中,可以得出结论:${}会有SQL注⼊的⻛险,所以我们尽量使⽤#{}完成查询 既然如此,是不是${}就没有存在的必要性了呢? 当然不是.

接下来我们看下${}的使⽤场景

Mapper实现

@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " + "from userinfo order by id ${sort} ") List queryAllUserBySort(String sort);

使⽤ ${sort} 可以实现排序查询,⽽使⽤ #{sort} 就不能实现排序查询了.

注意:此处sort参数为String类型,但是SQL语句中,排序规则是不需要加引号 ${sort} 也不加引号. 

 我们把 ${} 改成 #{}

@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " + "from userinfo order by id #{sort} ") List queryAllUserBySort(String sort);

运⾏结果:

可以发现,当使⽤ #{sort} 查询时,asc前后⾃动给加了引号,导致sql错误.

#{} 会根据参数类型判断是否拼接引号 '' 

除此之外,还有表名作为参数时,也只能使⽤ ${}.

5.4 like查询

like 使⽤#{}报错

@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " + "from userinfo where username like '%#{key}%' ") List queryAllUserByLike(String key);

把#{}改成${}可以正确查出来,但是${}存在SQL注⼊的问题,所以不能直接使⽤${}. 解决办法:使⽤mysql的内置函数concat()来处理,实现代码如下:

@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " + "from userinfo where username like concat('%',#{key},'%')") List queryAllUserByLike(String key);

6. 数据库连接池

在上⾯Mybatis的讲解中,我们使⽤了数据库连接池技术,避免频繁的创建连接,销毁连接.

6.1介绍

数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接, ⽽不是再重新建⽴⼀个.

没有使⽤数据库连接池的情况:每次执⾏SQL语句,要先创建⼀个新的连接对象,然后执⾏SQL语句,SQL 语句执⾏完,再关闭连接对象释放资源.这种重复的创建连接,销毁连接⽐较消耗资源

使⽤数据库连接池的情况:程序启动时,会在数据库连接池中创建⼀定数量的Connection对象,当客⼾ 请求数据库连接池,会从数据库连接池中获取Connection对象,然后执⾏SQL,SQL语句执⾏完,再把 Connection归还给连接池

优点:

1. 减少了⽹络开销

2. 资源重⽤

3. 提升了系统的性能

6.2 使⽤

常⻅的数据库连接池:

• C3P0

• DBCP

• Druid

• Hikari

⽬前⽐较流⾏的是Hikari,Druid

1. Hikari : SpringBoot默认使⽤的数据库连接池

Hikari 是⽇语"光"的意思(ひかり),Hikari也是以追求性能极致为⽬标

2. Druid

如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引⼊相关依赖即可.

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version>
</dependency>

参考官⽅地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter • Druid连接池是阿⾥巴巴开源的数据库连接池项⽬ • 功能强⼤,性能优秀,是Java语⾔最好的数据库连接池之⼀ • 学习⽂档:https://github.com/alibaba/druid/wiki/%E9%A6%96%E9%A1%B5 

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 必为主键, 类型为bigintunsigned, 单表时⾃增, 步⻓为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来完成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/25717.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# 基于.NET Framework框架WPF应用程序-MQTTNet库实现MQTT消息订阅发布

C# 基于.NET Framework框架WPF应用程序-MQTTNet库实现MQTT消息订阅发布 MQTT简述MQTTNet简述创建项目&#xff08;基于.NET Framework框架&#xff09;安装MQTTNet库项目源码运行效果 MQTT简述 mqtt官网 MQTTNet简述 MQTTnet MQTTnet 是一个强大的开源 MQTT 客户端库&#…

武汉大学生命科学学院与谱度众合(武汉)生命科技有限公司举行校企联培座谈会

2025年2月21日下午&#xff0c;武汉大学生命科学学院与谱度众合&#xff08;武汉&#xff09;生命科技有限公司&#xff08;以下简称“谱度众合”&#xff09;在学院学术厅举行校企联培专业学位研究生合作交流会。武汉大学生命科学学院副院长刘星教授、生命科学学院周宇教授、产…

【JSON2WEB】15 银河麒麟操作系统下部署JSON2WEB

【JSON2WEB】系列目录 【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSO…

Redis 持久化方式:RDB(Redis Database)和 AOF(Append Only File)

本部分内容是关于博主在学习 Redis 时关于持久化部分的记录&#xff0c;介绍了 RDB 和 AOF 两种持久化方式&#xff0c;详细介绍了持久化的原理、配置、使用方式、优缺点和使用场景。并对两种持久化方式做了对比。文章最后介绍了 Redis 持久化的意义并与其他常见的缓存技术做了…

华为云之使用鲲鹏弹性云服务器部署Node.js环境【玩转华为云】

华为云之使用鲲鹏弹性云服务器部署Node.js环境【玩转华为云】 一、本次实践介绍1.1 实践环境简介1.3 本次实践完成目标 二、 相关服务介绍2.1 华为云ECS云服务器介绍2.2 Node.js介绍 三、环境准备工作3.1 预置实验环境3.2 查看预置环境信息 四、登录华为云4.1 登录华为云4.2 查…

《Python实战进阶》No 7: 一个AI大模型聊天室的构建-基于WebSocket 实时通信开发实战

第7集&#xff1a; 一个AI大模型聊天室的构建-基于WebSocket 实时通信开发实战 在现代 Web 开发中&#xff0c;实时通信已经成为许多应用的核心需求。无论是聊天应用、股票行情推送&#xff0c;还是多人协作工具&#xff0c;WebSocket 都是实现高效实时通信的最佳选择之一。本…

(转)Java单例模式(1)

l单例模式的好多&#xff1a;节约了内存&#xff0c;提高了代码的执行效率。

【PCIe 总线及设备入门学习专栏 1.2 -- 访问 PCIe 设备过程】

文章目录 OverviewPCIe 系统软件层次TLP 通用格式配置过程PCIe 设备配置寄存器Type0 Configuration Request配置过程Overview 对于PCIe 设备来说,它与桥的连接直通过两条差分信号,那么当桥下面接入多个PCIe 设备时,它是如何选中某个设备的呢?我面前面一篇文件介绍了 PCI设…

HarmonyOS NEXT组件深度全解:十大核心组件开发指南与实战

文章目录 引言&#xff1a;组件化开发的未来趋势第一章&#xff1a;基础UI组件精要1.1 Button&#xff1a;交互设计的基石1.1.1 多态按钮实现1.1.2 高级特性 1.2 Text&#xff1a;文字渲染的进阶技巧1.2.1 富文本混排1.2.2 性能优化 第二章&#xff1a;布局组件深度解析2.1 Fle…

win11编译pytorch cuda128版本流程

Geforce 50xx系显卡最低支持cuda128&#xff0c;torch cu128 release版本目前还没有释放&#xff0c;所以自己基于2.6.0源码自己编译wheel包。 1. 前置条件 1. 使用visual studio installer 安装visual studio 2022&#xff0c;工作负荷选择【使用c的桌面开发】,安装完成后将…

log4j2中<logger>中没有指定appender的输出

一 优先级 1.1 规则 1.如果一个 <logger> 没有显式配置 appender&#xff0c;Log4j2 会将该日志事件传递给其 父 Logger 的 appender。 2.这种传递行为会一直向上追溯&#xff0c;直到找到配置了 appender 的 Logger&#xff0c;或者到达 Root Logger。 3.如果日志事…

【MySQL】(1) 数据库基础

一、什么是数据库 数据库自行选择了合适的数据结构来组织数据&#xff0c;方便用户写入&#xff08;存储介质&#xff0c;如硬盘&#xff0c;机器断电不会丢失数据&#xff09;和查询数据。在数据结构部分&#xff0c;我们讲到的 ArrayList、HashMap 集合类对象也能存储数据&am…

基于Spring Boot的产业园区智慧公寓管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

nginx+keepalived负载均衡及高可用

1 项目背景 keepalived除了能够管理LVS软件外&#xff0c;还可以作为其他服务的高可用解决方案软件。采用nginxkeepalived&#xff0c;它是一个高性能的服务器高可用或者热备解决方案&#xff0c;Keepalived主要来防止服务器单点故障的发生问题&#xff0c;可以通过其与Nginx的…

LeapVAD:通过认知感知和 Dual-Process 思维实现自动驾驶的飞跃

25年1月来自浙江大学、上海AI实验室、慕尼黑工大、同济大学和中科大的论文“LeapVAD: A Leap in Autonomous Driving via Cognitive Perception and Dual-Process Thinking”。 尽管自动驾驶技术取得长足进步&#xff0c;但由于推理能力有限&#xff0c;数据驱动方法仍然难以应…

STM32G431RBT6——(2)浅析Cortex-M4内核

本篇博客是一个对Cortex-M4内核了解性的简介&#xff0c;不会涉及到深奥的理论&#xff0c;请大家放心食用。 我们所学习的STM32G431RBT6单片机是基于ARM的Cotex-M4内核&#xff0c;因此我们有必要对此内核做一个大概了解。其实M4内核和M3内核有很大的相似之处&#xff0c;很多…

python-leetcode-删除并获得点数

740. 删除并获得点数 - 力扣&#xff08;LeetCode&#xff09; 解法 1&#xff1a;动态规划&#xff08;O(n) 时间&#xff0c;O(n) 空间&#xff09; class Solution:def deleteAndEarn(self, nums: List[int]) -> int:if not nums:return 0# 统计每个数的贡献points Cou…

【北京迅为】iTOP-RK3568OpenHarmony系统南向驱动开发-第4章 UART基础知识

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

git 强推

1、查看git版本 git --version 如果你已经安装了 Git&#xff0c;可以检查是否安装成功&#xff1a; 打开命令提示符&#xff08;CMD&#xff09;或 PowerShell。输入 git --version&#xff0c;如果安装成功&#xff0c;应该会显示 Git 的版本信息。 2、强推 git push or…

server.servlet.session.timeout: 12h(HTTP 会话的超时时间为 12 小时)

从你提供的配置文件&#xff08;应该是 Spring Boot 的 application.yml 或 application.properties 文件&#xff09;来看&#xff0c;以下部分与会话超时时间相关&#xff1a; server:servlet:session:timeout: 12h # timeout: 30cookie:name: VENDER_SID会话超时时间的…