文档章节

jdbc事务处理和连接池

芥末小弟
 芥末小弟
发布于 2017/03/01 09:54
字数 3543
阅读 3
收藏 0
点赞 0
评论 0

JDBC:

* JDBC概念:Java DataBase Connectivity(Java数据库连接)

SUN公司提供的一组连接数据库API.

* JDBC开发步骤:

* 1.注册驱动.

* 2.获得连接.

* 3.执行SQL.

* 4.释放资源.

* JDBC入门案例:

* JDBC的API详解:

* DriverManager:

* 管理驱动:

* Class.forName(“com.mysql.jdbc.Driver”);

* 获得连接:

* getConnection(String url,String username,String password);

* Connection:

* 创建执行SQL的对象:

* createStatement();

* prepareStatement(String sql);

* 管理事务:

* setAutoCommit(boolean flag);

* commit();

* rollback();

* Statement:

* 执行SQL语句

* executeQuery(String sql);

* executeUpdate(String sql);

* execute(String sql);

* 执行批处理

* addBatch(String sql);

* executeBatch();

* clearBatch();

* ResultSet:

* 结果集:

* 默认只能向下不可以修改.

* 多条记录:

while(rs.next()){

rs.getXXX();

}

* 一条记录:

if(rs.next()){

}

* 滚动结果集:(了解)

JDBC开发CRUD的操作:

* 资源释放:

* Connection的资源是非常稀有的,应该做到晚创建,早释放!

* 抽取工具类:

* 提取了一个配置文件db.properties

EE开发中的DAO模式:

* DAO模式主要用来解决持久层的问题!

* 操作数据库!

DAO模式编写了一个登陆案例:

* 页面---->Servlet---->UserService---->UserDao

* 演示了SQL注入漏洞:

* 解决:

* PreparedStatement:对SQL进行预编译!

大文件读写:

批处理:

1.1 JDBC中事务管理:

1.1.1 事务:

事务:指的是逻辑上一组操作!一组操作要么全都成功,要么全都失败!

1.1.2 MYSQL数据库中操作事务

MYSQL的数据库的事务是自动提交的!!!Oracle不是自动提交的!!!

MySQL数据库,写一个SQL就是一个事务!!!

创建一个表:

create database day18;

use day18;

create table account(

id int primary key auto_increment,

name varchar(20),

money double

);

insert into account values (null,'美美',10000);

insert into account values (null,'小边',10000);

insert into account values (null,'冠希',10000);

insert into account values (null,'白鸽',10000);

insert into account values (null,'小白',10000);

MYSQL中管理事务:

有两种方式管理事务:

一、使用命令方式:

* start transaction; --- 开启事务

* commit; --- 事务提交

* rollback; --- 事务回滚

二、设置MYSQL数据库参数:

* show variables like '%commit%';

+--------------------------------+-------+

| Variable_name | Value |

+--------------------------------+-------+

| autocommit | ON |

| innodb_commit_concurrency | 0 |

| innodb_flush_log_at_trx_commit | 1 |

+--------------------------------+-------+

* 设置自动提交参数为OFF(0) ON(1)

set autocommit = 0;

1.1.3 JDBC中使用事务:
JDBC的事务

通过Connection对象中的

* setAutoCommit(false);

* commit();/rollback();

案例:

@Test

// 冠希给美美转账 1000元

public void demo1(){

Connection conn = null;

PreparedStatement stmt = null;

try{

// 获得连接

conn = JDBCUtils.getConnection();

// 管理事务:事务不是自动提交!

conn.setAutoCommit(false);

// 编写一个SQL语句

String sql = "update account set money = money + ? where name = ?";

// 预编译SQL

stmt = conn.prepareStatement(sql);

// 设置参数:

// 扣去冠希的1000元

stmt.setDouble(1, -1000);

stmt.setString(2, "冠希");

stmt.executeUpdate();

// 如果现在没有事务的保证:当扣除冠希的1000元之后,会发生什么情况?

// 这个时候 冠希的钱就被扣除了,但是美美没有收到钱!!!

// 所以多条SQL要由事务保证:事务就是保证逻辑上的一组操作要么全都成功,要么全都失败!

int a = 10 / 0;

// 为美美增加1000元

stmt.setDouble(1, 1000);

stmt.setString(2, "美美");

stmt.executeUpdate();

// 提交事务:

conn.commit();

}catch(Exception e){

// 异常发生了:

try {

conn.rollback();

} catch (SQLException e1) {

e1.printStackTrace();

}

e.printStackTrace();

}finally{

JDBCUtils.release(stmt, conn);

}

}

JDBC事务的保存点:(了解)

保存点的作用:回滚的时候不用回滚到最初始的状态!!!

Connection中有

* setSavepoint() --- 设置一个保存点

* rollback(SavePoint savePoint); --- 事务回滚到保存点的位置

@Test

// 小边跟美美说你借我1w,设置一个保存点,我还你10w.但是小边的账号余额不足.回滚到保存点的位置!

public void demo2(){

Connection conn = null;

PreparedStatement stmt = null;

ResultSet rs = null;

try{

// 获得连接

conn = JDBCUtils.getConnection();

// 开启事务:

conn.setAutoCommit(false);

// 编写一个sql语句.

String sql = "update account set money = money + ? where name = ?";

// 预编译SQL

stmt = conn.prepareStatement(sql);

// 扣除美美的1w元

stmt.setDouble(1, -10000);

stmt.setString(2, "美美");

stmt.executeUpdate();

// 给小边增加1w元

stmt.setDouble(1, 10000);

stmt.setString(2, "小边");

stmt.executeUpdate();

// 设置一个保存点

Savepoint savePoint = conn.setSavepoint();

// 小边还给美美10w

stmt.setDouble(1, -100000);

stmt.setString(2, "小边");

stmt.executeUpdate();

stmt.setDouble(1, 100000);

stmt.setString(2, "美美");

stmt.executeUpdate();

sql = "select * from account where name = ?";

stmt = conn.prepareStatement(sql);

// 设置参数

stmt.setString(1, "小边");

rs = stmt.executeQuery();

if(rs.next()){

if(rs.getDouble("money")<0){

conn.rollback(savePoint);

System.out.println("上当了!!!");

}

}

// 事务提交

conn.commit();

}catch(Exception e){

try {

conn.rollback();

} catch (SQLException e1) {

e1.printStackTrace();

}

e.printStackTrace();

}finally{

JDBCUtils.release(rs, stmt, conn);

}

}

1.1.4 事务的特性:(面试)

原子性:

* 强调的是事务的不可分割!

一致性:

* 事务执行的前后,数据的完整性保持一致!

隔离性:

* 一个事务的执行,不应该受到另一个事务的打扰!

持久性:

* 事务执行结束!数据就永久的保存数据库中!

如果不考虑事务的隔离性:引发哪些问题?

* 引发三种读的问题.

* 脏读 :一个事务读到了另一个事务未提交的数据!!!

* 不可重复读 :一个事务读到了另一个事务已经提交数据(另一个事务中做的是update操作),导致多次查询结果在一个事务中不一致!

* 虚读(幻读) :一个事务读到了另一个事务已经提交数据(另一个事务中做的是insert操作),导致在事务中做的统计的结果不一致!!

* 如何解决问题?

* 数据库提供了四个隔离级别解决三种读问题!!!

* read uncommitted :未提交读.那么脏读、不可重复读、虚读都是有可能发生!!

* read committed :已提交读.避免脏读。但是不可重复读和虚读是有可能发生!!

* repeatable read :重复读.避免脏读、不可重复读。但是虚读是有可能发生的!!

* serializable :串行的.避免脏读、不可重复读、虚读的发生!!

* 安全性:

read uncommitted < read committed < repeatable read < serializable

* 效率性:

read uncommitted > read committed > repeatable read > serializable

***** 数据库中使用的时候一般不会采用最低和最高的!!!

* MYSQL中使用的是repeatable read Oracle中read committed

通过命令查看事务的隔离级别:

* select @@tx_isolation;

设置数据库隔离级别:

* set session transaction isolation level 隔离级别;

演示脏读发生:

脏读:一个事务读到了另一个事务未提交的数据!!!

1.开启两个窗口A、B!分别连接到MYSQL数据库!

* 分别在两个窗口中查看隔离级别!

* select @@tx_isolation;

2.设置A窗口的隔离级别为read uncommitted

* set session transaction isolation level read uncommitted;

* 查看A窗口的隔离级别

* A:READ-UNCOMMITTED

* B:REPEATABLE-READ

3.两个窗口分别开启事务:

* start transaction;

4.在B窗口中完成转账的代码:但是事务不要提交!

* update account set money = money - 1000 where name = '冠希';

* update account set money = money + 1000 where name = '美美';

5.在A窗口中进行查询:

* select * from account;

* 发现钱已经到账了!!!脏读.(A窗口已经读到B窗口还没有提交的数据)

演示不可重复读的发生(避免脏读)

不可重复读:一个事务读到另一个事务已经提交的数据!(另一个事务update)导致当前的事务多次的查询结果不一致

* 避免脏读:设置隔离级别为read committed

1.开启两个窗口A、B!分别连接到MYSQL数据库!

* 分别在两个窗口中查看隔离级别!

* select @@tx_isolation;

2.在A窗口中设置数据库隔离级别为read committed

* set session transaction isolation level read committed;

* A:READ-COMMITTED

* B:REPEATABLE-READ

3.两个窗口分别开启事务:

* start transaction;

4.在B窗口完成转账代码:

* update account set money = money - 1000 where name = '冠希';

* update account set money = money + 1000 where name = '美美';

***** 先不提交事务!

5.在A窗口进行查询!

* select * from account;

* 这个时候钱没有到账!(避免脏读了 一个事务没有读到另一个事务未提交的数据)

6.在B窗口提交事务:

* commit;

7.在A窗口再次去查询!

* select * from account;

* 发现这次结果和上次的结果不一致(两次查询是在一个事务中的!):不可重复读.

演示避免不可重复读

1.开启两个窗口A、B!分别连接到MYSQL数据库!

* 分别在两个窗口中查看隔离级别!

* select @@tx_isolation;

2.设置A窗口的隔离级别:repeatable read

* set session transaction isolation level repeatable read;

* A:REPEATABLE-READ

* B:REPEATABLE-READ

3.两个窗口分别开启事务:

* start transaction;

4.在B窗口完成转账:

* update account set money = money - 1000 where name = '冠希';

* update account set money = money + 1000 where name = '美美';

事务没有提交

5.在A窗口查询:

* select * from account;

* 数据没有变化:避免脏读!

6.在B窗口提交事务:

* commit;

7.在A窗口再次查询:

* select * from account;

* 数据还是没有变化:避免了不可重复读!

演示隔离级别为serializable:

Serializable:串行的!可以避免脏读、不可重复读、虚读!

1.开启两个窗口A、B!分别连接到MYSQL数据库!

* 分别在两个窗口中查看隔离级别!

* select @@tx_isolation;

2.设置A窗口的隔离级别为 Serializable

* set session transaction isolation level Serializable;

* A:SERIALIZABLE

* B:REPEATABLE-READ

3.在两个窗口分别开启事务:

* start transaction;

4.在B窗口中插入一条记录:

* 事务不提交:

5.在A窗口中进行查询:

* select * from account;

* 屏幕卡住了:因为另一个事务没有提交.这个事务就不能执行,串行的!!!

1.1.5 JDBC中如何设置隔离级别

通过Connection对象的:

* setTransactionIsolation(int level) ;

Connection中有以下的常量:

static int TRANSACTION_READ_COMMITTED

指示不可以发生脏读的常量;不可重复读和虚读可以发生。

static int TRANSACTION_READ_UNCOMMITTED

指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。

static int TRANSACTION_REPEATABLE_READ

指示不可以发生脏读和不可重复读的常量;虚读可以发生。

static int TRANSACTION_SERIALIZABLE

指示不可以发生脏读、不可重复读和虚读的常量。

1.1.6 丢失更新:(扩展)

第一类丢失更新

A事务撤销时,把已提交的B事务的数据覆盖掉。这种错误会造成非常严重的后果。

第二类丢失更新

A事务提交时,把已提交的B事务的数据覆盖掉。这种错误会造成非常严重的后果。

解决丢失更新问题:

* 悲观锁:假设丢失更新一定存在!

* 使用的是数据库的一种锁机制:排他锁.

* 乐观锁:假设丢失更新不一定存在!

1.2 连接池

1.2.1 什么是连接池

连接池:就是一个装了很多连接一个容器.(内存中的一块空间,装了很多连接.)

1.2.2 使用连接池:

1.编写一个类 实现DataSource接口.

2.重写getConnection()的方法.

自定义连接池的代码:

public class MyDataSource implements DataSource{

private List<Connection> list = new ArrayList<Connection>();

// 提供一个构造方法:

public MyDataSource(){

// 创建3个连接

for (int i = 1; i <=3; i++) {

// 向list集合放入连接

list.add(JDBCUtils.getConnection());

}

}

// 从连接池中获得连接

public Connection getConnection() throws SQLException {

// 从连接池中获得连接.连接池的扩容

if(list.isEmpty()){

for (int i = 1; i <=3; i++) {

// 向list集合放入连接

list.add(JDBCUtils.getConnection());

}

}

Connection conn = list.remove(0);

return conn;

}

// 用完之后,不是销毁连接而是归还到连接池

public void addBack(Connection conn){

list.add(conn);

}

public PrintWriter getLogWriter() throws SQLException {

return null;

}

public void setLogWriter(PrintWriter out) throws SQLException {

}

public void setLoginTimeout(int seconds) throws SQLException {

}

public int getLoginTimeout() throws SQLException {

return 0;

}

public <T> T unwrap(Class<T> iface) throws SQLException {

return null;

}

public boolean isWrapperFor(Class<?> iface) throws SQLException {

return false;

}

public Connection getConnection(String username, String password)

throws SQLException {

return null;

}

}

****** 自定义连接池中在使用的使用!需要让程序员额外记住一些自定义的方法例如addBack();而且在构造的时候不能使用接口构造.

能不能有一种方式实现:使用接口构造连接池,而且在归还的时候不需要额外提供方法???

* 原来的Connection的close方法是将连接对象销毁!现在是否可以增强close方法,使方法的逻辑改变,归还连接而不是销毁连接?

* 增强Java中类的方法有几种方式?

* 继承 :

class Person{

public void run(){

System.out.println("跑步1000米...");

}

}

class SuperPerson extends Person{

@Override

public void run() {

// super.run();

System.out.println("跑步10000米...");

}

}

测试:

@Test

public void demo1(){

Person p = new SuperPerson();

p.run(); // 子类的方法.

}

***** 继承这种增强某个类的方法使用条件的:

* 必须能够控制这个类的构造!

* 装饰者模式 :

代码:

interface Bird{

public void fly();

}

// 被增强的类

class MaQue implements Bird{

public void fly() {

System.out.println("飞200米...");

}

}

// 增强的类

class DaMaQue implements Bird{

private Bird bird;

public DaMaQue(Bird bird){

this.bird = bird;

}

public void fly() {

bird.fly();

System.out.println("飞500米...");

}

}

测试:

@Test

public void demo1(){

// Bird bird = new MaQue();

// bird.fly();

Bird bird = new DaMaQue(new MaQue());// IO流

bird.fly();

}

***** 装饰者这种增强某个类的方法使用条件的:

* 1.增强类和被增强类实现相同的接口.

* 2.在增强的类中获得到被增强类的引用.

***** 缺点:

* 接口中的方法特别多!

* 动态代理 :

1.2.3 DBCP连接池:

DBCP(DataBase connection pool),数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件。

使用DBCP连接池

1.引入jar包:

* commons-dbcp-1.4.jar

* commons-pool-1.5.6.jar

2.核心类:

* BasicDataSource

* 手动设置参数.

* BasicDataSourceFactory

* 采用配置文件的方式.

1.2.4 C3P0连接池:

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等

使用C3P0连接池

1.导入jar包:

* c3p0-0.9.1.2.jar

2.ComboPooledDataSource

1.2.5 Tomcat内置连接池:(JNDI技术.)

JNDI:Java Naming and Directory Interface,Java命名和目录接口.

* 对一个Java对象起一个名字.通过这个名称查找到该对象.

JNDI连接池:

* 需要有一个配置:<Context>标签的配置.

* <Context>标签:在Tomcat学习的时候使用.配置Tomcat的虚拟路径.

* <Context>标签可以配置在三个地方:

* Tomcat/conf/context.xml:可以被Tomcat下的所有的虚拟主机、虚拟路径访问!

* Tomcat/conf/Catalina/localhost/context.xml:可以Tomcat下的localhost虚拟主机下的所有路径访问!

* 当前的工程下/META-INF/context.xml:只能被当前的工程访问!

使用JNDI连接池:

* 1.在工程的/META-INF/创建一个context.xml

<Context>

<Resource name="jdbc/EmployeeDB" auth="Container"

type="javax.sql.DataSource" username="root" password="123"

driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql:///day18"

maxActive="8" maxIdle="4"/>

</Context>

* 2.Tomcat维护这些连接.这些连接是由Tomcat服务器创建的.

* 必须在Tomcat/lib下copy一个数据库驱动包.

* 3.操作连接池这个类必须是运行在Tomcat服务器中的类!

* 运行在Tomcat中的类是Servlet!!!

© 著作权归作者所有

共有 人打赏支持
芥末小弟
粉丝 0
博文 202
码字总数 120715
作品 0
昌平
主流Java数据库连接池比较及前瞻

本文转载自微信公众号「工匠小猪猪的技术世界」 主流数据库连接池 常用的主流开源数据库连接池有C3P0、DBCP、Tomcat Jdbc Pool、BoneCP、Druid等 C3p0: 开源的JDBC连接池,实现了数据源和JND...

渣渣(Charles) ⋅ 04/30 ⋅ 0

ThreadLocal可能引起的内存泄露

  threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用thr...

天天顺利 ⋅ 06/15 ⋅ 0

Spring之jdbc Template实现CRUD操作

Spring为各种持久化技术都提供了简单操作的模板回调。比如jdbc、hibernate、Mybatis以及JPA等。 这里我们就以JDBC为例,看看JDBC template怎么实现CRUD操作。 JdbcTemplate主要提供以下几类方...

Java攻城玩家 ⋅ 05/31 ⋅ 0

Java面试题之Hibernate

1.简书一下Hibernated的开发流程 第一步:加载Hibernate的配置文件,读取配置文件的参数, 第二步:创建SessionFactory会话工厂(内部有连接池) 第三步:打开Session 连接 第四步:开启事务...

年轻诠释我们的梦想_705b ⋅ 06/05 ⋅ 0

编写高性能 Java 代码的最佳实践

摘要:本文首先介绍了负载测试、基于APM工具的应用程序和服务器监控,随后介绍了编写高性能Java代码的一些最佳实践。最后研究了JVM特定的调优技巧、数据库端的优化和架构方面的调整。以下是译...

这篇文章 ⋅ 今天 ⋅ 0

【J2EE】之常用的接口和协议

前言 初接触J2EE,会遇到很多之前没有遇到过的术语,下面我们来简单地汇总一下这些接口和协议吧。 各大接口和协议详解 JDBC 1.定义:Java数据库连接 2.地位:用于Java应用程序连接数据库的标...

m18633778874 ⋅ 04/26 ⋅ 0

一篇简单易懂的原理文章,让你把JVM玩弄与手掌之中

jvm原理 Java虚拟机是整个java平台的基石,是java技术实现硬件无关和操作系统无关的关键环节,是java语言生成极小体积的编译代码的运行平台,是保护用户机器免受恶意代码侵袭的保护屏障。JVM...

烂猪皮 ⋅ 05/08 ⋅ 0

Tomcat8的jvm和线程池配置

目标 配置Tomcat的jvm的响应时间较短 配置Tomcat的连接池尽可能的高效 jvm启动参数 沿用文章tomcat中配置文件之setenv.sh提到的配置文件,配置如下: 增加的参数作用: :优先启用G1垃圾回收器...

亚林瓜子 ⋅ 05/03 ⋅ 0

Java面试题全集(上)+JavaSE基础

第一阶段:开发常用 JavaSE基础、Spring、Spring Boot、Spring Cloud、Hibernate、Mybatis、MySQL、Oracle、MariaDB(支持文档数据) 第二阶段:基础提升 JavaSE深入、JVM、数据结构+算法、H...

guod369 ⋅ 2017/12/28 ⋅ 0

JVM学习总结(一)运行时数据区

《深入Java虚拟机》这本书买了有一段时间了,当时看的时候就只是看,并没有边看边总结啥的,最后发现到脑子里面的根本所剩无几了。现在开始要好好归纳总结地再学习一遍。 运行时数据区域 JV...

hensemlee ⋅ 04/22 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

OSChina 周三乱弹 —— 这样的女人私生活太混乱了

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @ 胖达panda :你经历过体验到人生的大起大落吗?我一朋友在10秒内体验了,哈哈。@小小编辑 请点一首《almost lover》送给他。 《almost love...

小小编辑 ⋅ 21分钟前 ⋅ 5

自己动手写一个单链表

文章有不当之处,欢迎指正,如果喜欢微信阅读,你也可以关注我的微信公众号:好好学java,获取优质学习资源。 一、概述 单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对...

公众号_好好学java ⋅ 27分钟前 ⋅ 0

Centos7重置Mysql 8.0.1 root 密码

问题产生背景: 安装完 最新版的 mysql8.0.1后忘记了密码,向重置root密码;找了网上好多资料都不尽相同,根据自己的问题总结如下: 第一步:修改配置文件免密码登录mysql vim /etc/my.cnf 1...

豆花饭烧土豆 ⋅ 今天 ⋅ 0

熊掌号收录比例对于网站原创数据排名的影响[图]

从去年下半年开始,我在写博客了,因为我觉得业余写写博客也还是很不错的,但是从2017年下半年开始,百度已经推出了原创保护功能和熊掌号平台,为此,我也提交了不少以前的老数据,而这些历史...

原创小博客 ⋅ 今天 ⋅ 0

LVM讲解、磁盘故障小案例

LVM LVM就是动态卷管理,可以将多个硬盘和硬盘分区做成一个逻辑卷,并把这个逻辑卷作为一个整体来统一管理,动态对分区进行扩缩空间大小,安全快捷方便管理。 1.新建分区,更改类型为8e 即L...

蛋黄Yolks ⋅ 今天 ⋅ 0

Hadoop Yarn调度器的选择和使用

一、引言 Yarn在Hadoop的生态系统中担任了资源管理和任务调度的角色。在讨论其构造器之前先简单了解一下Yarn的架构。 上图是Yarn的基本架构,其中ResourceManager是整个架构的核心组件,它负...

p柯西 ⋅ 今天 ⋅ 0

uWSGI + Django @ Ubuntu

创建 Django App Project 创建后, 可以看到路径下有一个wsgi.py的问题 uWSGI运行 直接命令行运行 利用如下命令, 可直接访问 uwsgi --http :8080 --wsgi-file dj/wsgi.py 配置文件 & 运行 [u...

袁祾 ⋅ 今天 ⋅ 0

JVM堆的理解

在JVM中,我们经常提到的就是堆了,堆确实很重要,其实,除了堆之外,还有几个重要的模块,看下图: 大 多数情况下,我们并不需要关心JVM的底层,但是如果了解它的话,对于我们系统调优是非常...

不羁之后 ⋅ 昨天 ⋅ 0

推荐:并发情况下:Java HashMap 形成死循环的原因

在淘宝内网里看到同事发了贴说了一个CPU被100%的线上故障,并且这个事发生了很多次,原因是在Java语言在并发情况下使用HashMap造成Race Condition,从而导致死循环。这个事情我4、5年前也经历...

码代码的小司机 ⋅ 昨天 ⋅ 2

聊聊spring cloud gateway的RetryGatewayFilter

序 本文主要研究一下spring cloud gateway的RetryGatewayFilter GatewayAutoConfiguration spring-cloud-gateway-core-2.0.0.RC2-sources.jar!/org/springframework/cloud/gateway/config/G......

go4it ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部