【JavaEE】MyBatis 实战指南:从 JDBC 到高效数据库操作的进阶教程

目录


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 向数据库中添加⼀条记录,修改⼀条记录,查询⼀条记录的操作。

sql 复制代码
-- 创建数据库
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 操作的具体实现代码:

java 复制代码
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class SimpleJdbcOperation {
    private final DataSource dataSource;

    public SimpleJdbcOperation(DataSource dataSource) {
        this.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_isb
            );
            //参数绑定
            stmt.setString(1, "Spring in Action" );
            stmt.setString(2, "Craig Walls" );
            stmt.setString(3, "9787115417305" );
            //执⾏语句
            stmt.execute();
        } catch (SQLException e) {
            //处理异常信息
        } finally {
            //清理资源
            try {
                if (stmt != null) {
                    stmt.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                //
            }
        }
    }

    /**
     * 更新⼀本书
     */
    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
            );
            //参数绑定
            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) {
                //
            }
        }
    }

    public static class Book {
        private String name;
        private String author;
        private String isbn;
        //省略 setter getter ⽅法
    }
}

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

答案是肯定的,这就是我们要学习 MyBatis 的真正原因,它可以帮助我们更⽅便、更快速的操作数据库.

什么是MyBatis?

  • MyBatis 是⼀款优秀的 持久层 框架,⽤于简化JDBC的开发。而且本身和 Spring 没有任何关系。本质就是操作数据库的框架

  • MyBatis 本是 Apache的⼀个开源项⽬iBatis,2010年这个项⽬由 apache 迁移到了 google code,并且改名为 MyBatis 。2013年11⽉迁移到Github。

  • 官⽹:https://mybatis.org/mybatis-3/zh/index.html

在上⾯我们提到⼀个词:持久层

  • 持久层:指的就是持久化操作的层, 通常指数据访问层(dao), 是⽤来操作数据库的.

简单来说 MyBatis 是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库⼯具

MyBatis可以看做是Java程序和数据库之间的桥梁,具体实现还是MySQL实现的(JDBC实现的)。简化JDBC的开发

接下来,我们就通过⼀个⼊⻔程序,让⼤家感受⼀下通过Mybatis如何来操作数据库

MyBatis⼊⻔

Mybatis操作数据库的步骤:

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

MyBatis写法

  1. 注解
  2. xml
  1. 测试
1. 准备⼯作

创建工程

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

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

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

或者如果以前做的比如图书管理系统现在也想用到Mybatis,下面这样也可以

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

xml 复制代码
		<!--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>

数据准备

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

sql 复制代码
-- 创建数据库
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' );

企业建表规范:需要具备三个字段

  1. id
  2. 创建时间
  3. 更新时间

哪怕你只要维护一个username也要加上这三个字段

命名规范:

  1. 字段名/表名:全部小写

创建对应的实体类 UserInfo

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

sql 复制代码
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. 配置数据库连接字符串

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

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

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

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

注意事项:

如果使⽤ MySQL 是 5.x 之前的使⽤的是 "com.mysql.jdbc.Driver",如果是⼤于 5.x 使⽤的是 "com.mysql.cj.jdbc.Driver".

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

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=123456
3. 写持久层代码

在项⽬中, 创建持久层接⼝UserInfoMapper

java 复制代码
import com.Hsu.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 * from userinfo")
    List<UserInfo> selectAll();
}

Mybatis的持久层接⼝规范⼀般都叫 XxxMapper

@Mapper注解:表⽰是MyBatis中的Mapper接⼝

  • 程序运⾏时, 框架会⾃动⽣成接⼝的实现类对象(代理对象),并给交Spring的IoC容器管理
  • @Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容.

import org.apache.ibatis.annotations.Mapper;

ibatis是Mybatis的前身

加了Mapper之后,mybatis就会告诉Spring,把对象交给Spring管理

这里为什么定义为接口interface而不能抽象类呢?

因为抽象类要有子类继承,这里不需要用到子类

4. 单元测试

可以这样测试

java 复制代码
@RestController
public class UserInfoController {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @RequestMapping("/selectAll")
    public List<UserInfo> selectAll(){
        return userInfoMapper.selectAll();
    }
}

访问http://127.0.0.1:8080/selectAll

可能图看不太清

但上面这样比较麻烦

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

java 复制代码
@SpringBootTest
class DemoApplicationTests {
   @Autowired
   private UserInfoMapper userInfoMapper;

   @Test
   void contextLoads() {
      List<UserInfo> userInfoList = userInfoMapper.selectAll();
      System.out.println(userInfoList);
   }

}

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

运⾏结果如下:

返回结果中, 可以看到, 只有SQL语句中查询的列对应的属性才有赋值

使⽤Idea ⾃动⽣成测试类

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

  1. 在需要测试的Mapper接⼝中, 右键 -> Generate -> Test
  1. 选择要测试的⽅法, 点击 OK
  1. 书写测试代码
java 复制代码
@Slf4j
@SpringBootTest
class UserInfoMapperTest {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Test
    void selectAll() {
        List<UserInfo> list=userInfoMapper.selectAll();
        log.info(list.toString());
    }
}

记得加 @SpringBootTest 注解, 启动Spring容器,加载Spring运⾏环境

运⾏结果:

使用MyBatis可能遇到的问题
  1. 没有配置数据库相关信息

启动Spring就错误了

  1. 账号密码错误

启动还没报错,运行对应的方法就错误了

  1. 数据库错误
  1. 表不存在
  1. 字段错误

MyBatis的基础操作

我们学习了Mybatis的查询操作, 接下来我们学习MyBatis的增, 删, 改操作

在学习这些操作之前, 我们先来学习MyBatis⽇志打印

打印⽇志

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

建议只出现在开发环境中,不要出现在线上环境

开发环境(本地开发,一些公司也会提供单独的服务器进行开发调试)开发环境的数据库

测试环境(给测试人员使用)测试的数据库/配置等等

预发布环境(和线上环境一样,但不对外提供服务)

发布环境(线上环境)

预发布环境和发布环境通常用的是同一个数据库
灰度发布:是指发布(上线)环境,比如发布环境有200台机器,发布的时候是一批一批的发布。通常开始就发布一台,也就是1/200的流量进入了,观察一段时间后如果没问题就继续发布下一批(可能是3、5、10台),继续观察,如果没有问题就继续发布...

在配置⽂件中进⾏配置即可

这个只在开发环境配置

日志的打印也是影响性能的

打印的日志越多,受影响越多

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

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

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

重新运⾏程序, 可以看到SQL执⾏内容, 以及传递参数和执⾏结果

①: 查询语句

②: 传递参数及类型

③: SQL执⾏结果

参数传递

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

java 复制代码
@Select("select * from userinfo where id=4")
UserInfo selectOne();

但是这样的话, 只能查找 id=4 的数据, 所以SQL语句中的id值不能写成固定数值,需要变为动态的数值

解决⽅案:在selectOne⽅法中添加⼀个参数(id),将⽅法中的参数,传给SQL语句

使⽤ #{} 的⽅式获取⽅法中的参数

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

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

添加测试⽤例

java 复制代码
	@Test
    void selectOne() {
        log.info(userInfoMapper.selectOne(1).toString());
    }

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

java 复制代码
@Select("select * from userinfo where id=#{userId}")
UserInfo selectOne2(@Param("userId") Integer id);

传递参数

使用 #{} 来接收参数

  1. 传递单个参数,#{}可以为任意
  2. 传递多个参数,默认的参数名就是Java接口方法声明的形参。如果需要修改参数名,使用@Param
  3. 传递对象,默认会给这个对象的每个属性,都有一个参数,以对象的属性名为参数名。如果对 对象参数 使用@Param进行重命名,使用时,需要加上对象名#{对象名.属性名}。这也是JavaScript获取对象的属性的方式
增(Insert)

SQL 语句:

sql 复制代码
insert into userinfo (username,password,age,gender,phone) values ("aaa","aaa",5,1,"15975253308);

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

Mapper接⼝

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

注意这些括号不要用错,也不要用多了

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

测试代码:

java 复制代码
@Test
void insert() {
    UserInfo userInfo=new UserInfo();
    userInfo.setUsername("zhaoliu");
    userInfo.setPassword("zhaoliu");
    userInfo.setAge(6);
    userInfo.setGender(0);
    userInfo.setPhone("15975253308");
    Integer result= userInfoMapper.insert(userInfo);
    log.info("insert方法,执行结果:{}",result);
}

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

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

返回主键

自增id

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

但有些情况下, 数据插⼊之后, 还需要有后续的关联操作, 需要获取到新插⼊数据的id

⽐如订单系统

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

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

java 复制代码
//设置返回自增id,把这个自增id赋值给 id字段
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username,password,age,gender,phone) " +
        "values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
  • useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false.

  • keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)

测试数据:

java 复制代码
@Test
void insert() {
    UserInfo userInfo=new UserInfo();
    userInfo.setUsername("zhaoliu");
    userInfo.setPassword("zhaoliu");
    userInfo.setAge(6);
    userInfo.setGender(0);
    userInfo.setPhone("15975253308");
    Integer result= userInfoMapper.insert(userInfo);
    //后面是获取自增id,用的是userInfo获取的id
    //注意这里是用 {} 表示占位符,接受后面的结果的
    log.info("insert方法,执行结果:{},自增id:{}",result,userInfo.getId());
}

运⾏结果:

注意: 设置 useGeneratedKeys=true 之后, ⽅法返回值依然是受影响的⾏数, ⾃增id 会设置在上述 keyProperty 指定的属性中.

参数为对象时,对参数进行重命名

java 复制代码
//对insert的参数进行重命名
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username,password,age,gender,phone) " +
        "values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert2(@Param("userInfo") UserInfo userInfo);

测试数据:

java 复制代码
@Test
void insert2() {
    UserInfo userInfo=new UserInfo();
    userInfo.setUsername("zhaoliu");
    userInfo.setPassword("zhaoliu");
    userInfo.setAge(6);
    userInfo.setGender(0);
    userInfo.setPhone("15975253308");
    Integer result= userInfoMapper.insert2(userInfo);
    //后面是获取自增id,用的是userInfo获取的id
    //注意这里是用 {} 表示占位符,接受后面的结果的
    log.info("insert方法,执行结果:{},自增id:{}",result,userInfo.getId());
}

结果:报异常,

改为这样,通过对象名.属性获取

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

结果:

删(Delete)

SQL 语句:

sql 复制代码
delete from userinfo where id=8

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

Mapper接⼝

java 复制代码
@Delete("delete from userinfo where id = #{id}")
void delete(Integer id);

测试代码:

java 复制代码
@Test
void delete() {
    userInfoMapper.delete(8);
}
改(Update)

SQL 语句:

sql 复制代码
update userinfo set age=8 where id=7

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

Mapper接⼝

java 复制代码
@Update("update userinfo set age=#{age} where id=#{id}")
    Integer update(UserInfo userInfo);

测试代码:

java 复制代码
@Test
void update() {
    UserInfo userInfo=new UserInfo();
    userInfo.setAge(8);
    userInfo.setId(7);
    Integer result=userInfoMapper.update(userInfo);
    if(result>0){
        log.info("数据修改成功");
    }
}
查(Select)

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

MyBatis会自动把MySQL返回的数据和Java对象进行映射(映射规则:名称一致)

如果MySQL字段名和Java对象的属性不一致,对于注解的实现方式:

  1. 起别名
  2. 通过@Results注解来实现
  3. 通过配置,自动转驼峰

对于XML的实现方式只有2不同:

  1. 通过<ResultMap>标签来实现

接下来我们多查询⼀些数据

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

查询结果:

从运⾏结果上可以看到, 我们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. 开启驼峰命名
起别名
java 复制代码
    /**
     * 查找
     * 方法1:对字段进行重命名
     * @return
     */
    @Select("select id, username, password, age, gender, phone," +
            " delete_flag as deleteFlag, create_time as createTime, update_time as updateTime" +
            " from userinfo")//注意这里换行后前面的from要加个空格,因为字符串拼接这里连起来的话这句sql语句就是错的
    List<UserInfo> selectAll();

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

测试结果

结果映射
java 复制代码
/**
 * 查找
 * 方法2:使用注解
 * @return
 */
@Select("select * from userinfo")
@Results({
        // column="字段名",property="Java对象属性"
        @Result(column = "delete_flag",property = "deleteFlag"),
        @Result(column = "create_time",property = "createTime"),
        @Result(column = "update_time",property = "updateTime")
})
List<UserInfo> selectAll2();
java 复制代码
@Test
void selectAll2() {
    List<UserInfo> list=userInfoMapper.selectAll2();
    log.info(list.toString());
}

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

java 复制代码
/**
 * 查找
 * 方法2:使用注解
 * @return
 */
@Select("select * from userinfo")
@Results(id="BaseMap",value = {
        // column="字段名",property="Java对象属性"
        @Result(column = "delete_flag",property = "deleteFlag"),
        @Result(column = "create_time",property = "createTime"),
        @Result(column = "update_time",property = "updateTime")
})
List<UserInfo> selectAll2();

@ResultMap(value = "BaseMap")
@Select("select * from userinfo where id=#{id}")
UserInfo selectOne(Integer id);

id="BaseMap",value = 这部分就是需要复用时才需要加的

java 复制代码
@Test
void selectOne() {
    log.info(userInfoMapper.selectOne(1).toString());
}

使⽤ id 属性给该 Results 定义别名, 使⽤ @ResultMap 注解来复⽤其他定义的 ResultMap

如果字段和Java属性名称一样,可以省略

开启驼峰命名(推荐)

通常数据库列使⽤蛇形命名法进⾏命名(下划线分割各个单词), ⽽ Java 属性⼀般遵循驼峰命名法约定.

为了在这两种命名⽅式之间启⽤⾃动映射,需要将 mapUnderscoreToCamelCase 设置为 true。

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

驼峰命名规则: abc_xyz => abcXyz

  • 表中字段名:abc_xyz
  • 类中属性名:abcXyz
java 复制代码
/**
 * 查找
 * 方法3:使用配置,自动转为驼峰
 * @return
 */
@Select("select * from userinfo")
List<UserInfo> selectAll3();
java 复制代码
@Test
void selectAll3() {
    List<UserInfo> list=userInfoMapper.selectAll3();
    log.info(list.toString());
}

添加上述配置, 运⾏代码:

字段全部进⾏正确赋值.

MyBatis XML配置⽂件

Mybatis的开发有两种⽅式:

  1. 注解
  2. XML

上⾯学习了注解的⽅式, 接下来我们学习XML的⽅式

使⽤Mybatis的注解⽅式,主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能 ,建议使⽤XML来配置映射语句,也就是将SQL语句写在XML配置⽂件中.

注解和XML是可以共存的

MyBatis XML的⽅式需要以下两步:

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

XML的方式:

  1. 需要配置数据库
  2. 指明xml的路径
  3. 写xml的实现
配置连接字符串和MyBatis

此步骤需要进⾏两项设置,数据库连接字符串设置和 MyBatis 的 XML ⽂件配置。

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

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #配置打印 MyBatis日志
    map-underscore-to-camel-case: true #配置驼峰⾃动转换
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
  mapper-locations: classpath:mapper/**Mapper.xml

指定MyBatis的mapper文件位置。这里设置的路径意味着Spring Boot会扫描resources/mapper目录及其子目录中所有名称以Mapper.xml结尾的XML文件。这些XML文件通常包含了SQL语句和MyBatis的映射配置

名字可以随机写但是要对应

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

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
#连接数据库的⽤⼾名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=root
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
写持久层代码

持久层代码分两部分

  1. ⽅法定义 Interface
  2. ⽅法实现 XXX.xml
添加 mapper 接⼝

数据持久层的接⼝定义:

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

数据持久成的实现,MyBatis 的固定 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.Hsu.demo.mapper.UserInfoXMLMapper">

</mapper>

com.Hsu.demo.mapper.UserInfoXMLMapper这个是要实现的那个接口,要写接口的全限定类名

不能写错,验证:按住ctrl然后点击能找到就行

namespace="com.Hsu.demo.mapper.UserInfoXMLMapper"定义了这个映射文件的命名空间,通常对应于一个接口路径,这有助于MyBatis找到和这个文件关联的方法

创建UserInfoXMLMapper.xml, 路径参考yml中的配置

查询所有⽤⼾的具体实现 :

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.Hsu.demo.mapper.UserInfoXMLMapper">
    <select id="selectAll" resultType="com.Hsu.demo.model.UserInfo">
        select * from userinfo
    </select>
</mapper>

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

  • <mapper> 标签:需要指定 namespace 属性,表⽰命名空间,值为 mapper 接⼝的全限定名 ,包括全包名.类名

  • <select>查询标签:是⽤来执⾏数据库的查询操作的:

    • id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法
    • resultType :是返回的数据类型,也就是开头我们定义的实体类

select id="selectAll"这里要写对应的方法名

resultType="com.Hsu.demo.model.UserInfo"这个是返回的类型

这里是返回一个List的UserInfo,返回的就是UserInfo,全限定类名

如果返回的是一个UserInfo,那也写UserInfo

不管返回的是对象还是List,定义的都是返回的数据的类型

测试代码

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

    @Test
    void selectAll() {
        List<UserInfo> userInfos = userInfoXMLMapper.selectAll();
        log.info(userInfos.toString());
    }
}

常见错误:

这是没有找到对应的实现

  1. xml文件处和接口定义的方法名称不一致
  2. mapper的路径配置和xml文件的路径不一致
  3. xml namespace写错了
增删改查操作
增(Insert)

UserInfoMapper接⼝:

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

返回值通常表示插入操作影响的行数。如果返回1,则通常意味着成功插入了一行数据;如果返回0,则没有行被插入。

UserInfoMapper.xml实现:

xml 复制代码
<insert id="insert">
    insert into userinfo (username, password, age, gender, phone)
    values (#{username}, #{password}, #{age}, #{gender}, #{phone})
</insert>
java 复制代码
@Test
void insert() {
    UserInfo userInfo=new UserInfo();
    userInfo.setUsername("liu666");
    userInfo.setPassword("liu666");
    userInfo.setAge(1);
    userInfo.setGender(2);
    userInfo.setPhone("15975253308");
    Integer result = userInfoXMLMapper.insert(userInfo);
    log.info("影响行数:{}",result);
}

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

UserInfoMapper接⼝:

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

UserInfoMapper.xml实现:

xml 复制代码
<insert id="insertUser">
	insert into userinfo (username, `password`, age, gender, phone) values
	(#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gende
</insert>
返回主键

接⼝定义不变, Mapper.xml 实现 设置 useGeneratedKeys 和 keyProperty 属性

xml 复制代码
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into userinfo (username, password, age, gender, phone)
    values (#{username}, #{password}, #{age}, #{gender}, #{phone})
</insert>
删(Delete)

UserInfoMapper接⼝:

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

返回值通常表示被删除的行数。

UserInfoMapper.xml实现:

xml 复制代码
<delete id="deleteUser">
	delete from userinfo where id = #{id}
</delete>
java 复制代码
@Test
void delete() {
    userInfoXMLMapper.delete(11);
}
改(Update)

UserInfoMapper接⼝:

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

返回值通常表示更新的行数。

UserInfoMapper.xml实现:

xml 复制代码
<update id="update">
    update userinfo set gender=#{gender} where id =#{id}
</update>
java 复制代码
@Test
void update() {
    UserInfo userInfo=new UserInfo();
    userInfo.setGender(1);
    userInfo.setId(12);
    userInfoXMLMapper.update(userInfo);
}
查(Select)

同样的, 使⽤XML 的⽅式进⾏查询, 也存在数据封装的问题

我们把SQL语句进⾏简单修改, 查询更多的字段内容

xml 复制代码
<select id="queryAllUser" resultType="com.example.demo.model.UserInfo">
	select id, username,`password`, age, gender, phone, delete_flag, create_time
</select>

结果显⽰: deleteFlag, createTime, updateTime 也没有进⾏赋值.

解决办法和注解类似:

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

其中1,3的解决办法和注解⼀样,不再多说, 接下来看下xml如果来写结果映射

UserInfoXMLMapper.xml

xml 复制代码
<resultMap id="XMLBaseMap" type="com.Hsu.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="selectAll2" resultMap="XMLBaseMap">
    select * from userinfo
</select>

resultType是结果类型,resultMap是结果映射

结果映射resultMap要对应resultMap id="XMLBaseMap"这里的

对于有结果映射就不需要结果类型了

id="XMLBaseMap":结果映射的唯一标识符

type="com.Hsu.demo.model.UserInfo":指定映射到的Java类型

内部的<id><result>元素定义了数据库表中的字段和Java对象属性之间的映射关系

这部分呢,建议是要写就全都写了,而不只是要改的才写,别的在数据库中的字段名和Java对象属性名一样的也要写上去,统一操作一下,因为有些场景没写全可能会导致bug

java 复制代码
List<UserInfo> selectAll2();
java 复制代码
@Test
void selectAll2() {
    List<UserInfo> userInfos = userInfoXMLMapper.selectAll();
    log.info(userInfos.toString());
}

这个主键是Mysql主键

开发中使⽤注解还是XML的⽅式?

关于开发中使⽤哪种模式这个问题, 没有明确答案. 仁者⻅仁智者⻅智, 并没有统⼀的标准, 更多是取决于你的团队或者项⽬经理, 项⽬负责⼈.

其他查询操作
多表查询

其实公司工作中是尽量避免使用多表查询的,尤其是对性能要求比较高的项目

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

准备工作

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

⽂章表的uid, 对应⽤⼾表的id.

数据准备

sql 复制代码
 -- 创建⽂章表
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:

java 复制代码
@Data
public class ArticleInfo {
    //文章相关信息
    private Integer id;
    private String titie;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

接口定义:

java 复制代码
/**
 * 多表查询
 * @param articleId
 * @return
 */
@Select("select ta.* , tb.username, tb.age from articleinfo ta" +
        " LEFT JOIN userinfo tb on ta.uid = tb.id" +
        " where ta.id = 1")
ArticleInfo selectArticleAndUserById(Integer articleId);

测试代码:

java 复制代码
@Test
void selectArticleAndUserById() {
    ArticleInfo articleInfo=articleInfoMapper.selectArticleAndUserById(1);
    log.info(articleInfo.toString());
}

如果只是这样直接运行测试

这是因为ArticleInfo中没有对应的字段属性。因此看下面

数据查询

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

SQL:

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

补充实体类:

java 复制代码
@Data
public class ArticleInfo {
    //文章相关信息
    private Integer id;
    private String titie;
    private String content;
    private Integer uid;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
    //⽤⼾相关信息
    private String username;
    private Integer age;
}

接⼝定义:

java 复制代码
/**
 * 多表查询
 * @param articleId
 * @return
 */
@Select("select ta.* , tb.username, tb.age from articleinfo ta" +
        " LEFT JOIN userinfo tb on ta.uid = tb.id" +
        " where ta.id = 1")
ArticleInfo selectArticleAndUserById(Integer articleId);

测试代码:

java 复制代码
@Test
void selectArticleAndUserById() {
    ArticleInfo articleInfo=articleInfoMapper.selectArticleAndUserById(1);
    log.info(articleInfo.toString());
}

SQL中直接查询多个表,把查询的结果放在一个对象即可,也就是上面的ArticleInfo
如果名称不⼀致的, 采⽤ResultMap, 或者别名的⽅式解决, 和单表查询⼀样

Mybatis 不分单表还是多表, 主要就是三部分: SQL, 映射关系和实体类

通过映射关系, 把SQL运⾏结果和实体类关联起来.

#{} 和 ${}

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

#{} 和 ${} 使⽤
  1. 先看Interger类型的参数
java 复制代码
@ResultMap(value = "BaseMap")
@Select("select * from userinfo where id=#{id}")
UserInfo selectOne(Integer id);

观察我们打印的⽇志

发现我们输出的SQL语句:

sql 复制代码
select * from userinfo where id=?

我们输⼊的参数并没有在后⾯拼接,id的值是使⽤ ? 进⾏占位. 这种SQL 我们称之为"预编译SQL"

MySQL 中 JDBC编程使⽤的就是预编译SQL

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

java 复制代码
@ResultMap(value = "BaseMap")
@Select("select * from userinfo where id=${id}")
UserInfo selectOne(Integer id);

可以看到, 这次的参数是直接拼接在SQL语句中了.这种称之为"即时SQL"

  1. 接下来我们再看String类型的参数
java 复制代码
@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username=#{username}")
UserInfo selectByName(String username);

观察我们打印的⽇志, 结果正常返回

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

java 复制代码
@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username=${username}")
UserInfo selectByName(String username);

可以看到, 这次的参数依然是直接拼接在SQL语句中了, 但是字符串作为参数时, 需要添加引号 '' , 使⽤ ${} 不会拼接引号 '' , 导致程序报错.

select * from userinfo where username='admin' 这是正确的语句,拼接的是这样的:select * from userinfo where username=admin 少了两个引号

修改代码如下:

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

再次运⾏, 结果正常返回

从上⾯两个例⼦可以看出:

#{} 使⽤的是预编译SQL, 通过 ? 占位的⽅式, 提前对SQL进⾏编译, 然后把参数填充到SQL语句中。**#{} 会根据参数类型, ⾃动拼接引号 '' **。而${}是直接拼接

${} 会直接进⾏字符替换, ⼀起对SQL进⾏编译. 如果参数为字符串, 需要加上引号 '' .

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

#{} 和 ${} 区别

#{}${} 的区别就是预编译SQL和即时SQL的区别.

简单回顾:

当客⼾发送⼀条SQL语句给服务器后, ⼤致流程如下:

  1. 解析语法和语义, 校验SQL语句是否正确
  2. 优化SQL语句, 制定执⾏计划
  3. 执⾏并返回结果

⼀条 SQL如果⾛上述流程处理, 我们称之为 Immediate Statements(即时 SQL)

  1. 性能更⾼

绝⼤多数情况下, 某⼀条 SQL 语句可能会被反复调⽤执⾏, 或者每次执⾏的时候只有个别的值不同(⽐如 select 的 where ⼦句值不同, update 的 set ⼦句值不同, insert 的 values 值不同). 如果每次都需要经过上⾯的语法解析, SQL优化、SQL编译等,则效率就明显不⾏了.

预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译(只是输⼊的参数不同), 省去了解析优化等过程, 以此来提⾼效率

  1. 更安全(防⽌SQL注⼊)

SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的⽅法。

由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。

sql注入代码: ' or 1='1

select * from userinfo where username=' ' or1=1

那么在SQL语句中where 相当于判断语句,并且是由 or 连接的,所以 username=' ' 和 1=1 中有一个为真就为真。1=1一定为真,所以语句又等价于:select * from userinfo

先来看看SQL注⼊的例⼦

java 复制代码
@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username='${username}'")
List<UserInfo> selectByName(String username);

测试代码:

正常访问情况:

java 复制代码
@Test
void selectByName() {
	List<UserInfo> userInfos = userInfoMapper.selectByName("admin");
	System.out.println(userInfos);
}

结果运⾏正常

SQL注⼊场景:

java 复制代码
@Test
void selectByName() {
    log.info(userInfoMapper.selectByName("'or 1='1").toString());
}

结果依然被正确查询出来了, 其中参数 or被当做了SQL语句的⼀部分

可以看出来, 查询的数据并不是⾃⼰想要的数据. 所以⽤于查询的字段,尽量使⽤ #{} 预查询的⽅式

java 复制代码
@ResultMap(value = "BaseMap")
@Select("select * from userinfo where username=#{username}")
List<UserInfo> selectByName(String username);
java 复制代码
@Test
void selectByName() {
    log.info(userInfoMapper.selectByName("'or 1='1").toString());
}

SQL注⼊是⼀种⾮常常⻅的数据库攻击⼿段, SQL注⼊漏洞也是⽹络世界中最普遍的漏洞之⼀.

如果发⽣在⽤⼾登录的场景中, 密码输⼊为 ' or 1='1 , 就可能完成登录(不是⼀定会发⽣的场景,需要看登录代码如何写)

控制层: UserController

java 复制代码

业务层: UserService

java 复制代码

数据层: UserInfoMapper

java 复制代码

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

程序正常运⾏

接下来访问SQL注⼊的代码:

password 设置为 ' or 1='1

http://127.0.0.1:8080/login?name=admin\&password=' or 1='1

排序功能

从上⾯的例⼦中, 可以得出结论: ${} 会有SQL注⼊的⻛险, 所以我们尽量使⽤#{}完成查询

既然如此, 是不是 ${} 就没有存在的必要性了呢?

当然不是.

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

Mapper实现

java 复制代码
@Select("select * from userinfo order by id ${sort}")
List<UserInfo> selectUserBySort(String sort);

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

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

我们把 ${} 改成 #{}

java 复制代码
@Select("select * from userinfo order by id #{sort}")
List<UserInfo> selectUserBySort(String sort);

运⾏结果:

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

select * from userinfo order by id asc这是正确的SQL语句,用了#{sort}变成了select * from userinfo order by id 'asc'了,多了两个引号

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

如果参数类型为String,就会加上 引号.

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

like 查询

模糊查询

like 使⽤ #{} 报错

java 复制代码
/**
     * 模糊查询
     * @param username
     * @return
     */
    @Select("select * from userinfo where username like'%#{username}%'")
    List<UserInfo> selectUserByLike(String username);

#{} 改成 ${} 可以正确查出来, 但是${}存在SQL注⼊的问题, 所以不能直接使⽤ ${}.

解决办法: 使⽤ mysql 的内置函数 concat() 来处理,实现代码如下:

java 复制代码
/**
 * 模糊查询
 * @param username
 * @return
 */
@Select("select * from userinfo where username like concat('%',#{username},'%')")
List<UserInfo> selectUserByLike(String username);
java 复制代码
@Test
void selectUserByLike() {
    log.info(userInfoMapper.selectUserByLike("liu").toString());
}
数据库连接池

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

下⾯我们来了解下数据库连接池

介绍

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

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

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

优点:

  1. 减少了⽹络开销
  2. 资源重⽤
  3. 提升了系统的性能
使⽤

常⻅的数据库连接池:

  • C3P0
  • DBCP
  • Druid
  • Hikari

⽬前⽐较流⾏的是 Hikari, Druid

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

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

  1. Druid

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

xml 复制代码
<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

⼆者对⽐,参考: Hikaricp和Druid对⽐_数据库_晚⻛暖-华为云开发者联盟

总结

MySQL 开发企业规范
  1. 表名, 字段名使⽤⼩写字⺟或数字, 单词之间以下划线分割. 尽量避免出现数字开头或者两个下划线中间只出现数字. 数据库字段名的修改代价很⼤, 所以字段名称需要慎重考虑。

MySQL 在 Windows 下不区分⼤⼩写, 但在 Linux 下默认是区分⼤⼩写. 因此, 数据库名, 表名, 字段名都不允许出现任何⼤写字⺟, 避免节外⽣枝

正例: aliyun_admin, rdc_config, level3_name

反例: AliyunAdmin, rdcConfig, level_3_name

  1. 表必备三字段: id, create_time, update_time

id 必为主键, 类型为 bigint unsigned, 单表时⾃增, 步⻓为 1

create_time, update_time 的类型均为 datetime 类型, create_time表⽰创建时间,update_time表⽰更新时间

有同等含义的字段即可, 字段名不做强制要求

  1. 在表查询中, 避免使⽤ * 作为查询的字段列表, 标明需要哪些字段(课堂上给⼤家演⽰除外).
  1. 增加查询分析器解析成本
  2. 增减字段容易与 resultMap 配置不⼀致
  3. ⽆⽤字段增加⽹络消耗, 尤其是 text 类型的字段
#{} 和${} 区别
  1. #{}:预编译处理, ${}:字符直接替换
  2. #{} 可以防⽌SQL注⼊, ${}存在SQL注⼊的⻛险, 查询语句中, 可以使⽤ #{} ,推荐使⽤ #{}
  3. 但是⼀些场景, #{} 不能完成, ⽐如 排序功能, 表名, 字段名作为参数时, 这些情况需要使⽤${}
  4. 模糊查询虽然${}可以完成, 但因为存在SQL注⼊的问题,所以通常使⽤mysql内置函数concat搭配#{}来完成
相关推荐
MeAT ITEM2 分钟前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
dovens6 分钟前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
IOT.FIVE.NO.16 分钟前
claude code desktop cowork报错解决和记录Workspace..The isolated Linux environment ...
linux·服务器·数据库
Rick199315 分钟前
mysql 慢查询怎么快速定位
android·数据库·mysql
952367 小时前
MyBatis
后端·spring·mybatis
科技小花7 小时前
全球化深水区,数据治理成为企业出海 “核心竞争力”
大数据·数据库·人工智能·数据治理·数据中台·全球化
X56618 小时前
如何在 Laravel 中正确保存嵌套动态表单数据(主服务与子服务)
jvm·数据库·python
虹科网络安全10 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库
2301_7717172110 小时前
解决mysql报错:1406, Data too long for column
android·数据库·mysql
小江的记录本10 小时前
【Kafka核心】架构模型:Producer、Broker、Consumer、Consumer Group、Topic、Partition、Replica
java·数据库·分布式·后端·搜索引擎·架构·kafka