手写实现一个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.rhkb.cn/news/370568.html

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

相关文章

C++ 多态篇

文章目录 1. 多态的概念和实现1.1 概念1.2 实现1.2.1 协变1.2.2 析构函数1.2.3 子类虚函数不加virtual 2. C11 final和override3.1 final3.2 override 3. 函数重载、重写与隐藏4. 多态的原理5. 抽象类6.单继承和多继承的虚表6.1 单继承6.2 多继承 7. 菱形继承的虚表(了解)7.1 菱…

I/O多路复用

参考面试官&#xff1a;简单说一下阻塞IO、非阻塞IO、IO复用的区别 &#xff1f;_unix环境编程 阻塞io和非阻塞io-CSDN博客 同步阻塞(BIO) BIO 以流的方式处理数据 应用程序发起一个系统调用&#xff08;recvform&#xff09;&#xff0c;这个时候应用程序会一直阻塞下去&am…

Interview preparation--Https 工作流程

HTTP 传输的弊端 如上图&#xff0c;Http进行数据传输的时候是明文传输&#xff0c;导致任何人都有可能截获信息&#xff0c;篡改信息如果此时黑客冒充服务器&#xff0c;或者黑客窃取信息&#xff0c;则其可以返回任意信息给客户端&#xff0c;而且不被客户端察觉&#xff0c;…

2、图形验证码

1、图形验证码设计 1.1思路 现今&#xff0c;市面上的图形验证码付费的&#xff0c;免费的多种多样&#xff0c;主要形式有滑动拼图、文字点选、语序点选、字体识别、空间推理、智能随机等。 而处理也分为web端和sever端两部分 此处以免费的kaptcha 为例&#xff0c;进行数字图…

认识流式处理框架Apache Flink

目录 一、Apache Flink 的基础概念 1.1 Apache Flink是什么&#xff1f; 1.2 Flink的定义 二、Apache Flink 的发展史 2.1 Flink前身Stratosphere 2.2 Flink发展时间线及重大变更 三、Flink核心特性 3.1 批流一体化 3.2 同时支持高吞吐、低延迟、高性能 3.3 支持事件时…

全新UI自助图文打印系统小程序源码 PHP后端 附教程

最新自助图文打印系统和证件照云打印小程序源码PHP后端&#xff0c;为用户用户自助打印的服务&#xff0c;包括但不限于文档、图片、表格等多种格式的文件。此外&#xff0c;它们还提供了诸如美颜、换装、文档打印等功能&#xff0c;以及后台管理系统&#xff0c;方便管理员对打…

小(微)间距P1.538COB渠道现货销售将加速全面升级替换SMD产品。

COB&#xff08;Chip on Board&#xff09;技术&#xff0c;如一颗璀璨的星辰&#xff0c;在上世纪60年代的科技夜空中悄然升起。它巧妙地将LED芯片镶嵌在PCB电路板的怀抱中&#xff0c;再用特种树脂为其披上一层坚韧的外衣&#xff0c;宛如一位精心雕琢的艺术家在创作一幅完美…

实战whisper第三天:fast whisper 语音识别服务器部署,可远程访问,可商业化部署(全部代码和详细部署步骤)

Fast Whisper 是对 OpenAI 的 Whisper 模型的一个优化版本,它旨在提高音频转录和语音识别任务的速度和效率。Whisper 是一种强大的多语言和多任务语音模型,可以用于语音识别、语音翻译和语音分类等任务。 Fast Whisper 的原理 Fast Whisper 是在原始 Whisper 模型的基础上进…

从0到1:培训老师预约小程序开发笔记二

背景调研 培训老师预约小程序&#xff1a; 教师和学生可以更便捷地安排课程&#xff0c;并提升教学质量和学习效果&#xff0c;使之成为管理和提升教学效果的强大工具。培训老师可以在小程序上设置自己的可预约时间&#xff0c;学员可以根据老师的日程安排选择合适的时间进行预…

Studying-代码随想录训练营day27| 贪心算法理论基础、455.分发饼干、376.摆动序列、53.最大子序和

第27天&#xff0c;贪心开始&#xff01;(ง •_•)ง&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 贪心算法理论基础 贪心的套路 贪心的一般解题步骤 总结 455.分发饼干 376.摆动序列 53.最大子序和 总结 贪心算法理论基础 什么是贪心&#xff1f;—— 贪…

计算机组成原理学习笔记(一)

计算机组成原理 [类型:: [[计算机基础课程]] ] [来源:: [[B站]] ] [主讲人:: [[咸鱼学长]] ] [评价:: ] [知识点:: [[系统软件]] & [[应用软件]] ] [简单解释:: 管理计算机系统的软件&#xff1b; 按照任务需要编写的程序 ] [问题:: ] [知识点:: [[机器字长]] ] [简单…

三相感应电机的建模仿真(2)基于ABC相坐标系S-Fun的仿真模型

1. 概述 2. 三相感应电动机状态方程式 3. 基于S-Function的仿真模型建立 4. 瞬态分析实例 5. 总结 6. 参考文献 1. 概述 前面建立的三相感应电机在ABC相坐标系下的数学模型是一组周期性变系数微分方程&#xff08;其电感矩阵是转子位置角的函数&#xff0c;转子位置角随时…

ubuntu22 sshd设置

专栏总目录 一、安装sshd服务 sudo apt updatesudo apt install -y openssh-server 二、配置sshd 使用文本编辑器打开/etc/ssh/sshd_config sudo vi /etc/ssh/sshd_config &#xff08;一&#xff09;配置sshd服务的侦听端口 建议将ssh的侦听端口改为7000以上的端口&#…

安装 tesseract

安装 tesseract 1. Ubuntu-24.04 安装 tesseract2. Ubuntu-24.04 安装支持语言3. Windows 安装 tesseract4. Oracle Linux 8 安装 tesseract 1. Ubuntu-24.04 安装 tesseract sudo apt install tesseract-ocr sudo apt install libtesseract-devreference: https://tesseract-…

Android- Framework 非Root权限实现修改hosts

一、背景 修改system/etc/hosts&#xff0c;需要具备root权限&#xff0c;而且remount后&#xff0c;才能修改&#xff0c;本文介绍非root状态下修改system/etc/hosts方案。 环境&#xff1a;高通 Android 13 二、方案 非root&#xff0c;system/etc/hosts只有只读权限&…

【分布式系统】ELK 企业级日志分析系统

目录 一.ELK概述 1.简介 1.1.可以添加的其他组件 1.2.filebeat 结合 logstash 带来好处 2.为什么使用ELK 3.完整日志系统基本特征 4.工作原理 二.部署ELK日志分析系统 1.初始化环境 2.完成JAVA部署 三. ELK Elasticsearch 集群部署 1.安装 2.修改配置文件 3.es 性…

Linux运维之管道符、重定向与环境变量

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、输入输出重定向 二、管道命令符 三、命令行的通配符 四、常用的转义字符 五、重要的环境变量 致谢 一、输入输出重定向 输入重定向是…

《昇思25天学习打卡营第13天|onereal》

今天学习的内容如下&#xff1a; DCGN生成漫画头像 在下面的教程中&#xff0c;我们将通过示例代码说明DCGAN网络如何设置网络、优化器、如何计算损失函数以及如何初始化模型权重。在本教程中&#xff0c;使用的动漫头像数据集共有70,171张动漫头像图片&#xff0c;图片大小均为…

进程控制-exec函数

让父子进程来执行不相干的操作 能够替换进程地址空间的代码.text段 执行另外的程序&#xff0c;不需要创建额外的的地址空间 当前程序中调用另外一个应用程序 指定执行目录下的程序 int execl(const char *path, const char *arg&#xff0c;/* (char *) NULL */); /* pat…

[学习笔记]SQL学习笔记(连载中。。。)

学习视频&#xff1a;【数据库】SQL 3小时快速入门 #数据库教程 #SQL教程 #MySQL教程 #database#Python连接数据库 目录 1.SQL的基础知识1.1.表(table)和键(key)1.2.外键、联合主键 2.MySQL安装&#xff08;略&#xff0c;请自行参考视频&#xff09;3.基本的MySQL语法3.1.规…