0
点赞
收藏
分享

微信扫一扫

吴恩达深度学习笔记:神经网络的编程基础2.15-2.17

扶摇_hyber 2024-03-28 阅读 8

一、查询

1、多表查询

尽量避免使用多表查询,尤其是对性能要求较高的项目。因为多表查询必然会导致性能变低。

例如:select *from ta运行需要10ms,select *from tb 运行也需要10s。但是,select *from ta left join tb on ta.xx==tb.xx 必然大于10ms,

并且数据库集群是很多项目一起使用的,当出现慢查询时,会影响整个集群,也就是会影响其他服务的速度。

在数据库上再建立一个文章表:

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 CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP
) DEFAULT CHARSET = 'utf8mb4';

INSERT INTO articleinfo (title, content, uid) VALUES ('Java', 'Java正文', 1);
INSERT INTO articleinfo (title, content, uid) VALUES ('Python', 'Python正文', 2);

对应Model层的实体类:

package com.example.mybatisdemo.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;
}

根据uid查询作者的名称等相关信息,进行多表查询的sql语句应该为:

SELECT ta.*, tb.username
FROM articleinfo ta
LEFT JOIN userinfo tb ON ta.uid = tb.id
WHERE ta.id = 1;

所以,我们要补充实体类,在刚刚的ArticleInfo类中加入用户相关信息,便于映射:

@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;
}

对应的ArticlenInfoMapper接口:

@Mapper
public interface ArticlenInfoMapper {
    //多表查询
    @Select("select ta.*,tb.username from articleinfo ta " +
            "left join userinfo tb on ta.uid = tb.id " +
            "where ta.id = #{articleId}")
    ArticleInfo selectArticlenAndUserByID(Integer articleId);
}

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

2、#{} 和 ${}

Ⅰ、区别

#{}和${}都是MyBatis框架中使用的占位符。

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

image-20240319102440963

然后把#{}换成${}

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

image-20240319102604288

使用${}时,MyBatis不会自动添加引号。{}用于直接替换SQL语句中的文本,因此在某些情况下,如果替换的值是字符串,则需要手动添加引号。

#{}利用预编译SQL的方式工作,它通过在SQL语句中使用?占位符来提前编译SQL命令,并在执行时将参数值安全地绑定到这些占位符上。MyBatis会根据参数的类型自动添加必要的引号,例如字符串类型的参数会被加上引号'',以确保SQL语句的正确性和安全性。相反,${}则采用简单的字符串替换机制,它在SQL语句编译之前直接将参数值替换到SQL命令中。这意味着如果参数值是字符串,需要手动添加引号''来确保SQL语句的语法正确性。

总结:

#{}${}在MyBatis中的区别主要体现在以下几个方面:

  1. 预编译处理
    • #{}:使用预编译语句(PreparedStatement),参数会被替换为?,并在SQL执行时绑定参数值。这种方式可以防止SQL注入,因为参数值会被数据库引擎视为数据,而不是SQL命令的一部分。
    • ${}:不使用预编译语句,参数值会直接替换到SQL语句中。这种方式不会防止SQL注入,因为参数值被视为SQL语句的一部分,如果参数值中包含SQL关键字或特殊字符,可能会改变原SQL语句的结构。
  2. 参数替换方式
    • #{}:参数替换后,MyBatis会根据参数的类型自动添加引号,例如字符串类型的参数会被加上引号''
    • ${}:参数替换后,不会自动添加引号,如果参数是字符串类型,需要手动添加引号。
  3. 使用场景
    • #{}:适用于大部分情况,尤其是处理用户输入或不可信数据时,提供安全保障。
    • ${}:适用于需要动态指定表名、列名或其他SQL关键字的情况,但使用时需要确保参数值的安全性。
  4. 性能影响
    • #{}:通常不会对性能产生负面影响,因为预编译语句可以被数据库缓存和重用。
    • ${}:如果用于字符串替换,可能会导致数据库无法有效缓存执行计划,从而影响性能。
  5. 安全性
    • #{}:提供了更好的安全性,可以防止SQL注入攻击。
    • ${}:存在SQL注入的风险,应该尽量避免使用,或者在确保参数值安全的情况下谨慎使用。

Ⅱ、SQL注入

${}存在一个非常大的问题,那就是SQL注入。当使用${}时,MyBatis不会对替换的参数值进行任何转义或预处理。这意味着,如果参数值包含特殊字符或SQL关键字,它们将直接插入到SQL语句中。如果这些值来自于用户的输入,且没有得到适当的验证和清理,攻击者就可以利用这一点来执行恶意SQL代码。

    @Select("select * from userinfo where username like '${username}'")
    List<UserInfo> selectUserByName(String username);

测试代码:

@Test
void selectUserByName() {
    log.info(userInfoMap.selectUserByName("' or 1 = '1").toString());
}

image-20240319112222814

SQL注⼊代码: ' or 1='1。这里可以看见,结果被正确查询出来了, 其中参数 or 被当做了SQL语句的⼀部分。由于没有对用户输⼊进行充分检查,而SQL⼜是拼接⽽成,在用户输⼊参数时,在参数中添加⼀些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。

3、排序查询

@Select("SELECT id, username, age, gender, phone, delete_flag, create_time, update_time " +
        "FROM userinfo " +
        "ORDER BY id ${sort}")
List<UserInfo> selectAllUserBySort(String sort);

这里使用 ${sort} 可以实现排序查询,而使用#{sort} 就不能实现排序查询。因为,此处 sort 参数为String类型,但是SQL语句中,排序规则是不需要加引号 '' 的,所以此时的${sort} 也不加引号。如果此时,使用 #{sort} 查询时, sort参数前后会自动给加了引号, 导致出现 sql 错误。

4、模糊查询

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

和前面的排序查询一样,在这个查询中,由于#{}的工作方式,MyBatis会把'%#{key}%'当作一个整体,所以 '%#{key}%' 的预期结果是,参数key被包围在两个%通配符之间。所以,当使用like查询的时候,应该使用${},但是这样又会出现SQL注入的安全问题。

为了解决这个问题,可以使用MySQL 的CONCAT函数来动态地构造like查询的参数,像这样:

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

二、数据库连接池

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

image-20240319124043111

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

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

目前比较流行的是:Hikari,Druid

  1. Hikari : SpringBoot默认使用的数据库连接池
  2. Druid:阿里巴巴开源的数据库连接池

如果想把默认的数据库连接池从Hikari连接池切换为Druid连接池, 只需要在pom.xml中引入相关依赖即可

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

学习文档:常见问题 · alibaba/druid Wiki (github.com)

举报

相关推荐

0 条评论