怎么查数据库的数据

怎么查数据库的数据,第1张

为什么查询数据。在生活中我们有时需要查询一张表,例如下面一张表查询所在班级 = 'S202'的所有学生信息。

查询语句的语法。我们使用SQL语句进行查询数据,我们来分析下如何查询语句的构成部分,便于我们理解怎样使用查询语句。

查询全部列和行。我们了解了sql查询语句后就可以使用了,下面是我经常用的查询方式查询数据,明白原理后可以探索下其他的方式。

查询部分列和行。我们在查询数据时很少一下子把数据一张表的数据都查询出来,很多时候都是查询一张表的部分数据,我们使用查询部分行或列就可以了。

使用别名查询。我们通常在查询数据时采用别名的习惯使我们在编写时容易编写,而且在执行联表查询使用别名利于方便 *** 作。

查询空值办法。对数据进行查询时,如果某列值是空的,我们怎么查询呢,我们可以使用 is null 作为判断条件。

查询限制行数列。我们可以看见奥运金牌的排行榜是以固定的名次排列的,有时候只显示前5名,我们使用查询限制行数列实现这个效果。

查询单列排序。考试成绩从高到底自动排序等可以使用单列排序实现,我们使用关键字DESC (降序排列)或者ASC(升序)实现对数据的排序。

查询多列排序。如果成绩按升序,课程编号按降序,该如何编写?我们就需要使用多列排序实现我们的需求。

select top n * from table

n为 你要从地一个取的行数

select top n * from table order by 列名(基本上任意) desc

这个就是从 后面往前取

要是去中间的 比如 20到30的

select top 10 from table where 列名 not in(select top 20 列名 from table)

这个就是先取出前20 在去不在前20行里的余下的 所有数据的前10行 就是咱们要的20到30行

其他的 你自己研究吧 基本上都是这样

jdbc驱动

java jdbc连接_2019最新JDBC面试题(附JDBC教程)

weixin_39665379

关注

0点赞·410人阅读

1.什么是JDBC API,何时使用它?

Java DataBase Connectivity API允许我们使用关系数据库。JDBC API接口和类是part java.sql和javax.sqlpackage的一部分。我们可以使用JDBC API来获取数据库连接,在数据库服务器中运行SQL查询和存储过程并处理结果。

JDBC API的编写方式允许我们的Java程序和实际的JDBC驱动程序之间的松散耦合,这使我们可以轻松地从一个数据库切换到另一个数据库服务器。

2.JDBC驱动程序有哪几种类型?

有四种类型的JDBC驱动程序。任何与数据库一起工作的java程序都有两个部分,第一部分是JDBC API,第二部分是执行实际工作的驱动程序。

JDBC-ODBC Bridge加ODBC驱动程序(类型1):它使用ODBC驱动程序连接到数据库。我们应该安装ODBC驱动程序来连接数据库,这就是为什么这个驱动程序几乎已经过时的原因。

Native API部分支持Java技术的驱动程序(类型2):此驱动程序将JDBC类转换为数据库服务器的客户端API。我们应该安装数据库客户端API。由于对数据库客户端API驱动程序的额外依赖性,这也不是首选驱动程序。

用于数据库中间件的纯Java驱动程序(类型3):此驱动程序将JDBC调用发送到可以连接到不同类型数据库的中间件服务器。我们应该安装一个中间件服务器来使用这个驱动程序。这增加了额外的网络调用和性能降低,这就是为什么不广泛使用JDBC驱动程序。

直接到数据库的纯Java驱动程序(类型4):此驱动程序将JDBC调用转换为数据库服务器可以理解的网络协议。该解决方案简单,适用于网络上的数据库连接。但是对于此解决方案,我们应该使用特定于数据库的驱动程序,例如Oracle for Oracle DB的OJDBC jar和MySQL Connector的MySQL Connector / J.

3.JDBC API如何帮助我们实现Java程序和JDBC驱动程序API之间的松散耦合?

JDBC API使用Java Reflection API实现java程序和JDBC驱动程序之间的松散耦合。如果你看一个简单的JDBC例子,你会发现所有的编程都是用JDBC API完成的,而驱动程序只有在通过反射使用Class.forName()方法加载时才会出现。

我认为这是在核心java类中使用Reflection的最佳示例之一,以确保我们的应用程序不能直接使用Drivers API,这使得从一个数据库移动到另一个数据库变得非常容易。

4.什么是JDBC连接?解释在简单的java程序中获取数据库连接的步骤。

JDBC Connection就像使用数据库服务器创建的Session。您还可以将Connection 视为来自数据库服务器的Socket连接。

创建JDBC连接非常简单,需要两个步骤:

注册并加载驱动程序:使用Class.forName(),驱动程序类注册到DriverManager并加载到内存中。

使用DriverManager获取Connection对象:我们DriverManager.getConnection()通过传递数据库URL字符串,用户名和密码作为参数来获取连接对象。

Connection

5.JDBC DriverManager类有什么用?

JDBC DriverManager是我们通过它获取数据库连接对象的工厂类。当我们加载JDBC Driver类时,它将自己注册到DriverManager,您可以查找JDBC Driver类源代码来检查它。

当我们通过传递数据库配置细节来调用方法DriverManager.getConnection()时,DriverManager使用已注册的驱动程序来获取Connection并将其返回给调用者程序。

6.如何在java程序中获取数据库服务器的详细信息?

我们可以使用DatabaseMetaDataobject来获取数据库服务器的详细信息。成功创建数据库连接后,我们可以通过调用getMetaData()方法获取元数据对象。我们可以使用DatabaseMetaData中的方法来获取数据库产品名称,版本和详细的配置信息。

DatabaseMetaData

7.什么是JDBC Statement?

JDBC API Statement用于在数据库中执行SQL查询。我们可以通过调用Connection createStatement()方法来创建Statement对象。我们可以使用Statement通过不同的执行方法传递查询来执行静态SQL查询,例如execute(),executeQuery(),executeUpdate()等。

由于查询是在java程序中生成的,如果未正确验证用户输入,则可能导致SQL注入问题,可以在SQL注入示例中找到更多详细信息。

默认情况下,每个Statement对象只能同时打开一个ResultSet对象。因此,如果我们想要使用多个ResultSet对象,则每个对象必须由不同的Statement对象生成。Statement接口中的所有execute()方法都隐式关闭一个statment的当前ResultSet对象(如果存在open对象)。

8.execute,executeQuery,executeUpdate有什么区别?

Statement execute(String query)用于执行任何SQL查询,如果结果是ResultSet(如运行Select查询),则返回TRUE。当没有ResultSet对象(如运行Insert或Update查询)时,输出为FALSE。我们可以使用getResultSet()获取ResultSet和getUpdateCount()方法来检索更新计数。

Statement executeQuery(String query)用于执行Select查询并返回ResultSet。即使没有与查询匹配的记录,返回的ResultSet也永远不会为null。执行select查询时,我们应该使用executeQuery方法,这样如果有人试图执行insert / update语句,它将抛出java.sql.SQLException,并显示消息“executeQuery方法不能用于更新”。

语句executeUpdate(String query)用于执行不返回任何内容的Insert / Update / Delete(DML)语句或DDL语句。输出为int,等于SQL数据 *** 作语言(DML)语句的行数。对于DDL语句,输出为0。

只有在不确定语句类型时才应使用execute()方法,否则使用executeQuery或executeUpdate方法。

9.什么是JDBC PreparedStatement?

JDBC PreparedStatement对象表示预编译的SQL语句。我们可以使用它的setter方法来设置查询的变量。

由于PreparedStatement是预编译的,因此可以使用它多次有效地执行此语句。PreparedStatement是Statement的更好选择,因为它会自动转义特殊字符并避免SQL注入攻击。

10.如何在JDBC PreparedStatement中设置NULL值?

我们可以使用PreparedStatement setNull()方法将null变量绑定到参数。例如,setNull方法将index和SQL Types作为参数 ps.setNull(10, java.sql.Types.INTEGER)。

11.Statement中的getGeneratedKeys()方法有什么用?

有时,表可以使用自动生成的键来插入主键的唯一列值。我们可以使用Statement getGeneratedKeys()方法获取此自动生成密钥的值。

12.PreparedStatement对Statement有什么好处?

PreparedStatement对Statement的一些好处是:

PreparedStatement帮助我们防止SQL注入攻击,因为它会自动转义特殊字符。

PreparedStatement允许我们使用参数输入执行动态查询。

PreparedStatement比Statement快。当我们重用PreparedStatement或使用它的批处理方法执行多个查询时,它变得更加明显。

PreparedStatement帮助我们使用setter方法编写面向对象的代码,而使用Statement我们必须使用String Concatenation来创建查询。如果要设置多个参数,则使用字符串连接编写查询看起来非常难看并且容易出错。

13.PreparedStatement的限制是什么以及如何克服它?

PreparedStatement的一个限制是我们不能直接在IN子句中使用它。将PreparedStatement与IN子句一起使用的一些替代方法是:

执行单一查询 性能非常慢,不推荐使用

使用存储过程 特定于数据库,因此不适用于多个数据库应用程序。

动态创建PreparedStatement查询 良好的方法但失去了缓存的PreparedStatement的好处。

在PreparedStatement查询中使用NULL 当您知道变量输入的最大数量时,这是一种很好的方法,可以通过部分执行来扩展以允许无限制的参数。可以在JDBC PreparedStatement IN子句替代中找到更详细的分析。

14.什么是JDBC ResultSet?

JDBC ResultSet就像一个表示数据库结果集的数据表,通常通过执行查询数据库的语句来生成。

ResultSet对象维护指向其当前数据行的游标。最初,光标位于第一行之前。next()方法将光标移动到下一行。如果没有更多行,则next()方法返回false,并且可以在while循环中使用它来迭代结果集。

默认的ResultSet对象不可更新,并且只有一个向前移动的游标。因此,您只能迭代一次,并且只能从第一行到最后一行。可以使用以下语法生成可滚动和/或可更新的ResultSet对象。

Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,

ResultSet.CONCUR_UPDATABLE)

当生成它的Statement对象关闭,重新执行或用于从多个结果序列中检索下一个结果时,ResultSet对象将自动关闭。

我们可以使用ResultSet getter方法,列名或索引号从1开始检索列数据。

15.ResultSet有哪些不同的类型?

在创建Statement时,我们可以根据用户输入获得不同类型的ResultSet对象。如果您将查看Connection方法,您将看到createStatement()和prepareStatement()方法被重载以提供ResultSet类型和并发作为输入参数。

ResultSet对象有三种类型。

ResultSet.TYPEFORWARDONLY:这是默认类型,游标只能在结果集中向前移动。

ResultSet.TYPESCROLLINSENSITIVE:游标可以前后移动,结果集对创建结果集后其他人对数据库所做的更改不敏感。

ResultSet.TYPESCROLLSENSITIVE:游标可以向前和向后移动,结果集对创建结果集后其他人对数据库所做的更改很敏感。基于并发性,有两种类型的ResultSet对象。

ResultSet.CONCURREADONLY:结果集是只读的,这是默认的并发类型。

ResultSet.CONCUR_UPDATABLE:我们可以使用ResultSet更新方法来更新行数据。

16.Statement中的setFetchSize()和setMaxRows()方法有什么用?

我们可以使用setMaxRows(int i)方法来限制数据库从查询返回的行数。您可以使用SQL查询本身实现相同的功能。例如,在MySQL中,我们可以使用LIMIT子句来设置查询返回的最大行数。

理解fetchSize可能很棘手,因为你应该知道Statement和ResultSet是如何工作的。当我们在数据库中执行查询时,将在数据库缓存中获取并维护结果,并返回ResultSet。ResultSet是具有对数据库中结果的引用的游标。

假设我们有一个返回100行的查询,并且我们将fetchSize设置为10,因此在每次数据库访问时,JDBC驱动程序将只获取10行,因此将有10次访问以获取所有行。如果每行需要大量处理时间并且结果中的行数很大,那么设置最佳fetchSize会很有帮助。

我们可以通过Statement对象设置fetchSize,但可以通过ResultSet对象setFetchSize()方法覆盖它。

17.如何使用JDBC API调用存储过程?

存储过程是一组SQL查询,它们在数据库中编译,可以从JDBC API执行。JDBC CallableStatement可用于在数据库中执行存储过程。初始化CallableStatement的语法是

CallableStatement

//我们需要在调用存储过程之前注册外部参数

stmt

我们需要在执行CallableStatement之前注册OUT参数。

18.什么是JDBC批处理,它有什么好处?

有时我们需要为数据库运行类似的批量查询,例如将数据从CSV文件加载到关系数据库表。我们知道我们可以选择使用Statement或PreparedStatement来执行查询。除此之外,JDBC API还提供了批处理功能,通过该功能,我们可以一次性为数据库执行大量查询。

JDBC API支持通过Statement和PreparedStatement addBatch()以及executeBatch()方法进行批处理。

批处理比一次执行一个语句更快,因为数据库调用的数量较少。

19.什么是JDBC事务管理?我们为什么需要它?

默认情况下,当我们创建数据库连接时,它以自动提交模式运行。这意味着无论何时执行查询并完成查询,都会自动触发提交。因此,我们触发的每个SQL查询都是一个事务,如果我们运行一些DML或DDL查询,则每个SQL语句完成后,更改都会保存到数据库中。

有时我们希望一组SQL查询成为事务的一部分,以便我们可以在所有查询运行正常时提交它们,如果我们得到任何异常,我们可以选择回滚作为事务的一部分执行的所有查询。

JDBC API提供了一种方法,setAutoCommit(boolean flag)通过该方法我们可以禁用连接的自动提交功能。我们应该仅在需要时禁用自动提交,因为除非我们在连接上调用commit()方法,否则不会提交事务。数据库服务器使用表锁来实现事务管理,这是资源密集型过程。所以我们应该在完成交易后立即提交交易。

20.如何回滚JDBC事务?

我们可以使用Connection对象rollback()方法来回滚事务。它将回滚事务所做的所有更改,并释放此Connection对象当前持有的所有数据库锁。

21.什么是JDBC Savepoint?如何使用它?

有时,事务可以是多个语句的组,我们希望回滚到事务中的特定点。JDBC Savepoint帮助我们在事务中创建检查点,并且我们可以回滚到该特定检查点。

为事务创建的任何保存点都会自动释放,并在提交事务时或在回滚整个事务时变为无效。将事务滚动回保存点会自动释放并使在相关保存点之后创建的任何其他保存点无效。

22.什么是JDBC DataSource?它的好处是什么?

JDBC DataSource是javax.sql包中定义的接口,它比DriverManager更强大,可用于数据库连接。我们可以使用DataSource来创建数据库连接,而Driver实现类可以用来获取连接的实际工作。除了获取数据库连接外,DataSource还提供了一些其他功能,例如:

缓存PreparedStatement以加快处理速度 连接超时设置 记录功能 ResultSet最大大小阈值 使用JNDI支持在servlet容器中连接池 在JDBC DataSource上阅读有关DataSource的更多信息。

23.如何在Apache Tomcat Server中使用JDBC DataSource和JNDI实现JDBC连接池?

对于部署在servlet容器中的Web应用程序,创建JDBC连接池非常简单,只需要几个步骤。

在容器配置文件中创建JDBC JNDI资源,通常是server.xml或context.xml。例如

server

在Web应用程序中,使用InitialContext查找在第一步中配置的JNDI资源,然后获取连接。

Context

24.什么是Apache DBCP API?

如果您使用DataSource获取数据库连接,通常用于获取连接的代码与特定于驱动程序的DataSource实现紧密耦合。除了选择DataSource实现类之外,大多数代码都是样板代码。

Apache DBCP通过提供DataSource实现来帮助我们摆脱这些问题,DataSource实现充当我们的程序和不同JDBC驱动程序之间的抽象层。Apache DBCP库依赖于Commons Pool库,因此请确保它们都在构建路径中。

25.什么是JDBC连接隔离级别?

当我们使用JDBC事务来实现数据完整性时,DBMS使用锁来阻止其他人访问事务所访问的数据。DBMS使用锁来防止脏读,不可重复读和幻像读问题。

DBMS使用JDBC事务隔离级别来使用锁定机制,我们可以通过Connection getTransactionIsolation()方法获取隔离级别信息,并使用setTransactionIsolation()方法设置它。

26.什么是JDBC RowSet?RowSet有哪些类型?

JDBC RowSet以更灵活的方式保存表格数据,即ResultSet。所有RowSet对象都是从ResultSet派生的,因此它们具有ResultSet的所有功能以及一些其他功能。RowSet接口在javax.sql包中定义。

RowSet提供的一些附加功能包括:

具有属性的Java Bean及其getter-setter方法。RowSet使用JavaBeans事件模型,它们可以向任何已注册的组件发送通知,用于事件,例如光标移动,更新/插入/删除行以及更改为RowSet内容。

默认情况下,RowSet对象是可滚动和可更新的,因此如果DBMS不支持可滚动或可更新的ResultSet,我们可以使用RowSet来获取这些功能。

RowSet大致分为两种类型:

已连接的RowSet对象 - 这些对象连接到数据库,与ResultSet对象最相似。JDBC API仅提供一个连接的RowSet对象javax.sql.rowset.JdbcRowSet,它的标准实现类是com.sun.rowset.JdbcRowSetImpl 断开连接的RowSet对象 - 这些RowSet对象不需要连接到数据库,因此它们更轻量级且可序列化。它们适合通过网络发送数据。有四种类型的断开连接的RowSet实现。

CachedRowSet - 它们可以获取连接并执行查询并读取ResultSet数据以填充RowSet数据。我们可以在数据断开连接时 *** 作和更新数据,并重新连接到数据库并写入更改。WebRowSet派生自CachedRowSet - 它们可以读写XML文档。JoinRowSet派生自WebRowSet - 它们可以形成SQL JOIN而无需连接到数据源。从WebRowSet派生的FilteredRowSet - 我们可以应用过滤条件,以便只有选定的数据可见。

27.ResultSet和RowSet有什么不同?

RowSet对象派生自ResultSet,因此它们具有ResultSet的所有功能以及一些附加功能。RowSet的一大好处是它们可以断开连接,使其轻量级,并且易于通过网络传输。

是否使用ResultSet或RowSet取决于您的要求,但如果您计划使用ResultSet更长的持续时间,则断开连接的RowSet是释放数据库资源的更好选择。

28.常见的JDBC异常有哪些?

一些常见的JDBC异常是:

java.sql.SQLException - 这是JDBC异常的基本异常类。java.sql.BatchUpdateException - 当Batch *** 作失败时抛出此异常,但它依赖于JDBC驱动程序是否抛出此异常或基本SQLException。java.sql.SQLWarning - 用于SQL *** 作中的警告消息。java.sql.DataTruncation - 当数据值因超出MaxFieldSize而被意外截断时。

29.JDBC中的CLOB和BLOB数据类型是什么?

字符大对象(CLOB)是由具有关联代码页的单字节字符组成的字符串。此数据类型适用于存储面向文本的信息,其中信息量可能超出常规VARCHAR数据类型的限制(上限为32K字节)。

二进制大对象(BLOB)是由字节组成的二进制字符串,没有关联的代码页。此数据类型可以存储大于VARBINARY(32K限制)的二进制数据。此数据类型适用于存储图像,语音,图形和其他类型的业务或特定于应用程序的数据。

30.什么是JDBC中的“脏读”?哪个隔离级别可防止脏读?

当我们处理事务时,有可能更新行,同时其他查询可以读取更新的值。这会导致脏读,因为更新后的值不是永久性的,已更新行的事务可以回滚到先前的值,从而导致无效数据。

隔离级别TRANSACTIONREADCOMMITTED,TRANSACTION_REPEATABLEREAD和TRANSACTIONSERIALIZABLE阻止了脏读。

31.什么是2阶段提交?

当我们在涉及多个数据库的分布式系统中工作时,我们需要使用2阶段提交协议。2阶段提交协议是分布式系统的原子承诺协议。在第一阶段,事务管理器向所有事务资源发送commit-request。如果所有事务资源都正常,则事务管理器将为所有资源提交事务更改。如果任何事务资源响应为Abort,则事务管理器可以回滚所有事务更改。

32.JDBC中有哪些不同类型的锁定?

从广义上讲,有两种类型的锁定机制可以防止数据损坏,因为多个用户使用相同的数据。

乐观锁定 - 使用代码实现此锁定。表中引入了一个额外的列以保持更新计数。当您选择该行时,您也会阅读此列,比如“version”。现在,当您尝试更新/删除行时,将在where子句中传递此“version”。因此,如果在其间执行其他线程的更新,则更新将失败。这是避免数据损坏的好方法,但如果有人错过更新其更新语句中的“version”,则可能容易出错。通过这种锁定方式,更新查询看起来如下所示。

mysql>update emp SET name = ‘David’, version = 5 WHERE id = 10 and version = 4

悲观锁定 - 将记录从选择锁定到读取,更新和提交阶段。这通常由数据库供应商软件完成,并通过使用SELECT FOR UPDATE查询触发。如果线程处理锁定较长时间,则这种锁定行的方式可能导致性能降低和死锁。

除此之外,一些DBMS系统提供锁定机制来锁定单行,表或数据库。

33.你对DDL和DML语句有什么了解?

数据定义语言(DDL)语句用于定义数据库模式。创建,更改,删除,截断,重命名语句属于DDL语句,通常它们不返回任何结果。

数据 *** 作语言(DML)语句用于 *** 纵数据库模式中的数据。选择,插入,更新,删除,调用等是DML语句的示例。

34.java.util.Date和java.sql.Date有什么区别?

java.util.Date包含有关日期和时间的信息,而java.sql.Date包含仅有关日期的信息,它没有时间信息。因此,如果您必须在数据库中保留时间信息,建议使用Timestamp或DateTime字段。

35.如何将图像或原始数据插入数据库?

我们可以使用BLOB将图像或原始二进制数据插入数据库。

36.什么是幻像读取以及哪种隔离级别阻止了它?

虚拟读取是指事务多次执行查询并获取不同数据的情况。假设事务正在执行查询以根据条件获取数据,然后另一个事务插入与条件匹配的行。现在,当同一事务再次执行查询时,新行将成为结果集的一部分。这个新行被称为Phantom Row,这种情况称为Phantom Read。

只有使用TRANSACTION_SERIALIZABLE隔离级别才能阻止幻像读取。

37.什么是SQL警告?如何在JDBC程序中检索SQL警告?

SQLWarning是SQLException的子类,我们可以通过在Connection,Statement和ResultSet对象上调用getWarnings()方法来检索它。SQL警告不会停止脚本的执行,但会警告用户警告。

38.如何使用数据库对象作为IN / OUT调用Oracle存储过程?

如果Oracle存储过程具有作为数据库对象的IN / OUT参数,那么我们需要在程序中创建相同大小的Object数组,然后使用它来创建Oracle STRUCT对象。然后我们可以通过调用setSTRUCT()方法为数据库对象设置此STRUCT对象并使用它。

39.我们什么时候得到java.sql.SQLException:找不到合适的驱动程序?

当SQL URL字符串格式不正确时,您将得到没有合适的驱动程序发现异常。您可以在使用DriverManager的简单Java应用程序或使用DataSource的JNDI资源中获取此异常。异常堆栈跟踪如下所示。

org

在调试此异常时,只需检查日志中打印的URL,如上面的日志中URL URL是’jdbc:mysql:// localhost:3306 / UserDB,而它应该是jdbc:mysql:// localhost:3306 / UserDB 。

40.JDBC有哪些最佳实践?

一些JDBC最佳实践是:

数据库资源很重,因此请确保在完成后立即关闭它。Connection,Statement,ResultSet和所有其他JDBC对象都定义了close()方法来关闭它们。

始终在代码中显式关闭结果集,语句和连接,因为如果您在连接池环境中工作,则可能会将连接返回到池,从而导致打开的结果集和语句对象导致资源泄漏。

关闭finally块中的资源,以确保即使在异常情况下它们也会关闭。

使用批处理进行类似的批量 *** 作。

始终对Statement使用PreparedStatement以避免SQL注入并获得PreparedStatement的预编译和缓存优势。

如果要将批量数据检索到结果集中,则为fetchSize设置最佳值有助于获得良好的性能。

数据库服务器可能不支持所有隔离级别,因此请在假设之前进行检查。

更严格的隔离级别会导致性能降低,因此请确保为数据库连接设置了最佳隔离级别。

如果要在Web应用程序中创建数据库连接,请尝试使用JNDI上下文使用JDBC DataSource资源来重用连接。

当您需要长时间使用ResultSet时,请尝试使用断开连接的RowSet。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/sjk/6760568.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-27
下一篇 2023-03-27

发表评论

登录后才能评论

评论列表(0条)

保存