开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

Java泛型详解(史上最全泛型知识详解)

发表于 2024-04-28

第一部分:泛型基础

1. 泛型的定义和作用

泛型是Java语言中的一种强大的类型安全的机制,它允许开发者在编译时就确定类型,从而避免了类型转换和类型检查的开销。泛型的核心思想是允许开发者定义类和方法时不指定具体的类型,而是使用一个类型参数(或称为类型变量)来代替,这个类型参数在类的实例化或方法的调用时才确定具体的类型。

泛型提供了一种灵活的代码复用方式,它允许开发者编写出既通用又安全的代码。在没有泛型之前,我们通常使用Object类型作为容器来存储任何类型的数据,但这种方式牺牲了类型安全,因为所有数据都需要在运行时进行类型检查。泛型的引入,使得我们可以在编译时就得到类型安全的保障,极大地提高了代码的可读性和可维护性。

2. 泛型的语法

泛型类、接口和方法的声明在Java中有着明确的语法规则。

泛型类和接口的声明

1
2
3
4
5
6
7
8
9
10
11
java复制代码public class Box<T> {
private T t;

public Box(T t) {
this.t = t;
}

public T get() {
return t;
}
}

在这个例子中,Box类是一个泛型类,它使用类型参数T来表示存储在Box中的对象类型。T可以是任何有效的类型名称,通常使用单个大写字母作为类型参数的命名约定。

泛型方法的声明

1
2
3
4
5
6
java复制代码public <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " ");
}
System.out.println();
}

泛型方法printArray可以接收任何类型数组并打印出来。类型参数T在方法名后面的尖括号内声明。

3. 类型参数

类型参数不仅可以是具体的类型,还可以是通配符,表示不确定的类型。

通配符的使用

1
2
3
java复制代码public void performOperation(List<?> list) {
// 可以调用list的通用方法,如size(),但不能调用get()等方法
}

这里List<?>表示一个未知通配符类型的列表,问号?代表可以是任何类型,但是调用该列表的方法时会受到限制,不能进行类型特定的操作。

第二部分:泛型的类型安全

1. 类型擦除

定义:
Java泛型采用了类型擦除的方式实现,这意味着泛型类型信息在编译时被擦除,运行时不存在。编译器会确保泛型的类型安全,但运行时数组和类型转换操作需要开发者手动处理。

案例源码:

1
2
3
4
java复制代码List<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0); // 正常编译
// list.add(1); // 编译时错误,类型不匹配

类型擦除是Java泛型实现的一个关键特性,它允许泛型在不牺牲Java平台兼容性的前提下提供类型安全的集合操作。然而,类型擦除也带来了一些限制,比如不能创建泛型数组,因为数组的类型在运行时必须确定。此外,类型擦除也意味着运行时无法直接通过反射获取泛型的类型参数信息。

2. 泛型的类型限制

类型限制:
泛型可以通过类型限制来指定类型参数的上下界,从而限制可以作为类型参数的具体类型。

案例源码:

1
2
3
4
5
6
7
java复制代码public class Example<T extends Number> {
// T 被限制为 Number 类或其子类
}

public void process(List<? super String> list) {
// list 可以存储任何 String 的超类类型,如 Object
}

类型限制是泛型中一个强大的特性,它允许开发者定义更具体的类型要求,从而提供更丰富的类型安全检查。上界限定(extends)和下界限定(super)分别允许我们指定类型参数必须是某个类型的子类或超类,这在设计泛型类和方法时非常有用。

然而,类型限制也增加了代码的复杂性,需要开发者对类型层次结构有清晰的认识。此外,过度使用类型限制可能会限制泛型的灵活性,因此在使用时需要权衡代码的通用性和类型安全性。

3. 泛型数组和反射

泛型数组的限制:
由于类型擦除,Java中不能创建泛型类型的数组。

案例源码:

1
2
java复制代码List<String>[] lists; // 正确
// List<String>[] lists2 = new List<String>[10]; // 错误,编译时不通过

泛型数组的限制是类型擦除的一个直接后果。虽然这限制了泛型的使用,但它保证了Java程序的类型安全和向后兼容性。在需要使用数组时,开发者通常需要使用其他方式,如创建一个具体类型的数组,或者使用List等集合类作为替代。

反射与泛型的结合使用需要特别小心,因为反射可以绕过泛型的类型检查。在实际开发中,应该尽量避免在泛型和反射之间进行操作,以保持代码的类型安全。

总的来说,泛型提供了一种在编译时进行类型检查的机制,而类型擦除、类型限制和对数组及反射的处理是泛型实现中的关键概念。正确理解和使用这些概念对于编写类型安全、灵活且高效的Java代码至关重要。

第三部分:泛型的高级特性

1. 协变与逆变

协变(Covariance):
协变允许子类型可以被赋值给父类型。在泛型中,这意味着如果Sub是Super的子类型,那么List<Sub>也是List<Super>的子类型。

案例源码:

1
2
3
4
5
6
java复制代码List<? extends Number> listNum = new ArrayList<Integer>();
Number num = listNum.get(0); // 正确,因为Integer是Number的子类
// listNum.add(new Number()); // 错误,因为编译器不知道Number的具体类型

List<Number> listSuper = new ArrayList<>();
listSuper = new ArrayList<SubNumber>(); // 正确,因为SubNumber是Number的子类

协变提供了一种安全的子类型替换机制,使得我们可以将一个参数化的子类型赋值给一个参数化的父类型。这在处理继承关系时非常有用,因为它允许开发者在不牺牲类型安全的前提下进行更灵活的代码编写。

然而,协变也带来了一些限制,特别是在涉及到泛型数组时。由于泛型数组的不安全性,Java 编译器禁止了泛型数组的创建,以避免运行时错误。

逆变(Contravariance):
逆变与协变相反,它允许父类型可以被赋值给子类型。在泛型中,这意味着如果Sub是Super的子类型,那么List<Super>是List<Sub>的子类型。

案例源码:

1
2
3
4
5
6
7
java复制代码public void addNumber(List<? super Integer> list) {
list.add(42);
// list.add("String"); // 错误,因为List期望的是Integer或其超类
}

List<SuperNumber> superList = new ArrayList<>();
addNumber(superList); // 正确,因为SuperNumber是Number的超类

个人看法:
逆变在泛型方法中特别有用,它允许开发者定义可以接受任何超类型实例化的方法。这在设计函数时提供了更大的灵活性,因为你不需要为每个可能的超类型编写一个方法重载。

逆变的一个典型用例是在策略模式中,其中函数参数需要一个可以操作多种类型对象的策略接口。逆变使得可以将策略接口的父类型传递给期望其子类型的函数。

2. 有界类型参数

有界类型参数:
有界类型参数允许在声明泛型时指定类型参数的上下界。

案例源码:

1
2
3
4
5
6
7
8
9
10
11
java复制代码public interface Animal {}
public class Dog implements Animal {}
public class Cat implements Animal {}

public <T extends Animal> void doSomething(T animal) {
// 这里的T可以是Animal的任何子类
}

doSomething(new Dog()); // 正确
doSomething(new Cat()); // 正确
// doSomething(new Object()); // 错误,因为Object不是Animal的子类

有界类型参数为泛型提供了更精确的控制,允许开发者定义类型参数必须满足的约束。这在需要类型参数满足特定接口或继承自特定类时非常有用。

然而,过度使用有界类型参数可能会使代码变得复杂,并且可能会限制泛型的灵活性。因此,开发者需要在代码的通用性和类型安全性之间做出权衡。

3. 泛型的嵌套

嵌套泛型:
嵌套泛型是指在泛型类内部定义另一个泛型。

案例源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码public class OuterClass<T> {
public class InnerClass<U> {
private T t;
private U u;

public InnerClass(T t, U u) {
this.t = t;
this.u = u;
}

public T getOuter() {
return t;
}

public U getInner() {
return u;
}
}
}

OuterClass<String>.InnerClass<Integer> inner =
new OuterClass<String>().new InnerClass<>("Hello", 42);

嵌套泛型提供了一种强大的方式,使得内部类可以与外部类的类型参数进行交互,或者拥有自己的类型参数。这种方式在设计复杂的数据结构或算法时非常有用,比如在实现复合数据结构时。

然而,嵌套泛型的使用可能会增加代码的复杂性,并且对于初学者来说可能难以理解。因此,在使用嵌套泛型时,需要确保其确实提高了代码的清晰度和表达力,而不是简单地增加复杂性。

泛型的高级特性为Java语言提供了更深层次的类型安全和灵活性。开发者应该根据具体的应用场景和需求,合理地使用这些特性来提高代码的质量和性能。同时,也需要对泛型的工作原理有深入的理解,以避免常见的陷阱和错误。

第四部分:泛型与Java集合

1. 泛型集合

泛型在Java集合框架中的使用极大地增强了类型安全。在Java中,List、Set和Map等接口都支持泛型,允许开发者指定存储在集合中的对象类型。

案例源码:

1
2
3
4
5
6
7
8
9
10
11
java复制代码List<String> stringList = new ArrayList<>();
stringList.add("Hello"); // 正确
// stringList.add(1); // 编译错误,因为List期望String类型

Set<Integer> intSet = new HashSet<>();
intSet.add(10); // 正确
// intSet.add("Ten"); // 编译错误,因为Set期望Integer类型

Map<String, List<Integer>> map = new HashMap<>();
map.put("Numbers", new ArrayList<>());
map.get("Numbers").add(20); // 正确

泛型集合的使用提高了代码的可读性和可维护性。通过在声明时指定具体的类型参数,开发者可以避免类型转换的开销,并且可以在编译时发现类型不匹配的错误。这减少了运行时错误的可能性,提高了程序的稳定性。

2. 泛型集合的性能考量

泛型集合虽然提供了类型安全,但在某些情况下可能会影响性能。

案例源码:

1
2
3
java复制代码List list = new ArrayList<String>();
list.add("Hello");
String str = (String) list.get(0); // 需要类型转换

在泛型集合中,由于类型擦除,运行时的类型信息丢失,因此在取出元素时可能需要进行类型转换。这虽然是一个小的性能开销,但在对性能要求极高的场景中,如大量数据的迭代处理,可能需要考虑这种影响。

为了提高性能,可以考虑使用原生类型(如List而不是List<String>),但这样会牺牲类型安全。因此,开发者需要在类型安全和性能之间做出权衡。

3. 泛型与迭代器

泛型迭代器允许在迭代集合时保持类型安全。

案例源码:

1
2
3
4
5
6
7
8
9
10
java复制代码List<String> list = new ArrayList<>();
for (String str : list) {
System.out.println(str);
}

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
}

泛型迭代器提供了一种类型安全的方式来遍历集合。使用增强的for循环或迭代器时,泛型确保了取出的元素类型与集合声明时的类型参数一致,从而避免了类型转换。

然而,泛型迭代器也有其局限性。例如,不能使用泛型数组来存储迭代器返回的对象,因为数组的类型在运行时必须确定。

总的来说,泛型与Java集合框架的结合使用,为开发者提供了一种类型安全的方式来处理对象集合。通过合理使用泛型集合,可以在编译时发现潜在的错误,提高代码的稳定性和可维护性。同时,开发者也需要考虑泛型集合在特定场景下的性能影响,并在必要时做出适当的权衡。

第五部分:泛型的实际应用案例

1. 设计模式中的泛型

泛型在设计模式中的应用可以提高代码的复用性和可读性。以下是几个常见设计模式中泛型使用的例子。

策略模式(Strategy Pattern)

在策略模式中,可以使用泛型来定义策略接口和上下文类。

案例源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码public interface Strategy<T> {
T execute(Object data);
}

public class ConcreteStrategyA implements Strategy<Integer> {
public Integer execute(Object data) {
// 实现策略A的逻辑
return (Integer) data * 2;
}
}

public class Context {
private Strategy<?> strategy;

public Context(Strategy<?> strategy) {
this.strategy = strategy;
}

public <T> T executeStrategy(Object data) {
return (T) strategy.execute(data);
}
}

// 使用
Context context = new Context(new ConcreteStrategyA());
Integer result = context.executeStrategy(5); // 返回10

在策略模式中使用泛型,可以使得Context类更加通用,能够适应不同的策略实现。同时,泛型也使得策略的定义更加清晰,每个策略类都明确了其操作的数据类型。

观察者模式(Observer Pattern)

泛型同样适用于观察者模式,允许观察者和主题处理特定类型的数据。

案例源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
java复制代码public interface Observer<T> {
void update(T data);
}

public interface Subject<T> {
void attach(Observer<T> observer);
void detach(Observer<T> observer);
void notifyObservers(T data);
}

public class ConcreteSubject extends Subject<String> {
private List<Observer<String>> observers = new ArrayList<>();

public void attach(Observer<String> observer) {
observers.add(observer);
}

public void detach(Observer<String> observer) {
observers.remove(observer);
}

public void notifyObservers(String data) {
for (Observer<String> observer : observers) {
observer.update(data);
}
}
}

// 使用
ConcreteSubject subject = new ConcreteSubject();
Observer<String> observer = data -> System.out.println("Received data: " + data);
subject.attach(observer);
subject.notifyObservers("Hello, World!");

在观察者模式中,泛型的使用确保了主题和观察者之间数据类型的一致性,避免了类型转换的需要。此外,泛型也使得观察者模式更加灵活,可以用于不同类型的数据。

2. 泛型在框架开发中的应用

泛型在框架开发中也非常重要,它允许框架的使用者以类型安全的方式定义和使用框架。

泛型DAO

在数据访问对象(DAO)模式中,泛型可以使得DAO类更加通用。

案例源码:

1
2
3
4
5
6
7
8
9
10
java复制代码public interface GenericDAO<T, ID> {
T findById(ID id);
List<T> findAll();
void save(T entity);
void delete(T entity);
}

public class UserDAO extends GenericDAO<User, Long> {
// 实现方法
}

泛型DAO的设计使得DAO类可以适用于不同的实体类和ID类型,提高了代码的复用性。同时,泛型也使得DAO的使用者能够清楚地知道操作的数据类型,从而编写出更安全、更易于维护的代码。

第六部分:泛型的局限性与替代方案

1. 泛型的局限性

泛型虽然为Java带来了类型安全,但它也有一些局限性。

局限性一:类型擦除

由于Java泛型是基于类型擦除实现的,这意味着泛型的类型信息在运行时是不可知的。这限制了泛型与某些Java特性的兼容性。

案例源码:

1
2
3
4
5
6
7
8
java复制代码public class GenericType<T> {
private Class<T> type;

public GenericType() {
// 错误:类型擦除导致无法获取参数化类型的Class对象
this.type = (Class<T>) (Object) T.class;
}
}

类型擦除是泛型实现的核心,但这也意味着泛型类型不能用于传统的运行时反射操作。在某些需要运行时类型信息的场景下,这可能需要额外的设计考量。

局限性二:泛型数组 创建

Java不允许创建参数化类型的数组,因为数组的类型在运行时必须具体化。

案例源码:

1
2
3
4
5
6
7
8
9
10
java复制代码public void createArray(List<String> list) {
// 错误:无法创建泛型数组
String[] strings = list.toArray(new String[0]);
}

// 替代方案
public void createArray(List<String> list) {
String[] strings = new String[list.size()];
list.toArray(strings);
}

泛型数组的限制是出于安全性的考虑,以避免运行时的类型不匹配错误。在需要使用数组时,开发者需要寻找替代方案,如使用具体类型数组或集合。

局限性三:非具体化的通配符实例化

使用通配符时,不能直接实例化具体化的泛型。

案例源码:

1
2
3
4
5
6
7
8
9
java复制代码public void printList(List<String> list) {
// 错误:无法实例化具体化的泛型
List<String> myList = new ArrayList<String>();
}

// 替代方案
public void printList(List<?> list) {
List<String> myList = new ArrayList<String>(); // 正确,使用非具体化的通配符
}

通配符的使用提供了灵活性,但限制了泛型的实例化。在需要实例化泛型时,需要使用非具体化的通配符或者不使用泛型。

2. 替代方案

尽管泛型有局限性,但Java提供了一些替代方案来解决特定问题。

替代方案一:使用具体类

在不需要类型参数的情况下,可以使用具体类而不是泛型类。

案例源码:

1
2
3
4
java复制代码// 使用具体类
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0);

个人看法:
使用具体类可以避免泛型带来的一些限制,但牺牲了类型安全和代码的可读性。

替代方案二:使用类型转换

在某些情况下,可以通过类型转换来解决泛型的局限性。

案例源码:

1
2
3
4
5
6
java复制代码public <T> void performAction(T item) {
if (item instanceof String) {
String str = (String) item;
// 执行与String相关的操作
}
}

类型转换可以在运行时确定对象的具体类型,但它要求开发者确保转换的安全性,否则可能会抛出ClassCastException。

替代方案三:使用运行时类型标记

通过使用运行时类型标记(如instanceof)和类型转换,可以在一定程度上模拟泛型的行为。

案例源码:

1
2
3
4
5
6
7
8
java复制代码public void process(List<?> list) {
for (Object obj : list) {
if (obj instanceof Integer) {
Integer num = (Integer) obj;
// 执行与Integer相关的操作
}
}
}

运行时类型标记和类型转换可以在不使用泛型的情况下提供类型安全,但需要开发者手动进行类型检查,增加了代码的复杂性。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

React Playground 实现原理揭秘

发表于 2024-04-28

大家应该都用过在线写代码的工具,比如 vue 的 playground:

左边写代码,右边实时预览。

右边还可以看到编译后的代码:

这是一个纯前端项目。

类似的,也有 React Playground

那它是怎么实现的呢?我们自己能实现一个么?

可以的,今天我们来分析下实现思路。

首先是编译:

编译用的 @babel/standalone,这个是 babel 的浏览器版本。

可以用它实时把 tsx 代码编译为 js。

试一下:

1
javascript复制代码npx create-vite

进入项目安装 @babel/standalone 和它的 ts 类型:

1
2
3
css复制代码npm install
npm i --save @babel/standalone
npm i --save-dev @types/babel__standalone

去掉 index.css 和 StrictMode:

改下 App.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
javascript复制代码import { useRef, useState } from 'react'
import { transform } from '@babel/standalone';

function App() {

const textareaRef = useRef<HTMLTextAreaElement>(null);

function onClick() {
if(!textareaRef.current) {
return ;
}

const res = transform(textareaRef.current.value, {
presets: ['react', 'typescript'],
filename: 'guang.tsx'
});
console.log(res.code);
}

const code = `import { useEffect, useState } from "react";

function App() {
const [num, setNum] = useState(() => {
const num1 = 1 + 2;
const num2 = 2 + 3;
return num1 + num2
});

return (
<div onClick={() => setNum((prevNum) => prevNum + 1)}>{num}</div>
);
}

export default App;
`
return (
<div>
<textarea ref={textareaRef} style={{ width: '500px', height: '300px'}} defaultValue={code}></textarea>
<button onClick={onClick}>编译</button>
</div>
)
}

export default App

在 textarea 输入内容,设置默认值 defaultValue,用 useRef 获取它的 value。

然后点击编译按钮的时候,拿到内容用 babel.transform 编译,指定 typescript 和 react 的 preset。

打印 res.code。

可以看到,打印了编译后的代码:

但现在编译后的代码也不能跑啊:

主要是 import 语句这里:

运行代码的时候,会引入 import 的模块,这时会找不到。

当然,我们可以像 vite 的 dev server 那样做一个根据 moduleId 返回编译后的模块内容的服务。

但这里是纯前端项目,显然不适合。

其实 import 的 url 可以用 blob url。

在 public 目录下添加 test.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
html复制代码<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

<script>
const code1 =`
function add(a, b) {
return a + b;
}
export { add };
`;

const url = URL.createObjectURL(new Blob([code1], { type: 'application/javascript' }));
const code2 = `import { add } from "${url}";

console.log(add(2, 3));`;

const script = document.createElement('script');
script.type="module";
script.textContent = code2;
document.body.appendChild(script);
</script>
</body>
</html>

浏览器访问下:

这里用的就是 blob url:

我们可以把一段 JS 代码,用 URL.createObjectURL 和 new Blob 的方式变为一个 url:

1
javascript复制代码URL.createObjectURL(new Blob([code], { type: 'application/javascript' }))

那接下来的问题就简单了,左侧写的所有代码都是有文件名的。

我们只需要根据文件名替换下 import 的 url 就好了。

比如 App.tsx 引入了 ./Aaa.tsx

1
2
3
4
5
javascript复制代码import Aaa from './Aaa.tsx';

export default function App() {
return <Aaa></Aaa>
}

我们维护拿到 Aaa.tsx 的内容,然后通过 Bob 和 URL.createObjectURL 的方式把 Aaa.tsx 内容变为一个 blob url,替换 import 的路径就好了。

这样就可以直接跑。

那怎么替换呢?

babel 插件呀。

babel 编译流程分为 parse、transform、generate 三个阶段。

babel 插件就是在 transform 的阶段增删改 AST 的:

通过 astexplorer.net 看下对应的 AST:

只要在对 ImportDeclaration 的 AST 做处理,把 source.value 替换为对应文件的 blob url 就行了。

比如这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
javascript复制代码import { transform } from '@babel/standalone';
import type { PluginObj } from '@babel/core';

function App() {

const code1 =`
function add(a, b) {
return a + b;
}
export { add };
`;

const url = URL.createObjectURL(new Blob([code1], { type: 'application/javascript' }));

const transformImportSourcePlugin: PluginObj = {
visitor: {
ImportDeclaration(path) {
path.node.source.value = url;
}
},
}


const code = `import { add } from './add.ts'; console.log(add(2, 3));`

function onClick() {
const res = transform(code, {
presets: ['react', 'typescript'],
filename: 'guang.ts',
plugins: [transformImportSourcePlugin]
});
console.log(res.code);
}

return (
<div>
<button onClick={onClick}>编译</button>
</div>
)
}

export default App

这里插件的类型用到了 @babel/core 包的类型,安装下:

1
css复制代码npm i --save-dev @types/babel__core

我们用 babel 插件的方式对 import 的 source 做了替换。

把 ImportDeclaration 的 soure 的值改为了 blob url。

这样,浏览器里就能直接跑这段代码。

那如果是引入 react 和 react-dom 的包呢?这些也不是在左侧写的代码呀

这种可以用 import maps 的机制:

在 public 下新建 test2.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
html复制代码<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18.2.0",
}
}
</script>
<script type="module">
import React from "react";

console.log(React);
</script>
</body>
</html>

访问下:

可以看到,import react 生效了。

为什么会生效呢?

你访问下可以看到,返回的内容也是 import url 的方式:

这里的 esm.sh 就是专门提供 esm 模块的 CDN 服务:

这是它们做的 react playground:

这样,如何引入编辑器里写的 ./Aaa.tsx 这种模块,如何引入 react、react-dom 这种模块我们就都清楚了。

分别用 Blob + URL.createBlobURL 和 import maps + esm.sh 来做。

那编辑器部分如何做呢?

这个用 @monaco-editor/react

安装下:

1
bash复制代码npm install @monaco-editor/react

试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
javascript复制代码import Editor from '@monaco-editor/react';

function App() {

const code =`import { useEffect, useState } from "react";

function App() {
const [num, setNum] = useState(() => {
const num1 = 1 + 2;
const num2 = 2 + 3;
return num1 + num2
});

return (
<div onClick={() => setNum((prevNum) => prevNum + 1)}>{num}</div>
);
}

export default App;
`;

return <Editor height="500px" defaultLanguage="javascript" defaultValue={code} />;
}

export default App;

Editor 有很多参数,等用到的时候再展开看。

接下来看下预览部分:

这部分就是 iframe,然后加一个通信机制,左边编辑器的结果,编译之后传到 iframe 里渲染就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
javascript复制代码import React from 'react'

import iframeRaw from './iframe.html?raw';

const iframeUrl = URL.createObjectURL(new Blob([iframeRaw], { type: 'text/html' }));

const Preview: React.FC = () => {

return (
<iframe
src={iframeUrl}
style={{
width: '100%',
height: '100%',
padding: 0,
border: 'none'
}}
/>
)
}

export default Preview;

iframe.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
html复制代码<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Preview</title>
<style>
* {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18.2.0",
"react-dom/client": "https://esm.sh/react-dom@18.2.0"
}
}
</script>
<script>

</script>
<script type="module">
import React, {useState, useEffect} from 'react';
import ReactDOM from 'react-dom/client';

const App = () => {
return React.createElement('div', null, 'aaa');
};

window.addEventListener('load', () => {
const root = document.getElementById('root')
ReactDOM.createRoot(root).render(React.createElement(App, null))
})
</script>

<div id="root">
<div style="position:absolute;top: 0;left:0;width:100%;height:100%;display: flex;justify-content: center;align-items: center;">
Loading...
</div>
</div>

</body>
</html>

这里路径后面加个 ?raw 是通过字符串引入(webpack 和 vite 都有这种功能),用 URL.createObjectURL + Blob 生成 blob url 设置到 iframe 的 src 就好了:

渲染的没问题:

这样,我们只需要内容变了之后生成新的 blob url 就好了。

至此,从编辑器到编译到预览的流程就理清了。

案例代码上传了react 小册仓库。

总结

我们分析了下 react playground 的实现思路。

编辑器部分用 @monaco-editor/react 实现,然后用 @babel/standalone 在浏览器里编译。

编译过程中用自己写的 babel 插件实现 import 的 source 的修改,变为 URL.createObjectURL + Blob 生成的 blob url,把模块内容内联进去。

对于 react、react-dom 这种包,用 import maps 配合 esm.sh 网站来引入。

然后用 iframe 预览生成的内容,url 同样是把内容内联到 src 里,生成 blob url。

这样,react playground 整个流程的思路就理清了。

什么?光思路不过瘾,你想实现一个完整版?

这是我小册 《React 通关秘籍》的一个项目,感兴趣的话可以上车一起做。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

赋能开发者,腾讯云与你共探AI提升十倍生产力之路

发表于 2024-04-28

赋能开发者,腾讯云与你共探AI提升十倍生产力之路

引言

AI 技术发展迅速,对于开发者而言,AI 既可能是提高生产力的神兵利器,也可能成为职业生涯潜在的“威胁”。开发者如何与 AI 协同进化,提升个人能力和价值;如何利用提高 AI 生产力,推动企业创新,实现降本提效?

4 月 13 日,由腾讯云 TVP、CSDN、中电光谷联合主办的【AI 提升十倍生产力——Techo TVP 技术沙龙】在长沙正式召开,邀请五位 AI 领域的杰出技术专家进行技术解读,分享企业最佳实践经验和创新探索思路,共同探讨 AI 提高十倍生产力的秘诀。

AI大模型赋能企业IT平台智能化升级

AI 大模型正推动企业 IT 平台的转型,逐渐在自动化流程、安全管理、产品创新等多个领域发挥重要作用,在实际落地的过程中,企业如何利用 AI 大模型进行智能化升级和推动业务创新?

融链科技董事长、腾讯云 TVP 李颖悟在《AI 大模型在企业 IT 平台的融合趋势及应用案例》的主题报告中,深入分享其独特见解。

融链科技董事长、腾讯云TVP李颖悟

李颖悟详细介绍了企业信息化的发展历程,从早期的业务操作电子化到业务流程信息化,再到业务和管理的数字化,如今来到业务决策智慧化阶段,AI 大模型赋予企业信息化系统更多的价值:例如更高的效率,AI 大模型帮助企业IT平台更高效地处理数据,提高工作效率;又如更低的成本,它减少企业 IT 平台的人力成本,降低运营成本;还有更好的用户体验,AI 大模型可以提供 24 小时在线的智能服务,增强用户体验。

李颖悟分享 AI 大模型可在智能客服、数据分析、自动化流程应用、AI助理等领域与企业信息化系统可进行更深度的融合。随后他列举具体案例说明AI如何帮助企业提升效率、降低成本、增强智能化程度和改善用户体验,如帮助展通集团构建安全生产管理系统,通过整合物联网设备实时数据、运用 AI 和区块链技术,有效提升数据的实时性和不可篡改性,还强化对潜在风险的预测和响应能力。在城市建设上,携手打造字解数梯电梯维保平台大模型应用,让 AI 实时监控电梯的运行状态,还能帮助维修工人辅助诊断。又如通过构建立辰科技 AI 测聘平台,解决应聘者和企业的沟通效率,AI 帮助 HR 智能筛选简历、提供智能测评等,大大提高招聘效率。

与此同时,他提出了大模型在应用中常见的问题:数据积累少,多样性不足;数据质量不高,要做大量的数据清洗和标注工作;如何确保数据安全性;如何控制大模型的开发、部署和维护成本;如何和业务结合开展创新应用,真正产生价值等。

对此,李颖悟团队提供企业数智化服务,服务内容主要包括数智化转型的战略规划与咨询、业务优化重塑以及技术方案的具体实施与定制化模型的构建,旨在助力企业全面步入数字化时代并最大化利用自身资源创造价值。

最后,李颖悟展望道,未来将有更多专属、自建模型将率先在中大型企业涌现。他鼓励企业寻找自身业务领域的独特应用场景,充分利用现有技术和数据资源,推动 AI 大模型在企业的落地与创新应用。

RPA+大模型强强联手为企业发展加码

北京来也网络科技有限公司联合创始人、首席产品官,腾讯云 TVP 褚瑞在《大模型驱动的智能自动化》的分享中,详细阐述大模型在智能自动化中的应用现状、挑战以及未来趋势,并通过实际案例展示通过结合大模型与 RPA 技术,推动企业实现数字化转型和业务效率提升。

北京来也网络科技有限公司联合创始人、首席产品官,腾讯云TVP 褚瑞

RPA 可自动执行大量重复性高、规则性强的任务,有效提高工作效率,当前 RPA 在财务管理、人力资源和运营等领域有广泛应用。

结合 RPA 和 AI 技术来解决工作中需要人力完成的任务,可称为“智能自动化”。随着大模型时代的到来,将 RPA 和大模型结合,两者形成一个良好的互补。因为 RPA 基于规则,擅长结构化数据处理,以端为主,执行能力强等;大模型基于概率,擅长非结构化数据处理,以云为主,具有推理和决策,这两者结合起来,褚瑞形容是“天生地造的一对”。

他进一步阐述道,将大模型与 RPA 结合后,可形成两种应用范式:更智能的自动化和更自动的智能化。其中,“更智能的自动化”范式入口是 RPA,利用大模型将 RPA 增强,从而覆盖更多的工作;而“更自动的智能化”的入口是智能助手,可以更方便、更灵活地指挥智能助手完成工作,以覆盖更多的人群。

展望未来发展,褚瑞有以下洞察:发展趋势将由大炼模型到炼大模型,大模型就像今天的云计算一样,没必要每个企业都建私有云,而是“集中力量办大事”,开发者如何基于大模型做出有价值的应用才是王道。

此外,大模型和传统程序开发是完全不同的体验,大模型有传统程序难以实现的优点,同时可能存在“偷懒”等缺点,需要将两者结合起来开发运用,降低开发遇到的挑战。

同时他给出忠告,面对大模型驱动的智能自动化时代,开发门槛不断降低,开发者需提升自身价值,成为复合型人才,才是破局之道。而对产品经理而言,需要及时更新认知,找到大模型的价值,并积极应用在业务中。

从数字人到数智人助力重塑千行百业

如今,AI 技术已润物细无声地融入千行百业中,不断改变我们工作、生活的方方面面,例如在 2022 年冬奥会上,央视频 AI 手语主播“聆语”被大众熟知出圈,它帮助听障人士更好地理解手语表意,感受冬奥魅力。

腾讯云智能高级产品架构师 李镐在《数智人助力开发者重塑千行百业》的主题分享中,围绕数智人在企业数字化转型中的应用和未来发展进行详细介绍。

腾讯云智能高级产品架构师 李镐

数字人概念由来已久,早在二十年前,腾讯的 QQ 秀可视为数字人的雏形。李镐介绍道,如今数字人在社会认知与技术创新的双轮驱动下蓬勃发展。一方面,公众对于数字人的认知日益加深,伴随元宇宙概念的普及以及国家政策对相关技术的大力支持,市场需求呈现爆炸式增长,加上数字人网红的亮相,大家对数字人的期待值不断提升,吸引更多开发者和企业探索数字人的创新应用。另一方面,随着数字人建模、渲染等技术成熟,生态和标准等底层支撑数字人产业蓬勃发展。

李镐观察到数智人有以下发展趋势,一是随着大模型的引入,在原本拥有“好看皮囊”的数字人再赋予“有趣的灵魂”,使数字人具备智能分析和决策能力,从而在企业应用中发挥更大作用。数智人与大模型紧密关联,大模型成为其智能化的核心驱动力,将进一步提升数字人的实用性和商业价值。

二是得益于自动化工具和大模型技术的发展,数智人制作效率提高,制作成本将更低、门槛更低。

三是未来每一家公司都可能需要一个数智人,数智人技术将深入应用到千家百户,普惠大众。

腾讯凭借自身的 AI 技术和大模型积累,推出了一系列数智人产品和服务,服务诸多行业客户。腾讯还为开发者和企业提供一站式数智人平台,结合大模型能力和交互 API,让开发者可以快速、低成本地开发定制化数智人应用,今年还将推出全套 iPaaS 工具箱,让开发者可以在天级别完成过往 1-2 个月才能完成的垂类场景应用,快速抓住市场机遇完成商业化落地。

目前,数智人可应用于企业经营全生命周期,李镐着重分享了在品牌宣传、数智化营销、用户运营、客服服务等领域,与合作伙伴携手打造出国家博物馆数字人讲解员、“数字总裁”、3D 卡通数智人、税务大厅智能引导等前沿应用,助力重塑千行百业的服务形态,实现“数字人”到“数智人”的智能化升级。

作为新一代多模态人机交互系统,腾讯云智能数智人具备形象生产、交互对话、音视频播报等核心能力,特别是支持 2D 精品、2D 小样本、3D 写实、3D 半写实、3D 卡通等多种形象类型选择。其中,2D 小样本数智人形象效果市场领先,产品矩阵市场最全,包含专属口型、通用口型、4K 高清以及多风格照片数智人全系类产品。

此外,依托腾讯云行业大模型与数智人形象生产工厂内置的丰富 AI 算法模型,数智分身复刻缩短至 1 小时,成本低至百元级别,且独家支持客户全流程自助,在客户服务、AI 面试、智能问诊、1V1 陪练等新兴场景应用深度大幅提升。

目前,腾讯云智能数智人已入职金融、传媒、文旅、出行、政务等多个行业场景,作为“数智员工”,践行着服务于人的理念。

实践出真知,水羊AI创新应用与落地实践

在本次沙龙上,多位嘉宾强调 AI 需与实际业务结合,方能创造更多实际价值,帮助我们开发者厘清思路,逐时代浪潮。

水羊 AI 技术负责人 胡正军结合自身丰富的实践经验,通过多个真实案例介绍水羊公司如何落地AI技术,展示AI在提高工作效率、节省成本和创新业务流程等方面的积极作用。

水羊AI技术负责人 胡正军

在水羊的日常工作环境中,通过将 AI 机器人集成到企业内部协同办公软件里,帮助员工高效完成翻译、OA 流程、智能搜索、图片创作等工作。例如由于公司业务涉及众多海外品牌,员工常常进行跨国沟通,针对大量邮件和文档翻译工作,员工只需将内容发给 AI 机器人,就能快速完成翻译任务,显著提高翻译效率,提升员工工作效率。自从 AI 机器人上线后,每天有两三 百名员工使用,每月约有上千名员工将它运用在工作中,AI 机器人在企业内部应用较广。目前团队正在探索更多创新场景,例如让 AI 机器人可以直接处理 PPT 或 Word 文档的翻译,无需员工逐句复制粘贴,大大节省了时间。

检索、总结信息,是每位员工日常工作、学习中都有的需求场景。员工可将长文章发给 AI 机器人速读,并生成摘要总结,帮助员工提高阅读效率。在信息检索上,AI 机器人可根据员工的意图,提取搜索关键字和内容,快速回答员工问题。商品在营销过程中,创意海报是关键的手段之一。利用 AI 机器人,员工一句话实现创意草图设计,有效提高和设计师沟通效率。此外,AI 机器人在办公流程自动化方面发挥大作用,员工可以一句话快捷发起请假流程,大大节省行政和办公事务的时间。

水羊拥有丰富的产品矩阵,在不同的营销活动中常常需要准备不同的营销话术,这部分的工作量大。在文案生成场景中,使用 AI 构建高效的产品话术生成系统,员工通过输入产品或活动介绍文档,机器人能按照预设模板一键生成客服所需的标准话术,减少了人工编写话术的工作量,每月可节省大量工时。不仅如此,AI 还用于批量生成客服培训考试题目,降低人力资源消耗。

在文案审核场景中,AI 能帮助员工审核客户话术、检查风险词,如检测产品宣传材料是否符合法规要求,避免含有违规词汇的图片或文案发布,保证品牌形象和业务合规性,避免违反相关规定导致商品被迫下架的风险。此外,AI 也被应用于话术复核,不仅对 AI 自动生成的话术进行二次确认,同时也核查人工撰写的话术内容,确保准确性。除了上述场景,AI 还在其他业务环节发挥作用,比如简化商家地址修正流程、协助复盘售后问题单的原因分析以及对编程过程中的代码编写辅助等。

胡正军表示,我们探索 AI 技术的落地应用过程中,不能盲目追求技术本身,需要让技术与业务结合,找到 AI 真正解决的企业业务痛点与落地方式,真正推动企业数字化转型与效率提升。水羊正是如此,在探索和实践 AI 的过程中坚持寻找符合业务实际、易于标准化实施的场景,逐步推进 AI 技术在企业内的融合与发展,帮助员工解放生产力。

解放程序员双手AI驱动下的Design to Code

作为一名开发者,我们在开发的过程中常常思考如何能提高代码开发效率,更好地专注创新工作?

湖南昊宇辉锋智慧科技有限公司 CTO 张吉贵也不例外,探索通过 AI 将设计稿转代码,降低前端开发人员手动编写代码的繁琐,提升开发效率。在本次分享中,他聚焦探索 AI 驱动的 Design to Code(设计稿转代码,下文简称 D2C)技术的实践。

湖南昊宇辉锋智慧科技有限公司CTO张吉贵D2C 涵盖三个步骤:一是布局还原,二是组件识别,三是业务逻辑处理。张吉贵通过整合 Figma 等设计工具的 API,结合 AI 算法,实现设计稿的自动化解析和代码生成。尽管这一过程中存在布局还原准确性、组件识别和业务逻辑映射等难题,但目前已有初步成果,可以生成具有一定质量的前端代码,减轻开发人员工作负担。

张吉贵表示,当前 D2C 技术尚未达到完美状态,存在实现成本较高、行业没有标杆产品、用户习惯培养难度大、需要精通设计工具、了解设计行为习惯等难题,但张吉贵团队正在尝试通过各种方式去打破这些壁垒,让产品更加完善,让 D2C 成为设计和研发的桥梁。

观点碰撞,开放交流

除了上述技术分享外,本次技术沙龙注重交流与互动,在最后的 AI 技术大辩论环节中,五位演讲嘉宾同台,针对 AI 时代开发者最关心的热门话题,随机抽取持方,展开精彩的辩论。

圆桌对话环节

AIGC真的会取代作者、程序员等知识工作者吗?

李颖悟抽取到了“不能取代”的持方,他从这几个方面进行了阐述:首先,AI 生成的质量很大程度依赖于用户的输入提示词质量,其次,生成后的内容往往需要人工后期调整来完善内容和补充个人观点;再者,以编程为例,AI 生成代码片段后,仍需要开发者优化和处理业务逻辑,完成细节的工作。因此,AI 大模型能够显著提高作者、设计师和程序员的工作效率,尤其在处理简单或重复性任务上,但它并不能完全取代人工创作,相信在可预见的未来内,大部分创造性工作仍将需要人类亲自完成。

而抽到“会取代”观点的褚瑞则选择人与人的竞争关系作为切入点进行反驳,随着 AI 的发展,尤其是 AIGC 等技术的应用,程序员的价值将面临巨大挑战。他强调,并非直接是 AI 取代个体,而是通过 AI “武装”更多人,可能会取代固步自封、不求改变的程序员。他建议开发者应持续学习和适应新技术,才能避免被时代淘汰。

反方另一代表李镐表示,AIGC 技术并不会完全取代现有的工作。首先,从褚瑞老师的观点延申,如同汽车并未淘汰马车夫而是促使他们转型成为汽车的司机,AI 无法淘汰适应变化的人,可如果还坚守马车不愿改变的,则被取代;其次,AI 在工作中扮演高效助手的角色,类似一位实习生,它能高效完成特定任务但缺乏独立完成整体工作流程的能力。所以我们在与 AI 协作过程中,仍需要负责定义、拆解任务、监控执行过程和评估结果等工作。最后,还是以程序员为例,AI 或许能承担部分编程工作,但理解和洞察客户需求、灵活沟通和处理复杂人际关系等人类技能是 AI 无法替代的。

正方代表胡正军则表示,AI 和先进的生产力工具会逐渐取代部分工作。以张老师分享的 D2C 为例,原本由程序员编写的代码,未来可由产品经理设计原型后直接生成代码,这样一来,可以说是取代了部分程序员的工作量。此外,以前很多企业配备 DBA 这类岗位,未来将云服务普及下,逐渐被云厂商提供的标准化服务取代,可能未来一些中小企业不再需要专职的 DBA,这也是一种取代。随着国家对数字化建设的推动和标准化进程的发展,未来企业里一些技术人员的角色将被更高效的服务和工具所替代。

AI浪潮是否会改变现在互联网厂商格局,诞生新的巨头公司?

持否定立场的李颖悟认为,由于大模型研发投入巨大,需要一定的人才储备,普通企业可能没这方面的积累,而互联网公司在相关人才储备和技术积累上更具优势。因此,在未来的人工智能领域中,他认为互联网公司将占据主导地位。

持肯定立场的褚瑞提出不同的看法,人们将大模型的横空出世与蒸汽机的发明相提并论,认为大模型作为一项划时代的创新技术,有可能像蒸汽机引领工业革命那样,开启全新的时代,同时催生出一批新的领军企业,正如历史上的朝代更替,这是正常的社会演进现象。

反方立场的李镐从以下几点原因,分析 AI 浪潮不会改变现有的互联网厂商的格局:第一,算力制约。如果一个 AI 企业要实现颠覆性成果需要庞大规模的算力支持,对于AI初创公司而言,受限于高昂成本和供应限制,难以拥有大规模自建算力,主要依赖云厂商租赁算力。第二,用户付费意愿低。对于 C 端用户来说,不愿意直接为大模型技术付费,而 B 端用户仅愿意为解决具体业务场景的应用付费,而非技术本身。第三,行业应用场景多。企业应用场景不同,需求各不相同,有一些企业还需要高度定制化,大模型难以满足所有行业应用场景的需求,从而无法形成一个能够通吃各行业市场的单一公司,未来的市场将会呈现百花齐放的局面。

正方立场的胡正军从不同的角度谈到,目前互联网已深入到我们生活的方方面面,完全取代不现实,但在某个或多个领域,随着技术发展,从长远来看,有一些 AI 技术可能会带来颠覆。例如 AI 搜索很可能取代传统搜索,即使是现有互联网巨头也可能自我革新。此外,胡正军提到人形机器人是未来 AI 发展的重要趋势之一,随着 GPT 等技术进步及与物理硬件结合,人形机器人的智能化程度将有所提高,成本亦有望降低,未来有机会如同汽车一样普及,有可能在一定程度上颠覆现有的互联网服务模式,从而取代一部分互联网功能。

AIGC给普通开发者/创业者带来了更大机遇,还是提高了门槛?

持正方论点的李颖悟表示,AI 技术为普通开发者带来前所未有的机遇,原本需要庞大团队协作才能完成的工作,借助 AI,个体可完成从前需要跨多个专业领域才能处理的任务,使得创业门槛降低,如今一个人凭借创意想法便有可能成就一番事业。据观察,许多成功且规模庞大的公司往往初始团队精简。我们正迎来新的时代,AI 让更多人有机会实现原本难以触及的梦想,把握住曾经错过的机会,因此 AI 时代为我们提供更大的发展空间和机遇。

持反方论点的褚瑞则表示,随着 AI 的普及与发展,知识工作者的入职门槛实质上有所提高。如同原来司机是一种职业,随着汽车的普及,司机从稀缺技能转变为大众技能后,对司机的综合素质要求却随之增加,知识工作者亦然,仅掌握基本技能在 AI 时代可能并不足够,还需具备更多高级能力和综合素养。

紧接着,正方立场的李镐从 AIGC 带来生产力的提升和更大的市场规模谈起,如司机职业随着汽车技术的发展而普及,现在很多人业余也可以开车接单。其次,AIGC 将我们从重复性劳动解放出来,让我们更专注于创新思考和流程优化。最后,凭借 AI 工具,让一个人成为一个团队,增强个人创业者的单兵作战能力,独立承担起以往需要多人团队完成的信息化项目,这为更多个人创业者创造了更多机遇。

反方立场的胡正军表示,话题提到了两种角色:AI 对于创业者而言有更多的机遇,因为他们通常具备较高的综合素质和资源优势,然而,对于大多数普通开发者来说,AI 则构成了较大的挑战。在技术快速发展下,普通开发者面临被精英开发者替代的风险。开发者不能单单满足于编码实现,需不断提升自我,不仅要强化技术能力,更要学会深入理解用户需求、有效沟通并挖掘潜在需求,紧跟技术与市场变化的步伐,否则将无法适应行业变革,进而遭遇职业生涯的挑战。

AI浪潮席卷,个人开发者最核心的立足之道是什么?各行各业该如何布局转型?

李颖悟认为,AI 浪潮席卷下,个人和企业应紧跟时代步伐。对于科研机构的研究者,应深入钻研算法和技术,寻求创新,提升效率;对于开发者来说,应更多关注 AI 技术在实际业务中的应用,找到企业痛点与 AI 结合,真正用 AI 技术解决实际问题,为企业创造价值。

褚瑞表示,企业和个人都要找到属于自己的“井冈山”,在当今竞争激烈的环境中,企业和个人必须明确自身的核心竞争力和不可替代性,构建稳固的“护城河”。

李镐谈到,首先,个人和企业未来立足的关键在于持续学习,积极拥抱新技术、新工具,并将它们应用于生活和工作中。当面对大模型等快速迭代的技术时,我们更要与时俱进。其次,应分析和拆解个人或企业业务,分析哪些工作可被 AI 代替的,哪些不会被取代;同时我们需强化自身无法被 AI 替代的优势,如情绪价值、沟通能力等。最后,拥有 AI 技术的人应当利用自身优势来帮助尚未适应 AI 变革的传统行业,通过技术革新去改变和引领这些行业的发展。

胡正军建议开发者在应对 AI 的发展时可采取两条策略:一是成为 AI 技术专家,指导他人,二是专注于短期内 AI 难以替代的工作,如架构设计、管理等方向。对于企业,尤其是传统企业,如果面临员工对 AI 接纳度不高等问题,可通过培训、案例展示、激励机制等方式培养全员使用 AI 的习惯,多运营推广,将 AI 融入业务实际,实现价值创造,而非仅依赖 IT 人员推动技术层面的应用。在推广落地 AI 过程中,让用户亲身参与和体验,共同挖掘和落地 AI 应用场景。

张吉贵以亲身经历分享,面临大模型的更新迭代,较早关注和适应新技术的个人和企业能更好地规避技术局限性,并高效利用工具提升业务处理能力和竞争力。IT 行业将越来越卷,开发者需不断更新知识储备,找准自身优势,紧跟前沿技术,以适应日益激烈的行业竞争环境。对于企业而言,采用 AI 大模型技术不仅是一种提效手段,更是增强自身竞争力,否则将面临被采用先进工具的同行降维打击的风险。

结语

伴随一轮又一轮嘉宾们精彩的辩论交流,此次 Techo TVP 技术沙龙·长沙站也进入了尾声。通过本次活动,开发者更直观地感受到 AI 对生产力提升的重要作用,也收获来自企业专家、技术领袖更专业的建议与分享。

未来,【Techo TVP 技术沙龙】将走进更多城市,腾讯云 TVP 将携手更多合作伙伴,持续通过最用心的分享、最前沿的洞察、最实际的观点,为开发者朋友献上更多技术盛宴。

注:公众号后台回复【长沙AI】,即可获取活动讲师PPT完整内容资料~

现场花絮集锦

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

厉害了!这个工具帮助你生成朋友圈转发截图

发表于 2024-04-28

大家好,我是 Java陈序员。

在日常的工作生活中,我们经常会遇到应付各类强制要求转发朋友圈的行为,或者是朋友圈集赞的行为。

今天,给大家介绍一个工具,可以帮助你生成朋友圈转发截图。

关注微信公众号:【Java陈序员】,获取开源项目分享、AI副业分享、超200本经典计算机电子书籍等。

项目介绍

WechatMomentScreenshot —— 一个因为不喜欢也不想往朋友圈发某些不得不发的废文而做出来的摸鱼产物。

接下来我们来体验下~

工具支持自定义用户名和头像,在使用时,我们可以改成自己的微信昵称和头像。

朋友圈的内容文案也可以个性化定制。

有 纯文字、分享网页/公众号文章、图片(单张)、图片(九宫格) 四种截图类型。

我们日常发朋友圈的时候,有时会带上定位,工具支持输入定位位置。

发布时间、截图时间、点赞数等这些也支持个性化设置。这样我们在做一些活动任务时,就不用求点赞了~

还有一些高级功能,比如显示评论区。

为了使截图具有更逼真的效果,工具支持随机信号和电量,以及自定义通知栏!

工具还有一个强大的功能,同时支持旧版本的微信朋友圈和 7.0 以上版本白色界面的朋友圈!

生成的图片效果

安装部署

1、拉取代码

1
bash复制代码git clone https://github.com/TransparentLC/WechatMomentScreenshot.git

2、构建镜像

1
bash复制代码docker build -t wechat-moment-screenshot:1.0 .

3、启动项目

1
2
bash复制代码# 修改18888为你想要使用的端口
docker run -dp 18888:80 wechat-moment-screenshot:1.0

4、浏览器访问

1
arduino复制代码http://{ip/域名}:18888

WechatMomentScreenshot 这款朋友圈转发截图生成工具,可以说功能十分强大,操作也简洁明了。

下次,如果你需要朋友圈集赞,可以试试这款截图工具~

最后,贴上项目地址:

1
arduino复制代码https://github.com/TransparentLC/WechatMomentScreenshot

在线体验地址:

1
arduino复制代码https://akarin.dev/WechatMomentScreenshot/

最后

推荐的开源项目已经收录到 GitHub 项目,欢迎 Star:

1
bash复制代码https://github.com/chenyl8848/great-open-source-project

或者访问网站,进行在线浏览:

1
bash复制代码https://chencoding.top:8090/#/

大家的点赞、收藏和评论都是对作者的支持,如文章对你有帮助还请点赞转发支持下,谢谢!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring中控制反转究竟反转的什么

发表于 2024-04-28

你好,这里是codetrend专栏“Spring6全攻略”。

控制反转(Inversion of Control, IoC)是一种软件设计原则,它将传统的程序设计中的控制权从应用程序代码转移到框架或容器,从而实现了松耦合和更好的可维护性。

在控制反转的概念中,应用程序的组件不再负责自己的创建和管理,而是交给外部容器来负责。这样做的好处是降低了组件之间的依赖关系,提高了代码的灵活性和可测试性。

Spring框架是一个经典的IoC容器,它通过依赖注入(Dependency Injection, DI)的方式实现了控制反转。在Spring中,开发者只需要定义组件及其依赖关系,而框架负责实例化和管理这些组件,将依赖关系注入到需要的地方。

依赖注入(Dependency Injection, DI)是IoC的一种专门形式,其中对象仅通过构造函数参数、工厂方法参数或在对象实例被构建后设置的属性来定义它们的依赖关系(即与之协同工作的其他对象)。接着,在创建bean时,IoC容器会注入这些依赖项。这一过程本质上是对bean自身直接控制其依赖项的实例化或定位方式的反转(因此得名“控制反转”),通常采用直接构造类或类似服务定位器模式的机制。

整个过程用mermaid流程图表示如下:

是否应用程序IoC容器创建Bean实例化Bean解析依赖关系依赖注入通过构造函数参数或工厂方法参数或属性设置注入依赖项使用服务定位器等机制定位依赖项
org.springframework.beans 和 org.springframework.context 包构成了Spring框架IoC容器的基础。关于IoC的代码实现都是放在这里面的。

BeanFactory 接口提供了一个高级配置机制,能够管理任何类型的对象。而ApplicationContext 是 BeanFactory 的一个子接口,并增加了以下功能:

  • 更易于集成Spring的AOP特性
  • 消息资源处理(用于国际化)
  • 事件发布
  • 应用层特定上下文,例如Web应用程序中使用的WebApplicationContext

简而言之,BeanFactory 提供了配置框架和基本功能,而ApplicationContext 则扩展了更多企业级特有的功能。ApplicationContext 完全包含了BeanFactory 的所有功能。

在Spring中,构成应用程序核心并由Spring IoC容器管理的对象被称为bean。

bean是由Spring IoC容器实例化、组装和管理的对象。除此之外,bean只是应用中的众多对象之一。bean及其之间的依赖关系体现在容器所使用的配置元数据中。

SpringBean的历史渊源

Jakarta EE中定义了一个Enterprise Beans。由于Spring6框架和Jarkata EE中的规范是息息相关的,通过对比的方法来一探究竟。

Spring Bean 是指在Spring框架中由IoC容器管理的对象实例,也被称作“Spring组件”。这些Bean构成了应用程序的主要部分,负责承载业务逻辑和服务功能。

Spring Bean的特点如下:

  • 容器管理:Spring IoC(控制反转)容器负责Bean的生命周期管理,包括创建、初始化、装配依赖、销毁等一系列操作。
  • 依赖注入:Bean之间的依赖关系通过依赖注入(Dependency Injection,DI)来建立,容器负责将所需的依赖项注入到Bean中,而不是由Bean自身去寻找或创建这些依赖。
  • 配置元数据:Spring Bean的定义和配置信息通常存储在XML配置文件、Java配置类或者注解中,这些配置元数据指导了IoC容器如何创建和管理Bean。
  • 作用域:Spring Bean有多种作用域,如Singleton(单例)、Prototype(原型)、Request、Session、Application和WebSocket等,不同的作用域决定了Bean实例在应用程序中的创建和共享策略。
  • 生命周期:Spring Bean拥有完整的生命周期,允许开发者通过实现特定的接口(如InitializingBean、DisposableBean或使用@PostConstruct/@PreDestroy注解)来自定义初始化和销毁逻辑。
  • 可扩展性:通过BeanPostProcessor和FactoryBean等扩展点,可以进一步自定义Bean的创建过程和行为。
  • 松耦合:通过依赖注入实现松耦合,使得各组件间相互独立,更容易维护和替换。
  • 面向切面编程(AOP):Spring Bean能够无缝地与Spring的AOP机制相结合,支持诸如事务管理、日志记录、权限检查等横切关注点的统一处理。
  • 自动装配:Spring支持自动装配功能,可以通过@Autowired注解或其他机制自动匹配并注入相应的依赖服务。

Jakarta EE 中的EJB(Enterprise JavaBeans)是一种用于开发企业级分布式应用程序的标准组件模型,它为开发人员提供了封装业务逻辑并在多个客户端之间复用的能力。

EJB主要具有如下特点:

  • 容器管理:EJB运行在EJB容器中,容器负责管理Bean的生命周期、安全、事务、并发、资源池化等非功能性需求,减轻了开发者的工作负担。
  • 事务管理:EJB提供全面的事务支持,包括全局事务(Global Transactions)和局部事务(Container-Managed Transactions, CMT),能够跨多个数据库或消息队列资源进行事务管理。
  • 安全性:EJB容器支持基于角色的安全性,开发者可以在EJB级别定义访问控制策略,确保只有授权用户或角色才能访问特定的业务服务。
  • 消息驱动:消息驱动Bean可以监听JMS(Java Message Service)消息,实现异步处理和解耦,适用于高性能的消息传递场景。
  • 持久化支持:实体Bean特别设计用于映射数据库表,提供了ORM(对象关系映射)的功能,使得业务对象可以自动持久化至数据库。
  • 远程访问:EJB支持远程调用,客户端可以通过RMI(Remote Method Invocation)协议访问部署在服务器上的EJB组件。

与Spring6框架对比,EJB的一些复杂性和重量级特性逐渐显得过重,尤其是在易用性、测试友好度以及性能方面。

Spring6通过提供更为简洁的编程模型和灵活的事务管理等功能,一定程度上替代了EJB在某些场景下的应用。

Spring6通过组件项目的方式提供了对EJB的替代。Spring只提供最基础的核心功能。比如spring-jms用于与 JMS(Java Message Service)消息队列的集成、 spring-tx提供了对事务管理的支持。

所以Spring6的设计在于轻量级、组件可选的方式来完成了一个又一个企业级应用的搭建。

说说什么是SpringBean

Spring IoC容器管理一个或多个bean。这些bean是根据您提供给容器的配置元数据创建的(例如,以XML <bean/> 定义的形式)。

在容器内部,这些bean定义被表示为BeanDefinition对象,其中包含(除其他信息外)以下元数据:

  • 带包限定名的类名:通常是指定bean的实际实现类。
  • Bean行为配置元素,描述了bean在容器中应该如何表现(作用域、生命周期回调等)。
  • 对于bean完成其工作所必需的其他bean的引用。这些引用也被称为协作者或依赖项。
  • 其他配置设置,用于在新创建的对象上设置属性——例如,在管理连接池的bean中设置池大小限制或使用连接数。

这些元数据转换成构成每个bean定义的一组属性。下表描述了这些属性:

属性 描述章节
Class 实例化Bean
Name 命名Bean
Scope Bean作用域
Constructor arguments 依赖注入
Properties 依赖注入
Autowiring mode 自动装配协作者
Lazy initialization mode 懒加载Bean
Initialization method 初始化回调
Destruction method 销毁回调

表1. Bean定义属性

通过Bean定义属性这张表格可以看出Bean的全景图,而Spring6基于此提供了一个完整的实现方案。

除了包含创建特定bean所需信息的bean定义之外,ApplicationContext实现还允许注册由用户在容器外部创建的现有对象。这通过访问ApplicationContext的BeanFactory来实现,即调用getBeanFactory()方法,该方法返回DefaultListableBeanFactory实现。DefaultListableBeanFactory通过registerSingleton(..)和registerBeanDefinition(..)方法支持这种注册功能。然而,典型的应用程序通常仅使用通过常规bean定义元数据定义的bean。

注意:bean元数据和手动提供的单例实例需要尽早注册,以便容器在自动装配和其他内省步骤中正确地解析它们。

虽然在一定程度上支持覆盖现有元数据和现有单例实例,但在运行时(与对工厂的实时访问同时)注册新的bean并未得到官方支持,这可能会导致并发访问异常、bean容器状态不一致,或者两者兼有。

说说 Spring IoC容器

org.springframework.context.ApplicationContext 接口代表了Spring的IoC(控制反转)容器,并负责bean的实例化、配置和组装。该容器通过读取配置元数据获取关于需要实例化、配置和组装哪些对象的指令。

这些配置元数据可以以XML、Java注解或Java代码的形式表示,它允许你明确表达组成应用程序的对象以及这些对象之间的丰富依赖关系。

Spring提供了多个ApplicationContext接口的实现版本。在独立应用程序中,通常会创建一个ClassPathXmlApplicationContext或FileSystemXmlApplicationContext实例。

尽管XML是定义配置元数据的传统格式,但可以通过提供少量XML配置来声明性地启用对Java注解或代码作为元数据格式的支持,从而指导容器使用这些额外的元数据格式。

以下mermaid流程图简单展示了Spring工作过程。业务类与配置元数据相结合,使得在Spring容器ApplicationContext被创建并初始化后,得到的是一个完全配置好且可执行的系统或应用程序。

产生业务类POJOSpring容器ApplicationContext配置元数据Configuration Metadata可执行的系统/应用程序

说说配置元数据(Configuration Metadata)

Spring的Configuration Metadata是指一组用于描述和指导Spring IoC(控制反转)容器如何创建、配置和装配应用中各个对象(即所谓的“bean”)的信息。这种元数据传统上是以一种直观且简洁的XML格式提供的,但也可以采用Java注解或纯Java代码的形式表示。

Configuration Metadata包含了如下关键信息:

  • Bean定义: 对象的类型、名称、构造器参数、属性值和依赖关系等,这些信息告诉Spring容器如何实例化对象。
  • 装配指示: 如何将一个bean与其他bean关联起来,包括设置属性值、引用其他bean、注入集合元素等。
  • 生命周期回调方法: 定义在bean的生命周期中何时调用特定的方法,例如初始化后(@PostConstruct)或销毁前(@PreDestroy)。
  • 容器配置: 容器自身的配置,如自动扫描哪些包以发现组件、启用特定的特性(如自动装配或AOP代理)等。

在XML配置文件中,配置元数据表现为<bean>元素及其内部属性和嵌套元素;在Java配置中,配置元数据则通过标注了@Configuration的类以及标注了@Bean的方法来定义。

Spring Configuration Metadata是程序员向Spring IoC容器传达应用程序对象结构和依赖关系的蓝图,是Spring框架动态装配和管理对象的基础。通过解析和应用这些配置元数据,Spring IoC容器能够在运行时生成一个完全配置好并准备就绪的应用程序对象图。

以下是一个基于xml的Spring配置文件的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
xml复制代码<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>

关于作者

来自一线全栈程序员nine的探索与实践,持续迭代中。

欢迎关注或者点个小红心~

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL-连接查询

发表于 2024-04-28

一.为什么需要连接查询


😊在前面的文章中我们一直使用的是单表查询,虽然我们学习了子查询,但是我们只是使用了子查询的结果作为我们想要查询的那张表的条件,我们并不能将两张表的内容合并在一起,解决这个问题的方法就是连接查询,当然我们也可以将两张表合并在一起,这样就可以满足两张表的内容都查询到了,我们首先分别看下这两张表。

  1. student_info表

  1. student_score表

那么将这两个表合并后保证查询到两张表的数据,那么合并后的数据应该是这样的

number name sex id_number department major enrollment_time subject score
20180101 杜子腾 男 158177199901044792 计算机学院 计算机科学与工程 2018-09-01 母猪的产后护理 78
20180101 杜子腾 男 158177199901044792 计算机学院 计算机科学与工程 2018-09-01 论萨达姆的战争准备 88
20180102 杜琦燕 女 151008199801178529 计算机学院 计算机科学与工程 2018-09-01 母猪的产后护理 100
20180102 杜琦燕 女 151008199801178529 计算机学院 计算机科学与工程 2018-09-01 论萨达姆的战争准备 98
20180103 范统 男 17156319980116959X 计算机学院 软件工程 2018-09-01 母猪的产后护理 59
20180103 范统 男 17156319980116959X 计算机学院 软件工程 2018-09-01 论萨达姆的战争准备 61
20180104 史珍香 女 141992199701078600 计算机学院 软件工程 2018-09-01 母猪的产后护理 55
20180104 史珍香 女 141992199701078600 计算机学院 软件工程 2018-09-01 论萨达姆的战争准备 46
20180105 范剑 男 181048200008156368 航天学院 飞行器设计 2018-09-01 NULL NULL
20180106 朱逸群 男 197995199801078445 航天学院 电子信息 2018-09-01 NULL NULL

可能你就有疑问了为什么杜子腾 杜琦燕原来的表中是一条数据,为什么在合并后就变成两条了?因为首先我们看下第一张表,包含了杜子腾的一条记录,这个是它的个人信息,因为这个表也是个人信息表,但是第二个表是成绩表,包含了杜子腾的很多门课的成绩,我们从上面这个表中就可以看到,包含了两门分别是母猪的产后护理和论萨达姆的战争准备,所以我们想要合并为一张表,对应的肯定是两条数据,个人信息部分是一模一样的,但是仅仅成绩部分不同的两条数据,甚至如果我们想要再添加一个科目成绩的话我们必须再抄写一遍个人信息。

将两张甚至多张表合并为一张会带啦来的问题:

  1. 浪费存储空间,因为这个同学的基本信息合并之后每增加一个科目会被抄写一遍。
  2. 当修改某个学生的基本信息的时候必须修改多处,很容易造成信息不一致,增大维护的困难。

🤡当然鱼和熊掌不可兼得,虽然拆分之后确实解决了数据冗余的问题,但是也带来了查询的困难,为了解决这个问题,所以就有了连接查询的概念和方法。

二.连接的概念


😁为了弄清连接的概念,首先我们先建两张表

1
2
sql复制代码CREATE TABLE t1 (m1 int, n1 char(1));
CREATE TABLE t2 (m2 int, n2 char(1));

然后我们为这两张表插入数据

1
2
sql复制代码INSERT INTO t1 VALUES(1, 'a'), (2, 'b'), (3, 'c');
INSERT INTO t2 VALUES(2, 'b'), (3, 'c'), (4, 'd');

🦊我们在连接第一个表的时候知道,在没有主外键依赖的情况下,连接就是把各个表中的数据都拿出来,进行排列组合合并为一张新的表如下:

像这样表连接的结果集就被称为笛卡尔积,我们来尝试着连接查询一下。

1
sql复制代码SELECT * FROM t1,t2;

其实我们还有几种等价的写法,结果依然是一样的。

1
sql复制代码SELECT t1.m1,t1.n1,t2.m2,t2.n2 FROM t1,t2;

由于t1 t2表中的列名没有重复的所以,MySQL服务器并不会懵逼,我门也可以直接这样写。

1
sql复制代码SELECT m1,m2,n1,n2 FROM t1,t2;

也等价于如下的查询语句。

1
sql复制代码SELECT t1.*,t2.* FROM t1,t2;

三.连接过程简介


👽首先我们先看下下面这个语句,然后分析表连接的过程

1
sql复制代码SELECT * FROM t1, t2 WHERE t1.m1 > 1 AND t1.m1 = t2.m2 AND t2.n2 < 'd';

在这个查询中我们看到了三个条件。

t1.m1 > 1

t1.m1 = t2.m2

t2.n2 < d

  1. 首先会确定第一个需要查询的表,这个表被称之为驱动表,那么在这个查询中驱动表就是t1就会查询满足条件t1.m1>1的这个条件的数据,单表查询到的数据如下。
1
2
3
4
5
6
sql复制代码+------+------+
| m1 | n1 |
+------+------+
| 2 | b |
| 3 | c |
+------+------+
  1. 然后t1表查找到第一条记录,也就是t1.m1=2同时t1.b1 = b这个时候会匹配过滤条件,t1.m1 = t2.m2 AND t2.n2 < 'd'匹配到的时候t2.m2=2并且t2.n2也是小于d的,就会得到如下结果。
1
2
3
4
5
sql复制代码+------+------+------+------+
| m1 | n1 | m2 | n2 |
+------+------+------+------+
| 2 | b | 2 | b |
+------+------+------+------+
  1. 然后按照上面的步骤一次匹配类推得到t1.m1=3的时候结果如下。
1
2
3
4
5
sql复制代码+------+------+------+------+
| m1 | n1 | m2 | n2 |
+------+------+------+------+
| 3 | c | 3 | c |
+------+------+------+------+

🧙在最后MySQL会帮我们把所有查询的数据放在一块,就得到了最后的结果,并且我们可以看到整个过程t1只被查询了一次但是这个过程中t2被查询了多次。

1
2
3
4
5
6
sql复制代码+------+------+------+------+
| m1 | n1 | m2 | n2 |
+------+------+------+------+
| 2 | b | 2 | b |
| 3 | c | 3 | c |
+------+------+------+------+

四.内连接和外连接


🎯我们首先来同学们的查询下基本信息和成绩。

1
sql复制代码SELECT student_info.number,name,major,subject FROM `student_info`,`student_score` WHERE student_info.number = student_score.number;

由于范剑和朱逸群两个人在成绩表里面没有,可能是没有参加考试,导致两个number并不能匹配,所以最后的记录是查询不到两个人的记录的,但是有的时候我们可能想把所有的记录都查出来,不管它有没有参加考试。

🎪为了解决这个问题就有了内连接和外连接我们上边使用的就是内连接,接下来我们看下外连接如何使用,提起内连接和外连接就必须先区分两个关键字那就是WHERE和ON那么WEHER和ON究竟什么区别哪?

where就是我们前边使用的那种,不论是内连接还是外连接,不符合的都不放在结果集中。

on对于外连接而言,如果在被驱动表中无法找到对应的记录被驱动表中的记录仍然会放入结果集,用NULL填充,在内连接中和where是一样的,on一般被称之为连接条件,where被称为过滤条件。

  1. 左侧表为驱动表:左外连接。
1
sql复制代码SELECT * FROM t1 LEFT [OUTER] JOIN t2 ON 连接条件 [WHERE 普通过滤条件];

比如我们使用左外连接查询一下学生的基本信息和成绩。

1
sql复制代码SELECT student_info.number,name,major,subject FROM `student_info` LEFT JOIN `student_score` ON student_info.number =student_score.number;

  1. 右侧表为驱动表:右外连接。
1
sql复制代码SELECT * FROM t1 RIGHT [OUTER] JOIN t2 ON 连接条件 [WHERE 普通过滤条件];

我们把查询方法改为右连接查询。

1
sql复制代码SELECT student_info.number,name,major,subject FROM `student_info` RIGHT JOIN `student_score` ON student_info.number =student_score.number;

😊内连接:内连接和外连接的根本区别就是在驱动表中的记录不符合ON子句中的连接条件时不会把该记录加入到最后的结果集。

1
sql复制代码SELECT * FROM t1 [INNER | CROSS] JOIN t2 [ON 连接条件] [WHERE 普通过滤条件];

其实我们最开始使用的都是内连接的语法,内连接的写法比较多,一下几种都是等价的,推荐使用INNER JOIN因为语义比较明确,容易和左外连接,右外连接区分。

1
2
3
4
sql复制代码SELECT * FROM t1 JOIN t2;
SELECT * FROM t1 INNER JOIN t2;
SELECT * FROM t1 CROSS JOIN t2;
SELECT * FROM t1, t2;

🚨需要注意的是:内连接驱动表和被驱动表可以互换,但是外连接互换后结果会不同。

五.总结与扩展


😊上边说了很多,给大家的感觉不是很直观,我们直接把表t1和t2的三种连接方式写在一起,这样大家理解起来就很easy了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
sql复制代码SELECT * FROM t1 INNER JOIN t2 ON t1.m1 = t2.m2;
+------+------+------+------+
| m1 | n1 | m2 | n2 |
+------+------+------+------+
| 2 | b | 2 | b |
| 3 | c | 3 | c |
+------+------+------+------+


SELECT * FROM t1 LEFT JOIN t2 ON t1.m1 = t2.m2;
+------+------+------+------+
| m1 | n1 | m2 | n2 |
+------+------+------+------+
| 2 | b | 2 | b |
| 3 | c | 3 | c |
| 1 | a | NULL | NULL |
+------+------+------+------+


SELECT * FROM t1 RIGHT JOIN t2 ON t1.m1 = t2.m2;
+------+------+------+------+
| m1 | n1 | m2 | n2 |
+------+------+------+------+
| 2 | b | 2 | b |
| 3 | c | 3 | c |
| NULL | NULL | 4 | d |
+------+------+------+------+

🤡表的别名,表和列名一样也可以设置别名 依然使用AS,如下:

1
sql复制代码SELECT s1.number, s1.name, s1.major, s2.subject, s2.score FROM student_info AS s1 INNER JOIN student_score AS s2 WHERE s1.number = s2.number;

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

读懂 GraphRAG:提升LLM企业落地能力,智能问答革命

发表于 2024-04-28

在企业中单纯的使用LLM并不会产生太好的效果,因为它们不会对有关组织活动的特定领域专有知识进行编码,而这些知识实际上会给信息对话界面带来价值萃取。很多企业尝试通过RAG来优化这个过程,并且越来越多的人在RAG的方向上不断的研究,今天我们来讨论一下GraphRAG,这种结合知识图谱、图数据库作为大模型结合私有知识系统的最新技术,解析它是如何 释放RAG 的潜力,增强LLM回答复杂问题的准确性和相关性。

什么是RAG?

RAG 是一种自然语言查询方法,用于通过外部知识增强现有的LLM,因此如果问题需要特定知识,问题的答案会更相关。它包括一个检索信息组件,用于从外部源获取附加信息,也称为“基础上下文”,然后将其馈送到 LLM 提示以更准确地回答所需的问题。

这种方法是最便宜和最标准的方法,可以通过额外的知识来增强 LLM 以回答问题。此外,它被证明可以减少 LLM 产生幻觉的倾向,因为这一代人更坚持来自上下文的信息,而这些信息通常是可靠的。由于该方法的这种性质,RAG 成为增强生成模型输出的最流行的方法。

除了问答之外,RAG 还可以用于许多自然语言处理任务,例如从文本中提取信息、推荐、情感分析和摘要等。

但RAG在解决问题的时候,也会有表现非常差的情况:

  • 基本 RAG 很难将关键点联系起来。当回答问题需要通过共享属性遍历不同的信息以提供新的综合见解时,就会发生这种情况。
  • 要求基本 RAG 全面理解大型数据集合甚至单个大型文档的概括语义概念时,基础 RAG 表现不佳。

Graph RAG

GraphRAG是一种结合了知识图谱和大型语言模型(LLM)的技术,旨在提高问答系统的能力。微软研究人员宣布了GraphRAG,这是一种新方法,通过AI生成的知识图谱来增强AI驱动的问答系统。GraphRAG技术要求大型语言模型根据私有数据集创建知识图谱,从而改善问答过程。

GraphRAG利用图神经网络(GNN)的结果中的图嵌入来增强文本嵌入,以提高用户查询响应推理的能力。这种方法被称为软提示(Soft-prompting),是一种提示技术。此外,GraphRAG还被用于训练LLMs在不直接提供数据的情况下,通过图基数据表示进行学习,这使得模型能够访问大量的结构化知识。

如何执行 RAG?

要实现用于问答的 Graph RAG,您需要选择可以将哪些信息发送给 LLM。这通常是通过根据用户问题的意图查询数据库来完成的。为此目的最合适的数据库是向量数据库,它通过嵌入捕获连续向量空间中的潜在语义、句法结构和项目之间的关系。丰富的提示包含用户问题以及预先选择的附加信息,因此生成的答案会将其考虑在内。

一个简单的 Graph RAG 可以如下去简单实现:

  1. 使用 LLM(或其他)模型从问题中提取关键实体
  2. 根据这些实体检索子图,深入到一定的深度
  3. 利用获得的上下文利用 LLM 产生答案。

例如 LlamaIndex 这样的 LLM 编排工具,开发者可以专注于 LLM 的编排逻辑和 pipeline 设计,而不用亲自处理很多细节的抽象与实现。

所以,用 LlamaIndex,我们可以轻松搭建 Graph RAG,甚至整合更复杂的 RAG 逻辑,比如 Graph + Vector RAG。

尽管基本实施很简单,但您需要考虑一系列挑战和注意事项,以确保结果的良好质量:

  • 数据质量和相关性对于 Graph RAG 的有效性至关重要,因此应该考虑如何获取最相关的内容来发送 LLM 以及发送多少内容等问题。
  • 处理动态知识通常很困难,因为需要不断用新数据更新向量索引。根据数据的大小,这可能会带来进一步的挑战,例如系统的效率和可扩展性。
  • 生成结果的透明度对于使系统值得信赖和可用非常重要。有一些快速工程技术可以用来刺激LLM解释答案中包含的信息的来源。

Graph RAG 的不同种类

Graph RAG 是对流行的 RAG 方法的增强。 Graph RAG 包括一个图形数据库,作为发送到 LLM 的上下文信息的来源。向LLM提供从较大尺寸文档中提取的文本块可能会缺乏必要的上下文、事实正确性和语言准确性,而LLM无法深入理解收到的文本块。与向 LLM 发送纯文本文档块不同,Graph RAG 还可以向 LLM 提供结构化实体信息,将实体文本描述与其许多属性和关系相结合,从而鼓励 LLM 产生更深入的见解。借助 Graph RAG,矢量数据库中的每条记录都可以具有丰富的上下文表示,从而提高特定术语的可理解性,因此 LLM 可以更好地理解特定主题领域。Graph RAG 可以与标准 RAG 方法结合起来,以获得两全其美的效果——图表示的结构和准确性与大量文本内容相结合。

我们可以根据问题的性质、现有知识图中的领域和信息总结Graph RAG 的几种变体:

  • 图形作为内容存储:提取相关的文档块并要求 LLM 使用它们来回答。这种多样性需要一个包含相关文本内容和元数据的知识图谱,并与矢量数据库集成。
  • 作为主题专家的图表实体链:提取与自然语言(NL)问题相关的概念和实体的描述,并将其作为附加的“语义上下文”传递给LLM。理想情况下,描述应包括概念之间的关系。这种多样性需要具有全面概念模型的知识图谱,包括相关本体、分类法或其他实体描述。实现需要或其他机制来识别与问题相关的概念。
  • 图形作为数据库:将 NL 问题(部分)映射到图形查询,执行查询并要求 LLM 总结结果。这种多样性需要一个包含相关事实信息的图表。这种模式的实现需要某种 NL 到图查询工具和实体链接。

总结

GraphRAG(Graph Retrieval-Augmented Generation)是一种结合了图数据库和检索增强生成技术的先进方法,它在多种应用场景中展现出了其独特的价值和潜力。通过结合图数据库的强大表示能力和大语言模型的理解能力,随着技术的进一步发展,GraphRAG的应用场景将会更加广泛和深入

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL-条件查询

发表于 2024-04-28

一.简单搜索条件


😊在上篇文章我们讲解了基本的简单查询,但是在实际的工作中,数据库表中的数据往往是比较多,我们一般也不会全部将数据查出来,我们往往会通过某些条件进行查询,比如姓名id等等,比如,查询名称为范剑同学的基本信息。

1
sql复制代码SELECT `name`,`number`,`id_number` FROM `student_info` WHERE name='范剑';

其实这个就是where子句,我们可以在where子句之后使用一些条件限制来查询对应的数据,在where子句中还有很多操作限制符号,如下:

1
2
3
4
5
6
7
css复制代码a = b  // a等于b
a<>b // a不等于b
a<b // a小于b
a<=b // a小于等于b
a>=b // a大于等于b
a BETWEEN b and c // a满足 b<=a<=c
a Not BETWEEN b and c // a不满足 b<=a<=c

🥴然后我们根据上述的限制符号来写一下下面的几个例子

  1. 查询学号大于20180103的学生信息。
1
sql复制代码SELECT * FROM `student_info` WHERE number > 20180103;

  1. 查询专业不是计算机科学与工程的一些学生信息。
1
sql复制代码SELECT * FROM `student_info` WHERE major<>'计算机科学与工程';

  1. 查询学号在20180102~20180104间的学生信息。
1
sql复制代码SELECT * FROM `student_info` WHERE `number` BETWEEN 20180102 AND 20180104;

  1. 查询学号不在20180102~20180104这个区间内的所有学生信息
1
sql复制代码SELECT * FROM `student_info` WHERE `number` NOT BETWEEN 20180102 AND 20180104;

二.匹配列表中的元素


😁在一些情况下我们并不会仅仅按照某一个条件查询,我们可能查询的是某一类数据,比如计算机学院和航天学学院的学生信息,这种查询情况,上述的条件就无法满足,这个时候我们就需要使用IN和NOT IN。

1
2
sql复制代码a IN  (b1,b2,b3....)  // a是b1,b2.b3中的某一个
a NOT IN (b1,b2,b3...) // a不是b1,b2,b3中的某一个
  1. 查询软件工程和飞行器设计专业的学生信息。
1
sql复制代码SELECT * FROM `student_info` WHERE `major` IN ('计算机科学','软件工程');

  1. 查询不是这两个专业的学生的信息。
1
sql复制代码SELECT * FROM `student_info` WHERE `major` NOT IN ('计算机科学','软件工程');

三.匹配NULL值


😊在SQL中NULL代表没有值,代表这个数据中你并没有进行数据的填写,所以有的时候我们会匹配某些字段的值是否为NULL。

1
2
sql复制代码IS NULL  // a IS NULL  a的值为NULL
IS NOT NULL // a IS NOT NULL a的值不为NULL
  1. 查询student_info表的name列不是NULL的学生记录有哪些
1
sql复制代码SELECT * FROM `student_info` WHERE `name` IS NOT NULL;

四.多个搜索条件的查询


🦊在上述查询中不论是简单的搜索,还是判断是否为NULL等,都是简单的单个搜索条件的查询,接下来我们看下多个条件是如何查询的。

  1. AND操作符:当符合多个条件的时候使用,比如student_score表中找出科目为’母猪的产后护理’并且成绩大于75分的记录。
1
sql复制代码SELECT * FROM `student_score` WHERE `subject`='母猪的产后护理' AND `score` > 75;

如果再增加条件哪?比如再加上一个条件,限定number为20180101的数据。

1
sql复制代码SELECT * FROM `student_score` WHERE `subject`='母猪的产后护理' AND `number`=20180101 AND `score` > 75;

  1. 我们有时需要某条记录在符合某一个搜索条件的时候就将其加入结果集中,这种情况我们可以使用OR操作符来连接多个搜索条件。
1
sql复制代码SELECT * FROM `student_score` WHERE `score` > 95 OR `score` < 55;

  1. 更复杂的搜索条件的组合,比如从student_score表中找出课程为’论萨达姆的战争准备’,并且成绩大于95分或者小于55分的记录。
1
sql复制代码SELECT * FROM `student_score` WHERE (`score` > 95 OR `score` < 55) AND `subject` = '母猪的产后护理';

五.通配符


😶‍🌫️有的时候我们并不能精确的查找的自己想要的结果,这个时候我们就需要使用通配符进行模糊查询。

1
2
sql复制代码% // 代表任意一个字符串
_ // 代表任意一个字符
  1. 查询student_info表中name以’杜’开头的记录
1
sql复制代码SELECT * FROM `student_info` WHERE name LIKE '杜%';

  1. 学生名字里边包含了一个’香’字,那我们可以这么查。
1
sql复制代码SELECT * FROM `student_info` WHERE `name` LIKE '%香%';

  1. 我们想查询姓’范’,并且姓名只有2个字符的记录,我们需要使用_通配符
1
sql复制代码SELECT * FROM `student_info` WHERE `name` LIKE '范_';

六.转义通配符


👽有的时候我们表中存储的内容可能本身就包括类似于%_这些字符,但是可能MySQL会识别为通配符,这个情况我们如何解决哪?解决方法就是进行转义,就是告诉MySQL 这个是普通字符。

1
2
sql复制代码'%'代表普通字符'%'
'_'代表普通字符'_'

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL-简单查询

发表于 2024-04-28

一.什么是DQL


🥴DQL:Data Query Language数据查询语言,select用于从一个或者多个表中检索中的行(Recod)

🎯在开始进行数据库查询之前,我们需要创建几张表并且为这几张表插入相应的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sql复制代码# 创建学生信息表
CREATE TABLE student_info (
number INT PRIMARY KEY,
name VARCHAR(5),
sex ENUM('男', '女'),
id_number CHAR(18),
department VARCHAR(30),
major VARCHAR(30),
enrollment_time DATE,
UNIQUE KEY (id_number)
);

# 创建学生成绩表
CREATE TABLE student_score (
number INT,
subject VARCHAR(30),
score TINYINT,
PRIMARY KEY (number, subject),
CONSTRAINT FOREIGN KEY(number) REFERENCES student_info(number)
);

然后我们向我们创建的表中插入对应的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sql复制代码# 向学生信息表中插入数据
INSERT INTO student_info ( number, NAME, sex, id_number, department, major, enrollment_time )
VALUES
( 20180101, '杜子腾', '男', '158177199901044792', '计算机学院', '计算机科学与工程', '2018-09-01' ),
( 20180102, '杜琦燕', '女', '151008199801178529', '计算机学院', '计算机科学与工程', '2018-09-01' ),
( 20180103, '范统', '男', '17156319980116959X', '计算机学院', '软件工程', '2018-09-01' ),
( 20180104, '史珍香', '女', '141992199701078600', '计算机学院', '软件工程', '2018-09-01' ),
( 20180105, '范剑', '男', '181048199308156368', '航天学院', '飞行器设计', '2018-09-01' ),
( 20180106, '朱逸群', '男', '197995199501078445', '航天学院', '电子信息', '2018-09-01' );

# 向学生成绩表中插入数据
INSERT INTO student_score ( number, SUBJECT, score )
VALUES
( 20180101, '母猪的产后护理', 78 ),
( 20180101, '论萨达姆的战争准备', 88 ),
( 20180102, '母猪的产后护理', 100 ),
( 20180102, '论萨达姆的战争准备', 98 ),
( 20180103, '母猪的产后护理', 59 ),
( 20180103, '论萨达姆的战争准备', 61 ),
( 20180104, '母猪的产后护理', 55 ),
( 20180104, '论萨达姆的战争准备', 46 );

然后我们可以在Navicat上面查询自己创建的表首先是学生信息表。

然后我们再查看学生成绩表。

二.简单的查询


😊查询表中的某一列的数据。

1
sql复制代码SELECT `name` from `student_info`;

我们在查询某一列的数据的时候还可以指定列的别名,通过以下的方式。

1
sql复制代码SELECT `name` AS 'othername' from `student_info`;

我们会发现查询的结果列的别名发生了变化,从原来的name变成了我们指定的othername,别名也有简写的方式来指定。

1
sql复制代码SELECT `name` '名字' FROM `student_info`;


🧙一次性查询多个表中的字段,多个字段之间通过,分割。

1
sql复制代码SELECT `name`,`sex`,`number` FROM `student_info`;

🤡一次性查询表中的所有数据字段;

1
sql复制代码SELECT * FROM `student_info`;

😁对查询结果去重,我们查询下student_info然后查看下结果。

1
sql复制代码SELECT `department` FROM `student_info`;

我们会看到有很多重复的内容,我们需要对它们进行去重,去重需要使用DISTINCT放在列名前进行去重

1
sql复制代码SELECT DISTINCT `department` FROM `student_info`;

然后我们就会发现重复的内容被我们去掉了。


👽对多行的重复的情况进行去重

1
sql复制代码SELECT DISTINCT `department`,`major` FROM `student_info`;


🦊对查询的条数进行限制,因为有的时候数据很多会直接把屏幕撑爆,所以我们很多情况下需要限制条数

1
sql复制代码LIMIT 开始行, 限制条数;
1
sql复制代码SELECT `number`,`name`,`department` FROM `student_info` LIMIT 0,4;

当然我们也可以使用MySQL指定的默认行,仅仅对条数进行限制。

1
sql复制代码SELECT `number`,`name`,`department` FROM `student_info` LIMIT 4;


😶‍🌫️对查询结果进行排序,排序的语法如下:其中ASC表示升序,DESC表示降序。

1
sql复制代码ORDER BY 列名 ASC|DESC

那么我们对刚才新建的成绩表进行按照score进行降序排序。

1
sql复制代码SELECT * FROM `student_score` ORDER BY `score` DESC;


🎯按照多列的值进行排序,语法如下,不同的字段使用,隔开,不写排序方式默认升序排序。

1
sql复制代码ORDER BY 列1 ASC|DESC, 列2 ASC|DESC ...
1
sql复制代码SELECT * FROM `student_score` ORDER BY `subject`,`score` DESC;

三.案例练习


😊找出成绩表中成绩最低的那条记录。

1
sql复制代码SELECT * FROM `student_score` ORDER BY `score` ASC LIMIT 1;

🧙其实这就涉及到了limit和order by的混合使用,limit需要在order by后面使用,然后让整个成绩表进行升序,然后取第一条就是成绩最低的那条数据。


本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

MySQL-DDL与DML语言

发表于 2024-04-28

一.认识SQL语句


🤡我们在数据库中希望操作数据库中的数据,就需要有和数据库直接沟通的语言,这个语言就是SQL:

  1. SQL称之为结构化查询语言。
  2. 使用SQL编写出来的语句,就称之为SQL语句。
  3. SQL语言用于对数据库进行操作。

😭事实上,常见的关系型数据库SQL语句都是比较相似的,所以你学会了MySQL中的SQL语句,之后去操作oracle或者其他关系型数据库,也是非常的方便。

🔔SQL语句的常用规范:

  1. 通常关键字使用大写的,比如CREATE TABLE SHOW等等;
  2. 一条语句结束后,需要以;结尾;
  3. 如果遇到关键字作为表明或者字段名称可以使用``包裹;

二.SQL语句的分类


😊常见的SQL语句分为四类:

  1. DDL语句:数据定义语言,可以通过DDL语句对数据库或者表进行,创建,删除,修改操作。
  2. DML语句:数据操作语言,可以通过DML语句进行添加,删除,修改等操作。
  3. DQL语句:数据查询语言,可以通过DQL从数据库中查询记录(重点)。
  4. DCL语句:数据控制语言,对数据库,表格的权限进行相关性访问控制操作。

三.DDL之对数据库进行操作


🎯首先我们需要在Navicat的某个数据库下新建一个查询,就可以在查询界面执行相应的SQL语句。

  1. 查看当前所有的数据库;
1
sql复制代码SHOW DATABASES;

  1. 使用某一个数据库
1
sql复制代码USE coderhub;

  1. 查看当前使用的数据库;
1
sql复制代码SELECT DATABASE();
  1. 创建一个数据,但是这样当有了这个数据库的时候会报错的,我们更推荐使用下方的写法。
1
sql复制代码CREATE DATABASE IF NOT EXISTS mycode;
  1. 删除某一个数据库,如果数据库不存在就会报错,我们需要判断是否存在。
1
sql复制代码DROP DATABASE IF EXISTS mycode;
  1. 修改数据库(不常用了解即可)
1
sql复制代码ALTER DATABASE coderhub CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;

四.DDL之对表进行操作


🥴查看当前数据库中有哪些表。

1
sql复制代码SHOW TABLES;

🎪查看某张表的结构。

1
sql复制代码DESC user;

😶‍🌫️创建一张新的表。

1
2
3
4
5
sql复制代码CREATE TABLE IF NOT EXISTS `grades`(
name VARCHAR(10),
age INT,
height DOUBLE
)

😊删除某张表

1
sql复制代码DROP TABLE IF EXISTS `user`;

🥴创建一个完整的表结构

1
2
3
4
5
6
sql复制代码CREATE TABLE IF NOT EXISTS `myuser`(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20) UNIQUE NOT NULL,
level INT DEFAULT 0,
telPhone VARCHAR(20) UNIQUE NOT NULL
);

五.SQL数据类型-数字类型


👽我们知道不同的数据类型的数据会划分为不同的数据类型,在数据库中也一样,MySQL支持的数据库的书库类型有:数字类型日期和时间字符串类型(字符和字节)空间类型和JSON数据类型。

  1. 数字类型:MySQL对数字类型支持的有很多

  1. 浮点数字类型:FLOAT DOUBLE(FLOAT是四个字节,DOUBLE是八个字节)
  2. 精确数据类型:DECIMAL NUMBERIC(DECIMAL是NUMERIC的实现形式)

六.SQL数据类型-日期类型


🤡MYSQL的日期类型也很多

  1. YEAR以YYYY格式显示范围是1901到2155,和0000;
  2. DATE类型用于具有日期部分但是没有时间部分的值,显示格式是YYYY-MM-DD显示值。
  3. DATETIME用于表示具有时间部分的日期,格式是YYYY-MM-DD hh:mm:ss显示值。
  4. TIMESTAMP数据用于包含日期时间的值,但是范围是UTC的时间范围1970-2038。

七.SQL数据类型-字符串类型


😭MySQL的字符串类型表示方式如下:

  1. CHAR类型在创建表时为固定长度,长度可以是0-255之间的任意值,在查询时候会删除后面的空格。
  2. VARCHAR类型的值是可变长度的字符串,长度可以指定0-65535之间的值,在被查询时候不会删除空格。
  3. BINARY和VARBINARY类型用于存储二进制字符串,存储的是字节字符串。
  4. BLOB用于存储的二进制类型。
  5. TEXT用于存储大的字符串类型。

八.表约束


😊PRIMARY KEY:主键,在一张表中我们为了区分每一条数据的唯一性,必须有一个字段是永远不会重复的,并且不能为空,这个字段我们通常将它设置为主键。

  1. 主键是表中唯一的索引;
  2. 并且必须是NOT NULL的,如果没有设置NOT NULL,那么MySQL也会隐式设置为NOT NULL;
  3. 主键也可以是多列索引,PRMARY KEY(key_part…)我们一般称之为联合主键。
  4. 建议:开发中的组件字段应该是和业务无关的,尽量不要使用业务字段来做主键。

📆UNIQUE:某些字段在开发中我们希望是唯一的,不会重复的,

  1. 比如手机号,身份证号码,这些字段我们使用UNIQUE来约束,使用UNIQUE字段在表中必须是不同的。
  2. UNIQUE索引允许NULL包含的列具有多个值NULL。

🔔NOT NULL:不能为空。

  1. 某些字段我们要求用户必须插入值,不可以为空,这个时候我们就可以使用NOT NULL来约束。

🎯DEFAULT:默认值

  1. 某些字段我们希望在没有设置值的时候给予一个默认值,这个时候我们就可以使用DEFAULT来完成。

🎪AUTO_INCREMENT:自动增长。

  1. 某些字段我们不希望设置值的时候我们可以使用自动增长。

🚨外键也是最常见的一种表约束,等我们讲到多表关系的时候再进行讲解。

九.修改表结构


😊修改表结构:修改表名。

1
sql复制代码ALTER TABLE `myuser` RENAME TO `user`;

👽添加一个新的列

1
2
sql复制代码ALTER TABLE `user` ADD `publishTime` DATETIME;
ALTER TABLE `user` ADD `updateTime` DATETIME;

😶‍🌫️删除一列数据

1
sql复制代码ALTER TABLE `user` DROP `updateTime`;

🎯修改列的名称

1
sql复制代码ALTER TABLE `user` CHANGE `publishTime` `publishDate` DATE;

🤡修改列的数据类型

1
sql复制代码ALTER TABLE `user` MODIFY `id` INT;

十.什么是DML语句


🤡DML语句是数据库中的作用是进行数据库的操作,也被称为数据库操作语句。

1
2
3
4
5
6
7
sql复制代码CREATE TABLE IF NOT EXISTS `products`(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`title` VARCHAR(20),
`decription` VARCHAR(200),
`price` DOUBLE,
`publishTime` DATETIME
)

十一.插入数据


😁DML插入语句,向数据库中插入一条数据。

1
2
3
sql复制代码INSERT INTO `products`(title,decription,price,publishTime) VALUES ('iphone100','iphone100只要价格998',998,'2021-10-09');
INSERT INTO `products`(title,decription,price,publishTime) VALUES ('huawei','huawei-mate70价格只要1000',1000,'2021-10-10');
INSERT INTO `products`(title,decription,price,publishTime) VALUES('vivo','vivo x60只需要1200',1200,'2021-10-11');

十二.删除数据


🥴删除表中的所有的数据,(谨慎使用)

1
sql复制代码DELETE from 'products';

👽根据id删除一条数据。

1
sql复制代码DELETE FROM `products` WHERE id = 3;

十三.修改数据


🎯更改表中的某个字段。

1
sql复制代码UPDATE `products` SET decription = '假的别买';

🎪更改表中的某个具体字段。

1
sql复制代码UPDATE `products` SET decription = '好手机666' WHERE id=2;

😶‍🌫️当我们修改某一个数据的时候,显示最新的时间。

1
sql复制代码ALTER TABLE `products` ADD `updateTime` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;


本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

12…399

开发者博客

3990 日志
1304 标签
RSS
© 2024 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%