当前位置: 首页 > news >正文

《手写Spring渐进式源码实践》实践笔记(第二十章 实现简单ORM框架)

文章目录

  • 第二十章 简单ORM框架实现
    • 背景
      • 技术背景
        • 基本概念
        • 工作原理
        • 优点
        • 缺点
        • 常见的ORM框架
      • 业务背景
    • 目标
    • 设计
    • 实现
      • 代码结构
      • 类图
      • 实现步骤
    • 测试
      • 事先准备
      • 属性配置文件
      • 测试用例(selectOne)
      • 测试结果
      • 测试用例(selectList)
      • 测试结果
    • 总结


第二十章 简单ORM框架实现

背景

技术背景

ORM(Object-Relational Mapping)是一种用于简化开发的技术,它通过将面向对象编程语言中的对象与关系型数据库中的表进行映射,使得开发者可以用面向对象的方式操作数据库,而无需直接编写SQL语句。以下是对ORM的详细解释:

基本概念
  • ORM全称为对象关系映射(Object-Relational Mapping),它是一种编程技术,用于在面向对象的编程语言和关系型数据库管理系统之间建立桥梁。
  • ORM通过将编程语言中的类映射为数据库中的表,将类的实例(对象)映射为表中的记录,实现了对象和关系型数据库之间的数据交互。
工作原理
  • ORM技术允许开发者以面向对象的方式操作数据,例如创建、读取、更新和删除对象,而不需要直接写SQL语句。
  • 在ORM中,数据库表被视为对象类,表中的每一条记录被视为该类的一个实例,表的列则映射为对象的属性。
  • ORM框架通常提供了一系列的API和工具,使得开发人员可以通过面向对象的方式来进行数据库操作,如查询、插入、更新和删除等。
优点
  • ORM技术极大地简化了应用程序中的数据访问层开发,提高了开发效率和代码的可维护性。
  • ORM通过面向对象的方式来操作数据库,使得代码更加易于维护和扩展,同时也提高了代码的可读性和可重用性。
  • ORM支持多种数据库,使得开发人员可以更加灵活地选择数据库,提高了代码的可移植性和可扩展性。
缺点
  • ORM框架的性能可能不如手写SQL,对于大量数据的查询和操作可能会有一定的性能损失。
  • ORM框架需要掌握一定的知识和技能,学习成本较高。
  • 不同的ORM框架对于相同的数据类型和操作可能会有不同的支持程度,可能存在可移植性的问题。
  • ORM框架通常需要进行配置和映射,复杂性较高,需要一定的技术水平和经验。
常见的ORM框架
  • Java:Hibernate、MyBatis等。
  • Python:SQLAlchemy、Django ORM、Peewee等。
  • .NET:Entity Framework (EF) Core等。
  • PHP:Laravel ORM、Yii ORM、ThinkPHP ORM等。

业务背景

ORM 对象关系映射,是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换,也让我们可以更方便的使用数据库。那么,怎样实现类似于 MyBatis 这样的 ORM 框架呢?本章节我们就来以实现一个 ORM 框架为目标,看看该怎么设计和实现。另外关于 ORM 框架的实现,下一章节我们会继续完善这个ORM框架,并与前面章节实现的small-spring框架做对接。

目标

实现一个简易版的类似mybatis的orm框架,屏蔽了对 JDBC 操作的复杂性, 让外部的调用方可以更加简单的方式使用数据库。

设计

一个ORM框架要实现的核心内容, 包括参数映射、SQL解析、SQL执行、结果映射,这块的内容会被封装,对内通过jdbc与数据库进行交互,对外提供SqlSession工厂类进行接口调用。

整体设计结构如下图:

image-20241111183431280

实现

代码结构

image-20241111183804826

源码实现:https://github.com/swg209/spring-study/tree/main/step20-simple-orm

类图

image-20241111183852923

  1. 在整个类图中,SqlSession是与数据库交互的核心接口,定义了多种查询方法,包括selectOneselectList,分别用于查询单个对象和对象列表,同时提供了close方法用于关闭会话。依赖于SqlSessionFactory接口来创建其实例。
  2. SqlSessionFactory负责创建SqlSession实例,通过其openSession方法实现。
  3. DefaultSqlSession类是SqlSession接口的具体实现类,提供了selectOneselectListclose等方法的实现。包含了数据库连接connection和映射元素mapperElement等属性,用于管理数据库连接和SQL映射。提供了resultSet2ObjbuildParameter等辅助方法,用于结果集到对象的转换和参数构建。
  4. DefaultSqlSessionFactory类是SqlSessionFactory接口的具体实现类,负责创建和管理SqlSession对象。包含了配置configuration属性,用于存储数据库连接和SQL映射的配置信息。通过其openSession方法返回SqlSession实例。
  5. SqlSessionFactoryBuilder类用于构建SqlSessionFactory实例,通过其build方法读取配置文件并创建DefaultSqlSessionFactory对象。在构建过程中,会解析配置文件并生成相应的配置信息。

实现步骤

  1. 定义sqlSession接口

    public interface SqlSession {<T> T selectOne(String statement, Object parameter);<T> T selectOne(String statement);<T> List<T> selectList(String statement, Object parameter);<T> List<T> selectList(String statement);void close();}
    
  2. 定义sqlSession具体实现类 DefaultSqlSession

    public class DefaultSqlSession implements SqlSession {//连接信息.private Connection connection;//配置信息.private Map<String, XNode> mapperElement;public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {this.connection = connection;this.mapperElement = mapperElement;}@Overridepublic <T> T selectOne(String statement) {try {XNode xNode = mapperElement.get(statement);PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {List<T> list = new ArrayList<>();try {ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();//每次遍历一行值while (resultSet.next()) {//创建对象T obj = (T) clazz.newInstance();//遍历每一列for (int i = 1; i <= columnCount; i++) {//获取列名String columnName = metaData.getColumnName(i);//驼峰命名columnName = WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");//获取列值Object value = resultSet.getObject(i);//获取set方法String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);Method method;if (value instanceof Timestamp) {method = clazz.getMethod(setMethod, Date.class);//调用set方法} else {method = clazz.getMethod(setMethod, value.getClass());//调用set方法}method.invoke(obj, value);}list.add(obj);}} catch (Exception e) {e.printStackTrace();}return list;}@Overridepublic <T> T selectOne(String statement, Object parameter) {XNode xNode = mapperElement.get(statement);Map<Integer, String> parameterMap = xNode.getParameter();try {PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, parameterMap);ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}private void buildParameter(PreparedStatement preparedStatement,Object parameter,Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {int size = parameterMap.size();// 单个参数if (parameter instanceof Long) {for (int i = 1; i <= size; i++) {preparedStatement.setLong(i, Long.parseLong(parameter.toString()));}return;}if (parameter instanceof Integer) {for (int i = 1; i <= size; i++) {preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));}return;}if (parameter instanceof String) {for (int i = 1; i <= size; i++) {preparedStatement.setString(i, parameter.toString());}return;}Map<String, Object> fieldMap = new HashMap<>();// 对象参数Field[] declaredFields = parameter.getClass().getDeclaredFields();for (Field field : declaredFields) {String name = field.getName();field.setAccessible(true);Object obj = field.get(parameter);field.setAccessible(false);fieldMap.put(name, obj);}for (int i = 1; i <= size; i++) {String parameterDefine = parameterMap.get(i);Object obj = fieldMap.get(parameterDefine);if (obj instanceof Short) {preparedStatement.setShort(i, Short.parseShort(obj.toString()));continue;}if (obj instanceof Integer) {preparedStatement.setInt(i, Integer.parseInt(obj.toString()));continue;}if (obj instanceof Long) {preparedStatement.setLong(i, Long.parseLong(obj.toString()));continue;}if (obj instanceof String) {preparedStatement.setString(i, obj.toString());continue;}if (obj instanceof Date) {preparedStatement.setDate(i, (java.sql.Date) obj);}}}@Overridepublic <T> List<T> selectList(String statement, Object parameter) {try {XNode xNode = mapperElement.get(statement);PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, xNode.getParameter());ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects;} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic <T> List<T> selectList(String statement) {try {XNode xNode = mapperElement.get(statement);PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());ResultSet resultSet = preparedStatement.executeQuery();List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects;} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic void close() {}
    }
    
  3. 定义sqlSessionFactory接口

    public interface SqlSessionFactory {SqlSession openSession();
    }
  4. 定义sqlSessionFactory具体实现类 DefaultSqlSessionFactory

    public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {// 创建会话return new DefaultSqlSession(configuration.connection, configuration.mapperElement);}
    }
  5. 定义SqlSessionFactoryBuilder,实现读取配置中的xml文件,解析获取配置源信息dataSource、

​ SQL语句信息mapperElement、数据库连接信息Connection, 写入Configuration

public class SqlSessionFactoryBuilder {/*** 构建会话工厂.** @param reader* @return*/public SqlSessionFactory build(Reader reader) {SAXReader saxReader = new SAXReader();try {Document document = saxReader.read(new InputSource(reader));Configuration configuration = parseConfiguration(document.getRootElement());return new DefaultSqlSessionFactory(configuration);} catch (DocumentException e) {e.printStackTrace();}return null;}private Configuration parseConfiguration(Element root) {Configuration configuration = new Configuration();configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));configuration.setConnection(connection(configuration.dataSource));configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));return configuration;}/*** 获取数据源配置信息.*/private Map<String, String> dataSource(List<Element> list) {Map<String, String> dataSource = new HashMap<>(4);Element element = list.get(0);List content = element.content();for (Object o : content) {Element e = (Element) o;String name = e.attributeValue("name");String value = e.attributeValue("value");dataSource.put(name, value);}return dataSource;}/*** 获取connection信息.*/private Connection connection(Map<String, String> dataSource) {try {Class.forName(dataSource.get("driver"));return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));} catch (ClassNotFoundException | SQLException e) {e.printStackTrace();}return null;}/*** 获取SQL语句信息.*/private Map<String, XNode> mapperElement(List<Element> list) {Map<String, XNode> map = new HashMap<>();Element element = list.get(0);List content = element.content();for (Object o : content) {Element e = (Element) o;String resource = e.attributeValue("resource");try {Reader reader = Resources.getResourceAsReader(resource);SAXReader saxReader = new SAXReader();Document document = saxReader.read(new InputSource(reader));Element root = document.getRootElement();//命名空间String namespace = root.attributeValue("namespace");//SELECTList<Element> selectNodes = root.selectNodes("select");for (Element node : selectNodes) {String id = node.attributeValue("id");String parameterType = node.attributeValue("parameterType");String resultType = node.attributeValue("resultType");String sql = node.getText();// ? 匹配Map<Integer, String> parameter = new HashMap<>();Pattern pattern = Pattern.compile("(#\\{(.*?)})");Matcher matcher = pattern.matcher(sql);for (int i = 1; matcher.find(); i++) {String g1 = matcher.group(1);String g2 = matcher.group(2);parameter.put(i, g2);sql = sql.replace(g1, "?");}XNode xNode = new XNode();xNode.setNamespace(namespace);xNode.setId(id);xNode.setParameterType(parameterType);xNode.setResultType(resultType);xNode.setSql(sql);xNode.setParameter(parameter);map.put(namespace + "." + id, xNode);}} catch (Exception ex) {ex.printStackTrace();}}return map;}
}

测试

事先准备

mysql数据库,配置好连接信息, 建表语句。

#创建数据库()
CREATE DATABASE IF NOT EXISTS mybatis;#创建用户表
USE mybatis;CREATE TABLE `my_user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',`user_id` varchar(9) DEFAULT NULL COMMENT '用户ID',`user_head` varchar(16) DEFAULT NULL COMMENT '用户头像',`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',`user_name` varchar(64) DEFAULT NULL COMMENT '用户名',`user_password` varchar(64) DEFAULT NULL COMMENT '用户密码',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;# 造数
INSERT INTO my_user (user_id, user_head, create_time, update_time, user_name, user_password) VALUES
('1', '头像1', '2024-11-12 18:00:12', '2024-11-12 18:00:12', '小苏1', 's123asd'),
('2', '头像2', '2024-11-12 18:00:12', '2024-11-12 18:00:12', '小苏2', 's123asd');

User类

  • 创建 User类,承载数据库表的对象数据。IUserDao定义用户表Dao查询方法。
public class User {private Long id;private String userId;          // 用户IDprivate String userName;    // 昵称private String userHead;        // 头像private String userPassword;    // 密码private Date createTime;        // 创建时间private Date updateTime;        // 更新时间public User() {}public User(String userName) {this.userName = userName;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getUserName() {return userName;}public void setUserName(String userNickName) {this.userName = userNickName;}public String getUserHead() {return userHead;}public void setUserHead(String userHead) {this.userHead = userHead;}public String getUserPassword() {return userPassword;}public void setUserPassword(String userPassword) {this.userPassword = userPassword;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public Date getUpdateTime() {return updateTime;}public void setUpdateTime(Date updateTime) {this.updateTime = updateTime;}}

IUserDao

public interface IUserDao {User queryUserInfoById(Long id);List<User> queryUserList(User user);
}

属性配置文件

mybatis-config-datasource.xml** ORM配置文件

配置数据库源连接信息,配置mappers声明.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><mapper resource="mapper/User_Mapper.xml"/></mappers></configuration>

User_Mapper.xml mapper配置文件

配置用户表查询方法.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.suwg.springframework.mybatis.test.dao.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long"resultType="cn.suwg.springframework.mybatis.test.po.User">SELECT id, user_id, user_name, user_head, user_password, create_timeFROM my_userwhere id = #{id}</select><select id="queryUserList" parameterType="cn.suwg.springframework.mybatis.test.po.User"resultType="cn.suwg.springframework.mybatis.test.po.User">SELECT id, user_id, user_name, user_head, user_password, create_time, update_timeFROM my_userwhere user_name = #{userName}</select></mapper>

测试用例(selectOne)

public class ApiTest {@Testpublic void test_queryUserInfoById() {String resource = "mybatis-config-datasource.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {User user = session.selectOne("cn.suwg.springframework.mybatis.test.dao.IUserDao.queryUserInfoById", 1L);System.out.println(JSON.toJSONString(user));} finally {session.close();reader.close();}} catch (Exception e) {e.printStackTrace();}}
}

测试结果

image-20241112105314330

测试用例(selectList)

public class ApiTest {@Testpublic void test_queryUserInfoList() {String resource = "mybatis-config-datasource.xml";Reader reader;try {reader = Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlMapper.openSession();try {List<User> userList = session.selectList("cn.suwg.springframework.mybatis.test.dao.IUserDao.queryUserList",new User("小苏"));System.out.println(JSON.toJSONString(userList));} finally {session.close();reader.close();}} catch (Exception e) {e.printStackTrace();}}
}

测试结果

image-20241112105408583

  • 从测试结果中可以看到,可以正常与数据库进行交互,可以正常查询到用户表的数据。

总结

  • 在本章节中,我们简单实现一个类似mybatis的ORM框架,当前只是实现了基本功能,后续可以基于这个框架再进行完善。

  • 通过编写测试用例,验证了该 ORM 框架的基本功能,包括查询单个对象和对象列表。测试结果表明,该框架能够正常与数据库进行交互,并正确映射查询结果到 Java 对象。

参考书籍:《手写Spring渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/small-spring

相关文章:

《手写Spring渐进式源码实践》实践笔记(第二十章 实现简单ORM框架)

文章目录 第二十章 简单ORM框架实现背景技术背景基本概念工作原理优点缺点常见的ORM框架 业务背景 目标设计实现代码结构类图实现步骤 测试事先准备属性配置文件测试用例(selectOne)测试结果测试用例(selectList)测试结果 总结 第二十章 简单ORM框架实现 背景 技术背景 ORM&…...

AI技术赋能电商行业:创新应用与未来展望

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《热点时事》 期待您的关注 引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术正逐步渗透到各行各业&a…...

windows 11编译安装ffmpeg(包含ffplay)

一、源码及安装包下载 1.1&#xff0c;ffmpeg源码包下载 下载地址&#xff1a;Download FFmpeg 1.2&#xff0c;mysys下载 下载地址&#xff1a;MSYS2 1.3&#xff0c;libx264源码包下载 下载地址&#xff1a;x264, the best H.264/AVC encoder - VideoLAN 二、软件安装 2.1&…...

系统启动时将自动加载环境变量,并后台启动 MinIO、Nacos 和 Redis 服务

服务器信息 服务器 IP&#xff1a;192.168.1.44服务器用户&#xff1a;changzhou用户密码&#xff1a;XXXXXXXX Nacos 数据库用户信息&#xff1a; 账号&#xff1a;cz_nacos密码&#xff1a;XXXXXXXX Nacos 内网地址&#xff1a;http://192.168.1.44:8848/nacos 账号&#x…...

[ACTF2020 新生赛]Upload 1--详细解析

信息收集 题目告诉我们是一道upload&#xff0c;也就是文件上传漏洞题目。 进入界面&#xff0c;是一个灯泡&#xff0c;将鼠标放在图标上就会出现文件上传的相应位置&#xff1a; 思路 文件上传漏洞&#xff0c;先看看有没有前端校验。 在js源码中找到了前端校验&#xff…...

power bi中的related函数解析

在Power BI中&#xff0c;RELATED函数是一种用于检索相关表中数据的函数。它用于在一个表中检索与当前行相关联的另一个表中的数据。 销售成本 [销售数量]*related(商品表[进价])...

目前区块链服务商备案支持的区块链技术类型

status"success"data1-name"比特币/Bitcoin/BTC"3-name"以太坊/Ethereum/ETH"875-name"超级账本/Hyperledger"5-name"柚子/EOS/EOS"6-name"恒星链/Stellar/XLM"1055-name"Quorum"7-name"莱特币/Li…...

CatBoost中的预测偏移和排序提升

在 CatBoost 中&#xff0c;预测偏移&#xff08;Prediction Shift&#xff09; 和 排序提升&#xff08;Ordered Boosting&#xff09; 是其关键概念和创新点。CatBoost 通过引入 排序提升 解决了梯度提升决策树&#xff08;GBDT&#xff09;算法中常见的 预测偏移问题&#x…...

python: postgreSQL using psycopg2 or psycopg

psycopg2 # encoding: utf-8 # 版权所有 2024 ©涂聚文有限公司 # 許可資訊查看&#xff1a;言語成了邀功的功臣&#xff0c;還需要行爲每日來值班嗎&#xff1f; # 描述&#xff1a; pip install --upgrade pip PostgreSQL database adapter for Python # pip install…...

从 MySQL 5.7 到 8.0:理解 GROUP BY 的新规则与实战优化20241112

&#x1f3af; 从 MySQL 5.7 到 8.0&#xff1a;理解 GROUP BY 的新规则与实战优化 &#x1f50e; 引言 随着 MySQL 的不断升级&#xff0c;从 5.7 到 8.0&#xff0c;不仅性能得到提升&#xff0c;其对 SQL 标准的严格执行也显著提高。GROUP BY 的行为变化就是一个典型例子。…...

npm完整发包流程(亲测可验证)

1. 准备工作 &#xff08;1&#xff09; 在npm官网上注册一个账号 &#xff08;2&#xff09; 注册成功之后&#xff0c;npm会发送一封邮件给你&#xff0c;点击邮件里面的链接&#xff0c;做确认关联操作&#xff08;必需&#xff09; 2. 创建自己的npm包 &#xff08;…...

学习threejs,使用JSON格式保存和加载模型

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE toJSON()方法 二、&a…...

中国首部《能源法》正式问世,它的亮点有哪些呢?

2024年11月8日&#xff0c;《中华人民共和国能源法》经十四届全国人大常委会第十二次会议审议通过&#xff0c;正式出台&#xff0c;将于明年1月1日起施行。 中国首部《能源法》正式问世&#xff0c;它的亮点有哪些呢&#xff1f; 一、填补立法空白&#xff0c;完善能源法律体…...

【外包】软件行业的原始形态,项目外包与独立开发者

【外包】互联网软件行业的原始形态&#xff0c;项目外包与独立开发者 本科期间写的一些东西&#xff0c;最近整理东西看到了&#xff0c;大致整理一下放出来&#xff0c;部分内容来自其他文章&#xff0c;均已引用。 文章目录 1、互联网软件行业的原始形态2、项目订单&#xff…...

工程数学线性代数(同济第七版)附册课后习题答案PDF

《线性代数附册 学习辅导与习题全解》是与同济大学数学科学学院编《工程数学 线性代数》第七版教材配套的教学辅导书&#xff0c;由同济大学作者团队根据教材内容和要求编写而成。本书在《工程数学 线性代数》第六版附册&#xff08;即辅导书&#xff09;的基础上修改而成。全书…...

【Ubuntu24.04】部署服务(基础)

目录 0 背景1 设置静态IP2 连接服务器3 部署服务3.1 安装JDK3.2 下载并安装MySQL8.43.2.1 从官网下载 APT Repository 配置文件3.2.2 安装 MySQL8.43.2.3 配置远程连接 3.3 下载并配置Redis3.4 上传jar包并部署应用3.5 开放端口 4 总结 0 背景 在成功安装了Ubuntu24.04操作系统…...

Linux符号使用记录

~ 账户 home 目录&#xff0c;如果是 root 账户就是 /root . 当前目录 .. 上层目录 | 管道符 & 后台工作&#xff0c;放在完整指令列的最后端&#xff0c;表示将该指令列放入后台中工作。 > 输出重定向&#xff0c;重新…...

初阶C++之C++入门基础

大家好&#xff01;欢迎来到C篇学习&#xff0c;这篇文章的内容不会很难&#xff0c;为c的引入&#xff0c;c的重点内容将在第二篇的文章中讲解&#xff0c;届时难度会陡然上升&#xff0c;请做好准备&#xff01; 我们先看网络上的一个梗&#xff1a;21天内⾃学精通C 好了&am…...

ODOO学习笔记(7):模块化架构(按需安装)

一、Odoo模块化架构概述 Odoo是一个功能强大的企业资源规划&#xff08;ERP&#xff09;系统&#xff0c;其模块化架构是它的核心优势之一。这种架构允许系统通过添加、移除或修改不同的模块来灵活地适应企业的各种业务需求。 核心模块与自定义模块&#xff1a; Odoo本身带有一…...

Java的dto,和多表的调用

1理论 需求是新增菜品eg&#xff1a;菜名:豆腐脑&#xff1b;口味&#xff1a;甜口&#xff0c;咸口&#xff0c; 菜单表&#xff1a;dish&#xff1b;口味表dish_flavor&#xff1b; 1dto:数据传输对象 新建一个dishDto对象有两个表里的属性 2用到两个表&#xff0c;dish,d…...

时序数据库TimescaleDB安装部署以及常见使用

文章目录 一、时序数据库二、TimescaleDB部署1、repository yum仓库配置2、yum在线安装3、插件配置4、TimescaleDB使用登录pg创建插件使用超表 一、时序数据库 什么是时序数据库&#xff1f;顾名思义&#xff0c;用于处理按照时间变化顺序的数据的数据库即为时序数据库&#x…...

MG算法(英文版)题解

翻译&#xff1a; 考虑一个加法流&#xff0c;其中一个特定项目出现 n^(1/2) 次&#xff0c;并且有 n - n^(1/2) - 1 个其他不同的项目&#xff0c;每个项目出现一次。在应用 Misra-Gries&#xff08;MG&#xff09;算法时&#xff0c;应该选择哪个 ε&#xff08;epsilon&…...

2-UML概念模型测试

1. (单选题, 1.0 分) UML中的关系不包括&#xff08;&#xff09;。 A. 抽象B. 实现C. 依赖D. 关联 我的答案:A正确答案: A 知识点&#xff1a; UML的构成 1.0分 2. (单选题, 1.0 分) 下列事物不属于UML结构事物的是&#xff08;&#xff09;。 A. 组件B. 类C. 节点D. 状…...

人工智能(AI)对于电商行业的变革和意义

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/402a907e12694df5a34f8f266385f3d2.png#pic_center> &#x1f393;作者简介&#xff1a;全栈领域优质创作者 &#x1f310;个人主页&#xff1a;百锦再新空间代码工作室 &#x1f4de;工作室&#xff1a;新空间代…...

智能病历xml提取

select * from (SELECT m.病人Id, m.主页Id, x.title, x.content.getclobval() 参考, x.content content --EXTRACTVALUE(x.Content, //zlxml//document//subdoc[antetypeid"3C38A8DAB01C473A9074A8EDD0B8553"]//utext) 主治医师, --EXTRACTVALUE(x.…...

RK3568平台开发系列讲解(GPIO篇)GPIO的sysfs调试手段

🚀返回专栏总目录 文章目录 一、内核配置二、GPIO sysfs节点介绍三、命令行控制GPIO3.1、sd导出GPIO3.2、设置GPIO方向3.3、GPIO输入电平读取3.4、GPIO输出电平设置四、Linux 应用控制GPIO4.1、控制输出4.2、输入检测4.3、使用 GPIO 中断沉淀、分享、成长,让自己和他人都能有…...

使用 Web Search 插件扩展 GitHub Copilot 问答

GitHub Copilot 是一个由 GitHub 和 OpenAI 合作开发的人工智能代码提示工具。它可以根据上下文提示代码&#xff0c;还可以回答各种技术相关的问题。但是 Copilot 本身不能回答非技术类型的问题。为了扩展 Copilot 的功能&#xff0c;微软发布了一个名为 Web Search 的插件&am…...

workerman的安装与使用

webman是一款基于workerman开发的高性能HTTP服务框架。webman用于替代传统的php-fpm架构&#xff0c;提供超高性能可扩展的HTTP服务。你可以用webman开发网站&#xff0c;也可以开发HTTP接口或者微服务。 除此之外&#xff0c;webman还支持自定义进程&#xff0c;可以做worker…...

QtQuick.Controls 控件介绍(都有哪些type)

这里写目录标题 主要控件 官方示例1. quickcontrols示例示例1 控制controlsSliders滑块bottom与tab 示例2 系统对话框 systemdialogs示例3 仪表盘示例4 uiforms 表格-客户通讯录 2. quickcontrols2示例1 gallery 展示2 flat Style 扁平化 帮助文档 主要控件 Button&#xff1a…...

Unity导出APK加速与导出失败总结(不定时更新)

APK导出加速 1、修改配置文件&#xff1a; 需要修改的文件位置&#xff1a;编辑器安装路径/Editor/Data/PlaybackEngines/AndroidPlayer/Tools/GradleTemplates 1.1 settingsTemplate.gradle文件修改 直接附上最终效果&#xff1a; pluginManagement {repositories {**ART…...