手写实现一个ORM框架

手写实现一个ORM框架

  • 什么是ORM框架、ORM框架的作用
  • 效果演示
  • 框架设计
  • 代码细节
    • SqlBuilder
    • Sql
    • Executor
    • StatementHandler
    • ParameterHandler
    • ResultSetHandler
    • 逆序生成实体类

大家好,本人最近写了一个ORM框架,想在这里分享给大家,让大家来学习学习。

在这里插入图片描述

废话不多说,直接进入正题。

什么是ORM框架、ORM框架的作用

首先介绍一下ORM框架的相关知识。

数据库表是行列格式的,而Java是面向对象的,我们需要通过操作JDBC的结果集ResultSet,一行行遍历,再一列一列的处理结果,在new一个对象去set对应的值,这就显得非常繁琐,也与Java的面向对象编程格格不入。

ORM框架就可以解决这个问题,通过对象与关系型数据库建立一个映射关系,就可以省去操作JDBC的结果集ResultSet这一步繁琐的操作,直接把对库表的查询结果映射成对应的对象。

在这里插入图片描述

除此之外,操作JDBC还要我们自己调用PreparedStatement把参数一个一个的set进去,ORM框架的另一个作用就是省去设置参数的繁琐操作,根据参数类型自动调用PreparedStatement对应的set方法设置参数。

最后JDBC的操作的一般都是模板代码:通过DriverManager取得Connection,通过Connection取得PreparedStatement,然后通过PreparedStatement执行查询或更新,最后把获取到的ResultSet处理成返回结果。使用ORM框架,这些模板代码不需要我们重复的写,ORM框架帮我们封装了这些模板代码。

在这里插入图片描述

了解了ORM框架的作用之后,下面就开始介绍我们自己手写的ORM框架。

效果演示

库表:test.student 学生表
在这里插入图片描述

编写测试类:

public class StudentTest {@Beforepublic void before() {Configuration.init("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8", "root", "root");}@Testpublic void testSimpleQuery() {Configuration configuration = Configuration.get();List<Student> result = SqlBuilder.createSql(configuration).select(Student.class).from(Student.class).where().eq(Student::getSex, "女").and().eq(Student::getGradeId, 1).build().query();System.out.println(result);}}	

执行测试类,控制台打印:

21:18:50.076 [main] INFO com.huangjunyi1993.easy.sql.sql.SqlBuilder - this sql is: select studentno, loginpwd, studentname, sex, gradeid, phone, address, borndate, email from student where sex=? and gradeid=? ;
[Student [studentNo=S1101002, loginPwd=228996246, studentName=洛飞, sex=女, gradeId=1, phone=666762663, address=天津市南开区, borndate=Wed Feb 07 00:00:00 CST 1990, email=jnqlpkdwb@nsjpt.com], Student [studentNo=S1101003, loginPwd=228996247, studentName=凌辉, sex=女, gradeId=1, phone=353149818, address=北京市海淀区成府路, borndate=Sun Apr 04 00:00:00 CST 1993, email=eepispykh@oitbl.com], Student [studentNo=S1101008, loginPwd=228996257, studentName=凌洋, sex=女, gradeId=1, phone=15812345680, address=湖南省长沙, borndate=Thu Nov 30 00:00:00 CST 1989, email=null], Student [studentNo=S1101011, loginPwd=228996267, studentName=圆荷, sex=女, gradeId=1, phone=13512344483, address=河北省石家庄, borndate=Thu Mar 16 00:00:00 CST 1989, email=idfwxlbjr@bkxko.com], Student [studentNo=S1101012, loginPwd=228996270, studentName=崔今生, sex=女, gradeId=1, phone=13512345684, address=河北省邯郸市, borndate=Fri Jan 05 00:00:00 CST 1990, email=qrakldetd@ogtso.com], Student [studentNo=S1101017, loginPwd=228996276, studentName=赵七, sex=女, gradeId=1, phone=511686053, address=北京市海淀区中关村, borndate=Thu Jun 27 00:00:00 CST 1985, email=ltshcitdp@qdpeh.com]]

一个字,酷!!!

在这里插入图片描述

框架设计

一共七个核心组件:Configuration、SqlBuilder、Sql、Executor、StatementHandler、ParameterHandler、ResultSetHandler。

在这里插入图片描述

Configuration是全局配置类,保存数据库启动类名、数据库url、用户名、密码等信息。

SqlBuilder是建造者模式的实现,流式编程的方式编写代码形式的sql,最后可以调用build()方法构造一个Sql对象,Sql对象就包含了真实的sql。

就像这样:

Sql<Student> sql = SqlBuilder.createSql(configuration).select(Student.class).from(Student.class).where().eq(Student::getSex, "女").and().eq(Student::getGradeId, 1).build()

SqlBuilder的build()创建一个Sql对象,Sql对象保存了要被执行的sql,以及用于执行Sql的Executor执行器。

在这里插入图片描述

调用Sql的query()方法或者execute()方法,sql将会被执行,里面会调用Executor执行sql。

Executor会通过JDBC的DriverManager获取数据库连接对象Connection,然后调用Connection的prepareStatement(sql)对sql进行预编译,获取到JDBC的PreparedStatement对象。然后把PreparedStatement对象交给StatementHandler处理。

在这里插入图片描述

StatementHandler会调用ParameterHandler进行预编译sql中的参数设置,然后调用JDBC的PreparedStatement对象的executeQuery()方法或者executeUpdate()方法执行sql,然后把JDBC的结果集对象ResultSet交给ResultSetHandler处理。

在这里插入图片描述

ParameterHandler保存了一个参数类型数组Class<?>[] parameterTypes,ParameterHandler根据参数类型调用PreparedStatement的setXXX方法进行参数设置。

ResultSetHandler保存了一个返回值类型Class<T> returnType,ResultSetHandler根据Class里面的字段类型调用ResultSet的getXXX方法获取返回值,通过反射的方式 field.set(obj, value) 设置到对象字段中。

在这里插入图片描述

代码细节

SqlBuilder

SqlBuilder有各种方法,比如select(Class<?> clazz),select(String… fieldNames)、update(Class<?> clazz)、set(String fieldName, Object parameter)、deleteFrom(Class<?> clazz)、insertInto(Class<?> clazz)、from(Class<?> clazz) 等等。满足大多数常用sql的编写。

我们看一个select(Class<?> clazz)方法:

	public <T> SqlBuilder<T> select(Class<T> clazz) {sql.append("select ");Field[] fields = clazz.getDeclaredFields();List<String> fieldNames = new ArrayList<>();for (Field field : fields) {FieldName fieldName = field.getAnnotation(FieldName.class);if (fieldName == null) {continue;}if (StringUtils.isBlank(fieldName.value())) {throw new RuntimeException(String.format("fieldName is blank, class=%s, field=%", returnType.getName(), field.getName()));}fieldNames.add(fieldName.value());}sql.append(String.join(", ", fieldNames) + " ");return (SqlBuilder<T>) this;}

反射获取类中的字段对象Field,获取字段上的的@FieldName注解,取出注解中的值作为字段名,拼接sql如:“select studentno, loginpwd, studentname, sex, gradeid, phone, address, borndate, email ”。

再看一下 SqlBuilder 的 from(Class<?> clazz) 方法:

	public SqlBuilder<T> from(Class<?> clazz) {TableName annotation = clazz.getAnnotation(TableName.class);if (annotation == null) {throw new RuntimeException("table name is null");}if (StringUtils.isBlank(annotation.value())) {throw new RuntimeException("table name is blank");}String tableName = annotation.value();return from(tableName);}public SqlBuilder<T> from(String tableName) {sql.append("from ").append(tableName).append(" ");return this;}

反射获取到类上的@TableName注解,取出注解中的值作为表名,与前面的select方法的sql进行拼接,把 “from 表名” 拼接在后面,此时的sql就是:“select studentno, loginpwd, studentname, sex, gradeid, phone, address, borndate, email from student ”。

where()方法和and()方法就不看了,非常简单。

再看一下SqlBuilder的eq(String fieldName, Object parameter):

	public <F> SqlBuilder<T> eq(SFunction<F, ?> function, Object parameter) {String fieldName = FieldNameUtil.getFieldName(function);sql.append(fieldName + "=? ");saveParameter(parameter);return this;}private void saveParameter(Object parameter) {if (parameters == null) {parameters = new ArrayList<>();}parameters.add(parameter);if (parameterTypes == null) {parameterTypes = new ArrayList<>();}if (parameter instanceof Long) {parameterTypes.add(Long.class);} else if (parameter instanceof Integer) {parameterTypes.add(Integer.class);} else if (parameter instanceof Short) {parameterTypes.add(Short.class);} else if (parameter instanceof Byte) {parameterTypes.add(Byte.class);} else if (parameter instanceof Double) {parameterTypes.add(Double.class);} else if (parameter instanceof Float) {parameterTypes.add(Float.class);} else if (parameter instanceof Character) {parameterTypes.add(Character.class);} else if (parameter instanceof Boolean) {parameterTypes.add(Boolean.class);} else if (parameter instanceof String) {parameterTypes.add(String.class);} else if (parameter instanceof Date) {parameterTypes.add(Date.class);} else if (parameter instanceof Sql) {parameterTypes.add(Sql.class);} else {throw new RuntimeException("no support type");}}

eq方法的第一行是通过方法引用取得字段上的@FieldName注解,然后取得注解中声明的字段名。

		String fieldName = FieldNameUtil.getFieldName(function);

比如Student类中有个字段:

		@FieldName("sex")private String sex;

那么调用FieldNameUtil.getFieldName(Student::getSex),就取到了“sex”这个列名(这里表列名跟Student类的字段名相同)。

eq方法设置sql中的等值查询条件,但是为了防止sql注入,我们使用的是预编译sql,所以此时拼接到sql的是一个问号,然后参数保存到List<Object> parameters属性中,参数类型保存到 List<Class<?>> parameterTypes 属性中。

		sql.append(fieldName + "=? ");saveParameter(parameter);

加上eq条件之后,拼出的sql就是:“select studentno, loginpwd, studentname, sex, gradeid, phone, address, borndate, email from student where sex=? and gradeid=? ;”。

	public Sql<T> build() {this.sql.append(";");LOGGER.info("this sql is: {}", this.sql);Sql<T> sql = new Sql<>(this.configuration,this.sql.toString().trim(), this.parameterTypes != null ? this.parameterTypes.toArray(new Class[]{}) : null, this.parameters != null ? this.parameters.toArray(new Object[]{}) : null, this.returnType);return sql;}

最后build()方法就是创建了一个Sql对象返回,创建Sql对象时把前面拼接的sql、以及组装好的参数信息parameters、parameterTypes 传递给了Sql对象的构造方法。

Sql

构造方法:

	public Sql(Configuration configuration, String sql, Class<?>[] parameterTypes, Object[] parameters, Class<T> returnType) {super();this.configuration = configuration;this.sql = sql;this.parameterTypes = parameterTypes;this.parameters = parameters;this.returnType = returnType;this.init();}private void init() {executor = new Executor<>(configuration, sql, parameterTypes, parameters, returnType);}

Sql对象的构造方法创建了一个Executor对象,并把sql、parameterTypes,、parameters等参数传递给Executor。

	public int execute() {return executor.executeUpdate();}@SuppressWarnings({ "unchecked", "hiding" })public List<T> query() {return executor.executeQuery();}

然后Sql中的execute方法和query方法都是直接调用Executor对象。

Executor

查询类型的sql(select)会调用Executor的executeQuery()方法,增删改类型的sql(insert、update、delete)会调用Executor的executeUpdate()方法。我们看看executeQuery()方法:

	public List<T> executeQuery() {CacheKey cacheKey = CacheKey.get(sql, parameterTypes, parameters);if (cache.containsKey(cacheKey)) {return (List<T>) cache.get(cacheKey);}Connection conn = null;PreparedStatement prepareStatement = null;try {conn = DriverManager.getConnection(configuration.getConnectionUrl(), configuration.getUserName(), configuration.getPassword());prepareStatement = conn.prepareStatement(sql);StatementHandler<T> statementHandler = new StatementHandler<>(parameterTypes, parameters, returnType, prepareStatement);List<T> reuslt = statementHandler.handleQuery();cache.put(cacheKey, reuslt);return reuslt;} catch (Exception e) {......} finally {......}}

首先构造一个CacheKey对象,从缓存查找看看有没有之前执行过的结果,有就取缓存的结果返回,不再往下执行。

		CacheKey cacheKey = CacheKey.get(sql, parameterTypes, parameters);if (cache.containsKey(cacheKey)) {return (List<T>) cache.get(cacheKey);}

cache中的缓存会在当前Executor执行executeUpdate()方法时被清空。

如果没有就要往下执行,查询数据库。

			conn = DriverManager.getConnection(configuration.getConnectionUrl(), configuration.getUserName(), configuration.getPassword());prepareStatement = conn.prepareStatement(sql);StatementHandler<T> statementHandler = new StatementHandler<>(parameterTypes, parameters, returnType, prepareStatement);List<T> reuslt = statementHandler.handleQuery();

DriverManager.getConnection(…) 是JDBC的方法,获取数据库连接对象Connection。conn.prepareStatement(sql)也是JDBC的方法,对sql进行预编译,返回一个PreparedStatement对象。然后创建StatementHandler,StatementHandler保存了参数类型parameterTypes、参数parameters、返回值类型returnType、prepareStatement等属性。然后调用statementHandler的handleQuery执行查询。

StatementHandler

我们看看StatementHandler的handleQuery()方法:

	public List<T> handleQuery() throws SQLException, InstantiationException, IllegalAccessException {parameterHandler.handle(preparedStatement);ResultSet resultSet = preparedStatement.executeQuery();List<T> result = resultSetHandler.handle(resultSet);if (resultSet != null) {resultSet.close();}return result;}

首先是通过ParameterHandler处理参数的设置:

parameterHandler.handle(preparedStatement);

然后调用JDBC的方法preparedStatement.executeQuery()执行查询,获取返回的结果集ResultSet:

ResultSet resultSet = preparedStatement.executeQuery();

然后调用ResultSetHandler处理结果集到返回对象的映射:

List<T> result = resultSetHandler.handle(resultSet);

ParameterHandler

ParameterHandler的handle处理参数的设置:

	public void handle(PreparedStatement preparedStatement) throws SQLException {if (parameterTypes == null) {return;}for (int i = 0; i < parameterTypes.length; i++) {if (Long.class.isAssignableFrom(parameterTypes[i]) || long.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setLong(i + 1, (long) parameters[i]);} else if (Integer.class.isAssignableFrom(parameterTypes[i]) || int.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setInt(i + 1, (int) parameters[i]);} else if (Short.class.isAssignableFrom(parameterTypes[i]) || short.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setShort(i + 1, (short) parameters[i]);} else if (Byte.class.isAssignableFrom(parameterTypes[i]) || byte.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setByte(i + 1, (byte) parameters[i]);} else if (Double.class.isAssignableFrom(parameterTypes[i]) || double.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setDouble(i + 1, (double) parameters[i]);} else if (Float.class.isAssignableFrom(parameterTypes[i]) || float.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setFloat(i + 1, (float) parameters[i]);} else if (Character.class.isAssignableFrom(parameterTypes[i]) || char.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setString(i + 1, String.valueOf((char) parameters[i]));} else if (Boolean.class.isAssignableFrom(parameterTypes[i]) || boolean.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setBoolean(i + 1, (boolean) parameters[i]);} else if (String.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setString(i + 1, (String) parameters[i]);} else if (Timestamp.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setTimestamp(i + 1, (Timestamp) parameters[i]);} else if (Date.class.isAssignableFrom(parameterTypes[i])) {preparedStatement.setDate(i + 1, new java.sql.Date(((Date) parameters[i]).getTime()));} else {throw new RuntimeException("no support type");}}}

就是根据parameterTypes中保存的参数类型,调用JDBC的方法,把parameters中的参数设置到sql中。比如Long类型,那么调用preparedStatement.setLong(index, parameter),如果是String类,调用
preparedStatement.setString(index, parameter)。

ResultSetHandler

ResultSetHandler的handle把结果集ResultSet转换成指定类型的对象:

	public List<T> handle(ResultSet resultSet) throws SQLException, InstantiationException, IllegalAccessException {if (returnType == null || Map.class.isAssignableFrom(returnType)) {return handleForMap(resultSet);}if (isBaseReturnType(returnType)) {return handleForBaseType(resultSet, returnType);}Field[] fields = returnType.getDeclaredFields();List<T> result = new ArrayList<>();while(resultSet.next()) {T t = returnType.newInstance();for (Field field : fields) {FieldName annotation = field.getAnnotation(FieldName.class);if (annotation == null) {continue;}if (StringUtils.isBlank(annotation.value())) {throw new RuntimeException(String.format("fieldName is blank, class=%s, field=%", returnType.getName(), field.getName()));}String name = annotation.value();Class<?> type = field.getType();field.setAccessible(true);Object value = getResultSetValue(resultSet, type, name);if (value != null) {field.set(t, value);}}result.add(t);}return result;}

反射获取指定类型里面的字段

		Field[] fields = returnType.getDeclaredFields();

然后遍历ResultSet,每一条查询记录对应一个对象:

		List<T> result = new ArrayList<>();while(resultSet.next()) {......}

每一条查询记录,创建一个指定类型的对象,给对象中的字段赋值,保存到返回的List中:

			T t = returnType.newInstance();for (Field field : fields) {......}result.add(t);

对于每个对象中的字段的赋值,就是反射取得字段上的@FieldName注解,取得注解里的字段名name,然后反射取得字段类型type,调用getResultSetValue(resultSet, type, name)从结果集ResultSet中获取该字段对象的列值value,然后反射field.set(t, value)设置字段值。

				FieldName annotation = field.getAnnotation(FieldName.class);// 一些校验......String name = annotation.value();Class<?> type = field.getType();field.setAccessible(true);Object value = getResultSetValue(resultSet, type, name);if (value != null) {field.set(t, value);}

getResultSetValue方法:

	private Object getResultSetValue(ResultSet resultSet, Class<?> type, String name) {Object value = null;try {if (Long.class.isAssignableFrom(type) || long.class.isAssignableFrom(type)) {value = resultSet.getLong(name);} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {value = resultSet.getInt(name);} else if (Short.class.isAssignableFrom(type) || short.class.isAssignableFrom(type)) {value = resultSet.getShort(name);} else if (Byte.class.isAssignableFrom(type) || byte.class.isAssignableFrom(type)) {value = resultSet.getByte(name);} else if (Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type)) {value = resultSet.getDouble(name);} else if (Float.class.isAssignableFrom(type) || float.class.isAssignableFrom(type)) {value = resultSet.getFloat(name);} else if (Character.class.isAssignableFrom(type) || char.class.isAssignableFrom(type)) {value = resultSet.getString(name).charAt(0);} else if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) {value = resultSet.getBoolean(name);} else if (String.class.isAssignableFrom(type)) {value = resultSet.getString(name);} else if (Timestamp.class.isAssignableFrom(type)) {value = resultSet.getTimestamp(name);} else if (Date.class.isAssignableFrom(type)) {value = new Date(resultSet.getDate(name).getTime());} else {throw new RuntimeException("no support type");}} catch (SQLException e) {LOGGER.warn("getResultSetValue: {}", e.getMessage());}return value;}

参数type是字段的类型,name是字段上@FieldName注解声明的列名。根据指定类型,调用对应的JDBC方法,比如long类型则调用resultSet.getLong(name),String类型则调用resultSet.getString(name)。

逆序生成实体类

我们定义了两个注解:一个是@TableName,添加到类上,用于声明该类对应的表名;一个是@FieldName,添加到字段上,用于声明该字段对应表中的哪一列。

就像这样:

@TableName("t_sensitive_word")
public class SensitiveWord{@FieldName("f_id")private String id;@FieldName("f_content")private String content;@FieldName("f_member_id")private String memberId;@FieldName("f_create_time")private long createTime;@FieldName("f_modify_date")private Timestamp modifyDate;@FieldName("f_company_id")private String companyId;// 下面各种set、get......
}	

每个表都要手动写一个这个实体类,太繁琐了。于是还写了个逆向生成实体类的框架EntityGenerator,根据数据库表逆向生成所有的实体类。

通过查询 INFORMATION_SCHEMA.TABLES 获取指定数据库上的所有表名,然后遍历所有表名,通过PreparedStatement的getMetaData方法获取结果集中的元数据信息,里面就包含表字段名和表字段类型等信息,就可以根据这些信息生成一个实体类。

代码就不贴上来了,有兴趣的可以从代码仓把代码拉取到本地研究。

代码仓地址:https://gitee.com/huang_junyi/easy-sql

在这里插入图片描述

全文完。

在这里插入图片描述

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

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

相关文章

leetcode力扣_排序问题

215.数组中的第K个最大元素 鉴于已经将之前学的排序算法忘得差不多了&#xff0c;只会一个冒泡排序法了&#xff0c;就写了一个冒牌排序法&#xff0c;将给的数组按照降序排列&#xff0c;然后取nums[k-1]就是题目要求的&#xff0c;但是提交之后对于有的示例显示”超出时间限制…

JavaWeb开发之环境准备-大合集

本文博客地址 JavaWeb开发 || 环境准备 1. 前言2. JDK8安装2.1 下载地址2.2 安装配置图示2.2.1 JDK安装2.2.2 配置系统环境变量 3. Maven安装3.1 Maven下载3.2 Maven解压及系统变量配置 4. Tomcat安装4.1 Tomcat下载4.2 Tomcat解压及系统变量配置 5. Redis安装5.1 Redis下载5.…

记录一次麒麟V10 安装sysbench各种报错(关于MySQL)处理过程

sysbench手工下载&#xff1a; https://github.com/akopytov/sysbench 下载.zip文件&#xff0c;上传到服务器上 解压、安装&#xff1a; unzip sysbench-master.zipcd sysbench-master/sh autogen.sh./configure 报错&#xff1a;没有mysql驱动 configure: error: mysql_c…

Marin说PCB之CAM350的软件使用知多少?

今天上海的气温那叫一个高啊&#xff0c;温度都达到了39左右了都&#xff0c;我都严重怀疑我不是在魔都上班而是在火焰山板砖去了。这么燥热的天气真的是严重影响了小编我的工作效率&#xff0c;没有心情工作啊&#xff0c;要去泳池避避暑&#xff0c;冲个凉也行啊。这种天气只…

力扣5----最长回文子串

给你一个字符串 s&#xff0c;找到 s 中最长的回文子串 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 解释&#xff1a;"aba" 同样是符合题意的答案。示例 2&#xff1a; 输入&#xff1a;s "cbbd" 输出…

Feign-未完成

Feign Java中如何实现接口调用&#xff1f;即如何发起http请求 前三种方式比较麻烦&#xff0c;在发起请求前&#xff0c;需要将Java对象进行序列化转为json格式的数据&#xff0c;才能发送&#xff0c;然后进行响应时&#xff0c;还需要把json数据进行反序列化成java对象。 …

G2.【C语言】EasyX绘制颜色窗口

1.窗口 窗口&#xff1a;宽度*高度&#xff08;单位都是像素&#xff09; #include <stdio.h> #include <easyx.h> int main() {initgraph(640, 480);getchar();return 0; } 640是宽&#xff0c;480是高 2.操作窗口的三个按钮 #include <stdio.h> #incl…

go语言day10 接口interface 类型断言 type关键字

接口&#xff1a; 空接口类型&#xff1a; 要实现一个接口&#xff0c;就要实现该接口中的所有方法。因为空接口中没有方法&#xff0c;所以自然所有类型都实现了空接口。那么就可以使用空接口类型变量去接受所有类型对象。 类比java&#xff0c;有点像Object类型的概念&#x…

免费去马赛克软件,亲测支持视频和图片,这AI功能逆天了!

有小伙伴私信问阿星有什么去除马赛克的免费软件&#xff0c;求推荐好用的去马赛克软件。 市面上去马赛克的软件多如牛毛&#xff0c;但真正好用的真不多&#xff0c;而免费的是更少。今天阿星就分享一款 AI智能去马赛克软件&#xff0c;免费使用。软件支持去除图片和视频的马赛…

51单片机STC89C52RC——15.1 AD/DA(模数数模)

目的/效果 1 LCD1602 显示 可调电阻、光敏电阻、热敏电阻值&#xff08;AD&#xff09; 2 模拟信号控制LED明暗&#xff08;DA&#xff09; 一&#xff0c;STC单片机模块 二&#xff0c;AD/DA 2.1 AD/DA 介绍 AD&#xff08;Analog to Digital&#xff09;&#xff1a;模拟…

第1章 项目背景(学成在线),项目介绍,环境搭建

1.项目背景 1.1 在线教育市场环境 以下内容摘自https://report.iresearch.cn/content/2021/01/358854.shtml 在线教育行业是一个有着极强的广度和深度的行业&#xff0c;从校内到校外&#xff1b;从早幼教到职业培训&#xff1b;从教育工具到全信息化平台等等。 2020年的新…

用Excel处理数据图像,出现交叉怎么办?

一、问题描述 用excel制作X-Y散点图&#xff0c;意外的出现了4个交叉点&#xff0c;而实际上的图表数据是没有交叉的。 二、模拟图表 模拟部分数据&#xff0c;并创建X-Y散点图&#xff0c;数据区域&#xff0c;X轴数据是依次增加的&#xff0c;因此散点图应该是没有交叉的。…

linux centos 安装niginx并且添加ssl(https)模块

文章目录 前言一、nginx安装教程1.流程步骤 总结 前言 一、nginx安装教程 1.流程步骤 代码如下&#xff08;示例&#xff09;&#xff1a; 1.先下载linux安装包 2.解压安装命令 sudo tar -zxvf nginx-1.20.1.tar.gz3.进入解压后的目录 sudo cd nginx-1.20.14.安装 sudo y…

欢迎加入国家智能网联汽车创新中心OS开发训练营大家庭

欢迎加入国家智能网联汽车创新中心OS开发训练营大家庭。&#x1f680; 导学阶段启动 在正式开营之前&#xff0c;我们特别设置了导学阶段&#xff0c;旨在帮助大家更好地迎接颇具挑战性的项目实战。导学阶段包括一系列精心准备的视频课程和配套习题。github链接&#xff1a;htt…

vue侦听器watch()

侦听器watch&#xff08;&#xff09; 侦听器侦听数据变化&#xff0c;我们可以使用watch 选项在每次响应式属性变化时触发一个函数。 <template><h3>侦听器watch</h3><hr> <p>{{nessage}}</p> <button click"exchage">…

基于YOLOv10+YOLOP+PYQT的可视化系统,实现多类别目标检测+可行驶区域分割+车道线分割【附代码】

文章目录 前言视频效果必要环境一、代码结构1、 训练参数解析2、 核心代码解析1.初始化Detector类2. torch.no_grad()3. 复制输入图像并初始化计数器4. 调用YOLOv10模型进行目标检测5. 提取检测结果信息6. 遍历检测结果并在图像上绘制边界框和标签7. 准备输入图像以适应End-to-…

【致知功夫 各随分限】成长需要时间,助人须考虑对方的承受程度

帮助他人需考虑各人的分限所能及的&#xff0c;初学圣学需时间沉淀&#xff0c;存养心性 任何人都应该受到教育&#xff0c;不应受到贫富、贵贱的差异而排除在教育之外&#xff0c;对于不同材质的学生&#xff0c;需要因材施教&#xff1b; 每天都有新的认知&#xff0c;大我…

STL—容器—string类【对其结构和使用的了解】【对oj相关练习的训练】

STL—容器—string类 其实string类准确来说并不是容器&#xff0c;因为他出现的时间比STL要早&#xff0c;但是也可以说是容器吧。 1.为什么要学习string类&#xff1f; 1.1C语言当中的字符串 C语言中&#xff0c;字符串是以’\0’结尾的一些字符的集合&#xff0c;为了操作…

CTFShow的RE题(三)

数学不及格 strtol 函数 long strtol(char str, char **endptr, int base); 将字符串转换为长整型 就是解这个方程组了 主要就是 v4, v9的关系&#xff0c; 3v9-(v10v11v12)62d10d4673 v4 v12 v11 v10 0x13A31412F8C 得到 3*v9v419D024E75FF(1773860189695) 重点&…

Windows ipconfig命令详解,Windows查看IP地址信息

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 ipconfig 1、基…