继MySQL之后的技术-JDBC-从浅到深-02

目录

概念

编程六部曲

SQL注入和statement

工具类的封装

JDBC事务

模糊查询

批处理

数据库连接池

Apache-DBUtils

BasicDao


概念

JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。

Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序得到数据库系统,从而完成对数据库的各种操作。

解耦合:降低程序的耦合度,提高程序的扩展力。

编程六部曲

第一步,注册驱动

第二步,获取连接

第三步,获取数据库操作对象

第四步,执行sql语句

第五步,处理查询结果集

第六步,释放资源

public static void main(String[] args) throws Exception{//类加载Class.forName("com.mysql.jdbc.Driver");//获取连接String url = "jdbc:mysql://localhost:3306/mydata";Connection connection = DriverManager.getConnection(url, "root", "123456");//获取数据库对象Statement statement = connection.createStatement();//执行sql语句String sql = "select no,name from student";ResultSet resultSet = statement.executeQuery(sql);//处理结果集while(resultSet.next()){int no = resultSet.getInt(1);String name = resultSet.getString(2);System.out.println(name + ":\t" + no);}//关闭流resultSet.close();statement.close();connection.close();
}

SQL注入和statement

目前存在的问题:用户输入的信息中患有sql语句的关键字,并且这些关键字参与sql语句的编译过程导致sql语句的愿意被扭曲,进而达到sql注入。

如何解决sql注入问题?

1、主要用户提供的信息不参与sql语句的编译过程,问题就解决了。

2、即使用户提供的信息中含有sql语句的关键字,但是没有参与编译,不起作用。

3、想要用户信息不参与sql语句的编译,那么必须使用java.sql.PreparedStatement

4、PreparedStatement接口继承了java.sql.Statement

5、PreparedStatement是属于预编译的数据库操作对象

6、PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值

解决SQL注入的关键是什么?

用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用。

 对比Statement和PreparedStatement

Statement存在sql注入问题,PreparedStatement解决了SQL注入问题。

Statement是编译一次执行一次。

PreparedStatement是编译一次,可执行N次,PreparedStatement效率较高。

PreparedStatement使用较多,凡是业务方面要求是需要进行sql语句拼接的,必须使用Statement。

工具类的封装

public class JDBCUtil{private static String url;private static String user;private static String password;private static String driver;static{try{Properties properties = new Properties();properties.load(new FileInputStream("mysql.properties"));driver = properties.getProperty("driver");url = properties.getProperty("url");user = properties.getProperty("user");password = properties.getProperty("password");//注册驱动Class.forName(driver);}catch (Exception e){throw new RuntimeException(e);}}//创建连接方法public static Connection getConnection(){try{return DriverManager.getConnection(url,user,password);} catch(SQLException throwables) {throw new RuntimeException(throwables);}}//关闭流public static void close(ResultSet rs, Statement ps,Connection c){if(rs != null){try{rs.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}if(ps != null){try{ps.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}if(c != null){try{c.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}}public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;String sql = "insert into t1 values('李四',2)";try{preparedStatement = connection.prepareStatement(sql);//执行sql语句preparedStatement.executeUpdate();} catch(SQLException throwables){throwables.printStackTrace();} finally{JDBCUtil.close(null,preparedStatement,connection);}}}

JDBC事务

JDBC中的事务是自动提交的,什么时候自动提交?

只要执行任意一条DML预计,则自动提交一次,这是JDBC默认的事务行为。但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败。

//重点三行代码
connection.setAutoCommit(false);
connection.commit();
connection.rollback();
public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;try {//设置为不自动提交,即开启事务connection.setAutoCommit(false);String sql = "update t1 set no = no - 100 where name = '马云'" ;preparedStatement = connection.prepareStatement(sql);preparedStatement.executeUpdate();String sql2 = "update t1 set no = no + 100 where name = '马化腾'";PreparedStatement preparedStatement1 = connection.prepareStatement(sql2);preparedStatement1.executeUpdate();//提交事务connection.commit();} catch (SQLException throwables){try{//程序执行到此处,说明程序报错了connection.rollback();} catch (SQLException e){e.printStackTrace();}throwables.printStackTrace();} finally{JDBCUtil.close(null, preparedStatement, connection);}
}

模糊查询

public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;ResultSet resultSet = null;try{String sql = "select name fron t1 where name like ?";preparedStatement = connection.prepareStatement(sql);//模糊查询preparedStatement.setString(1,"马%");resultSet = preparedStatement.executeQuery();while(resultSet.next()){String name = resultSet.getString("name");System.out.println(name);}} catch(SQLException throwables){throwables.printStackTrace();} finally{JDBCUtil.close(resultSet, preparedStatement, connection);}
}

批处理

1、当需要成批插入或者更新记录时,可以采用java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。

2、JDBC的批量处理语句包括以下方法:

        addBatch(); //添加需要批量处理的SQL语句或参数

        executeBatch(); //执行批量处理语句

        clearBatch(); // 清空批处理包下的语句

3、JDBC连接MySQL时,如果要使用批处理功能,请在url后添加:

        ?rewriteBatchedStatements=true

4、批处理往往和PreparedStatement一起搭配使用,减少变异次数,减少运行次数。

//以下是传统方法批量传入大量数据
public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;try{String sql = "insert into t1 values(?,?)";preparedStatement = connection.prepareStatement(sql);long start = System.currentTimeMillis();//批量插入数据for(int i = 0;i < 5000;i++){prepreadStatement.setString(1,"冻梨");preparedStatement.setInt(2,i);preparedStatement.executeUpdate();}long end = System.currentTimeMillis();} catch(SQLException throwables){throwables.printStackTrace();} finally{JDBCUtil.close(null, preparedStatement, connection);}
}
//以下是批处理方法
public static void main(String[] args){Connection connection = JDBCUtil.getConnection();PreparedStatement preparedStatement = null;try {String sql = "insert into t1 values(?,?)";preparedStatement = connection.preparedStatement(sql);long start = System.currentTimeMillis();//批量插入数据for(int i = 15000; i< 20000;i++){preparedStatement.setString(1,"冻梨");preparedStatement.setInt(2,i);//将sql语句加入批处理包内preparedStatement.addBatch();//达到一千条,在进行处理if((i + 1) % 1000 == 0){preparedStatement.executeBatch();preparedStatement.clearBatch();}}long end = System.currentTimeMillis();} catch (SQLException throwables){throwables.printStackTrace();} finally{JDBCUtil.close(null,preparedStatement, connection);}
}

数据库连接池

1、传统的JDBC数据库连接使用DriverManager来获取,每次向数据库连接的时候都要将Connection加载到内容中,再验证IP地址,用户名和密码需要数据库连接,会占用很多系统资源。

2、每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。

3、传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。

4、解决传统开发中的数据库连接问题,可以采用数据库连接池技术。

数据库连接池基本介绍:

        1、预先在缓冲池放入一定数据的连接,当需要建立数据库连接时,只需从缓冲池中取出一个,使用完毕之后再放回去。

        2、数据库连接池负责分配,管理和释放数据库连接,他允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。

        3、当应用程序向连接池请求的连接数超过最大连接数据时,需要等待。

JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口。

数据库连接池种类:

        1、C3P0:速度慢,稳定

        2、DBCP:较快,不稳定

        3、Proxool:有监控连接池窗台的功能,不稳定

        4、BonCP:数据快

        5、Druid:集以上优点于一身

//传统方式
public static void main(String[] args) throws Exception{long start = System.currentTimeMillis();for(int i = 0;i < 5000;i++){Connection connection = JDBCUtil.getConnection();connection.close();}long end = System.currentTimeMillis();}
//连接池,需要加入对应的jar和配置文件
public static void main(String[] args) throws Exception{//获取数据源对象ComboPooledDataSource comboPoolDataSource = new ComboPooledDataSource();//根据配置文件获取信息Properties properties = new Properties();properties.load(new FileInputStream("src//mysql.properties"));String driver = properties.getProperty('driver');String url = properties.getProperty('url');String user = properties.getProperty('user');String password = properties.getProperty('password');//给数据源设置参数comboPooledDataSource.setDriverClass(driver);comboPooledDataSource.setJdbcUrl(url);comboPooledDataSource.setUser(user);comboPooledDataSource.setPassword(password);//初始化连接数comboPooledDataSource.setInitialPoolSize(10);//设置最大连接数comboPooledDataSource.setMaxPoolSize(50);//获取连接for(int i = 0;i < 5000;i++){Connection connection = comboPooledDataSource.getConnection();connection.close();}}
//比如50w,Druid要快
public static void main(String[] args) throws Exception{Properties properties = new Properties();properties.load(new FileInputStream("src//druid.properties"));//创建一个Druid连接DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);for(int i = 0;i < 5000;i++){Connection connection = dataSource.getConnection();connection.close();}}
//关于Druid连接池的工具类
public class JDBCUtilsByDruid{private static DataSource ds;static {try{Properties properties = new Properties();properties.load(new FileInputStream("src//druid.properties"));ds = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e){throw new RuntimeException(e);}}//创建连接public static Connection getConnection(){try{return ds.getConnection();} catch(SQLException throwables){throw new RuntimeException(throwables);}}//不是真正的关闭,而是放回连接池public static void close(ResultSet rs, Statement ps, Connection c){if(rs != null){try{rs.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}if(ps != null){try{ps.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}if(c!= null){try{c.close();} catch(SQLException throwables){throw new RuntimeException(throwables);}}}
}

Apache-DBUtils

//以下是土方法把resultSet集合里的数据,封装到list集合中
public class T2{private Integer no;private String name;public T2(){}public T2(Integer no, String name){this.no = no;this.name = name;}public Integer getNo(){return no;}public static void main(String[] args){Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;String sql = "select * from t2";//存储结果ArrayList<T2> list = new ArrayList<>();try{connection = JDBCUtilsByDruid.getConnection();preparedStatement = connection.prepareStatement(sql);resultSet = preparedStatement.executeQuery();while(resultSet.next()){int no = resultSet.getInt(1);String name = resultSet.getString(2);list.add(new T2(no,name));}} catch(SQLExceotion throwables){throwables.printStackTrace();} finally{JDBCUtilByDruid.close(resultSet, preparedStatement, connection);}}
}

Apache-DBUtils:是Apache组织提供的一个开源JDBC工具类库,是对JDBC的封装,极大简化JDBC工作量。

Dbutils类:

1、QueryRunner类:该类封装了SQL的执行,是线程安全的。

2、ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。

ArrayHandler:把结果集中的第一行数据转成对象数组

ArrayListJamdler:把结果集中的每一行数据都换成一个数组,存放到List中

BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中

BeanListHandler:将结果集中的第一行数据都封装到一个对应得到JavaBean实例中,存放到List

ColumnListHandler:将结果集中某一列的数据存放到List中

KeyedHandler:将结果集中的每行数据都封装到Map里,再把这些map再存放到一个map里,其key为指定的key

MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值

MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List

public static void main(String[] args) throws SQLException{Connection connection = JDBCUtilsByDruid.getConnection();QueryRunner queryRunner = new QueryRunner();String sql = "select * from t2";/*第一个参数:一个连接对象第二个参数:sql语句第三个参数:new BeanListHandler<>(T2.class)在将resultSet封装成T2对象,然后加入list集合中底层使用反射机制,去获取T2类的属性,进行封装*/List<T2> list = queryRunner.query(connection, sql, new BeanListHandler<>(T2.class));for(T2 t: list){System.out.println(t);}JDBCUtilsByDruid.close(null,null,connection);
}
public static void main(String[] args) throws SQLException{Connection connection = JDBCUtilsByDruid.getConnection();QueryRunner queryRunner = new QueryRunner();String sql = "delete from t2 where no = 3";int update = queryRunner.update(connection, sql);JDBCUtilsByDruid.close(null,null,connection);
}

BasicDao

DAO和增上改查通用方法——BasicDao

1、DAO:data access object 数据访问对象

2、这样的通用类,成为BasicDao,是专门和数据库交互的,即完成对数据表的crud操作

3、在BasicDao的基础上,实现一张表对应一个Dao,更好的完成功能

Customemer表——Customer.java——CustomerDao,java

Apache-dbutils+Druid简化了JDBC开发,但还有不足:

1、SQL语句是固定的,不能通过参数传入,通用性不好,需要进行改进,更方便执行CRUD

2、对于select操作,如果有返回值,返回类型不能固定,需要使用泛型

3、将来的表很多,业务需求复杂,不可能只靠一个java类完成

public class BasicDAO<T> {private QueryRunner qr = new QueryRunner();//dml操作语句public int update(String sql, Object... parameters){Connection connection = null;try {connection = JDBCUtilsByDruid.getConnection();return qr.update(connection, sql, parameters);} catch (SQLException e){throw new RuntimeException(e);} finally {JDBCUtilsByDruid.close(null,null,connection);}}//根据传入的Class对象,返回对应泛型的集合public List<T> query(String ssql, Class<T> clazz, Object... parameters){Connection connection = null;try {connection = JDBCUtilsByDruid.getConnection();return qr.query(connection, sql, new BeanListHandler<T>(clazz),parameters);} catch (SQLException e){throw new RuntimeException(e);} finally{JDBCUtilsByDruid.close(null,null,connection);}}//查询单行结果public T querySingle(String sql, Class<T> clazz,Object... parameters){Connection connection = null;try{conenction = JDBCUtilsByDruid.getConnection();return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);} catch (SQLException e){throw new RuntimeException(e);} finally {JDBCUtilsByDruid.close(null,null,connection);}}//查询单行单列结果public Object queryScalar(String sql, Object... parameters){Connection connection = null;try{conenction = JDBCUtilsByDruid.getConnection();return qr.query(connection, sql, new ScalarHandler(), parameters);} catch (SQLException e){throw new RuntimeException(e);} finally {JDBCUtilsByDruid.close(null,null,connection);}}}
public class T2DAO extends BasicDAO<T2>{}public class T2{private Integer no;private String name;public T2(){}public T2(Integer no, String name){this.no = no;this.name = name;}public Integer getNo(){ return no;}public void setNo(Integer no) {this.no = no;}public String getName() {return name;}public void setName(String name){this.name = name;}@Overridepublic String toString(){}
}public class TestDao{public static void main(String[] args){T2DAO t2DAO = new T2DAO();//查询多行String sql = "select * from t2";List<T2> query = t2DAO.query(sql, T2.class);for(T2 t: query){System.out.println(t);}//查询单行    String sql3 = "select * from t2 where no = 2";T2 t2 = t2DAO.querySingle(sql3, T2.class);//查询单行单列String sql4 = "select name fron t2 where no = 2";Object o = t2DAO.queryScalar(sql4);//dml操作String sql2 = "insert into t2 values(4, '擦原配')";int update = t2DAO.update(sql2);}
}
public class JDBCUtilsByDruid{private static DataSource ds;static{try{Properties properties = new Properties();properties.load(new FileInputStream("src//druid.properties"));ds = DruidDataSourceFactory.createDataSource(Properties);} catch (Exception e){throw new RuntimeException(e);}}public static Connection getConnection(){try{return ds.getConnection();} catch (SQLException throwables){throw new RuntimeException(throwables);}}
}

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

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

相关文章

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…

浅谈python如何做接口自动化

工具与环境准备 开发工具 PyCharm专业版&#xff1a;支持项目视图、代码导航、调试功能和主流框架开发官方资源&#xff1a;JetBrains PyCharm 数据库操作 使用mysqlclient库操作MySQL&#xff08;Django官方推荐&#xff09;安装命令&#xff1a;pip install mysqlclient1.3.…

知识图谱技术概述

一、概述 知识图谱&#xff08;Knowledge Graph&#xff09; 是一种基于图结构的语义网络&#xff0c;用于表示实体及其之间的关系&#xff0c;旨在实现更智能的知识表示和推理。它通过将现实世界中的各类信息抽象为 “实体-关系-实体” 的三元组结构&#xff0c;构建出复杂的知…

NodeJS Koa 后端用户会话管理,JWT, Session,长短Token,本文一次性讲明白

前言 前几天&#xff0c;我写了一篇文章&#xff0c;《我设计的一个安全的 web 系统用户密码管理流程》。其中着重点是讲的如何利用非对称加密进行安全的设计&#xff0c;并在讲述了原理之后&#xff0c;又写了 《node 后端和浏览器前端&#xff0c;有关 RSA 非对称加密的完整…

0.5S 级精度背后:DJSF1352-RN-6 如何让储能电站的每 1kWh 都「有迹可循」?

1、背景 在能源转型的时代洪流里&#xff0c;大型储能电站作为保障电网稳定运行、平衡能源供需的核心基础设施&#xff0c;其战略价值愈发凸显。而储能电站的高效运转&#xff0c;始终离不开精准的电能计量体系支撑。今日为您重点推介一款针对 1500V 储能系统研发的专业电能表…

Linux运维笔记:服务器安全加固

文章目录 背景加固措施1. 修改用户密码2. 使用公钥认证替代密码登录3. 强化系统安全4. 扫描与清理残留威胁5. 规范软件管理&#xff08;重点&#xff09; 注意事项总结 提示&#xff1a;本文总结了大学实验室 Linux 电脑感染挖矿病毒后的安全加固措施&#xff0c;重点介绍用户密…

Pycharm 配置解释器

今天更新了一版pycharm&#xff0c;因为很久没有配置解释器了&#xff0c;发现一直失败。经过来回试了几次终于成功了&#xff0c;记录一下过程。 Step 1 Step 2 这里第二步一定要注意类型要选择python 而不是conda。 虽然我的解释器是conda 里面建立的一个环境。挺有意思的

【Linux】awk 命令详解及使用示例:结构化文本数据处理工具

【Linux】awk 命令详解及使用示例&#xff1a;结构化文本数据处理工具 引言 awk 是一种强大的文本处理工具和编程语言&#xff0c;专为处理结构化文本数据而设计。它的名称来源于其三位创始人的姓氏首字母&#xff1a;Alfred Aho、Peter Weinberger 和 Brian Kernighan。 基…

MS1023/MS1224——10MHz 到 80MHz、10:1 LVDS 并串转换器(串化器)/串并转换器(解串器)

产品简述 MS1023 串化器和 MS1224 解串器是一对 10bit 并串 / 串并转 换芯片&#xff0c;用于在 LVDS 差分底板上传输和接收 10MHz 至 80MHz 的并行字速率的串行数据。起始 / 停止位加载后&#xff0c;转换为负载编 码输出&#xff0c;串行数据速率介于 120Mbps…

跟我学c++中级篇——理解类型推导和C++不同版本的支持

一、类型推导 在前面反复分析过类型推导&#xff08;包括前面提到的类模板参数推导CTAD&#xff09;&#xff0c;类型推导其实就是满足C语言这种强类型语言的要求即编译期必须确定对象的数据类型。换一句话说&#xff0c;理论上如果编译器中能够自动推导所有的相关数据类型&am…

vue3+TS+eslint9配置

记录eslint升级到9.x的版本之后遇到的坑 在 ESLint 9 中&#xff0c;配置方式发生了变化。Flat Config 格式&#xff08;eslint.config.js 或 .ts&#xff09;不再支持 extensions 选项。所以vscode编辑器中的 extensions 需要注释掉&#xff0c;要不然保存的时候不会格式化。…

书籍推荐 --- 《筚路维艰:中国经济社会主义路径的五次选择》

萧冬连.筚路维艰:中国社会主义路径的五次选择[M]. 前不久看完的这本书&#xff0c;还是蛮受震撼的。 这本书比较细致地(引用了很多的史料)、从中央高层的视角讲解了从新中国成立一直到改革开放初期这30多年里(1949---1980年代)发生在我国的几次重大事件(三大改造、第一个五年计…

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 专栏介绍&#xff1a;《编程项目实战》 目录 一、为什么要开发一个日历程序&#xff…

(三)动手学线性神经网络:从数学原理到代码实现

1 线性回归 线性回归是一种基本的预测模型&#xff0c;用于根据输入特征预测连续的输出值。它是机器学习和深度学习中最简单的模型之一&#xff0c;但却是理解更复杂模型的基础。 1.1 线性回归的基本元素 概念理解&#xff1a; 线性回归假设输入特征和输出之间存在线性关系。…

二十五、面向对象底层逻辑-SpringMVC九大组件之HandlerMapping接口设计

一、引言&#xff1a;MVC架构的交通枢纽 在Spring MVC框架中&#xff0c;HandlerMapping接口扮演着"请求导航仪"的关键角色&#xff0c;它决定了HTTP请求如何被路由到对应的Controller处理器。作为MVC模式的核心组件之一&#xff0c;HandlerMapping在请求处理的生命…

凌晨四点的星光

凌晨四点的城市像台停止运转的老旧机器&#xff0c;陈明裹紧外套踩着路灯的残影往家走。键盘敲击声仿佛还在耳边回响&#xff0c;他揉了揉酸涩的眼睛&#xff0c;手机屏幕突然亮起&#xff0c;是妻子发来的消息&#xff1a;“孩子又发烧了&#xff0c;我带他去医院。” 这是他…

Kyosan K5BMC ELECTRONIC INTERLOCKING MANUAL 电子联锁

Kyosan K5BMC ELECTRONIC INTERLOCKING MANUAL 电子联锁

LeetCode 热题 100 74. 搜索二维矩阵

LeetCode 热题 100 | 74. 搜索二维矩阵 大家好&#xff0c;今天我们来解决一道经典的算法题——搜索二维矩阵。这道题在 LeetCode 上被标记为中等难度&#xff0c;要求我们在一个满足特定条件的二维矩阵中查找一个目标值。如果目标值在矩阵中&#xff0c;返回 true&#xff1b…

如何在 HTML 中添加按钮

原文&#xff1a;如何在 HTML 中添加按钮 | w3cschool笔记 &#xff08;请勿将文章标记为付费&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; 在网页开发中&#xff0c;按钮是用户界面中不可或缺的元素之一。无论是用于提交表单、触发动作还是导航&#xff0…

一篇文章实现Android图片拼接并保存至相册

系列文章目录 一篇文章实现Android图片拼接并保存至相册 文章目录 系列文章目录前言实现功能类定义和成员变量onCreate方法权限检查和图片选择处理选择的图片图片拼接功能图片保存功能 使用ImageStitcher类拼接图片代码解释&#xff1a;ImageStitcher.java类定义和方法计算拼接…