MyBatis原理分析手写持久层框架

目录

  • 1 JDBC操作数据库问题分析
  • 2 JDBC问题分析和解决思路
  • 3 自定义持久层框架_思路分析
    • 3.1 使用JDBC和使用持久层框架区别
    • 3.2 核心接口/类重点说明
    • 3.3 项目使用端
    • 3.4 自定义框架本身
    • 3.5 最终手写的持久层框架结构参考
  • 4 自定义持久层框架_编码
  • 5 自定义持久层框架优化


1 JDBC操作数据库问题分析

在这里插入图片描述

JDBC API 允许应用程序访问任何形式的表格数据,特别是存储在关系数据库中的数据

在这里插入图片描述

代码示例:

public static void main(String[] args) { Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {// 加载数据库驱动Class.forName("com.mysql.jdbc.Driver");// 通过驱动管理类获取数据库链接connection =
DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8", "root", "root");// 定义sql语句?表示占位符String sql = "select * from user where username = ?";// 获取预处理statementpreparedStatement = connection.prepareStatement(sql);// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1, "tom");// 向数据库发出sql执行查询,查询出结果集resultSet = preparedStatement.executeQuery();// 遍历查询结果集while (resultSet.next()) {int id = resultSet.getInt("id");String username = resultSet.getString("username");// 封装Useruser.setId(id);user.setUsername(username);}System.out.println(user);}} catch (Exception e) {e.printStackTrace();} finally {// 释放资源if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}
}

2 JDBC问题分析和解决思路

剖开代码,逐个分析:

(1)加载驱动,获取链接:

在这里插入图片描述

  • 存在问题1:数据库配置信息存在硬编码问题。

    优化思路:使用配置文件!

  • 存在问题2:频繁创建、释放数据库连接问题。

    优化思路:使用数据连接池!

(2)定义sql、设置参数、执行查询:

在这里插入图片描述

  • 存在问题3:SQL语句、设置参数、获取结果集参数均存在硬编码问题 。

    优化思路:使用配置文件!

(2)遍历查询结果集:

在这里插入图片描述

  • 存在问题4:手动封装返回结果集,较为繁琐

    优化思路:使用Java反射、内省!

针对JDBC各个环节中存在的不足,现在,我们整理出对应的优化思路,统一汇总:

存在问题优化思路
数据库配置信息存在硬编码问题使用配置文件
频繁创建、释放数据库连接问题使用数据连接池
SQL语句、设置参数、获取结果集参数均存在硬编码问题使用配置文件
手动封装返回结果集,较为繁琐使用Java反射、内省

3 自定义持久层框架_思路分析

JDBC是个人作战,凡事亲力亲为,低效而高险,自己加载驱动,自己建连接,自己 …

而持久层框架好比是多工种协作,分工明确,执行高效,有专门负责解析注册驱动建立连接的,有专门管理数据连接池的,有专门执行sql语句的,有专门做预处理参数的,有专门装配结果集的 …

优化思路: 框架的作用,就是为了帮助我们减去繁重开发细节与冗余代码,使我们能更加专注于业务应用开发。

3.1 使用JDBC和使用持久层框架区别

在这里插入图片描述

是不是发现,拥有这么一套持久层框架是如此舒适,我们仅仅需要干两件事:

  • 配置数据源(地址/数据名/用户名/密码)
  • 编写SQL与参数准备(SQL语句/参数类型/返回值类型)

框架,除了思考本身的工程设计,还需要考虑到实际项目端的使用场景,干系方涉及两端:

  • 使用端(实际项目)
  • 持久层框架本身

以上两步,我们通过一张架构图《 手写持久层框架基本思路 》来梳理清楚:

在这里插入图片描述

3.2 核心接口/类重点说明

分工协作角色定位类名定义
负责读取配置文件资源辅助类Resources
负责存储数据库连接信息数据库资源类Configuration
负责存储SQL映射定义、存储结果集映射定义SQL与结果集资源类MappedStatement
负责解析配置文件,创建会话工厂SqlSessionFactory会话工厂构建者SqlSessionFactoryBuilder
负责创建会话SqlSession会话工厂SqlSessionFactory
指派执行器Executor会话SqlSession
负责执行SQL (配合指定资源Mapped Statement)执行器Executor

正常来说项目只对应一套数据库环境,一般对应一个SqlSessionFactory实例对象,我们使用单例模式只创建一个SqlSessionFactory实例。

如果需要配置多套数据库环境,那需要做一些拓展,例如Mybatis中通过environments等配置就可以支持多套测试/生产数据库环境进行切换。

3.3 项目使用端

(1)调用框架API,除了引入自定义持久层框架的jar包

(2)提供两部分配置信息:1.sqlMapConfig.xml : 数据库配置信息(地址/数据名/用户名/密码),以及mapper.xml的全路径

                                            2.mapper.xml : SQL配置信息,存放SQL语句、参数类型、返回值类型相关信息

3.4 自定义框架本身

1、加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中。

在这里插入图片描述

2、 创建两个javaBean(容器对象):存放配置文件解析出来的内容

在这里插入图片描述

3、解析配置文件(使用dom4j) ,并创建SqlSession会话对象

在这里插入图片描述

4、创建SqlSessionFactory接口以及实现类DefaultSqlSessionFactory

在这里插入图片描述

5、创建SqlSession接口以及实现类DefaultSqlSession

在这里插入图片描述

6、创建Executor接口以及实现类SimpleExecutor

在这里插入图片描述

基本过程我们已经清晰,我们再细化一下类图,更好的助于我们实际编码:

在这里插入图片描述

3.5 最终手写的持久层框架结构参考

在这里插入图片描述

4 自定义持久层框架_编码

  <properties><!-- Encoding --><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.encoding>UTF-8</maven.compiler.encoding><java.version>11</java.version><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><!--引入ipersistent的依赖-->

在使用端项目中创建配置配置文件

创建 sqlMapConfig.xml

<configuration> <!--1.配置数据库配置信息--><dataSource><property name="driverClassName" value="com.mysql.jdbc.Driver"></property><property name="url" value="jdbc:mysql:///zdy_mybatis?useSSL=false&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"></property><property name="username" value="root"></property><property name="password" value="root"></property></dataSource><!--2.引入映射配置文件--><mappers><mapper resource="mapper/UserMapper.xml"></mapper></mappers></configuration> 

mapper.xml

<mapper namespace="User"><!--根据条件查询单个--><select id="selectOne" resultType="com.oldlu.pojo.User" parameterType="com.oldlu.pojo.User">select * from user where id = #{id} and username = #{username}</select><!--查询所有--><select id="selectList" resultType="com.oldlu.pojo.User">select * from user</select>
</mapper>

User实体

public class User {//主键标识private Integer id;//用户名private String username;public Integer getId() { return id;}public void setId(Integer id) { this.id = id;}public String getUsername() { return username;}public void setUsername(String username) { this.username = username;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' + '}';}
}

再创建一个Maven子工程并且导入需要用到的依赖坐标

  <properties><!-- Encoding --><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><maven.compiler.encoding>UTF-8</maven.compiler.encoding><java.version>11</java.version><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target></properties><dependencies><!-- mysql 依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><!--junit 依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><!--作用域测试范围--><scope>test</scope></dependency><!--dom4j 依赖--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!--xpath 依赖--><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency><!--druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.21</version></dependency><!-- log日志 --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies>

Resources

public class Resources {/*** 根据配置文件的路径,加载成字节输入流,存到内存中* @param path* @return*/public static InputStream getResourceAsSteam(String path){InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);return resourceAsStream;}

Configuration

/*** 存放核心配置文件解析的内容*/
public class Configuration {// 数据源对象private DataSource dataSource;// map : key :statementId  value : 封装好的MappedStatementprivate Map<String,MappedStatement> mappedStatementMap = new HashMap<>();public DataSource getDataSource() {return dataSource;}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}public Map<String, MappedStatement> getMappedStatementMap() {return mappedStatementMap;}public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {this.mappedStatementMap = mappedStatementMap;}
}

MappedStatement

/***  存放解析映射配置文件的内容*     <select id="selectOne" resultType="com.oldlu.pojo.User" parameterType="com.oldlu.pojo.User">*         select * from user where id = #{id} and username = #{username}*     </select>*/
public class MappedStatement {// 1.唯一标识 (statementId namespace.id)private String statementId;// 2.返回结果类型private String resultType;// 3.参数类型private String parameterType;// 4.要执行的sql语句private String sql;// 5.mapper代理:sqlcommandTypeprivate String sqlcommandType;public String getSqlcommandType() {return sqlcommandType;}public void setSqlcommandType(String sqlcommandType) {this.sqlcommandType = sqlcommandType;}public String getStatementId() {return statementId;}public void setStatementId(String statementId) {this.statementId = statementId;}public String getResultType() {return resultType;}public void setResultType(String resultType) {this.resultType = resultType;}public String getParameterType() {return parameterType;}public void setParameterType(String parameterType) {this.parameterType = parameterType;}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}
}

SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {/*** 1.解析配置文件,封装Configuration 2.创建SqlSessionFactory工厂对象* @return*/public SqlSessionFactory build(InputStream inputStream) throws DocumentException {// 1.解析配置文件,封装ConfigurationXMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = xmlConfigBuilder.parse(inputStream);SqlSessionFactory defatultSqlSessionFactory = new DefatultSqlSessionFactory(configuration);return  defatultSqlSessionFactory;}}

XMLConfigerBuilder

public class XMLConfigBuilder {private Configuration configuration;public XMLConfigBuilder() {configuration = new Configuration();}/*** 使用dom4j解析xml文件,封装configuration对象* @param inputStream* @return*/public Configuration parse(InputStream inputStream) throws DocumentException {Document document = new SAXReader().read(inputStream);Element rootElement = document.getRootElement();// 解析核心配置文件中数据源部分List<Element> list = rootElement.selectNodes("//property");//  <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>Properties properties = new Properties();for (Element element : list) {String name = element.attributeValue("name");String value = element.attributeValue("value");properties.setProperty(name,value);}// 创建数据源对象(连接池)DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setDriverClassName(properties.getProperty("driverClassName"));druidDataSource.setUrl(properties.getProperty("url"));druidDataSource.setUsername(properties.getProperty("username"));druidDataSource.setPassword(properties.getProperty("password"));// 创建好的数据源对象封装进configuration中、configuration.setDataSource(druidDataSource);// 解析映射配置文件// 1.获取映射配置文件的路径  2.解析  3.封装好mappedStatementList<Element> mapperList = rootElement.selectNodes("//mapper");for (Element element : mapperList) {String mapperPath = element.attributeValue("resource");InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);xmlMapperBuilder.parse(resourceAsSteam);}return configuration;}
}

XMLMapperBuilder

public class XMLMapperBuilder {private Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException {Document document = new SAXReader().read(inputStream);Element rootElement = document.getRootElement();String namespace = rootElement.attributeValue("namespace");List<Element> select = rootElement.selectNodes("select");for (Element element : select) { //id的值String id = element.attributeValue("id");String paramterType = element.attributeValue("paramterType");String resultType = element.attributeValue("resultType"); //输入参数classClass<?> paramterTypeClass = getClassType(paramterType);//返回结果classClass<?> resultTypeClass = getClassType(resultType);//statementIdString key = namespace + "." + id;//sql语句String textTrim = element.getTextTrim();//封装 mappedStatementMappedStatement mappedStatement = new MappedStatement();mappedStatement.setId(id);mappedStatement.setParamterType(paramterTypeClass);mappedStatement.setResultType(resultTypeClass);mappedStatement.setSql(textTrim);//填充 configurationconfiguration.getMappedStatementMap().put(key, mappedStatement);private Class<?> getClassType (String paramterType) throws ClassNotFoundException {Class<?> aClass = Class.forName(paramterType);return aClass;}
}

sqlSessionFactory 接口及D efaultSqlSessionFactory 实现类

public interface SqlSessionFactory {/*** 生产sqlSession :封装着与数据库交互的方法* @return*/public SqlSession openSession();}public class DefatultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefatultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {// 执行器创建出来Executor executor = new SimpleExecutor();DefaultSqlSession defaultSqlSession = new DefaultSqlSession(configuration,executor);return defaultSqlSession;}
}

sqlSession 接口及 DefaultSqlSession 实现类

public interface SqlSession {/*** 查询所有的方法 select * from user where username like '%aaa%' and sex = ''* 参数1:唯一标识* 参数2:入参*/public <E> List<E> selectList(String statementId,Object parameter) throws Exception;/*** 查询单个的方法*/public <T> T selectOne(String statementId,Object parameter) throws Exception;
}public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}@Override                    // user.selectList      1 tom userpublic <E> List<E> selectList(String statementId, Object params) throws Exception {MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);// 将查询操作委派给底层的执行器List<E> list = executor.query(configuration,mappedStatement,params);return list;}@Overridepublic <T> T selectOne(String statementId, Object params) throws Exception {List<Object> list = this.selectList(statementId, params);if(list.size() == 1){return (T) list.get(0);}else if(list.size() > 1){throw new RuntimeException("返回结果过多");}else {return null;}}
}   

Executor

public interface Executor {<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object params) throws Exception;
}

SimpleExecutor

public class SimpleExecutor implements Executor {/*** 执行JDBC操作* @param configuration* @param mappedStatement* @param params* @param <E>* @return*/@Override                                                                               // user productpublic <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object params) throws Exception {// 1. 加载驱动,获取连接Connection connection = configuration.getDataSource().getConnection();// 2. 获取prepareStatement预编译对象/*select * from user where id = #{id} and username = #{username}select * from user where id = ? and username = ?占位符替换 :#{}替换成? 注意:#{id}里面的id名称要保存*/String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSQL(sql);String finaLSql = boundSql.getFinaLSql();PreparedStatement preparedStatement = connection.prepareStatement(finaLSql);// 3.设置参数// 问题1: Object param(类型不确定 user/product/map/String)// 问题2:该把对象中的哪一个属性赋值给哪一个占位符呢?List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if(parameterMappings.size() > 0){// com.oldlu.pojo.UserString parameterType = mappedStatement.getParameterType();Class<?> parameterTypeClass = Class.forName(parameterType);for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);// idString content = parameterMapping.getContent();// 反射Field declaredField = parameterTypeClass.getDeclaredField(content);// 暴力访问declaredField.setAccessible(true);Object value = declaredField.get(params);preparedStatement.setObject(i+1 ,value);}}// 4.执行sql,发起查询ResultSet resultSet = preparedStatement.executeQuery();String resultType = mappedStatement.getResultType();Class<?> resultTypeClass = Class.forName(resultType);ArrayList<E> list = new ArrayList<>();// 5.遍历封装while (resultSet.next()){// 元数据信息中包含了字段名 字段的值ResultSetMetaData metaData = resultSet.getMetaData();Object obj = resultTypeClass.newInstance();for (int i = 1; i <= metaData.getColumnCount() ; i++) {// id  usernameString columnName = metaData.getColumnName(i);Object value = resultSet.getObject(columnName);// 属性描述器PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);Method writeMethod = propertyDescriptor.getWriteMethod();writeMethod.invoke(obj,value);}list.add((E) obj);}return list;}/***  1.将sql中#{}替换成? 2.将#{}里面的值保存* @param sql* @return*/private BoundSql getBoundSQL(String sql) {// 标记处理器:配合标记解析器完成标记的解析工作ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();// 标记解析器GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);String finalSql = genericTokenParser.parse(sql);// #{}里面的值的集合List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();BoundSql boundSql = new BoundSql(finalSql, parameterMappings);return boundSql;}
}

BoundSql

public class BoundSql {//解析过后的sql语句private String sqlText;//解析出来的参数private List<ParameterMapping> parameterMappingList = new ArrayList<ParameterMapping>();public BoundSql(String sqlText, List<ParameterMapping>parameterMappingList) {this.sqlText = sqlText;this.parameterMappingList = parameterMappingList;}public String getSqlText() {return sqlText;}public void setSqlText(String sqlText) {this.sqlText = sqlText;}public List<ParameterMapping> getParameterMappingList() {return parameterMappingList;}public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {this.parameterMappingList = parameterMappingList;}
}

5 自定义持久层框架优化

通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连 接,硬编码,手动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的自定义框架代码,有没 有什么问题?

问题如下:

  • dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方 法,关闭 sqlsession)
  • dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码

解决:使用代理模式来创建接口的代理对象

  @Testpublic void test2() throws Exception {InputStream resourceAsSteam = Resources.getResourceAsSteam(path: "sqlMapConfig.xml")SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);SqlSession sqlSession = build.openSession();User user = new User();user.setld(l);user.setUsername("tom");//代理对象UserMapper userMapper = sqlSession.getMappper(UserMapper.class);User userl = userMapper.selectOne(user);System・out.println(userl);}

在sqlSession中添加方法

public interface SqlSession {public <T> T getMappper(Class<?> mapperClass);

实现类

package com.oldlu.sqlSession;import com.oldlu.executor.Executor;
import com.oldlu.pojo.Configuration;
import com.oldlu.pojo.MappedStatement;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.List;public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}@Overridepublic <E> List<E> selectList(String statementId, Object param) throws Exception {// 要传递什么参数呢?MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);List<E> list = executor.query(configuration,mappedStatement,param);return list;}@Overridepublic <T> T selectOne(String statementId, Object param) throws Exception {// 调用selectList方法List<Object> list = selectList(statementId, param);if(list.size() == 1){return (T) list.get(0);}else if(list.size() > 1){throw new RuntimeException("返回结果过多...");}return null;}/*** 生成代理对象* @param mapperClass* @param <T>* @return*/@Overridepublic <T> T getMapper(Class<?> mapperClass) {// 使用JDK动态代理生成代理对象Object proxyInstance = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {// 参数1:Object o:代理对象的引用,很少用// 参数2:Method method :当前被调用的方法对象// 参数3:Object[] objects:被调用的方法的参数@Overridepublic Object invoke(Object o, Method method, Object[] objects) throws Throwable {// 具体的逻辑:执行底层的JDBC// 思路:通过调用sqlSession的方法来完成执行// 问题1:如何获取statementId 根据method获取Class<?> declaringClass = method.getDeclaringClass();// 类全路径= namespace的值String className = declaringClass.getName();String methodName = method.getName();String statementId = className + "." + methodName;MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);// 问题2:该调用增删改查什么方法呢? 优化:sqlCommandTypeString sqlCommandType = mappedStatement.getSqlCommandType();switch (sqlCommandType){case "select"://查询操作//问题3:调selectOne还是调selectAll呢?Class<?> returnType = method.getReturnType();boolean assignableFrom = Collection.class.isAssignableFrom(returnType);if(assignableFrom){if(mappedStatement.getParameterType() !=null) {return   selectList(statementId, objects[0]);}return selectList(statementId, null);}return selectOne(statementId,objects[0]);case "update":// 更新操作break;case "insert":// 更新操作break;case "delete":// 更新操作break;}return null;}});return (T) proxyInstance;}
}

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

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

相关文章

初始化一个 vite + vue 项目

创建项目 首先使用以下命令创建一个vite项目 npm create vite然后根据提示命令 cd 到刚创建的项目目录下&#xff0c;使用npm install安装所需要的依赖包&#xff0c;再使用npm run dev即可启动项目 配置 vite.config.js 添加process.env配置&#xff0c;如果下面 vue-route…

2023高教社杯数学建模国赛C题思路解析+代码+论文

如下为C君的2023高教社杯全国大学生数学建模竞赛C题思路分析代码论文 C题蔬菜类商品的自动定价与补货决策 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差, 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&…

如何统计网站的访问量

本文介绍的是使用redis的HyperLoglog实现uv的统计功能。 背景 首先我们先明确一下uv这个名词代表的实际意义。uv代表的是通过网页访问浏览的人数&#xff0c;和文章的阅读量差不多&#xff0c;但是需要注意的是&#xff0c;一个人即使是多次访问&#xff0c;也只算一次。 所…

新风机未来什么样?

新风机在未来将会有许多令人期待的发展和改进&#xff0c;让我们一起来看一看吧&#xff01;以下是新风机未来的一些可能性&#xff1a; 智能化和智能家居&#xff1a;新风机将更多地与智能家居系统整合&#xff0c;通过物联网和人工智能技术&#xff0c;实现智能控制和智能调节…

vue+antd——实现table表格的打印——分页换行,每页都有表头——基础积累

这里写目录标题 场景效果图功能实现1&#xff1a;html代码功能实现2&#xff1a;css样式功能实现3&#xff1a;js代码补充内容page-break-inside 属性page-break-after属性page-break-before 属性 场景 最近在写后台管理系统时&#xff0c;遇到一个需求&#xff0c;就是要实现…

Revit 几何体的三种上层应用:特征造型、体量和纯粹几何

排除掉墙梁板柱这些和建筑各专业相关的构件&#xff0c;Revit 的上层应用中&#xff0c;有三类和几何相关的应用&#xff0c;特征造型、体量和纯粹几何。这也是通常三维建模软件的几种基础建模组织形式。 特征造型 几何特征造型是一种设计方法&#xff0c;它强调使用几何学原理…

C/C++之链表的建立

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.头插 1.1简介 1.2代码实现头插 …

【Android Framework系列】第14章 Fragment核心原理(AndroidX版本)

1 简介 Fragment是一个历史悠久的组件&#xff0c;从API 11引入至今&#xff0c;已经成为Android开发中最常用的组件之一。 Fragment表示应用界面中可重复使用的一部分。Fragment定义和管理自己的布局&#xff0c;具有自己的生命周期&#xff0c;并且可以处理自己的输入事件。…

代码随想录笔记--回溯算法篇

1--回溯算法理论基础 回溯算法本质上是一个暴力搜索的过程&#xff0c;其常用于解决组合、切割、子集、排列等问题&#xff0c;其一般模板如下&#xff1a; void backTracking(参数){if(终止条件){// 1. 收获结果;// 2. return;}for(..遍历){// 1. 处理节点// 2. 递归搜索// 3.…

K8S 基础概念学习

1.K8S 通过Deployment 实现滚动发布&#xff0c;比如左边的ReplicatSet 的 pod 中 是V1版本的镜像&#xff0c;Deployment通过 再启动一个 ReplicatSet 中启动 pod中 镜像就是V2 2.每个pod 中都有一个pause 容器&#xff0c;他会连接本pod中的其他容器&#xff0c;实现互通。p…

【Java】基于SSM的单位人事管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

[Linux]动静态库

[Linux]动静态库 文章目录 [Linux]动静态库见一见库存在库的原因编写库模拟编写静态库模拟使用静态库模拟编写动态库模拟使用静态库 库的加载原理静态库的加载原理动态库的加载原理 库在可执行程序中的编址策略静态库在可执行程序中的编址策略动态库在可执行程序中的编址策略 见…

储能直流侧计量表DJSF1352

安科瑞 华楠 具有CE/UL/CPA/TUV认证 DJSF1352-RN导轨式直流电能表带有双路直流输入&#xff0c;主要针对电信基站、直流充电桩、太阳能光伏等应用场合而设计&#xff0c;该系列仪表可测量直流系统中的电压、电流、功率以及正反向电能等。在实际使用现场&#xff0c;即可计量总…

LT8711HE 是一款高性能的Type-C/DP1.2到HDMI2.0转换器

概述&#xff1a; LT8711HE是一种高性能的Type-C/DP1.2到HDMI2.0转换器&#xff0c;设计用于连接USB Type-C源或DP1.2源到HDMI2.0接收器。LT8711HE集成了一个DP1.2兼容的接收器&#xff0c;和一个HDMI2.0兼容的发射机。此外&#xff0c;还包括两个CC控制器&#xff0c;用于CC通…

自然语言处理——数据清洗

一、什么是数据清洗 数据清洗是指发现并纠正数据文件中可识别的错误的最后一道程序&#xff0c;包括检查数据一致性&#xff0c;处理无效值和缺失值等。与问卷审核不同&#xff0c;录入后的数据清理一般是由计算机而不是人工完成。 ——百度百科 二、为什么要数据清洗 现实生…

bboss 流批一体化框架 与 数据采集 ETL

数据采集 ETL 与 流批一体化框架 特性&#xff1a; 高效、稳定、快速、安全 bboss 是一个基于开源协议 Apache License 发布的开源项目&#xff0c;主要由以下三部分构成&#xff1a; Elasticsearch Highlevel Java Restclient &#xff0c; 一个高性能高兼容性的Elasticsea…

java开发之个人微信的二次开发

简要描述&#xff1a; 修改我在某群的昵称 请求URL&#xff1a; http://域名/updateIInChatRoomNickName 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参…

Python标准数据类型-List(列表)

✅作者简介&#xff1a;CSDN内容合伙人、阿里云专家博主、51CTO专家博主、新星计划第三季python赛道Top1&#x1f3c6; &#x1f4c3;个人主页&#xff1a;hacker707的csdn博客 &#x1f525;系列专栏&#xff1a;零基础入门篇 &#x1f4ac;个人格言&#xff1a;不断的翻越一座…

备份StarRocks数据到对象存储minio中/外表查minio中的数据

1.部署minio环境 docker pull minio/minio宿主机与容器挂在映射 宿主机位置容器位置/data/minio/config/data/data/minio/data/root/.minio 拉起环境&#xff1a; docker run -p 9000:9000 -p 9090:9090 --name minio \ -d --restartalways \ -e "MINIO_ACCESS_KEYadm…

基于Dubbo实现服务的远程调用

目录 前言 RPC思想 为什么使用Dubbo Dubbo技术框架 ​编辑 调用关系流程 基础实现 A.提供统一业务Api B.编辑服务提供者Product B.a 添加依赖 B.b 添加Dubbo 配置(基于yaml配置文件) B.c 编写并暴露服务 C.编辑服务消费者 C.a 添加依赖 C.b 添加Dubbo配置 C.c 引用…