MyBatis操作数据库

MyBatis操作数据库

前言
web一弄程序一般分为三层,Controller,Service,Dao
浏览器发送请求Controller,接收请求之后调用Service业务逻辑层,再调用Dao数据层,会数据需要从数据库中进行读取
使用JDBC操作数据库比较繁琐,因此这里就有了MyBatis可以更简单和方便操作数据库

MyBatis快速入门

什么是MyBatis

是一个数据持久层框架,简化JDBC开发,起源是iBatis

MyBatis中文网:https://mybatis.net.cn/

使用Spring Boot导入东西

Developer Tools中 Lombok,要使用@Data注解

Web 中 Spring Web ,web项目

SQL 中 MySQL Driver 、MyBatis Framework数据库相关框架和驱动

pom.xml文件中有对应依赖

Spring Boot3.x版本,其MyBatis也是3.X版本

https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

上面选取这些pom文件中有这些依赖

xml 复制代码
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
        <!--MyBatis框架-->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>3.0.5</version>
		</dependency>
		<!--MySQL驱动-->
		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!--lombok-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
        <!--测试相关-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter-test</artifactId>
			<version>3.0.5</version>
			<scope>test</scope>
</dependency>

数据库数据准备

MySQL数据准备

sql 复制代码
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;

CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
USE mybatis_test;

-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (
        `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() ON UPDATE now(),
        PRIMARY KEY ( `id` ) 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4; 

-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 17, 0, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );

这里需要一个实体类来作为接收数据库中每行的信息

这里名称要一一对应

UserInfo

java 复制代码
@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;
}

这里需要创建一个持久层接口 编写数据库对应操作UserInfoMapper

java 复制代码
@Mapper
public interface UserInfoMapper {

    //搜索所有
    @Select("select * from user_info")
    List<UserInfo> selectAll();

    @Select("select * from user_info where id = #{id}")
    UserInfo selectById(Integer id);

    @Select("select * from user_info where gender = #{gender} and age = #{age}")
    UserInfo selectByAgeAndGender(Integer age,Integer gender);
}

@Mapper注解:表示MyBatis中的Mapper接口

程序运行时,框架会自动生成接口实现类对象,交给Spring 的IOC容器进行管理

使用@Select注解即可,里面放数据库相关语句,但这里一些参数需要传递这里需要 #{id}放要传入的参数

这里名称要对应上,顺序可以不一致

java 复制代码
@Select("select * from user_info where gender = #{gender} and age = #{a}")
UserInfo selectByAgeAndGender(@Param("a") Integer age, Integer gender);

可以使用@Param注解取bi

这里通过测试代码进行测试

这里可以直接右键 Generate -> test -> 勾选对应这个类要进行测试的方法

就会生成相关测试代码

测试相关代码

@SpringBootTest,该测试类在运行时,自动加载Spring 运行,这里通过@Autowired注解进行初始化

java 复制代码
@SpringBootTest
class UserInfoMapperTest {

    @Autowired
    private UserInfoMapper userInfoMapper;

    @BeforeEach
    void setUp() {
    }

    @AfterEach
    void tearDown() {
    }

    @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoMapper.selectAll();
        for (UserInfo userInfo : userInfos){
            System.out.println(userInfo);
        }
        //userInfos.forEach(System.out::println);
    }

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

    @Test
    void selectByAgeAndGender() {
        UserInfo userInfo = userInfoMapper.selectByAgeAndGender(17,0);
        System.out.println(userInfo);
    }
}

如果没有使用@Autowired注入对象

这里测试代码是可以直接执行的,但是这里执行就会出现下面这样的错误

Error creating bean with name 'dataSource' defined in class path resource org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class: Failed to instantiate com.zaxxer.hikari.HikariDataSource: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class

这是因为 Spring Boot 无法自动检测到合适的 JDBC 驱动程序,上面我们并没有配置数据库相关信息,无法连接到数据库

数据库相关配置

这里使用的是MySQL8.0

这里使用yml文件为例进行配置

数据库相关连接配置

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

这里username和password就是对应数据库名称和密码,这里是本机的数据库

如果这里密码是纯数字的话,需要加上双引号,否则会和上面一样,爆出未连接数据库的问题

这样就可以执行测试代码了

测试的日志也是需要打印

配置yaml文件

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

数据库相关日志

1.查询语句

2.传入参数及类型

3.SQL执行结果

MyBatis的基础操作

主要是增、删、查、改等操作

Insert(增)

还是在上面这个UserInfoMapper接口下

SQL语句

sql 复制代码
insert into user_info (username,`password`,age,gender) VALUES("java1","java1",1,0);

UserInfoMapper

直接使⽤UserInfo对象的属性名来获取参数

以UserInfo对象作为参数,可以直接使用变量名进行赋值

java 复制代码
@Insert("insert into user_info (username,`password`,age,gender) " +
         "values(#{username},#{password},#{age},#{gender})")
Integer insertUser(UserInfo userInfo);

测试代码

java 复制代码
@Slf4j
@SpringBootTest
class UserInfoMapperTest {
    @Test
    void insertUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java1");
        userInfo.setPassword("java1");
        userInfo.setAge(1);
        userInfo.setGender(1);
        Integer result = userInfoMapper.insertUser(userInfo);
        //日志打印影响行数
        log.info("影响行数:{}",result);
    }
}

结果

执行SQL通常会返回这次操作影响的行数,这里也进行了接收

如果使用@Param注解取别名后,这里需要#{...}需要使⽤参数.属性来获取

java 复制代码
@Insert("insert into user_info (username,`password`,age,gender) " +
         "values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})")
Integer insertUser2(@Param("userInfo") UserInfo userInfo);
java 复制代码
  @Test
    void insertUser2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java2");
        userInfo.setPassword("java2");
        userInfo.setAge(1);
        userInfo.setGender(1);
        Integer result = userInfoMapper.insertUser2(userInfo);
        log.info("影响行数:{}",result);
    }

Insert语句默认返回的是受影响的⾏数

这里当然也可以获取到其新插入数据行的id

获取新插入数据行的id

java 复制代码
@Options(useGeneratedKeys = true,keyProperty = "id")
@Insert("insert into user_info (username,`password`,age,gender) " +
            "values(#{username},#{password},#{age},#{gender})")
Integer insertUser(UserInfo userInfo);

useGeneratedKeys :使MyBatis用JDBC的getGeneratedKeys方法取出数据库内的主键,像MySQL中这样关系型数据库的自增主键,默认值:false

keyProperty :指定唯一识别属性,通常使用getGeneratedKeys返回值或者insert语句的selectKey⼦元素设置它的值

这里测试代码将其自增主键也打印出来

java 复制代码
    @Test
    void insertUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java1");
        userInfo.setPassword("java1");
        userInfo.setAge(1);
        userInfo.setGender(1);
        Integer result = userInfoMapper.insertUser(userInfo);
        log.info("影响行数:{},自增主键:{}",result,userInfo.getId());
    }

Delete(删)

执行前

sql 复制代码
delete from `user_info` where id  = 9;
java 复制代码
//通过id进行删除
@Delete("delete from `user_info` where id = #{id}")
Integer deleteUserById(Integer id);
java 复制代码
 @Test
    void deleteUserById() {
        Integer result = userInfoMapper.deleteUserById(9);
        log.info("影响行数:{}",result);
    }

删除后

Update(改)

将id为8的gender修改为0

java 复制代码
update user_info set gender = 0 where id = 8;
java 复制代码
@Update("update user_info set gender = #{gender} where id = #{id}")
Integer updateUserById(Integer gender,Integer id);
java 复制代码
   @Test
    void updateUserById() {
        Integer result = userInfoMapper.updateUserById(0,8);
        log.info("影响行数:{}",result);
    }

成功修改

Select(查)

java 复制代码
 //搜索所有
@Select("select * from user_info")
List<UserInfo> selectAll();
java 复制代码
 @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoMapper.selectAll();
        for (UserInfo userInfo : userInfos){
            System.out.println(userInfo);
        }
        //userInfos.forEach(System.out::println);
    }

数据库中,delete_flag、create_time、 update_time都不是空,但是这里接收后这里三行都是null

这三个属性没有正确赋值

因为当自动映射查询结果时,MyBatis会获取结果中返回列名与Java中相同名称进行赋值,忽略大小写,例如:ID / id 都可以被MyBatis数据库中id进行赋值

但是这里最后三个属性名称和数据库中因为Java和MySQL命名规则不同,导致这里定义的时候名称不同,没能正确赋值

这里有三种解决方法

1.起别名

sql 复制代码
select id, username, password, age, gender, phone, 
       delete_flag as deleteFlag, 
	   create_time as createTime, 
	   update_time as updateTime from user_info;
java 复制代码
//搜索所有
@Select("select id, username, password, age, gender, phone," +
        "delete_flag as deleteFlag, " +
        "create_time as createTime, " +
        "update_time as updateTime from user_info;")
List<UserInfo> selectAll();

2.结果映射

java 复制代码
@Results({
    @Result(column = "delete_flag",property = "deleteFlag"),
    @Result(column = "create_time",property = "createTime"),
    @Result(column = "update_time",property = "updateTime"),
})
@Select("select id, username, password, age, gender, phone, delete_flag," +
        " create_time, update_time from user_info")
List<UserInfo> selectAll();

但是这样后面如果还需要写这个结果映射,因此可以使⽤ id 属性给该 Results 定义别名 ,@ResultMap 注解来复⽤其他定义的 ResultMap

这样就起到了复用的作用

3.配置驼峰自动转换

yaml 复制代码
mybatis: 
  configuration:
    map-underscore-to-camel-case: true #配置驼峰⾃动转换 

所有的yaml文件

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


mybatis:
  configuration: # 配置打印 MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #配置驼峰⾃动转换 

例如:abc_dd 变成为 abcDd

这里无需做任何处理

java 复制代码
@Select("select id, username, password, age, gender, phone, delete_flag," +
        " create_time, update_time from user_info")
List<UserInfo> selectAll();

MyBatis XML配置⽂件

使用MyBatis其实有两种方式,注解和XML文件形式,上面是通过注解的方式,这里使用XML方式操作数据库

这里需要多一个这个xml映射位置配置

yaml 复制代码
mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml
yaml 复制代码
# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: "060610"
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml
  configuration: # 配置打印 MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #配置驼峰⾃动转换

mapper接口

UserInfoXMLMapper

java 复制代码
@Mapper
public interface UserInfoXMLMapper {
    List<UserInfo> selectAll();
}

UserInfoMapper.xml模板

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.mybatis.demo.mapper.UserInfoXMLMapper">

</mapper>

这里的classpath也就是此时resources下的mybatis目录下,以Mapper.XML结尾的文件

实现查询所有的功能,也就是接口UserInfoXMLMapper 下的selectAll

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.mybatis.demo.mapper.UserInfoXMLMapper">

    <select id="selectAll" resultType="com.mybatis.demo.entity.UserInfo">
        select * from user_info
    </select>
</mapper>

mapper标签下 :namespace属性,是命名空间,全限定接口,全包名.类名 ,也就是此时UserInfoXMLMapper所在路径

select查询标签 :用户查询操作

id :是Interface接口下的方法名称 ,对应接口方法的实现

resultType :返回的数据类型

测试代码

java 复制代码
@SpringBootTest
class UserInfoXMLMapperTest {
    @Autowired
    private UserInfoXMLMapper userInfoXMLMapper;

    @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoXMLMapper.selectAll();
        userInfos.forEach(System.out::println);
    }
}

Insert(增)

UserInfoXMLMapper接口

java 复制代码
Integer insertUser(UserInfo userInfo);

UserInfoMapper.xml实现

xml 复制代码
<insert id="insertUser">
        insert into user_info (username,`password`,age,gender)
        values(#{username},#{password},#{age},#{gender})
</insert>
java 复制代码
    @Test
    void insertUser() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java12");
        userInfo.setPassword("java12");
        userInfo.setAge(1);
        userInfo.setGender(1);
        Integer result = userInfoXMLMapper.insertUser(userInfo);
        log.info("影响行数:{},自增主键:{}",result,userInfo.getId());
    }

插入前

插入后

@Param设置参数名称的话,使⽤⽅法和注解类似

就是和上面查询语句不一样

参数.属性名

UserInfoXMLMapper接口

java 复制代码
Integer insertUser2(@Param("userInfo") UserInfo userInfo);

UserInfoXMLMapper.xml

xml 复制代码
<insert id="insertUser2">
        insert into user_info (username,`password`,age,gender)
        values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})
</insert>
java 复制代码
@Test
    void insertUser2() {
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("java22");
        userInfo.setPassword("java22");
        userInfo.setAge(2);
        userInfo.setGender(0);
        Integer result = userInfoXMLMapper.insertUser2(userInfo);
        log.info("影响行数:{},自增主键:{}",result,userInfo.getId());
    }

但是发现这里获取自增主键,但是没有获取到,这里需要设置useGeneratedKeys和keyProperty属性

UserInfoXMLMapper.xml

xml 复制代码
 <insert id="insertUser2" useGeneratedKeys="true" keyProperty="id">
        insert into user_info (username,`password`,age,gender)
        values(#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})
 </insert>

Delete(删)

UserInfoMapper接⼝:

java 复制代码
Integer deleteUserById(Integer id);

UserInfoMapper.xml实现:

java 复制代码
<delete id="deleteUserById">
    delete from user_info where id = #{id};
</delete>

根据id进行删除,删除id = 12

java 复制代码
    @Test
    void deleteUserById() {
        userInfoXMLMapper.deleteUserById(12);
    }

Update(改)

UserInfoMapper接⼝:

java 复制代码
Integer updateUserById(Integer gender,Integer age);

UserInfoMapper.xml实现:

java 复制代码
 <update id="updateUserById">
        update user_info set age = 20 where age = #{age} and gender = #{gender}
 </update>

Select(查)

UserInfoMapper接⼝:

java 复制代码
List<UserInfo> selectAll();

UserInfoMapper.xml实现:

java 复制代码
<select id="selectAll" resultType="com.mybatis.demo.entity.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag, create_time, update_time from user_info
</select>
java 复制代码
    @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoXMLMapper.selectAll();
        userInfos.forEach(System.out::println);
    }

这里delete_flag, create_time, update_time为空,因为表中名称和这里名称不一样,没有赋值

这里和上面一样,依旧有三种方法解决

1.起别名

2.结果映射

3.开启驼峰命名

1.起别名

xml 复制代码
<select id="selectAll" resultType="com.mybatis.demo.entity.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag as deleteFlag, create_time as createTime, update_time as updateTime from user_info
</select>

2.结果映射

xml 复制代码
  <resultMap id="BaseMap" type="com.mybatis.demo.entity.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="selectAll" resultMap="BaseMap">
        select id, username, password, age, gender, phone,
        delete_flag, create_time, update_time from user_info
    </select>

3.开启驼峰命名

yaml 复制代码
mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml
  configuration: # 配置打印 MyBatis⽇志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true #配置驼峰⾃动转换

UserInfoMapper.xml实现:

java 复制代码
    <select id="selectAll" resultType="com.mybatis.demo.entity.UserInfo">
        select id, username, password, age, gender, phone,
        delete_flag, create_time, update_time from user_info
    </select>

这里有了配置驼峰⾃动转换,这里可以正常赋值

其他查询操作

多表查询

结合上面的user_info表和这个article_info 表

sql 复制代码
-- 创建文章表
DROP TABLE IF EXISTS article_info;
        
CREATE TABLE article_info (
        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 article_info ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1 );

article_info

这里article_info 的 uid 和 user_info中id列进行匹配,结合这两个表查找

sql 复制代码
select 
 ta.*,tb.username,tb.password
from 
  article_info ta LEFT JOIN user_info tb ON ta.uid = tb.id 
WHERE ta.id = 1;  

ArticleInfo 实体类

java 复制代码
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;

    //userInfo
    private String username;
    private String password;
}

ArticleInfoMapper映射类

java 复制代码
@Mapper
public interface ArticleInfoMapper {

    @Select("select ta.*,tb.username,tb.password " +
            "from article_info ta LEFT JOIN user_info tb ON ta.uid = tb.id " +
            "WHERE ta.id = #{id}")
    List<ArticleInfo> selectAll(Integer id);
}

test

java 复制代码
@SpringBootTest
class ArticleInfoMapperTest {

    @Autowired
    private ArticleInfoMapper articleInfoMapper;
    @Test
    void selectAll() {
        articleInfoMapper.selectAll(1).forEach(System.out::println);
    }
}

#{} 和 ${}

根据id进行查找,#{ }

java 复制代码
@Select("select * from user_info where id = #{id}")
UserInfo selectById(Integer id);

使用${ }

java 复制代码
@Select("select * from user_info where id = ${id}")
UserInfo selectById(Integer id);

这里使用名字进行查找

#{ }

UserInfoMapper

java 复制代码
@Select("select * from user_info where username = #{userName}")
UserInfo selectByName(String userName);
java 复制代码
 @Test
    void selectByName() {
        System.out.println(userInfoMapper.selectByName("java22"));
    }

${ }

java 复制代码
@Select("select * from user_info where username = ${userName}")
UserInfo selectByName(String userName);

这里换成这个就会出错

其本质上执行的是

sql 复制代码
select * from user_info where username = java22

这肯定会出错,因为这里的username是字符串类型,这里java22没有加上引号

这里给username加上引号即可

java 复制代码
@Select("select * from user_info where username = '${userName}'")
UserInfo selectByName(String userName);

通过对比可以发现,此时这里#{}使用的是预编译的SQL,?占位符,这里把参数填充到SQL语句中,也就是替换这里的?,这里会根据类型自动拼接上' ' (字符串类型会)

${}直接进行字符替换,一起对SQL进行编译,并不会自动加上' ',需要手动添加

#{}和${}区别

本质上是预编译SQL和即时SQL的区别

大致流程

1.解析语法和语义,检测SQL语法是否正确

2.优化SQL语句,制定执行计划

3.执行SQL并返回结果

1.性能方面

因为这里绝大多数情况下某一条SQL可能会被反复执行或者只是对应参数不同,如果每次都进行解析,优化SQL这样执行效率太低了

因此这里的预编译SQL ,会将其SQL缓存起来,后续执行只是传入参数不同,省去了解析和优化SQL过程,提高了效率

因此这里的#{}相对而言比${}效率高

安全性

SQL注入问题

SQL注入:需要传参,但是用户非法输入,输入有修改、删除或更新等对数据危险的操作来攻击数据库

正常

java 复制代码
@Select("select * from user_info where username = '${userName}'")
List<UserInfo> selectByName(String userName);
java 复制代码
@Test
void selectByName() {
    userInfoMapper.selectByName("admin").forEach(System.out::println);
}

这样是正常可以进行查找

但是这里如果是SQL注入

' or 1='1

java 复制代码
@Test
void selectByName() {
    userInfoMapper.selectByName("' or 1='1").forEach(System.out::println);
}

这里执行的SQL就会是这样,查询所有,明显不是我们要的结果,并且SQL还执行成功了

sql 复制代码
select * from user_info where username = '' or 1='1'

因此这里使用#{}

java 复制代码
@Select("select * from user_info where username = #{userName}")
List<UserInfo> selectByName(String userName);

这里因为#{}如果是String类型,会自动添加上引号,这里找username = ' or 1='1

然而${}需要手动添加' ',也就导致这里username 变成 ' ' or 1='1'

如果这里是登录场景,密码是' or 1='1

java 复制代码
@Select("select * from user_info where username = '${userName}' and password = '${password}'")
List<UserInfo> selectByNameAndPassword(String userName,String password);

UserInfoController

java 复制代码
@RestController
@RequestMapping("/user")
public class UserInfoController {
    @Autowired
    public UserService userService;

    @RequestMapping("/login")
    public Boolean login(String userName,String password){
        if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){
            return false;
        }
        List<UserInfo> userInfos = userService.LoginByNameAndPassword(userName,password);
        if(userInfos.size() > 0){
            return true;
        }
        return false;
    }
}

UserService

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;

    public List<UserInfo> LoginByNameAndPassword(String userName,String password){
        return userInfoMapper.selectByNameAndPassword(userName,password);

    }
}

正常登录

这里password = ' or 1='1

这里使用#{},输入这个没有SQL注入问题

排序功能

因为${}存在SQL注入的风险,因此这里能使用#{}尽量使用这个,

但是有些场景需要使用不了#{},就像这里的排序功能

sql 复制代码
//升序
select * from user_info order by id asc;
//降序
select * from user_info order by id desc;
java 复制代码
@Select("select id, username, password, age, gender, phone, delete_flag," +
        "create_time, update_time from user_info order by id #{sort}")
List<UserInfo> SelectBySort(String sort);
java 复制代码
@Test
  void selectBySort() {
    userInfoMapper.SelectBySort("asc");
}

这里使用#{}出现了SQL语法错误

因为这里asc是字符串,因此这里会自动加上引号,导致这里SQL出错

因此这里需要使用${},这里不加引号即可

java 复制代码
@Select("select id, username, password, age, gender, phone, delete_flag," +
        "create_time, update_time from user_info order by id ${sort}")
List<UserInfo> SelectBySort(String sort);

但是使用${}又会引发SQL注入问题,但是此时可以了避免,用户进行参数传递的时候,因为这里排序只可能哟 asc和desc这两个参数,因此用户传入参数时候,可以进行参数校验,如果是这两个参数就进行后续操作,如果不是就停止

like模糊查询

例如这里查询姓名中有 java的

sql 复制代码
select * from user_info where username like '%java%';
java 复制代码
@Select("select id, username, password, age, gender, phone, delete_flag," +
            "create_time, update_time from user_info username like %#{likeUserName}%")
List<UserInfo> selectUserByLikeUserName(String likeUserName);
java 复制代码
@Test
 void selectUserByLikeUserName() {
   userInfoMapper.selectUserByLikeUserName("java").forEach(System.out::println);
}

使用#{}肯定会出错

因为这里查询的时候 是 %'java'%多了一个引号,但是使用${}又会有SQL注入问题

这里可以使用concat字符串拼接

sql 复制代码
select * from user_info where username like concat('%',"java",'%');
java 复制代码
@Select("select id, username, password, age, gender, phone, delete_flag," +
            "create_time, update_time from user_info where username like CONCAT('%',#{likeUserName},'%')")
List<UserInfo> selectUserByLikeUserName(String likeUserName);

区别

1.#{}预编译处理,KaTeX parse error: Expected 'EOF', got '#' at position 13: {}是字符直接替换 2.#̲{}可以防止SQL注入,{}存在SQL注入的风险,能使用#{},推荐使用这个

3.一些场景,#{}不能完成,像上面排序、表名、字段名作为参数时候,需要使用${},但要采取一定措施方式SQL注入

数据库连接池

上面的Mybatis其实使用了数据库连接池技术,避免频繁的创建连接,销毁连接

没有数据库连接池 :这里每次执行SQL语句,都要创建一个新的对象,然后执行SQL语句,SQL语句执行完之后,关闭连接对象,释放资源,这里重复创建和销毁连接比较消耗资源

有连接池 :程序启动之后,会先创建一定的数量Connection对象,当客户端发送请求后,会从连接池中获取Connection对象建立连接,执行SQL,执行之后,会把Connection归还给连接池

有了连接池,这里就可以减少网络开销、Connection对象等资源重用、提高系统的性能

常见的数据库连接池有C3P0、DBCP、Druid、 Hikari

Druid、 Hikari这两个较为常用

Hikari是Spring Boot默认使用数据库连接池

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

这里将连接池换成Druid,只需要导入对应依赖即可

Spring Boot 3X版本使用这个

xml 复制代码
<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-3-starter</artifactId>
 <version>1.2.21</version>
</dependency>

如果Spring Boot是2X版本,使用这个

xml 复制代码
<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid-spring-boot-starter</artifactId>
 <version>1.1.17</version>
</dependency>

Druid连接池是阿⾥巴巴开源的数据库连接池项⽬,也是一个比较优秀的连接池之一

https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

相关推荐
程序猿乐锅1 小时前
面向对象练习:图书借阅系统里的封装、抽象类与多态
开发语言·前端·数据库·后端·python·学习·1024程序员节
严同学正在努力1 小时前
PostgreSQL 高可用教程(流复制 + Patroni 生产级实战)
运维·数据库·ai·postgresql·架构
牧瀬クリスだ1 小时前
Redis String深度解析:从底层到五大实战场景
数据库·redis·缓存
wno7041 小时前
Spring Boot中JdbcTemplate与Mybatis使用PostgreSQL
spring boot·postgresql·mybatis
Random Glow2 小时前
聚簇索引:为什么主键顺序决定插入性能
数据库·mysql·性能优化·innodb·索引
软件技术NINI2 小时前
基于PHP和MySQL的快递代收点管理系统的设计与实现
开发语言·javascript·数据库·mysql·毕业设计·php·毕设
吹什么轩2 小时前
MySQL 增删改查(CRUD)完全指南:从 INSERT 到 GROUP BY
数据库·mysql
为思念酝酿的痛2 小时前
MySQL表的约束
数据库·mysql
时间售卖铺2 小时前
MySQL存储过程流程控制全解析
运维·数据库·mysql·云原生