手写实现一个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 {
	
	@Before
	public void before() {
		Configuration.init("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8", "root", "root");
	}
	
	@Test
	public 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.mfbz.cn/a/779686.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

axios的使用,处理请求和响应,axios拦截器

1、axios官网 https://www.axios-http.cn/docs/interceptors 2、安装 npm install axios 3、在onMouunted钩子函数中使用axios来发送请求&#xff0c;接受响应 4.出现的问题&#xff1a; &#xff08;1&#xff09; 但是如果发送请求请求时间过长&#xff0c;回出现请求待处…

分布式共识算法

分布式的基石 分布式共识算法 前置知识&#xff1a;分布式的 CAP 问题&#xff0c;在事务一章中已有详细介绍。 正式开始探讨分布式环境中面临的各种技术问题和解决方案以前&#xff0c;我们先把目光从工业界转到学术界&#xff0c;学习两三种具有代表性的分布式共识算法&…

昇思MindSpore学习总结十——ResNet50迁移学习

1、迁移学习 &#xff08;抄自CS231n Convolutional Neural Networks for Visual Recognition&#xff09; 在实践中&#xff0c;很少有人从头开始训练整个卷积网络&#xff08;使用随机初始化&#xff09;&#xff0c;因为拥有足够大小的数据集相对罕见。相反&#xff0c;通常…

Flask之电子邮件

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、使用Flask-Mail发送电子邮件 1.1、配置Flask-Mail 1.2、构建邮件数据 1.3、发送邮件 二、使用事务邮件服务SendGrid 2.1、注册SendGr…

昇思25天学习打卡营第11天|MindSpore 助力下的 GPT2:数据集加载处理及模型全攻略

目录 环境配置 数据集下载和获取 数据集拆分 处理数据集 模型构建 ​​​​​​​模型训练 ​​​​​​​模型推理 环境配置 “%%capture captured_output”这一行指令通常旨在捕获后续整个代码块所产生的输出结果。首先&#xff0c;将已预装的 mindspore 库予以卸载。随后&a…

68.WEB渗透测试-信息收集- WAF、框架组件识别(8)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;67.WEB渗透测试-信息收集- WAF、框架组件识别&#xff08;7&#xff09; 右边这些是waf的…

【Java学习笔记】方法的使用

【Java学习笔记】方法的使用 一、一个例子二、方法的概念及使用&#xff08;一&#xff09;什么是方法&#xff08;二&#xff09;方法的定义&#xff08;三&#xff09;方法调用的执行过程&#xff08;四&#xff09;实参和形参的关系&#xff08;重要&#xff09;&#xff08…

第1节、基于太阳能的环境监测系统——MPPT充电板

一、更新时间&#xff1a; 本篇文章更新于&#xff1a;2024年7月6日23:33:30 二、内容简介&#xff1a; 整体系统使用太阳能板为锂电池充电和系统供电&#xff0c;天黑后锂电池为系统供电&#xff0c;本节主要介绍基于CN3722的MPPT太阳能充电模块&#xff0c;这块主要是硬件…

如何从相机的存储卡中恢复原始照片

“不好了。” 当您意识到自己不小心从存储卡中删除了照片&#xff0c;或者错误地格式化了相机的记忆棒时&#xff0c;您首先会喊出这两个词。这是一种常见的情况&#xff0c;每个人一生中都会遇到这种情况。幸运的是&#xff0c;有办法从相机的 RAW 记忆棒中恢复已删除的照片。…

关于小爱同学自定义指令执行

1.前言 之前买了小爱同学音响&#xff0c;一直想让其让我的生活变得更智能&#xff0c;编写一些程序来完成一些自动化任务&#xff0c;但是经过搜索发现&#xff0c;官方开发者平台不能用了&#xff0c;寻找api阶段浪费了我很长时间。最后在github 开源项目发现了俩个比较关键…

gcc的编译C语言的过程

gcc的简介 GCC&#xff08;GNU Compiler Collection&#xff09;是由GNU项目开发和维护的一套开源编程语言编译器集合。它支持多种编程语言&#xff0c;包括但不限于C、C、Objective-C、Fortran、Ada等。GCC被广泛应用于编译和优化各种程序&#xff0c;是许多开发者和组织的首选…

防火墙基础及登录(华为)

目录 防火墙概述防火墙发展进程包过滤防火墙代理防火墙状态检测防火墙UTM下一代防火墙&#xff08;NGFW&#xff09; 防火墙分类按物理特性划分软件防火墙硬件防火墙 按性能划分百兆级别和千兆级别 按防火墙结构划分单一主机防火墙路由集成式防火墙分布式防火墙 华为防火墙利用…

ubuntu22.04+pytorch2.3安装PyG图神经网络库

ubuntu下安装torch-geometric库&#xff0c;图神经网络 开发环境 ubuntu22.04 conda 24.5.0 python 3.9 pytorch 2.0.1 cuda 11.8 pyg的安装网上教程流传着许多安装方式&#xff0c;这些安装方式主要是&#xff1a;预先安装好pyg的依赖库&#xff0c;这些依赖库需要对应上pyth…

C++11|包装器

目录 引入 一、function包装器 1.1包装器使用 1.2包装器解决类型复杂 二、bind包装器 引入 在我们学过的回调中&#xff0c;函数指针&#xff0c;仿函数&#xff0c;lambda都可以完成&#xff0c;但他们都有一个缺点&#xff0c;就是类型的推导复杂性&#xff0c;从而会…

详解Amivest 流动性比率

详解Amivest 流动性比率 Claude-3.5-Sonnet Poe Amivest流动性比率是一个衡量证券市场流动性的重要指标。这个比率主要用于评估在不对价格造成重大影响的情况下,市场能够吸收多少交易量。以下是对Amivest流动性比率的详细解释: 定义: Amivest流动性比率是交易额与绝对收益率的…

一.2.(1)双极型晶体三极管的结构、工作原理、特性曲线及主要参数

1.双极型晶体三极管的结构 学会区分P管和N管&#xff0c;会绘制符号 2.工作原理 无论是PNP 还是NPN&#xff0c;本质上放大时&#xff0c;都是发射结正偏&#xff0c;集电极反偏。&#xff08;可以简单理解为pn为二极管&#xff0c;每个三极管都有两个二极管&#xff09; 其中电…

行内元素、块级元素居中

行内元素居中 水平居中 {text-align&#xff1a;center;}垂直居中 单行——行高等于盒子高度 <head><style>.father {width: 400px;height: 200px;/* 行高等于盒子高度&#xff1a;line-height: 200px; */line-height: 200px;background-color: pink;}.son {}&…

深入刨析Redis存储技术设计艺术(二)

三、Redis主存储 3.1、存储相关结构体 redisServer:服务器 server.h struct redisServer { /* General */ pid_t pid; /* Main process pid. */ pthread_t main_thread_id; /* Main thread id */ char *configfile; /* Absolut…

js获取当前浏览器地址,ip,端口号等等

前言&#xff1a; js获取当前浏览器地址&#xff0c;ip&#xff0c;端口号等等 window.location属性查询 具体属性&#xff1a; 1、获取他的ip地址 window.location.hostname 2、获取他的端口号 window.location.port 3、获取他的全路径 window.location.origin 4、获取…

EtherCAT转Profinet网关配置说明第一讲:配置软件安装及介绍

网关XD-ECPNS20为EtherCAT转Profinet协议网关&#xff0c;使EtherCAT协议和Profinet协议两种工业实时以太网网络之间双向传输 IO 数据。适用于具有EtherCAT协议网络与Profinet协议网络跨越网络界限进行数据交换的解决方案。 本网关通过上位机来进行配置。 首先安装上位机软件 一…