自定义持久层框架------从零到一手写一个mybatis

JDBC操作问题

JDBC代码

import java.sql.*;
import java.util.ArrayList;
import java.util.List;public class JDBC {public static void main(String[] args) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {//加载驱动Class.forName("com.mysql.jdbc.Driver");//创建连接connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", " root", " root");//写sql语句String sql = "select * from user where username = ?";//获取预处理对象preparedStatement = connection.prepareStatement(sql);//设置参数preparedStatement.setString(1, "tom");//执行sql,获取结果集resultSet = preparedStatement.executeQuery();//封装结果集List<User> userList = new ArrayList<>();while (resultSet.next()) {User user = new User();int id = resultSet.getInt("id");String username = resultSet.getString("username");user.setId(id);user.setUsername(username);userList.add(user);}System.out.println(userList);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();} finally {//关闭资源if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}}
}

JDBC问题总结

  1. 频繁创建、释放数据库连接,造成系统资源浪费
  2. sql语句在代码中硬编码,不易维护
  3. 传参存在硬编码,修改sql还要修改代码
  4. 手动解析结果集

JDBC问题解决思路

  1. 使用数据库连接池初始化连接资源
  2. 将sql语句抽取到xml配置文件中
  3. 使用反射技术,将实体属性与表字段自动映射

自定义框架设计

使用端

提供核心配置文件:

sqlMapConfig.xml:存放数据源信息,引入Mapper.xml的全路径

Mapper.xml:sql语句的配置文件信息

框架端

  1. 读取配置文件

读取后以流的形式存在于内存中,后创建JavaBean存储

Configuration:存放数据库的基本信息、Map<唯一标识,Mapper>

唯一标识:namespace+“.”+id

MappedStatement:sql语句、statement类型、输入参数类型、输出参数类型

  1. 解析配置文件

创建SqlSessionFactoryBuilder类

方法:SqlSessionFactory build()

使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement

创建SqlSessionFactory的实现类DefaultSqlSessionFactory。

  1. 创建SqlSessionFactory

方法:openSession():获取sqlSession接口的实现类的实例对象

  1. 创建sqlSession接口及其实现类

方法:selectOne(String statementId,Object… params)

selectList(String statementId,Object… params)

  1. 创建Executor接口及其实现类

封装JDBC代码实现CRUD

** 设计模式:建造者模式、工厂模式、代理模式**

自定义框架实现

在使用端创建配置文件

<configuration><dataSource><property name="driverClass" value="com.mysql.jdbc.Driver"></property><property name="jdbcUrl" value="jdbc:mysql:///test"></property><property name="username" value="root"></property><property name="password" value="@wdlm212027"></property></dataSource><mapper resource="UserMapper.xml"></mapper>
</configuration>
<mapper namespace="user"><select id="selectList" resultType="com.wdlm.pojo.User">select * from user</select><select id="selectOne" resultType="com.wdlm.pojo.User" parameterType="com.wdlm.pojo.User">select * from user where id=#{id} and username=#{username}</select>
</mapper>

在使用端创建实体类

public class User {private Integer id;private String username;public User() {}public User(Integer id, String username) {this.id = id;this.username = username;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}
}

在框架端导入Maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.wdlm.imybatis</groupId><artifactId>Imybatis</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.encoding>UTF-8</maven.compiler.encoding><java.version>1.8</java.version><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.17</version></dependency><dependency><groupId>c3p0</groupId><artifactId>c3p0</artifactId><version>0.9.1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.10</version></dependency><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency></dependencies></project>

在框架端创建配置类

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;public class Configuration {private DataSource dataSource;Map<String, MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();public DataSource getDataSource() {return dataSource;}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}public Map<String, MappedStatement> getMappedStatementMap() {return mappedStatementMap;}public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {this.mappedStatementMap = mappedStatementMap;}
}
public class MappedStatement {private String id;private String resultType;private String parameterType;private String sql;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getResultType() {return resultType;}public void setResultType(String resultType) {this.resultType = resultType;}public String getParameterType() {return parameterType;}public void setParameterType(String paramterType) {this.parameterType = paramterType;}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}
}

在框架端创建配置文件读取类

public class Resources {public static InputStream getResourceAsStream(String path) {InputStream resource = Resources.class.getClassLoader().getResourceAsStream(path);return resource;}
}

在框架端创建SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream resource) throws PropertyVetoException, DocumentException {XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = xmlConfigBuilder.parseConfig(resource);DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);return defaultSqlSessionFactory;}
}

在框架端创建解析类

public class XMLConfigBuilder {private Configuration configuration;public XMLConfigBuilder() {configuration = new Configuration();}public Configuration parseConfig(InputStream resource) throws DocumentException, PropertyVetoException {Document document = new SAXReader().read(resource);Element rootElement = document.getRootElement();List<Element> list = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element element : list) {String name = element.attributeValue("name");String value = element.attributeValue("value");properties.setProperty(name, value);}ComboPooledDataSource dataSource = new ComboPooledDataSource();dataSource.setDriverClass(properties.getProperty("driverClass"));dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));dataSource.setUser(properties.getProperty("username"));dataSource.setPassword(properties.getProperty("password"));configuration.setDataSource(dataSource);List<Element> mapperList = rootElement.selectNodes("//mapper");for (Element element : mapperList) {String mapperPath = element.attributeValue("resource");InputStream mapperResource = Resources.getResourceAsStream(mapperPath);XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);xmlMapperBuilder.parse(mapperResource);}return configuration;}
}
public class XMLMapperBuilder {private Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}public void parse(InputStream resource) throws DocumentException {Document document = new SAXReader().read(resource);Element rootElement = document.getRootElement();List<Element> list = rootElement.selectNodes("//select");String namespace = rootElement.attributeValue("namespace");for (Element element : list) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String parameterType = element.attributeValue("parameterType");String sql = element.getTextTrim();MappedStatement mappedStatement = new MappedStatement();mappedStatement.setId(id);mappedStatement.setParameterType(parameterType);mappedStatement.setResultType(resultType);mappedStatement.setSql(sql);String key = namespace + "." + id;configuration.getMappedStatementMap().put(key, mappedStatement);}}
}

在框架端创建SqlSessionFactory接口及其实现类

public interface SqlSessionFactory {public SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration);}
}

在框架端创建SqlSession接口及其实现类

public interface SqlSession {public <E> List<E> selectList(String statementId, Object... params);public <T> T selectOne(String statementId, Object... params) throws Exception;}
public class DefaultSqlSession implements SqlSession {private Configuration configuration;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;}@Overridepublic <E> List<E> selectList(String statementId, Object... params) throws Exception {SimpleExecutor simpleExecutor = new SimpleExecutor();MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);return (List<E>) list;}@Overridepublic <T> T selectOne(String statementId, Object... params) throws Exception {List<Object> objects = selectList(statementId, params);if (objects.size()==1){return (T) objects.get(0);}else {throw new RuntimeException("查询结果为空或者过多");}}
}

在框架端创建Executor接口及其实现类

public interface Executor {public <E>List<E> query(Configuration configuration, MappedStatement mappedStatement,Object... params);
}
public class SimpleExecutor implements Executor {@Overridepublic <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {DataSource dataSource = configuration.getDataSource();Connection connection = dataSource.getConnection();String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSql(sql);PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();String parameterType = mappedStatement.getParameterType();Class<?> parameterClass = getClassType(parameterType);for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);String content = parameterMapping.getContent();Field declaredField = parameterClass.getDeclaredField(content);declaredField.setAccessible(true);Object o = declaredField.get(params[0]);preparedStatement.setObject(i + 1, o);}ResultSet resultSet = preparedStatement.executeQuery();String resultType = mappedStatement.getResultType();Class<?> resultTypeClass = getClassType(resultType);Object o = resultTypeClass.newInstance();ArrayList<Object> objects = new ArrayList<>();while (resultSet.next()) {ResultSetMetaData metaData = resultSet.getMetaData();for (int i = 1; i <= metaData.getColumnCount(); i++) {String columnName = metaData.getColumnName(i);Object value = resultSet.getObject(columnName);PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);Method writeMethod = propertyDescriptor.getWriteMethod();writeMethod.invoke(o,value);objects.add(o);}}return (List<E>) objects;}private BoundSql getBoundSql(String sql) {ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);String parseSql = genericTokenParser.parse(sql);List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();BoundSql boundSql = new BoundSql(parseSql, parameterMappings);return boundSql;}private Class<?> getClassType(String parameterType) throws ClassNotFoundException {if (parameterType != null) {return Class.forName(parameterType);}return null;}
}

在框架端创建Sql解析类

public class BoundSql {private String sql;private List<ParameterMapping> parameterMappingList = new ArrayList<>();public BoundSql() {}public BoundSql(String sql, List<ParameterMapping> parameterMappingList) {this.sql = sql;this.parameterMappingList = parameterMappingList;}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}public List<ParameterMapping> getParameterMappingList() {return parameterMappingList;}public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {this.parameterMappingList = parameterMappingList;}
}

在框架端创建Sql解析工具类

public interface TokenHandler {String handleToken(String content);
}
public class GenericTokenParser {private final String openToken; //开始标记private final String closeToken; //结束标记private final com.wdlm.utils.TokenHandler handler; //标记处理器public GenericTokenParser(String openToken, String closeToken, com.wdlm.utils.TokenHandler handler) {this.openToken = openToken;this.closeToken = closeToken;this.handler = handler;}/*** 解析${}和#{}* @param text* @return* 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。* 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现*/public String parse(String text) {// 验证参数问题,如果是null,就返回空字符串。if (text == null || text.isEmpty()) {return "";}// 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。int start = text.indexOf(openToken, 0);if (start == -1) {return text;}// 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,// text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码char[] src = text.toCharArray();int offset = 0;final StringBuilder builder = new StringBuilder();StringBuilder expression = null;while (start > -1) {// 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理if (start > 0 && src[start - 1] == '\\') {builder.append(src, offset, start - offset - 1).append(openToken);offset = start + openToken.length();} else {//重置expression变量,避免空指针或者老数据干扰。if (expression == null) {expression = new StringBuilder();} else {expression.setLength(0);}builder.append(src, offset, start - offset);offset = start + openToken.length();int end = text.indexOf(closeToken, offset);while (end > -1) {存在结束标记时if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时// this close token is escaped. remove the backslash and continue.expression.append(src, offset, end - offset - 1).append(closeToken);offset = end + closeToken.length();end = text.indexOf(closeToken, offset);} else {//不存在转义字符,即需要作为参数进行处理expression.append(src, offset, end - offset);offset = end + closeToken.length();break;}}if (end == -1) {// close token was not found.builder.append(src, start, src.length - start);offset = src.length;} else {//首先根据参数的key(即expression)进行参数处理,返回?作为占位符builder.append(handler.handleToken(expression.toString()));offset = end + closeToken.length();}}start = text.indexOf(openToken, offset);}if (offset < src.length) {builder.append(src, offset, src.length - offset);}return builder.toString();}
}
public class ParameterMapping {private String content;public ParameterMapping(String content) {this.content = content;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}
}
public class ParameterMappingTokenHandler implements com.wdlm.utils.TokenHandler {private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();// context是参数名称 #{id} #{username}public String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return "?";}private ParameterMapping buildParameterMapping(String content) {ParameterMapping parameterMapping = new ParameterMapping(content);return parameterMapping;}public List<ParameterMapping> getParameterMappings() {return parameterMappings;}public void setParameterMappings(List<ParameterMapping> parameterMappings) {this.parameterMappings = parameterMappings;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/55730.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

根据传入的文件流链接实现前端下载

后端传入一个下载的url&#xff0c;实现点击按钮&#xff0c;下载文件。 方式一&#xff1a; 通过window.open(“URL”, _blank) 方式 PS&#xff1a;会打开一个新的页面 import React from react;const DownloadButton () > {// window.open("URL", "_…

Http 协议和 RPC 协议有什么区别?

Http 协议和 RPC 协议有什么区别&#xff1f; 三个层面来述说&#xff1a; 从功能特性来说&#xff1a; HTTP是一个属于应用层的超文本传输协议&#xff0c;是万维网数据通信的基础&#xff0c;主要服务在网页端和服务端的数据传输上。 RPC是一个远程过程调用协议&#xff0…

AI+视频监控:EasyCVR安防平台赋能火电制造行业的视频智能管理方案

随着信息技术的飞速发展和智能制造的深入推进&#xff0c;火电制造行业作为国民经济的重要组成部分&#xff0c;正面临着智能化转型的迫切需求。为了提升生产效率、保障设备安全、优化管理流程&#xff0c;火电制造企业迫切需要引入先进的视频监控与人工智能技术。EasyCVR安防监…

TinyOS 点对基站通信

文章目录 一、前言1.1 发包的BlinkToRadio的数据包格式 二、混淆基站源码分析2.1 Makefile2.2 组件连接2.3 主逻辑代码 一、前言 1.1 发包的BlinkToRadio的数据包格式 如下&#xff0c;注意&#xff1a;AM层类型(1byte)即handlerID使可以在组件中修改的。 二、混淆基站源码…

《安富莱嵌入式周报》第343期:雷电USB4开源示波器正式发布,卓越的模拟前端低噪便携示波器,自带100W电源的便携智能烙铁,NASA航空航天锂电池设计

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新一期视频教程 【授人以渔】CMSIS-RTOS V2封装层专题视频&#xff0c;一期视频将常用配置和用法梳理清楚&#xff0…

【Mybatis篇】Mybatis的注解开发

&#x1f9f8;安清h&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;【计算机网络】&#xff0c;【Mybatis篇】 &#x1f6a6;作者简介&#xff1a;一个有趣爱睡觉的intp&#xff0c;期待和更多人分享自己所学知识的真诚大学生。 文章目录 &#x1f3af; Select注解 …

自动猫砂盆有必要买吗?高性价比的自动猫砂盆怎么选通通看这篇

最近市面上又新出了很多款式的自动猫砂盆&#xff0c;有些铲屎官蠢蠢欲动&#xff0c;但又在犹豫&#xff0c;自己真的需要自动猫砂盆吗&#xff1f;作为养猫4年的资深铲屎官&#xff0c;在买过这么多猫咪智能用品的里面&#xff0c;最不后悔的就是自动猫砂盆了&#xff01;要知…

【北京迅为】《STM32MP157开发板嵌入式开发指南》-第二十五章 Source Insight 的安装和使用

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7单核cortex-M4异构处理器&#xff0c;既可用Linux、又可以用于STM32单片机开发。开发板采用核心板底板结构&#xff0c;主频650M、1G内存、8G存储&#xff0c;核心板采用工业级板对板连接器&#xff0c;高可靠&#xff0c;牢固耐…

ElasticSearch备考 -- Multi match

一、题目 索引task有3个字段a、b、c&#xff0c;写一个查询去匹配这三个字段为mom&#xff0c;其中b的字段评分比a、c字段大一倍&#xff0c;将他们的分数相加作为最后的总分数 二、思考 通过题目要求对多个字段进行匹配查询&#xff0c;可以考虑multi match、bool query操作。…

RabbitMQ的相关题

一、 MQ的作⽤及应⽤场景 类似问题: 项⽬什么场景下使⽤到了MQ, 为什么需要MQ? RabbitMQ 的作⽤?使⽤场景有哪些? RabbitMQ…

【JWT安全】portswigger JWT labs 全解

目录 1.利用有缺陷的 JWT 签名验证 ①接受任意签名 lab1:通过未验证的签名绕过 JWT 身份验证 ②接受无签名的token lab2:通过有缺陷的签名验证来绕过 JWT 身份验证 2.暴力破解密钥 ①使用hashcat lab3:通过弱签名密钥绕过 JWT 身份验证 3.JWT 标头参数注入 ①通过 jwk…

多模态大语言模型(MLLM)-InstructBlip深度解读

前言 InstructBlip可以理解为Blip2的升级版&#xff0c;重点加强了图文对话的能力。 模型结构和Blip2没差别&#xff0c;主要在数据集收集、数据集配比、指令微调等方面下文章。 创新点 数据集收集&#xff1a; 将26个公开数据集转换为指令微调格式&#xff0c;并将它们归类…

大数据新视界 --大数据大厂之 GraphQL 在大数据查询中的创新应用:优化数据获取效率

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

线程安全-原子性,可见性,有序性

原子性 一组操作要执行就都执行&#xff0c;不执行就都不执行。 怎么保证原子性 synchronized 关键字&#xff0c;可以保证只有一个线程持有锁&#xff0c;进入代码块&#xff0c;因此在执行过程中不会被其他线程所干扰&#xff0c;从而保证原子性显示锁 Lock 接口的相关实现…

【C++】--类和对象(3)

&#x1f911;个人主页: 起名字真南 &#x1f911;个人专栏:【数据结构初阶】 【C语言】 【C】 目录 1 深入构造函数2 类型转换3 static成员4 友元函数5 内部类6 匿名对象 1 深入构造函数 之前我们实现构造函数的时候&#xff0c;初始化成员变量都是在函数体内赋值&#xff0c…

selenium有多个frame页时的操作方法(5)

之前文章我们提到&#xff0c;在webdriver.WebDriver类有一个switch_to方法&#xff0c;通过switch_to.frame()可以切换到不同的frame页然后才再定位某个元素做一些输入/点击等操作。 比如下面这个测试网站有2个frame页&#xff1a;http://www.sahitest.com/demo/framesTest.h…

5.toString()、构造方法、垃圾回收、静态变量与静态方法、单例设计模式、内部类

文章目录 一、toString()1. 优缺点2. 使用方法举例① Dos类里更省事的方法 ② Application里 二、构造方法1. 导入2. 什么是构造方法3. 怎么写构造方法① 无参的构造方法(无参构造器)② 有参的构造方法(有参构造器)③ 注意 4. 构造方法的重载 三、再探this1. 给成员变量用2. 给…

数据结构——排序(交换排序)

目录 一、交换排序的总体概念 二、冒泡排序 三、快速排序 1.挖坑法 2.左右指针 3.前后指针 一、交换排序的总体概念 交换排序是一类排序算法&#xff0c;它的核心思想是通过交换元素的位置来达到排序的目的。在排序过程中&#xff0c;比较数组中的元素对&#xff0c;如果…

【用户管理 添加用户 超级用户 用户和组】

用户管理 添加用户超级用户用户和组 添加用户 介绍用户的管理操作 比如&#xff0c;添加一个用户 sudo useradd -m test1 其中&#xff0c;sudo表示管理员身份运行 修改用户密码 sudo passwd test1 删除用户 sudo userdel test 超级用户 1.首次使用时&#xff0c;需要给roo…

快速区分 GPT-3.5 与 GPT-4

问&#xff1a;鲁迅为什么暴打周树人&#xff1f; GPT3.5回答 各种稀奇古怪的理由 GPT4回答 正确区分鲁迅和周树人是同一个人 国内GPT入口 https://ai-to.cn/url/?ulihaimao