《手写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工厂类进行接口调用。
整体设计结构如下图:
实现
代码结构

源码实现:https://github.com/swg209/spring-study/tree/main/step20-simple-orm
类图
- 在整个类图中,SqlSession是与数据库交互的核心接口,定义了多种查询方法,包括
selectOne
和selectList
,分别用于查询单个对象和对象列表,同时提供了close
方法用于关闭会话。依赖于SqlSessionFactory
接口来创建其实例。 - SqlSessionFactory负责创建
SqlSession
实例,通过其openSession
方法实现。 - DefaultSqlSession类是
SqlSession
接口的具体实现类,提供了selectOne
、selectList
和close
等方法的实现。包含了数据库连接connection
和映射元素mapperElement
等属性,用于管理数据库连接和SQL映射。提供了resultSet2Obj
和buildParameter
等辅助方法,用于结果集到对象的转换和参数构建。 - DefaultSqlSessionFactory类是
SqlSessionFactory
接口的具体实现类,负责创建和管理SqlSession
对象。包含了配置configuration
属性,用于存储数据库连接和SQL映射的配置信息。通过其openSession
方法返回SqlSession
实例。 - SqlSessionFactoryBuilder类用于构建
SqlSessionFactory
实例,通过其build
方法读取配置文件并创建DefaultSqlSessionFactory
对象。在构建过程中,会解析配置文件并生成相应的配置信息。
实现步骤
-
定义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();}
-
定义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() {} }
-
定义sqlSessionFactory接口
public interface SqlSessionFactory {SqlSession openSession(); }
-
定义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);} }
-
定义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();}}
}
测试结果
测试用例(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();}}
}
测试结果
- 从测试结果中可以看到,可以正常与数据库进行交互,可以正常查询到用户表的数据。
总结
-
在本章节中,我们简单实现一个类似mybatis的ORM框架,当前只是实现了基本功能,后续可以基于这个框架再进行完善。
-
通过编写测试用例,验证了该 ORM 框架的基本功能,包括查询单个对象和对象列表。测试结果表明,该框架能够正常与数据库进行交互,并正确映射查询结果到 Java 对象。
参考书籍:《手写Spring渐进式源码实践》
书籍源代码:https://github.com/fuzhengwei/small-spring
相关文章:

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

AI技术赋能电商行业:创新应用与未来展望
💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《热点时事》 期待您的关注 引言 随着科技的飞速发展,人工智能(AI)技术正逐步渗透到各行各业&a…...

windows 11编译安装ffmpeg(包含ffplay)
一、源码及安装包下载 1.1,ffmpeg源码包下载 下载地址:Download FFmpeg 1.2,mysys下载 下载地址:MSYS2 1.3,libx264源码包下载 下载地址:x264, the best H.264/AVC encoder - VideoLAN 二、软件安装 2.1&…...
系统启动时将自动加载环境变量,并后台启动 MinIO、Nacos 和 Redis 服务
服务器信息 服务器 IP:192.168.1.44服务器用户:changzhou用户密码:XXXXXXXX Nacos 数据库用户信息: 账号:cz_nacos密码:XXXXXXXX Nacos 内网地址:http://192.168.1.44:8848/nacos 账号&#x…...

[ACTF2020 新生赛]Upload 1--详细解析
信息收集 题目告诉我们是一道upload,也就是文件上传漏洞题目。 进入界面,是一个灯泡,将鼠标放在图标上就会出现文件上传的相应位置: 思路 文件上传漏洞,先看看有没有前端校验。 在js源码中找到了前端校验ÿ…...

power bi中的related函数解析
在Power BI中,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 中,预测偏移(Prediction Shift) 和 排序提升(Ordered Boosting) 是其关键概念和创新点。CatBoost 通过引入 排序提升 解决了梯度提升决策树(GBDT)算法中常见的 预测偏移问题&#x…...
python: postgreSQL using psycopg2 or psycopg
psycopg2 # encoding: utf-8 # 版权所有 2024 ©涂聚文有限公司 # 許可資訊查看:言語成了邀功的功臣,還需要行爲每日來值班嗎? # 描述: pip install --upgrade pip PostgreSQL database adapter for Python # pip install…...
从 MySQL 5.7 到 8.0:理解 GROUP BY 的新规则与实战优化20241112
🎯 从 MySQL 5.7 到 8.0:理解 GROUP BY 的新规则与实战优化 🔎 引言 随着 MySQL 的不断升级,从 5.7 到 8.0,不仅性能得到提升,其对 SQL 标准的严格执行也显著提高。GROUP BY 的行为变化就是一个典型例子。…...

npm完整发包流程(亲测可验证)
1. 准备工作 (1) 在npm官网上注册一个账号 (2) 注册成功之后,npm会发送一封邮件给你,点击邮件里面的链接,做确认关联操作(必需) 2. 创建自己的npm包 (…...

学习threejs,使用JSON格式保存和加载模型
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE toJSON()方法 二、&a…...
中国首部《能源法》正式问世,它的亮点有哪些呢?
2024年11月8日,《中华人民共和国能源法》经十四届全国人大常委会第十二次会议审议通过,正式出台,将于明年1月1日起施行。 中国首部《能源法》正式问世,它的亮点有哪些呢? 一、填补立法空白,完善能源法律体…...

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

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

【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 目录,如果是 root 账户就是 /root . 当前目录 .. 上层目录 | 管道符 & 后台工作,放在完整指令列的最后端,表示将该指令列放入后台中工作。 > 输出重定向,重新…...

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

ODOO学习笔记(7):模块化架构(按需安装)
一、Odoo模块化架构概述 Odoo是一个功能强大的企业资源规划(ERP)系统,其模块化架构是它的核心优势之一。这种架构允许系统通过添加、移除或修改不同的模块来灵活地适应企业的各种业务需求。 核心模块与自定义模块: Odoo本身带有一…...

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

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
32单片机——基本定时器
STM32F103有众多的定时器,其中包括2个基本定时器(TIM6和TIM7)、4个通用定时器(TIM2~TIM5)、2个高级控制定时器(TIM1和TIM8),这些定时器彼此完全独立,不共享任何资源 1、定…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...