# 数据库

DB:DataBase,数据库,存储和管理数据的仓库
DBS:DataBase Management System,数据库管理系统,操作和管理数据库的软件
SQL:Structured Query Language,操作关系型数据库的编程语言,定义操作所有关系型数据库的统一标准
关系型数据库:建立在关系模型基础上,由多张相互连接的二维表组成的数据库

# MySQL

mysql download
SQL 语句:

  • DDL:Data Definition Language,数据定义语言,用来定义数据库对象(数据库,表,字段)
  • DML:Data Manipulation Language,数据操作语言,用来对数据库表中的数据进行增删改
  • DQL:Data Query Language,数据查询语言,用来查询数据库中表的记录
  • DCL:Data Control Language,数据控制语言,用来创建数据库用户、控制数据库的访问权限

# DDL

database 可替换为 schema ;MySQL8 默认字符集为 utf8mb4

l
-- 数据库
-- 查询所有数据库
show databases;
-- 查询当前数据库
select database();
-- 使用 / 切换数据库
use <数据库名>;
-- 创建数据库
create database [if not exists] <数据库名> [default charset utf8mb4];
-- 删除数据库
drop database [if exists] <数据库名>

数据类型

  • 数值类型:tinyint、int、bigint、float、double、decimal
  • 字符串类型:char(定长)、varchar(变长)
  • 日期类型:date、datetime

约束:作用于表中字段上的规则,用于限制存储在表中的数据

约束描述关键字
非空约束限制该字段值不能为 nullnot null
唯一约束保证字段的所有数据都是唯一、不重复的unique
主键约束主键是一行数据的唯一标识,要求非空且唯一primary key
默认约束保存数据时,如果未指定该字段值,则采用默认值default
外键约束让两张表的数据建立连接,保证数据的一致性和完整性foreign key

基础字段:id 主键、create_time 创建时间、update_time 修改时间

l
-- 表结构
create table <表名>(
    <字段1> <字段类型> [约束] [comment 字段1注释],
    ……
    <字段n> <字段类型> [约束] [comment 字段n注释]
)[comment 表注释];
create table user(
    id int primary key auto_increment comment 'ID,唯一标识',  -- 主键,自增 id
    username varchar(50) not null unique comment '用户名',  -- 非空,唯一
    name varchar(10) not null comment '姓名',  -- 非空
    age int comment '年龄',
    gender char(1) default '男' comment '性别',  -- 默认为男
    create_time  datetime  comment '创建时间',
    update_time  datetime  comment '修改时间'
)comment '用户信息表';
-- 查询当前数据库的所有表
show tables;
-- 查询表结构
desc <表名>;
-- 查询建表语句
show create table <表名>;
-- 修改表结构
alter table <表名> add <字段名类型>;  -- 添加字段
alter table <表名> modify <字段名> <新数据类型>;  -- 修改字段类型
alter table <表名> change <旧字段名> <新字段名> <新类型类型>;  -- 修改字段名与字段类型
alter table <表名> drop <字段名>;  -- 删除字段
alter table <表名> rename to <新表名>;  -- 修改表名
drop table [if exists] <表名> ;  -- 删除表

# DML

l
-- 添加数据
insert into <表名> (字段1,字段2) values (1,2) [,(1,2)……];
insert into <表名> values (1,2……) [,(1,2……) ……];
insert into user (name, create_time)
values ('mof',now()), ('fom', now());
-- 修改数据
update <表名> set 字段1 =1, 字段2 =2, …… [where 条件];
update user set create_time = NOW() where id = 1;
-- 删除数据
delete from <表名> [where 条件];
delete from user where id = 2;

# DQL

select 字段列表 from 表名列表 where 条件列表 group by 分组字段列表 having 分组后条件列表 order by 排序字段列表 limit 分页参数
like:模糊匹配, _ 单个字符, % 任意个字符
group by 后只能查询分组字段和聚合函数
聚合函数:将一列数据作为整体,纵向计算,不包括 null

  • count:count(*)、count(1)、count(id)
  • max、min、avg、sum

order by:默认升序 asc;降序 desc
limit 起始索引,查询记录数

l
-- distinct 不重复;as 取别名
select distinct name as '姓名' from user;
select gender, count(*) from user group by gender;
select * from user order by entry_time, update_time desc;
select * from user limit 10;

# 多表查询

多表关系

  • 一对多:多的表增加额外字段
  • 一对一:查询频率少的表增加额外字段
  • 多对多:中间表连接

外键

  • 物理外键:影响性能,只适用于单节点数据库,容易死锁
    alter table student add constraint fk_student_clazz_id foreign key (clazz_id) references clazz(id);
  • 逻辑外键:业务逻辑中实现

内连接:舍弃不满足连接条件的(去除 null 值)

l
-- 隐式连接,常用
select 字段 from1 [as 别名1],2 [as 别名2] where 连接条件;
-- 显式连接
select 字段 from1 [inner] join2 on 连接条件;

外连接:完全包含左表或右表(包括 null 值)

l
-- 左外连接,常用
select 字段 from1 left [outer] join2 on 连接条件;
-- 右外连接
select 字段 from1 right [outer] join2 on 连接条件;

子查询
查询每个部门最高薪资的员工信息

l
-- 多个字段用括号去比较
select * from emp where (emp.salary, emp.dept_id) in (select MAX(salary), dept_id from emp group by dept_id);
-- 子查询作临时表
select * from emp, (select dept_id, MAX(salary) maxsalary from emp group by dept_id) e where emp.dept_id = e.dept_id and emp.salary = e.maxsalary;

# 事务管理

事务是一组操作的集合,事务会把所有操作作为一个整体一起向系统提交或撤销操作请求,这些操作要么同时成功,要么同时失败。
四大特性(ACID):原子性、一致性、隔离性、持久性

  • 开启事务: start transactionbegin
  • 提交事务: commit ,全部操作成功
  • 回滚事务: rollback ,存在操作失败

Spring 事务管理注解: @Transactional(rollbackFor = {Exception.class}, propagation = Propagation.REQUIRED)

  • rollbackFor:用于控制出现何种异常类型,回滚事务,默认为 RuntimeException
  • propagation:事务传播行为,当一个事务方法被另一个事务方法调用时,这个事务方法如何进行事务控制,默认为 REQUIRED
属性值含义
REQUIRED需要事务,有则加入,无则创建新事务。
REQUIRES_NEW需要新事务,无论有无,总是创建新事务。
SUPPORTS支持事务,有则加入,无则在无事务状态中运行。
NOT_SUPPORTED不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务。
MANDATORY必须有事务,否则抛异常。
NEVER必须没事务,否则抛异常。

# JDBC

JDBC:Java DataBase Connectivity,使用 Java 操作关系型数据库的一套 API,由官方定义规范(接口),由数据库厂商实现接口(驱动)
常见框架:MyBatis、MyBatis Plus
url:JDBC: 数据库类型://ip:port / 数据库名
预编译 SQL:更安全(SQL 注入)、性能更高(缓存)
驱动依赖

l
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

使用

a
public void testJDBC() throws ClassNotFoundException, SQLException {
    // 注册驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    // 获取数据库连接
    String url = "jdbc:mysql://ip:port/database";
    String username = "";
    String password = "";
    Connection connection = DriverManager.getConnection(url, username, password);
    // 获取 SQL 语句执行对象
    Statement statement = connection.createStatement();
    // 执行 SQL 语句
    ResultSet resultSet = statement.executeQuery("select * from user");
    while (resultSet.next()){
        System.out.println(resultSet.getString("name"));
    }
    // 释放资源
    statement.close();
    connection.close();
}

# MyBatis

MyBatis:持久层框架,简化 JDBC 开发

# 配置

依赖

l
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

数据库配置

l
# 数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://ip:port/demo
    username: 
    password: 
#    type: com.alibaba.druid.pool.DruidDataSource  # Druid 连接池配置
mybatis:
  configuration:
    map-underscore-to-camel-case: true  # 下划线、驼峰命名转换
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 日志输出
  mapper-locations: classpath:mapper/*.xml  # xml 映射文件位置

# 数据库连接池

容器,负责分配、管理数据库连接,允许重复使用,空闲自动释放

  • 标准接口:DataSource,getConnection 获取连接
  • 常见产品:Druid、Hikari(SpringBoot 默认)
l
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
</dependency>

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 使用

实体类 pojo.User

a
@Data
public class User implements Serializable {
    private static final long serialVersionUID = -1105300093577690395L;
    private int id;
    private String name;
    private Date createTime;
}

持久层 Mapper.UserMapper

符号说明场景优缺点
#{}占位符。执行时,会将替换为,生成预编译 SQL参数值传递安全、性能高(推荐)
${}拼接符。直接将参数拼接在 SQL 语句中,存在 SQL 注入问题表名、字段名动态设置时使用不安全、性能低

springboot 官方骨架中使用 Maven 插件,保留形参名称,所以可以去除 Param 注解,aliyun 骨架的不行
@Options(useGeneratedKeys = true, keyProperty = "id") 插入成功后返回主键到 id

a
@Mapper
public interface UserMapper {
    @Select("select id, name, create_time from user")
    List<User> list();
    @Delete("delete from user where id = #{id}")
    int delete(int id);
    @Options(useGeneratedKeys = true, keyProperty = "id")
    @Insert("insert into user (name, create_time) values (#{name}, now())")
    int add(User user);
    @Update("update user set name = #{name} where id = ${id};")
    int update(User user);
    @Select("select * from user where id = #{id} and name = #{name}")
    User findByIdAndName(int id, String name);
    // User findByIdAndName(@Param("id") int id, @Param("name") String name);
    //springboot 官方骨架中使用 Maven 插件,保留形参名称,所以可以去除 Param 注解,aliyun 骨架的不行
    List<User> findByName(String name);
}

# XML 映射配置

插件:MyBatisX

  • mybatis.mapper-locations=classpath:mapper/*.xml 文件位置配置为 resource/mapper/
  • 文件名称与 Mapper 接口一致
  • namespace 设置为接口全限定名
  • id 与方法名一致

动态 SQL

l
<?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.mof.mapper.EmpMapper">
    <!-- where 会自动去除条件前面多余的 and 或 or 关键字 -->
    <select id="find" resultType="com.mof.pojo.Emp">
        select e.*, d.name deptName
        from emp e left join dept d on e.dept_id = d.id
        <where>
            <if test="name != null and name != ''">
                e.name like concat('%', #{name}, '%')
            </if>
            <if test="gender == 1 or gender == 2">
                and e.gender = #{gender}
            </if>
            <if test="begin != null">
                and e.entry_date > #{begin}
            </if>
            <if test="end != null">
                and #{end} > e.entry_date
            </if>
        </where>
        order by e.update_time desc
    </select>
    <!-- 遍历插入,collection 为集合,item 为集合内元素,separator 为遍历元素的分割符 -->
    <!-- open 为遍历开始前拼接的内容,close 为遍历结束后拼接的内容 -->
    <insert id="insertBatch">
        insert into emp_expr (emp_id, begin, end, company, job) values
        <foreach collection="exprList" item="expr" separator=",">
            (#{expr.empId}, #{expr.begin}, #{expr.end}, #{expr.company}, #{expr.job})
        </foreach>
    </insert>
</mapper>

# 分页查询

PageHelper:第三方分页插件,简化分页操作
依赖

l
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>

使用

a
PageHelper.startPage(page, pageSize);
List<Emp> emps = empMapper.findAll();  // 正常 sql 语句
Page<Emp> p = (Page<Emp>) emps;
p.getTotal();  // 获取总记录数
p.getResult();  // 获取数据

实现机制:
select count(0) ,计算总记录数
再加上 limit ?, ? ,分页查询
因此,自定义的 select 语句结尾不能加分号,不然会中断,报错

select e.*, d.name deptName from emp e left join dept d on e.dept_id = d.id order by e.update_time desc
Preparing: SELECT count(0) FROM emp e LEFT JOIN dept d ON e.dept_id = d.id
Preparing: select e.*, d.name deptName from emp e left join dept d on e.dept_id = d.id order by e.update_time desc LIMIT ?, ?

# MyBatis-plus

l
<!--mysql 驱动依赖 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
<!--mybatis-plus 起步依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
</dependency>
<!--lombok 依赖,添加 getter、setter、toString-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

# 使用

pojo.User
用户实体类,与数据表对应

a
@Data  // 添加 get 和 set 方法
@TableName("user")  // 表名
public class User{
    // 驼峰命名与数据库的下划线命名自动转换,不用在配置文件配置
    @TableId(type = IdType.AUTO)  // 自增键值
    private Integer userId;
    private String userName;
    private String password;
    private String userPhonenumber;
}

mapper.UserMapper
数据映射层,数据操作,这里继承 BaseMapper,封装了大部分数据库操作
启动类需要添加注解 @MapperScan(basePackages = "com.mof.demo.mapper")

a
public interface UserMapper extends BaseMapper<User> {
}

service.UserService
继承 IService,可以调用已实现的方法

a
public interface UserService extends IService<User> {
    String byId(Integer id);
}

方法一,直接调用封装的函数

a
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Override
    public String byId(Integer id) {
        return this.getById(id).toString();
    }
}

方法二,QueryWrapper 查询

a
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Override
    public String byId(Integer id) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", id);
        return userMapper.selectOne(queryWrapper);
    }
}

# 分页查询

配置类设置分页插件

a
@Configuration
public class UserConfiguration {
    // Mybatis 分页插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

具体操作

a
// 按类别 id 查询,按销量倒序排序
QueryWrapper<Product> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("category_id", categoryId).orderByDesc("product_sales");
// 按页查询
IPage<Product> ipage = new Page<>(1, 10);
IPage<Product> page = productMapper.selectPage(ipage, queryWrapper);
// 获取总记录数和数据
long total = page.getTotal();
List<Product> productList = page.getRecords();
更新于