ORM框架的发展历史

文章目录

  • JDBC
    • JDBC操作的特点
    • JDBC优化1.0
    • JDBC优化2.0
    • JDBC优化3.0
  • Apache DBUtils
    • 初始配置
    • 基本操作
  • SpringJDBC
    • 初始配置
    • CRUD操作
  • Hibernate
    • ORM介绍
    • Hibernate的使用
      • 创建项目
      • 配置文件
      • CRUD 操作
      • 其他方式
    • Hibernate总结
  • MyBatis

image.png

JDBC

JDBC操作的特点

最初的时候是直接通过jdbc来直接操作数据库的,如果本地数据库有一张t_user表,那么操作流程是

// 注册 JDBC 驱动
Class.forName("com.mysql.cj.jdbc.Driver");// 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");// 执行查询
stmt = conn.createStatement();
String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1";
ResultSet rs = stmt.executeQuery(sql);// 获取结果集
while (rs.next()) {Integer id = rs.getInt("id");String userName = rs.getString("user_name");String realName = rs.getString("real_name");String password = rs.getString("password");Integer did = rs.getInt("d_id");user.setId(id);user.setUserName(userName);user.setRealName(realName);user.setPassword(password);user.setDId(did);System.out.println(user);
}

具体的操作步骤是,首先在pom.xml中引入MySQL的驱动依赖,注意MySQL数据库的版本

  1. Class.forName注册驱动
  2. 获取一个Connection对象
  3. 创建一个Statement对象
  4. execute()方法执行SQL语句,获取ResultSet结果集
  5. 通过ResultSet结果集给POJO的属性赋值
  6. 最后关闭相关的资源

这种实现方式首先给我们的感觉就是操作步骤比较繁琐,在复杂的业务场景中会更麻烦。尤其是需要自己来维护管理资源的连接,如果忘记了,就很可能造成数据库服务连接耗尽。同时具体业务的SQL语句直接在代码中写死耦合性增强。每个连接都会经历这几个步骤,重复代码很多,总结上面的操作的特点:

  1. 代码重复
  2. 资源管理
  3. 结果集处理
  4. SQL耦合

JDBC优化1.0

针对常规jdbc操作的特点,可以先从代码重复和资源管理方面来优化,可以创建一个工具类来专门处理这个问题

public class DBUtils {private static final String JDBC_URL = "jdbc:mysql://localhost:3306/db?characterEncoding=utf-8&serverTimezone=UTC";private static final String JDBC_NAME = "root";private static final String JDBC_PASSWORD = "123456";private static  Connection conn;/*** 对外提供获取数据库连接的方法* @return* @throws Exception*/public static Connection getConnection() throws Exception {if(conn == null){try{conn = DriverManager.getConnection(JDBC_URL,JDBC_NAME,JDBC_PASSWORD);}catch (Exception e){e.printStackTrace();throw new Exception();}}return conn;}/*** 关闭资源* @param conn*/public static void close(Connection conn ){close(conn,null);}public static void close(Connection conn, Statement sts ){close(conn,sts,null);}public static void close(Connection conn, Statement sts , ResultSet rs){if(rs != null){try {rs.close();}catch (Exception e){e.printStackTrace();}}if(sts != null){try {sts.close();}catch (Exception e){e.printStackTrace();}}if(conn != null){try {conn.close();}catch (Exception e){e.printStackTrace();}}}
}

对应的jdbc操作代码可以简化如下

    /**** 通过JDBC查询用户信息*/public void queryUser(){Connection conn = null;Statement stmt = null;User user = new User();ResultSet rs = null;try {// 注册 JDBC 驱动// Class.forName("com.mysql.cj.jdbc.Driver");// 打开连接conn = DBUtils.getConnection();// 执行查询stmt = conn.createStatement();String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = 1";rs = stmt.executeQuery(sql);// 获取结果集while (rs.next()) {Integer id = rs.getInt("id");String userName = rs.getString("user_name");String realName = rs.getString("real_name");String password = rs.getString("password");Integer did = rs.getInt("d_id");user.setId(id);user.setUserName(userName);user.setRealName(realName);user.setPassword(password);user.setDId(did);System.out.println(user);}} catch (SQLException se) {se.printStackTrace();} catch (Exception e) {e.printStackTrace();} finally {DBUtils.close(conn,stmt,rs);}}/*** 通过JDBC实现添加用户信息的操作*/public void addUser(){Connection conn = null;Statement stmt = null;try {// 打开连接conn = DBUtils.getConnection();// 执行查询stmt = conn.createStatement();String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values('wangwu','王五','111',22,1001)";int i = stmt.executeUpdate(sql);System.out.println("影响的行数:" + i);} catch (SQLException se) {se.printStackTrace();} catch (Exception e) {e.printStackTrace();} finally {DBUtils.close(conn,stmt);}}

但是整体的操作步骤还是会显得比较复杂。

JDBC优化2.0

针对DML操作的方法来优化,先解决SQL耦合的问题,在DBUtils中封装DML操作的方法

    /*** 执行数据库的DML操作* @return*/public static Integer update(String sql,Object ... paramter) throws Exception{conn = getConnection();PreparedStatement ps = conn.prepareStatement(sql);if(paramter != null && paramter.length > 0){for (int i = 0; i < paramter.length; i++) {ps.setObject(i+1,paramter[i]);}}int i = ps.executeUpdate();close(conn,ps);return i;}

然后在DML操作的时候我们就可以简化为如下步骤

    /*** 通过JDBC实现添加用户信息的操作*/public void addUser(){String sql = "INSERT INTO T_USER(user_name,real_name,password,age,d_id)values(?,?,?,?,?)";try {DBUtils.update(sql,"wangwu","王五","111",22,1001);} catch (Exception e) {e.printStackTrace();}}

显然这种方式会比最初的使用要简化很多,但是在查询处理的时候还是没有解决ResultSet结果集的处理问题,所以还需要继续优化

JDBC优化3.0

针对ResultSet的优化需要从反射和元数据两方面入手,具体如下

    /*** 查询方法的简易封装* @param sql* @param clazz* @param parameter* @param <T>* @return* @throws Exception*/public static <T> List<T> query(String sql, Class clazz, Object ... parameter) throws  Exception{conn = getConnection();PreparedStatement ps = conn.prepareStatement(sql);if(parameter != null && parameter.length > 0){for (int i = 0; i < parameter.length; i++) {ps.setObject(i+1,parameter[i]);}}ResultSet rs = ps.executeQuery();// 获取对应的表结构的元数据ResultSetMetaData metaData = ps.getMetaData();List<T> list = new ArrayList<>();while(rs.next()){// 根据 字段名称获取对应的值 然后将数据要封装到对应的对象中int columnCount = metaData.getColumnCount();Object o = clazz.newInstance();for (int i = 1; i < columnCount+1; i++) {// 根据每列的名称获取对应的值String columnName = metaData.getColumnName(i);Object columnValue = rs.getObject(columnName);setFieldValueForColumn(o,columnName,columnValue);}list.add((T) o);}return list;}/*** 根据字段名称设置 对象的属性* @param o* @param columnName*/private static void setFieldValueForColumn(Object o, String columnName,Object columnValue) {Class<?> clazz = o.getClass();try {// 根据字段获取属性Field field = clazz.getDeclaredField(columnName);// 私有属性放开权限field.setAccessible(true);field.set(o,columnValue);field.setAccessible(false);}catch (Exception e){// 说明不存在 那就将 _ 转换为 驼峰命名法if(columnName.contains("_")){Pattern linePattern = Pattern.compile("_(\\w)");columnName = columnName.toLowerCase();Matcher matcher = linePattern.matcher(columnName);StringBuffer sb = new StringBuffer();while (matcher.find()) {matcher.appendReplacement(sb, matcher.group(1).toUpperCase());}matcher.appendTail(sb);// 再次调用复制操作setFieldValueForColumn(o,sb.toString(),columnValue);}}}

封装了以上方法后我们的查询操作就可以简化为

    /**** 通过JDBC查询用户信息*/public void queryUser(){try {String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = ?";List<User> list = DBUtils.query(sql, User.class,2);System.out.println(list);} catch (Exception e) {e.printStackTrace();}}

这样一来在操作数据库中数据的时候就只需要关注于核心的SQL操作了。当然以上的设计还比较粗糙,这时Apache 下的 DbUtils是一个很好的选择

Apache DBUtils

初始配置

DButils中提供了一个QueryRunner类,它对数据库的增删改查的方法进行了封装,获取QueryRunner的方式

    private static final String PROPERTY_PATH = "druid.properties";private static DruidDataSource dataSource;private static QueryRunner queryRunner;public static void init() {Properties properties = new Properties();InputStream in = DBUtils.class.getClassLoader().getResourceAsStream(PROPERTY_PATH);try {properties.load(in);} catch (IOException e) {e.printStackTrace();}dataSource = new DruidDataSource();dataSource.configFromPropety(properties);// 使用数据源初始化 QueryRunnerqueryRunner = new QueryRunner(dataSource);}

创建QueryRunner对象的时候我们需要传递一个DataSource对象,这时我们可以选择Druid或者Hikai等常用的连接池工具,这里用的是Druid。

druid.username=root
druid.password=123456
druid.url=jdbc:mysql://localhost:3306/db?characterEncoding=utf-8&serverTimezone=UTC
druid.minIdle=10
druid.maxActive=30

基本操作

QueryRunner中提供的方法解决了重复代码的问题,传入数据源解决了资源管理的问题。而对于ResultSet结果集的处理则是通过 ResultSetHandler 来处理。我们可以自己来实现该接口

    /*** 查询所有的用户信息* @throws Exception*/public void queryUser() throws Exception{DruidUtils.init();QueryRunner queryRunner = DruidUtils.getQueryRunner();String sql = "select * from t_user";List<User> list = queryRunner.query(sql, new ResultSetHandler<List<User>>() {@Overridepublic List<User> handle(ResultSet rs) throws SQLException {List<User> list = new ArrayList<>();while(rs.next()){User user = new User();user.setId(rs.getInt("id"));user.setUserName(rs.getString("user_name"));user.setRealName(rs.getString("real_name"));user.setPassword(rs.getString("password"));list.add(user);}return list;}});for (User user : list) {System.out.println(user);}}

或者用DBUtils中提供的默认的相关实现来解决

    /*** 通过ResultHandle的实现类处理查询*/public void queryUserUseBeanListHandle() throws Exception{DruidUtils.init();QueryRunner queryRunner = DruidUtils.getQueryRunner();String sql = "select * from t_user";// 不会自动帮助我们实现驼峰命名的转换List<User> list = queryRunner.query(sql, new BeanListHandler<User>(User.class));for (User user : list) {System.out.println(user);}}

通过Apache 封装的DBUtils是能够很方便的帮助我们实现相对比较简单的数据库操作

SpringJDBC

在Spring框架平台下,也提供的有JDBC的封装操作,在Spring中提供了一个模板方法 JdbcTemplate,里面封装了各种各样的 execute,query和update方法。

JdbcTemplate这个类是JDBC的核心包的中心类,简化了JDBC的操作,可以避免常见的异常,它封装了JDBC的核心流程,应用只要提供SQL语句,提取结果集就可以了,它是线程安全的。

初始配置

在SpringJdbcTemplate的使用中,我们依然要配置对应的数据源,然后将JdbcTemplate对象注入到IoC容器中。

@Configuration
@ComponentScan
public class SpringConfig {@Beanpublic DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setUsername("root");dataSource.setPassword("123456");dataSource.setUrl("jdbc:mysql://localhost:3306/db?characterEncoding=utf-8&serverTimezone=UTC");return  dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate template = new JdbcTemplate();template.setDataSource(dataSource);return template;}
}

CRUD操作

在操作数据库中数据的时候,只需要从容器中获取JdbcTemplate实例即可

@Repository
public class UserDao {@Autowiredprivate JdbcTemplate template;public void addUser(){int count = template.update("insert into t_user(user_name,real_name)values(?,?)","bobo","波波老师");System.out.println("count = " + count);}public void query2(){String sql = "select * from t_user";List<User> list = template.query(sql, new BeanPropertyRowMapper<>(User.class));for (User user : list) {System.out.println(user);}}}

Hibernate

Apache DBUtils和SpringJdbcTemplate虽然简化了数据库的操作,但是本身提供的功能还是比较简单的(缺少缓存,事务管理等),所以在实际开发中往往并没有直接使用上述技术,而是用到了Hibernate和MyBatis等这些专业的ORM持久层框架。

ORM介绍

ORM( Object Relational Mapping) ,也就是对象与关系的映射,对象是程序里面的对象,关系是它与数据库里面的数据的关系,也就是说,ORM框架帮助我们解决的问题是程序对象和关系型数据库的相互映射的问题

Hibernate的使用

Hibernate是一个很流行的ORM框架,2001年的时候就出了第一个版本。使用步骤如下

创建项目

创建一个Maven项目并添加相关的依赖即可,我们在此处直接通过 SpringDataJpa的依赖处理

<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.11</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

配置文件

在使用Hibernate的使用,我们需要为实体类创建一些hbm的xml映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC'-//Hibernate/Hibernate Mapping DTD 3.0//EN''http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd'>
<hibernate-mapping><class name="com.model.User" table="t_user"><id name="id" /><property name="userName" column="user_name"></property><property name="realName" column="real_name"></property></class>
</hibernate-mapping>

以及Hibernate的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration><session-factory><property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property><property name="hibernate.connection.url">jdbc:mysql://localhost:3306/db?characterEncoding=utf8&serverTimezone=UTC</property><property name="hibernate.connection.username">root</property><property name="hibernate.connection.password">123456</property><property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property><property name="hibernate.show_sql">true</property><property name="hibernate.format_sql">true</property><property name="hibernate.hbm2ddl.auto">update</property><mapping resource="User.hbm.xml"/></session-factory>
</hibernate-configuration>

CRUD 操作

然后在程序中我们可以通过Hibernate提供的 Session对象来实现CRUD操作

public class HibernateTest {/*** Hibernate操作案例演示* @param args*/public static void main(String[] args) {Configuration configuration = new Configuration();// 默认使用hibernate.cfg.xmlconfiguration.configure();// 创建Session工厂SessionFactory factory = configuration.buildSessionFactory();// 创建SessionSession session = factory.openSession();// 获取事务对象Transaction transaction = session.getTransaction();// 开启事务transaction.begin();// 把对象添加到数据库中User user = new User();user.setId(666);user.setUserName("hibernate");user.setRealName("持久层框架");session.save(user);transaction.commit();session.close();}
}

其他方式

在映射文件的位置,我们也可以通过注解的方式来替换掉映射文件

@Data
@Entity
@Table(name = "t_user")
public class User {@Id@Column(name = "id")private Integer id;@Column(name = "user_name")private String userName;@Column(name = "real_name")private String realName;@Column(name = "password")private String password;@Column(name = "age")private Integer age;@Column(name = "i_id")private Integer dId;
}

在Spring中给我们提供的JPA对持久层框架做了统一的封装,而且本质上就是基于HibernateJPA来实现的,所以我们在使用的时候也可以通过SpringDataJPA的API来操作

dao的接口只需要继承JpaRepository接口即可

public interface IUserDao extends JpaRepository<User,Integer> {
}

service层正常处理

import java.util.List;
@Service
public class UserServiceImpl implements IUserService {@Autowiredprivate IUserDao dao;@Overridepublic List<User> query() {return dao.findAll();}@Overridepublic User save(User user) {return dao.save(user);}
}

Hibernate总结

Hibernate的出现大大简化了我们的数据库操作,同时也能够更好的应对更加复杂的业务场景,Hibernate具有如下的特点

  1. 根据数据库方言自定生成SQL,移植性好
  2. 自动管理连接资源
  3. 实现了对象和关系型数据的完全映射,操作对象就想操作数据库记录一样
  4. 提供了缓存机制

Hibernate在处理复杂业务的时候同样也存在一些问题

  1. 比如API中的get(),update()和save()方法,操作的实际上是所有的字段,没有办法指定部分字段,换句话说就是不够灵活
  2. 自定生成SQL的方式,如果要基于SQL去做一些优化的话,也是非常困难的。
  3. 不支持动态SQL,比如分表中的表名,条件,参数变化等,无法根据条件自动生成SQL

因此我们需要一个更为灵活的框架

MyBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

“半自动”的ORM框架能够很好的解决上面所讲的Hibernate的几个问题,半自动化”是相对于Hibernate的全自动化来说的。它的封装程度没有Hibernate那么高,不会自动生成全部的SQL语句,主要解决的是SQL和对象的映射问题。

MyBatis的前身是ibatis,2001年开始开发,是“internet”和“abatis ['æbətɪs](障碍物)”两个单词的组合。04年捐赠给Apache。2010年更名为MyBatis。

在MyBatis里面,SQL和代码是分离的,所以会写SQL基本上就会用MyBatis,没有额外的学习成本。

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

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

相关文章

【苹果】SpringBoot监听Iphone15邮件提醒,Selenium+Python自动化抢购脚本

前言 &#x1f34a;缘由 Iphone15来了&#xff0c;两年之约你还记得吗&#xff1f; 两年前&#xff0c;与特别的人有一个特别的约定。虽物是人非&#xff0c;但思念仍在。 遂整合之前iphone13及iphone14的相关抢购代码&#xff0c;完成一个SpringBoot监听Iphone15有货邮件提…

Foxit PDF SDK Windows 9.1 Crack

Foxit PDF SDK 变更日志 Windows/Linux/Mac 2023 年 8 月 新功能/增强功能 在开始签名之前设置外观。支持使用共享字典添加签名。允许在调用 Signature::StartSign() 之前增量保存文档。在签名前修改现有未签名分页印章签名的外观。支持使用共享字典添加分页签名。忽略全角…

51单片机项目(13)——基于51单片机的智能台灯protues仿真

本次设计&#xff0c;使用protues软件进行仿真&#xff0c;详情如下&#xff1a; 1.输入部分:由热释电红外传感器、光敏传感器、超声波测距传感器所构成的子电路组成。 2.输出模块:由1602液晶显示及其蜂鸣器报警系统组成。 3.中央处理器:主要有AT89C52单片机构成。 4.工作过…

TF-A如何支持Firmware镜像放回滚的

快速链接: . 👉👉👉 个人博客笔记导读目录(全部) 👈👈👈 付费专栏-付费课程 【购买须知】:【精选】ARMv8/ARMv9架构入门到精通-[目录] 👈👈👈在TF-A中通过FIP工具打包镜像的时候,会在镜像的content cert中打包进一个Non-Volatile counter,即版本号 trus…

etc目录下的profile.d文件目录设置环境变量和全局脚本

一、设置环境变量 etc目录下的profile.d文件目录 /etc/profile.d 1、编写 vi test.sh文件内容 # jdk变量 export ZHK_HOME/root export PATH$PATH:$ZHK_HOME/test # 可以取出来ZHK_HOME变量给ZZZ_HOME赋值 export ZZZ_HOME${ZHK_HOME}/test2、刷新 执行source /etc/profile …

idea 使用 groovyScript 获取方法参数列表生成方法注释模板遇到的问题。

1、网上好多使用groovyScript来设置获取方法列表生成注释模板的代码&#xff0c;我这篇文章的是想讨论下这种方式存在的一个问题&#xff0c;希望有大佬能提供一个解决方案。 2、设置步骤什么的就省略了直接描述问题。 3、groovyScript代码段如下&#xff1a; groovyScript(…

解决2K/4K高分屏下Vmware等虚拟机下Kail Linux界面显示问题

问题现象 在我们日常使用VirtualBox、Vmware workstation、Hyper-V等虚拟机安装使用Kali系统&#xff0c;在2K/4K高分辨率电脑下Kali系统界面显示太小&#xff0c;包括各种软件及命令终端字体均无法很直观的看出&#xff0c;影响我们的正常测试及使用。 常规处理思路 很多人…

ipad触控笔有必要买原装吗?开学值得买电容笔推荐

要知道&#xff0c;一支苹果的原装电容笔&#xff0c;单单在价格上就要近千元。实际上&#xff0c;平替电容笔对没有太多预算的用户是个不错的选择。一支苹果的电容笔&#xff0c;价格是平替电容笔的4倍左右&#xff0c;但与苹果电容笔相比&#xff0c;在书写方面上&#xff0c…

ptmalloc源码分析 - Top chunk的扩容函数sysmalloc实现(09)

目录 一、sysmalloc函数基本分配逻辑 二、强制try_mmap分配方式 三、非主分配区分配的实现 1. 设置老的Top chunk的参数 2. 尝试使用grow_heap函数 3. 尝试使用new_heap函数 4. 尝试使用try_mmap方式 四、主分配区分配的实现 1. 设置Top扩容的size值 2. brk分配成功的…

RK3568开发板SG90 舵机模块的功能实现-迅为电子

1 模块说明 SG90 舵机模块如下图所示: 常见的舵机转向角度有 0-90 度&#xff0c;0-180 度&#xff0c;0-360 度&#xff0c;可以用在垃圾桶项目开盖用&#xff0c;智能小车的全比例转向&#xff0c;摄像头云台&#xff0c;机械臂等。 2 接线说明 SG90 舵机模块上三条线&…

跨境电商运营的新趋势:自养号测评补单技术解析

当前阶段&#xff0c;亚马逊、速卖通、虾皮、lazada等主流跨境电商平台的主要推广方式仍然是广告投放&#xff0c;毕竟这是平台的主要收入来源之一。然而&#xff0c;随着越来越多的卖家进军跨境市场&#xff0c;市场竞争日趋激烈&#xff0c;传统的广告投入效果逐渐减弱。在这…

如何实现一个简单的深度优先搜索(DFS)算法?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 实现深度优先搜索⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前…

【C语言】指针的进阶(一)

目录 前言 1. 字符指针 2. 指针数组 3. 数组指针 3.1 数组指针的定义 3.2 &数组名VS数组名 3.3 数组指针的使用 4. 数组参数、指针参数 4.1 一维数组传参 4.2 二维数组传参 4.3 一级指针传参 4.4 二级指针传参 5. 函数指针 前言 指针在C语言中可谓是有着举足轻重的…

单元测试的重要性:编写更安全、更可靠的代码

在软件开发过程中&#xff0c;测试是非常重要的一环。而在众多的测试方法中&#xff0c;单元测试占据了不可忽视的地位。那么&#xff0c;为什么我们需要进行单元测试呢&#xff1f;以下将从理论和实践两方面进行详细的解释。 一、单元测试的定义和目的 单元测试是指对软件中的…

企业架构LNMP学习笔记45

失效机制&#xff08;了解&#xff09; 1&#xff09;如果key过期了&#xff0c;value会及时删除么&#xff1f;空间会及时清理么&#xff1f; 2&#xff09;如果分配的存储空间&#xff0c;写满了&#xff0c;还允许写么&#xff1f; -m可以配置内存大小。 memcached 内部不…

计算机视觉与深度学习-卷积神经网络-纹理表示卷积神经网络-卷积神经网络-[北邮鲁鹏]

这里写目录标题 参考文章全连接神经网络全连接神经网络的瓶颈全连接神经网络应用场景 卷积神经网络卷积层(CONV)卷积核卷积操作卷积层设计卷积步长(stride)边界填充特征响应图组尺寸计算 激活层池化层(POOL)池化操作定义池化操作作用池化层超参数常见池化操作 全连接层(FC)样本…

【vue3页面展示代码】展示代码codemirror插件

技术版本&#xff1a; vue 3.2.40、codemirror 6.0.1、less 4.1.3、vue-codemirror 6.1.1、 codemirror/lang-vue 0.1.2、codemirror/theme-one-dark 6.1.2 效果图&#xff1a; 1.安装插件 yarn add codemirror vue-codemirror codemirror/lang-vue codemirror/theme-one-dar…

分类预测 | MATLAB实现WOA-CNN-LSTM-Attention数据分类预测

分类预测 | MATLAB实现WOA-CNN-LSTM-Attention数据分类预测 目录 分类预测 | MATLAB实现WOA-CNN-LSTM-Attention数据分类预测分类效果基本描述模型描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现WOA-CNN-LSTM-Attention数据分类预测&#xff0c;运行环境Matlab2021b及以…

VLAN相关知识点

文章目录 前言VLANVLAN数据帧格式QinQ报文封装格式总结 前言 本博客仅做学习笔记&#xff0c;如有侵权&#xff0c;联系后即刻更改 科普&#xff1a; 参考网址 VLAN VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网 是将一个物理的LAN在逻辑上划分成多…

电视盒子什么品牌好?数码小编盘点网络电视盒子排行榜

电视盒子什么品牌好&#xff1f;每个品牌的优势并不一样&#xff0c;我们要根据自己的需求选择&#xff0c;看视频选无广告的&#xff0c;投屏频繁选投屏功能完善的&#xff0c;不懂的新手们可以参考小编分享的网络电视盒子排行榜&#xff0c;堪称目前最专业权威的电视盒子排名…