Sql语言分类:
- DML 增 删 改 查
- DCL 用户 权限 事务
- DDl 逻辑库 数据表 视图 索引
规范
1.Sql语句不区分大小写, 但字符串区分;
2.且必须加上结束语句 "分号"
SELECT "HelloWorld";
CREATE DATABASE 逻辑库名称; # 创建逻辑库
SHOW DATABASES; # 查看有多少逻辑空间
DROP DATABASES 逻辑库名称; # 物理删除逻辑库
创建数据表
USE test; # 操作test表
CREATE TABLE student(
id INT UNSIGNED PRIMARY KEY, # 定义 id 为主键 primaryKey, 且为无符号的整形
name VARCHAR(20) NOT NULL ,# 定义 name,长度不固定,但是不得超过20 VARCHAR(20) ,必填字段
sex CHAR(1) NOT NULL, # 定义性别 ,长度固定为1 , 非空必填
birthday DATE NOT NULL, # 生日非空必填, 类型为日期 必填
tel CHAR(11) NOT NULL, # 手机号码固定长度 11 必填
remark VARCHAR(200) #备注 长度小于200 , 非必填
);
#添加数据
INSERT INTO student VALUES(1,"金城武","男","1977-4-6","13988733645",NULL);
SHOW tables; # 查看有哪些数据表
DESC student; # 查看student 表结构
SHOW CREATE TABLE student; # 查看创建学生表的指令
-- DROP TABLE student; # 彻底删除表
-- 使用 ALTER TABLE 表名
-- ADD 字段名 约束 来为表添加一些字段
-- CHNAGE oldName newName 来修改字段名称
-- MODIFY 字段名 修改字段属性(长度类型等)
-- DROP 字段名 直接删除字段(删除列)
-- ALTER TABLE student
-- ADD home_address VARCHAR(200) NOT NULL,
-- ADD home_tel VARCHAR(13) NOT NULL
--
-- DESC student;
DESC student; # 查看student 表结构
-- 使用 MODIFY 来修改字段 ,其余与ADD 一致
ALTER TABLE student
MODIFY home_address VARCHAR(100); # 此次home_address 被设置为非必填,
ALTER TABLE student
CHANGE home_address new_home_address VARCHAR(150) NOT NULL;
-- ALTER TABLE student # 删除列名(字段)
-- DROP new_home_address, # 使用上出多个时,使用逗号分隔
-- DROP name; # 使用分号结尾
DESC student; # 查看student 表结构(看看有没有成功)
-- 使用 CHANGE 来为 字段更换名字
-- CHANGE oldName newName 数据类型 [约束] [COMMENT 注释],
CREATE TABLE t_message(
id INT PRIMARY KEY AUTO_INCREMENT, # 定义为主键并自增
name VARCHAR(20) NOT NULL UNIQUE, # 名字定义为唯一值(不可重复)
type ENUM("公告","通报","个人信息") NOT NULL,
married BOOLEAN NOT NULL DEFAULT FALSE, # 是否结婚, 不可为空, 若不填则默认为false
INDEX t_type (type)
);
INSERT INTO t_message VALUES(0,"张三","公告");
INSERT INTO t_message VALUES(1,"李四","个人信息");
INSERT INTO t_message VALUES(2,"王五","公告");
INSERT INTO t_message VALUES(3,"赵六","个人信息");
DROP INDEX t_type on t_message; # 删除索引 - t_type 在 t_message上的
CREATE INDEX index_name on t_message(name); # 创建索引 "index_name" 在表 t_message 的 name 字段上
ALTER TABLE t_message ADD INDEX t_type(type); # 操作数据表 t_message 添加索引 t_type(type 字段)
SHOW INDEX FROM t_message; # 查询t_message的所有数据表
简单的数据查询
SELECT * FROM t_emp;
SELECT empno,sal *12 AS "INCOME" FROM t_emp LIMIT 3,5 ; # 从索引3(第四条)开始查询 紧随的5条数据;
# ORDER BY 字段名 (DESC); 查询出来的结果使用 某字段进行排序, 若添加上DESC 则 倒序.
# 我们使用 ORDER BY 规定首要的排序条件和次要的排序条件. 数据库会在首要条件相同的情况下,依次比较后续的条件.
-- SELECT empno, sal FROM t_emp LIMIT 10 ORDER BY sal DESC, empnoDESC;
-- 去除查询出来的重复记录
SELECT DISTINCT job from t_emp;
-- 注意 : (1) DISTINCT 关键字在查询多列数据的时候 会失效(除非出来的每一条数据都相同)
-- (2) DISTINCT 只能在SELECT字句中使用一次, 且必须紧跟在 SELECT 后面
-- 错误示例 SELECT ename ,DISTINCT job
-- WHERE 执行 条件选择 , 可使用 AND 或者 OR 来进行追加筛选
-- 为了提升性能, 应当将筛选掉记录最多的条件放在最左侧, 依次往右 以减少压力 (重点)
SELECT empno, ename ,sal
FROM t_emp
WHERE (deptno = 10 OR deptno = 20) AND sal >= 2000;
-- IFNULL( A, B ) 使用Sql 自带的 IFNULL 函数 ,如果A为null, 那么就将 B作为返回数据
-- DATEDIFF(expr1, expr2) 使用Sql 自带函数 去比较 expr1 和 expr2 的天数差
-- IN(A,B,c) 包含
-- IS NULL 为空; comm IS NULL
-- IS NOT NULL 不为; comm IS NOT NULL
-- BETWEEN AND 范围; sal BETWEEN 2000 AND 4000
-- LIKE 模糊查询; ename LIKE "A%"
-- REGEXP 正则表达式; ename REGEXP "[a-zA-Z]{4}"
-- AND 与关系 age > 18 AND sex = "男"
-- OR 或关系 empno = 8000 OR depino = 20
-- NOT 非关系 NOT deptno = 20
-- XOR 异或关系 age > 18 XOR sex = "男" (一个成立 一个不成立 就为true ; 否则 为false)
复杂数据查询
-- 聚合函数
-- 在数据的查询分析中, 应用时分管饭. 距和函数可以对数据求和, 求极值, 平均值等
# AVG([DISTINCT] expr) 求平均值
SELECT AVG(sal + IFNULL(comm,0)) AS "AVERAGE" FROM t_emp;
# SUM(expr) 求和
SELECT SUM(sal) FROM t_emp WHERE deptno IN(10, 20); # 求部门编号为10 和 20的部门 的 薪资总和
# MAX([DISTINCT] expr) 求最大值
# MIN([DISTINCT] expr) 最小值
SELECT MAX(sal + IFNULL(comm,0)) FROM t_emp WHERE deptno IN(10,20); # 求部门编号为10,20的部门所有员工薪资最高的员工
-- 上述两个sql 可以合二为一:
SELECT SUM(sal) AS "SUM",MAX(sal + IFNULL(comm,0)) AS "MAX" FROM t_emp WHERE deptno IN(10, 20);
# LENGTH(str) 长度
SELECT MAX(LENGTH(ename)) from t_emp;
# ROUND(expr) 四舍五入
# COUNT(DISTINCT expr,[expr...]) 获得包含空值的记录数
SELECT COUNT(*) FROM t_emp; # 返回所有数据的条数 无论是否有效
SELECT COUNT(comm) FROM t_emp; # 返回该列为 null 的数据条数
# 查询10和20部门中, 底薪超过20且工龄超过15年的人数
SELECT COUNT(*) FROM t_emp WHERE deptno IN (10,20) AND sal>2000 AND DATEDIFF(NOW(),hiredate)*365 >15;
-- 数据分组
#查询1985年以后入职的员工, 底薪超过公司平均底薪的员工数量
SELECT COUNT(*) FROM t_emp where sal > AVG()
查询语句中 ,各种语句的执行顺序
FROM -> WHERE -> GROUP BY -> SELECT -> ORDER By-> LIMIT
from 选择某张表, where 筛选数据 , 被选中的数据交由groupby 进行分组 ,再使用select 的 聚合函数来做汇总计算, 接着使用ORDER By 进行排序, 最终使用LIMIT切割
查询部分平均底薪超过2000的部门编号
-- 传统写法 SELECT deptno FROM t_emp where AVG(sal)>=2000 GROUP BY deptno; -- 上述写法会报错, 因为 where的 优先级会高于 GROUP BY ,这就导致还没有进行分组 就开始筛选. 因此报错 SELECT deptno FROM t_emp GROUP BY deptno HAVING AVG(sal)>=2000;
-- 传统写法 SELECT deptno FROM t_emp where AVG(sal)>=2000 GROUP BY deptno;
-- 上述写法会报错, 因为 where的 优先级会高于 GROUP BY ,这就导致还没有进行分组 就开始筛选. 因此报错
解决方案: 使用HAVING 语句替代, having语句的执行顺序在group By 之后,这样一来数据肯定已经分组完毕了
SELECT deptno FROM t_emp GROUP BY deptno HAVING AVG(sal)>=2000;
高级查询
-- 聚合函数
-- 在数据的查询分析中, 应用时分管饭. 距和函数可以对数据求和, 求极值, 平均值等
# AVG([DISTINCT] expr) 求平均值
SELECT AVG(sal + IFNULL(comm,0)) AS "AVERAGE" FROM t_emp;
# SUM(expr) 求和
SELECT SUM(sal) FROM t_emp WHERE deptno IN(10, 20); # 求部门编号为10 和 20的部门 的 薪资总和
# MAX([DISTINCT] expr) 求最大值
# MIN([DISTINCT] expr) 最小值
SELECT MAX(sal + IFNULL(comm,0)) FROM t_emp WHERE deptno IN(10,20); # 求部门编号为10,20的部门所有员工薪资最高的员工
-- 上述两个sql 可以合二为一:
SELECT SUM(sal) AS "SUM",MAX(sal + IFNULL(comm,0)) AS "MAX" FROM t_emp WHERE deptno IN(10, 20);
# LENGTH(str) 长度
SELECT MAX(LENGTH(ename)) from t_emp;
# ROUND(expr) 四舍五入
# COUNT(DISTINCT expr,[expr...]) 获得包含空值的记录数
SELECT COUNT(*) FROM t_emp; # 返回所有数据的条数 无论是否有效
SELECT COUNT(comm) FROM t_emp; # 返回该列为 null 的数据条数
# 查询10和20部门中, 底薪超过20且工龄超过15年的人数
-- SELECT COUNT(*) FROM t_emp WHERE deptno IN (10,20) AND sal>2000 AND DATEDIFF(NOW(),hiredate)*365 >15;
-- 使用聚合函数的时候,
SELECT deptno,ROUND(AVG(sal))
FROM t_emp GROUP BY deptno;
-- 注意:
-- 查询语句中如果含有 GROUP BY子句 ,那么SELECT 子句中的内容就必须遵守规定:
-- SELECT 子句仲可以包含聚合函数,或者GROUP BY 子句的分组列 , 其余内容均不可以出现在SELECT 子句中
-- 错误示范 错误示范 错误示范 错误示范
SELECT deptno ,AVG(sal),COUNT(*) ,sal FROM t_emp GROUP BY deptno; # 错误示范
-- 错误示范 错误示范 错误示范 错误示范
-- 因为 聚合函数返回的数据一定是一条数据 ,而类似sal 这种的返回的则是一系列的多条数据, 那么就无法在一条记录里显示, 因此报错了.
# 查询部分平均底薪超过2000的部门编号
-- 传统写法 SELECT deptno FROM t_emp where AVG(sal)>=2000 GROUP BY deptno;
-- 上述写法会报错, 因为 where的 优先级会高于 GROUP BY ,这就导致还没有进行分组 就开始筛选. 因此报错
SELECT deptno FROM t_emp GROUP BY deptno HAVING AVG(sal)>=2000;
-- 查询每个部门中, 1981年之后入职的且部门人数大于2 的部门编号,以及名称与薪水
SELECT deptno,GROUP_CONCAT(ename,":",sal) from t_emp WHERE hiredate >'1981-1-1' GROUP BY deptno HAVING COUNT(*)>2 ORDER BY deptno DESC;
-- 数据分组
#查询1985年以后入职的员工, 底薪超过公司平均底薪的员工数量
-- SELECT COUNT(*) FROM t_emp where sal > AVG()
-- with ROLLUP 在查询出来的数据末尾处,再添加一条数据, 数据的每一项是当前的 筛选条件
-- select job from t_emp GROUP BY job;
select deptno,GROUP_CONCAT( ename),GROUP_CONCAT( mgr),COUNT(*) from t_emp WHERE sal>2000 GROUP BY deptno;
JAVA
拆箱 装箱
int num = Integer.parseInt("2"); // 转换成 int 类型
String str = Integer.toString();
当我们使用Integer four = Integer.valueOf(num) 的时候, num刚好处于-128~127 之间的区间, 此时变量会被放置在缓存区中, 因此 :
Integer h1 = Integer.valueOf(100);
Integer h2 = Integer.valueOf(100); // h1 == h2
Integer t1 = Integer.valueOf(200);
Integer t2 = Integer.valueOf(200); // h1 != h2
JDBC
String dbDriver = "com.mysql.cj.jdbc.Driver"; // JDBC驱动类 (不同版本JDBC驱动类不同)
String dbUrl = "jdbc:mysql://localhost:3306/imooc"; // 链接字符串
String dbUsername = "root"; // 数据库用户名
String dbPassword = "123456"; // 数据库密码
1. 加载并初始化JDBC驱动
Class.forName(dbDriver);
2. 创建数据库连接
Connection connection = DriverManager.getConnection(dbURL, dbUsername, dbPassword);
3. 使用conn 创建 statement 对象 来完成 sql 语句
stmt = conn.createStatement();
4. ResultSet 结果集收集结果
res = stmt.executeQuery("select * from employee where dname = '" + depName + "'");
5. 关闭各种连接
if (conn != null && !conn.isClosed()) {
conn.close();
}
if (stmt != null) {
stmt.close();
}
if (res != null) {
res.close();
}
DriverManager
- 是在jdbc中提供的最重要的一个类, 用于对jdbc的驱动进行注册和管理.
- DriverManager.getConnection(链接字符串, 用户名, 密码)
- 返回值为Connection 对象, 对应着数据库的物理网络连接(也对应着java与数据库之间的网络桥梁)
Connection
- Conection 对象用于JDBC与数据库的网络通信对象
- java.sql.Connection是一个接口, 具体由驱动厂商实现「一个是接口, 一个是实现」 (存放在java.sql包中,而 java.sql包是jdbc的核心包, 它存放着大量的接口)
- 所有数据库的操作都建立在Connection基础之上(无论是发送Sql语句,还是接受查询结果,都要建立在此物理接口之上)
MySql连接字符串 「具体格式由厂商自行定义」
- 格式: jdbc:mysql://「主机ip或域名」「:端口」/数据库名?参数列表
- 主机ip与端口: 主机ip与端口是可选设置, 默认为:127.0.0.1 与 3306 (不过默认场景并不多见,因为安全考虑我们不会使用默认端口 并且 实际项目中往往数据库独立部署,并不会访问本机)
| 参数名 | 建议参数值 | 说明 |
|---|---|---|
| useSSl | Boolean (生产用true,开发用false) | 表示是否禁用ssl. ssl是Secure Sockets Layer 安全套接字协议,配置在服务器上,在网络传输过程中使用非对称加密进行传输 |
| useUnicode | true | 启动unicode编码传输数据, 用以规避中文带来的干扰 |
| characterEncoding | UTF-8 | 使用utf-8编码传输中文编码 |
| serverTimezone | Asia/Shanghai | 使用东8时区时间, UTC+8 |
| allowPublicKeyRetrieval | true | 允许从客户端获取公钥加密传输 |
参数列表: 采用url编码
Sql注入
在入参中混入sql语句, 蒙蔽数据库, 使其 返回入侵者希望看到的数据 或 执行入侵者的指令
PreparedStatement
为了阻隔Sql注入而出现
- 预编译Statement, 是Statement的子接口
// 进行连接
Connection conn = DriverManager.getCOnnection("jdbc:mysql://localhost:3306/...","root","root");
// 连接数据库后, 准备执行 sql 语句
String sql = "select * from employee where dname =?";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 填充sql语句中的变量
pstmt.setString(1, dname); // 1 表示为第一个参数进行赋值 根据传入数据的类型, 来选择使用setString 还是 setDouble 等
// 执行sql 并返回结果给ResultSet 对象
ResultSet res = pstmt.excuteQuery();
while(res.next()){// do something};
// 如果是更新操作, 那么 使用executeUpdate , 来查看更新的条数
Integer cnt = pstmt.executeUpdate();
if(cnt<1){sout("信息更新失败!")}
反射 reflect
- 反射是在
运行时动态访问类与对象的技术 - 反射是
JDK1.2后提供的高级特性,隶属于java.lang.reflect
Class类
- Class类是jvm中
特定为 "类和接口" 的 类(在此的释义) - Class对象包含了
某个类的具体信息 - 通过Class对象可以
获取该类 具体 的构造方法 成员方法 与 成员参数
Constructor类
- 用于对java类中构造方法的抽象
- 能得到构造方法的种类,每种构造方法
注意
- 前者class 也有一个newInstance() 方法, 但class 的 newInstance() 指代的是默认构造函数
- 此处的newInstacne() 指定特定的构造函数, 这代表着我们要传入特定的参数才可以
但是需要先使用 classObj.getConstructor() 得到对应的构造方法对象,接下来再使用newInstance()来调用特定的构造方法 才可以!
package com.imooc.reflect;
import com.imooc.reflect.entity.Employee;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ConstructSample {
public static void main(String[] args) {
try {
Class employeeClass = Class.forName("com.imooc.reflect.entity.Employee");
// 定义抽象类,并申明参数类型以及参数个数
Constructor constructor = employeeClass.getConstructor(new Class[]{
Integer.class, String.class, Float.class, String.class
});
Employee employee = (Employee) constructor.newInstance(new Object[]{10004, "张大炮", 0.5f, "狂魔部"});
System.out.println(employee.getEname());
} catch (ClassNotFoundException e) {
// 未找到类, Class.forName("....") 的参数出现问题
e.printStackTrace();
} catch (NoSuchMethodException e) {
// 未找到与之对应的格式的方法
e.printStackTrace();
} catch (InvocationTargetException e) {
// 当被调用的方法内部抛出了异常而没有被捕获到时 ,就使用 InstantiationException 进行捕获
e.printStackTrace();
} catch (InstantiationException e) {
// 实例化异常
e.printStackTrace();
} catch (IllegalAccessException e) {
// 权限拒绝访问, 比如调用私有的成员变量或方法
e.printStackTrace();
}
}
@Override
public String toString() {
return super.toString();
}
}
Field成员变量类
- Field 对应某个类中成员变量的申明
- Filed对象可使用类对象.getFiled()方法获取 : classObj.getField()
- Field对象可在运行时使用对某个对象的成员变量进行赋值或取值
(核心点)
getDeclared 系列方法
- getDeclearedConstructor
(s)|Method(s)|FIled(s)(s)表示获取集合->构造方法集合,方法集合,属性集合
反射的使用(概念代码)
lambda 表达式(匿名)
package com.imooc.lambda.com.imooc.lambada;
import com.imooc.lambda.MathOperation;
public class LambdaSample {
public static void main(String[] args) {
/**
* lambda 约束条件(Lambda表达式只能实现有且只有一个抽象方法的接口, java称之为"函数式接口")
* 有很多种 下面一一举列
* 1. 标准式
* 2. 忽略参数类型模式
* 3. 单行模式省略大括号与return (若参数只有一枚, 那么装载参数的括号也可以直接省略)
* */
// 标准式
MathOperation adition = (Integer a, Integer b) -> {
return a + b + 0f;
};
System.out.println(adition.operate(5, 6));
/** 不使用lambda的模式
* class Adition implements MathOperation{
*
* @Override
* public Float operate(Integer a, Integer b) {
* return a+b+0f;
* }
* };
*
* Adition aditionObj = new Adition();
* System.out.println(aditionObj.operate(5,9));
* */
// 2. 忽略参数类型模式
MathOperation substraction = (a, b) -> {
return a - b + 0f;
};
System.out.println("忽略参数模式 :"+substraction.operate(3,4));
// 3. 单行模式
MathOperation multiplication = (a,b)->a*b+0f;
System.out.println( "单行模式 :"+multiplication.operate(4,5));
}
}
函数式编程
- 基于函数式接口 并使用lambda表达的 编程方式
- 函数式编程
理念是将代码作为可重用数据代入到程序里面 - 函数式编程
侧重于要做什么,而不是怎么做
核心就是依托于函数接口, 将
代码片段作为判断条件,运行时动态嵌入程序, 使得程序更加灵活
package com.imooc.lambda;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateSample {
public static void main(String[] args) {
Predicate<String> predicate = str->str.length()>3;
// System.out.println(predicate.test("99999"));
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
PredicateSample.filter(list,num->num%2==0);
}
public static void filter(List<Integer> list, Predicate<Integer> predicate){
for(Integer item : list){
System.out.println(predicate.test(item));
}
}
}
函数式接口
- 函数式接口是有且只有一个抽象方法的接口
- java中用有大量函数式接口, 如 java.lang.Runnable
- JDK8后提供了一系列新的函数式接口位于 java.util.function
Consumer 接口只是传入一个参数, 在内部定义具体的业务逻辑去操作, 纯工具人一个 -.- 汗颜...
package com.imooc.lambda;
import java.util.Random;
import java.util.function.Function;
public class FunctionSample {
public static void main(String[] args) {
Function<Integer, String> randomStr = str->{
// 创建可动态修改长度的 stringBuffer 对象字符串
StringBuffer strbuf = new StringBuffer();
// 定义 用于取随机数据的字符串
String chars = "1234567890qwertyuiop";
Random random = new Random();
// str 是 传入的长度(希望返回多长的字符串)
for (int i = 0; i < str; i++) {
int index = random.nextInt(chars.length());
// 字符串拼接
strbuf.append(chars.charAt(index));
}
// 返回处理完毕的字符串
return strbuf.toString();
};
// 使用function方法, 生成 N 位长度的随机字符串 (N 自行传入)
String result = randomStr.apply(16);
System.out.println(result);
}
}
Tips: 可以使用@FunctionalInterface 来告知编译器,此class为一个函数式接口,令其在静态编译的时候检查抽象方法是否合法
stream 流(有点js链式调用的意思)
示例
文件流
SqlSessionFactory
- SqlSessionFactory 是MyBatis 的核心对象
- 用于初始化MyBatis, 创建SqlSession对象
- 保证SqlSession在应用中唯一
package com.imooc.mybatis;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.Reader;
import java.sql.Connection;
public class MyBatisTestor {
@Test
public void testSqlSessionFactory() throws IOException {
// 文本文件按照字符流的方式读取 mybatis-config.xml
// 会默认从当前classPath 类路径 下 去加载xml 文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 初始化SqlSessionFactory 对象, 同时解析mybatis-config.XML 文件
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
System.out.println("sqlSessionFactory加载成功");
// 创建SqlSession对象, SqlSession是JDBC扩展类, 用于数据库交互
SqlSession sqlSession = null;
try {
// sqlSessionFactory.openSession - 创建一个session对象
sqlSession = sqlSessionFactory.openSession();
// 创建数据库连接(测试使用) sqlSession.getConnection 得到底层的数据库对象
Connection connection = sqlSession.getConnection();
System.out.println(connection);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
// 注意 在myBatis-config 中, 如果设置
/**
* 注意 在myBatis-config 中, 如果设置 <dataSource type="POOLED">
* 则代表使用连接池, close则是将连接会收到连接池中
*
* 而type="POLLED" ,代表使用直连, close会直接调用Connection.close() 方法关闭连接
* */
sqlSession.close();
}
}
}
}
Mybatis 写入数据
说起写操作,自然离不开数据库事务; 事务的存在实际上是 保证数据库的完整性 的基础.在客户端操作后, 所有操作会临时存放在日志中,直到客户端继续发起commit操作,那么日志中的操作才会真正写入到数据库. 只要有一条操作没有成功, 那么就会执行rollBack, 一旦rollBack, 所有存放在日志中的临时操作, 都会被清空.
写操作有三种
新增较为复杂的一点是: 主键Id是自增的,这就意味着我们无法直接将primaryId赋值, 所以我们需要在 insert 标签内部,写一个selectKey, 其有一个select last_insert_id()方法, 调用其来获取最新加入对象的Id,再赋值给我们自己定义的类对象中.从而得到一个完整的对象
selectKey 与 useGenerateKeys 的区别
<!-- inert标签并无resultMap 与 resultType对象, 因为只是写数据,不必关注也没有返回的类型-->
<insert id="insert" parameterType="com.imooc.mybatis.entity.Goods">
INSERT INTO t_goods(title,sub_title,original_cost,current_price,discount,is_free_delivery)
VALUES(#{title},#{subTitle},#{originalCost},#{currentPrice},#{discount},#{isFreeDelivery})
-- 进行主键回添 after 代表sql语句执行后再进行selectKey 的操作
<selectKey resultType="Integer" keyProperty="goodsId" order="AFTER">
select last_insert_id()
</selectKey>
</insert>
<!-- 相应的 除了selectKey之外, 还有一个 useGeneratedKeys 我们需要如此操作
<insert id="insert"
parameterType="com.imooc.mybatis.entity.Goods"
useGenerateKey="true" // 是否自动获取主键 , 默认为false
keyProperty="goodsId" // 为哪个属性获取
keyColumn="goods_id" // 表中的字段名
>
INSERT INTO t_goods(title,sub_title,original_cost,current_price,discount,is_free_delivery)
VALUES(#{title},#{subTitle},#{originalCost},#{currentPrice},#{discount},#{isFreeDelivery})
</insert>
-->
1.显式与隐式
- selectKey标签需要明确编写 获取主键的sql语句 -> select last_insert_id()
- useGeneratedKeys属性会自动根据驱动生成对应的SQL语句 2.场景
- selectKey标签 支持所有关系型数据库
- useGeneratedKeys 只支持 "自增主键" 类型的数据库
3.总结
- selectKey是通用方案, 适合所有数据库, 但编写麻烦(因为每一种数据库获取的语句都是不同的,一旦发生数据迁移,那么所有的selectKey语句都要发生变化)
- useGeneratedKeys 只支持自增数据库, 但使用简单(只需要简单配置, 就会根据驱动类型来完成相应操作)