分享
 
 
 

第十一讲 JDBC编程

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

第十一讲 JDBC编程

JDBC(Java data Base Connectivity)是Java语言为了支持SQL功能而提供的与数据库相联的用户接口,JDBC中包括了一组由Java语言书写的接口和类,它们都是独立于特定的DBMS,或者说它们可以和各种数据相关联。有了JDBC以后,程序员可以方便地在Java语言中使用SQL语言,从而使Java应用程序或Java applet可以实现对分布在网络上的各种关系数据库的访问。使用了JDBC以后,程序员可以将精力集中于上层的功能实现,而不必关心底层与具体的DBMS的连接和访问过程。

11.1 关系数据库简介

关系数据库系统的思想是由IBM公司的E.F.Codd于1970年率先提出的。

关系数据库支持的数据模型是关系模型。关系模型的基本条件是其关系模式中每个属性值必须是一个不可分割的数据量。简单地说,在关系数据库中数据结构一般是张两维表,这张两维表的每一列的值必须是不可分割的数据项,而任两行应互不相同。

关系模型的主要特点是:

⑴结构简单,表示力强,易于掌握。

⑵语言一体化,非过程化的操作,方便用户使用。

⑶有一定的数学理论作基础,利于进一步研究和发展。

⑷操作耗时,执行效率低。

关系数据库管理系统一般向用户提供数据检索、数据插入、数据删除、数据修改四种基本操作功能。

11.2 JDBC概述

JDBC由一组Java语言编写的类和接口组成,使用内嵌式的SQL,主要实现三方面的功能:建立与数据库的连接,执行SQL声明以及处理SQL执行结果。JDBC支持基本的SQL功能,使用它可方便地与不同的关系型数据库建立连接,进行相关操作,并无需再为不同的DBMS分别编写程序。下面我们先介绍JDBC与SQL及ODBC的关系,再介绍JDBC支持的两种模型,最后介绍JDBC的抽象接口和数据库驱动器Driver。

11.2.1 JDBC与SQL

尽管一般数据库系统在很大范围内支持SQL的语义语法,但它们在复杂的高层次功能如存储功能调用和外部连接等方面往往不尽一致。为了解决这一矛盾,JDBC采用以下的几种措施:

(1)JDBC允许使用从属于DBMS的系统的任何询问语句,这些询问语句都将被传送给后台的DBMS实际执行。这样增加了使用的灵活性,一个应用程序的询问可以不是SQL形式的,而是SQL的特定引出形式,如为特定的DBMS提供的文件或图片查询。这样做的风险则在某些DBMS中可以会出现错误。

(2)一般认为ANSI SQL Entry Level的功能比较完备,而且是被广泛支持的。因此为了使JDBC与SQL一致,要求用户使用至少ANSI SQL 92 Entry Level以上的版本,这样就给那些要求广泛的可携带性的应用程序提供了共同命名的保证。

(3)对较复杂的应用,JDBC提供了接口类DatabaseMetadata,用户可通过这个类获取相应DBMS的描述信息,再根据所得信息采取特定的查询操作方式。

11.2.2 JDBC与ODBC

Microsoft的ODBC(Open DataBase Connectivity)是当前与关系型数据库连接最常用的接口。JDBC是建立在ODBC的基础上的,实际上可视为ODBD的Java语言翻译形式。当然两者都是建立在X/Open SQL CLI(Call Level Interface)的抽象定义之上的。而JDBC与ODBC相比,在使用上更为方便。

既然ODBC已经是成型的通用接口,我们可在Java程序中直接使用ODBC却要建立JDBC接口,这样做的原因和好处包括以下几点:

(1)ODBC使用的是C语言界面,而从Java直接调用C源码容易在安全性、健壮性和可移植性等方面产生问题,运行功效也受到影响。

(2)将ODBC的C语言API逐字译为Java也并不理想。比如,Java没有指针,ODBC的一种面向对象式的翻译界面,对Java的程序员来说更为自然方便。

(3)ODBC难于学习掌握,经常将简单的特性与复杂的特性混合使用。而JDBC相对简单明了许多,容易理解掌握。

(4)JDBC有助于实现“纯Java“的方案。当使用ODBC时,每一台客户机都要求装入ODBC的驱动器和管理器。而当使用JDBC,驱动器完全由Java语言编写时,JDBC代码可以在所有的Java平台上自动装入、移植,而且是安全的。

当然,在JDBC也可以使用ODBC,但是要通过中介JDBC-ODBC Bridge使用。

11.2.3 JDBC支持的两种模型

在与数据库的连接操作中,JDBC支持两种不同的模型。这两川模型根据用户与数据库的关系层次不同,分别称为两模型和三层模型。

两层模型中,Java的应用程序(Applet或Application)直接与数据库联系。用户的SQL声明被提交给数据库,执行的结果回送给用户,如下图所示。这种模型具有客户机/服务器结构,用户的机器如同客户机,存放数据库的机器则如同服务器,连接两者的可以是局域网,也可以是广域网。

┌─────────┐

│JavaApplication │

│Java Applet │ Clinet Machine

├─────────┤

│JDBC│

└─────────┘

┌─────────┐

│DBMS│ Database Serve

└─────────┘

图11.1 JDBC支持的两层模型

在三层模型中,用户不直接与数据库联系。用户的命令首先发送给一个所谓“中间层”,中间层再将SQL声明发给DMBS。执行的结果也同样由中间层转交,如图11.2所示。三层模型的好处是,可以通过中间层保持对存取权限和公有数据允许修改类型的控制,便于安全管理。同时,用户可以使用一种较为友善的高层API,由中间层转化为恰当的低层命令,保证较好地运行功效。到目前为止,中间层多用C或C++语言编写。随着Java语言的逐步推广,将出现用Java编写的中间层,更好地利用它的健壮性、多线程,安全性等特点。

┌──────────┐

│Java Applet│

│HTML Browse│

└──────────┘

┌──────────┐

│Application Server │

│(Java)│

├──────────┤

│JDBC│

└──────────┘

┌──────────┐

│DBMS│

└──────────┘

图11.2 JDBC支持的三层模型

11.2.4 JDBC的抽象接口

JDBC中最重要的部分是定义了一系列的抽象接口,通过这些接口,JDBC实现了三个基本的功能:建立与数据的连接、执行SQL声明和处理执行结果。

这些接口都存在Java的sql包中,它们的名称和基本功能是:

*java.sql.DriverMagnager

管理驱动器,支持驱动器与数据连接的创建。

*java.sql.Connection

代表与某一数据库的连接,支持SQL声明的创建。

*java.sql.Statement

在连接中执行一静态的SQL声明并取得执行结果。

*java.sql.PreparedStatement

Statement的子类,代表预编译的SQL声明。

*java.sql.CallableStatement

Statement的子类,代表SQL的存储过程。

java.sql.ResultSet

代表执行SQL声明后产生的数据结果。

11.2.5 JDBC的数据库驱动器Driver

Java的应用程序员通过sql包中定义的一系列抽象类对数据库进行操作,而实现这些抽象类,实际完成操作,则是由数据库驱动器Driver运行的。它们之间的层次关系如图11.3所示

┌─────────┐

│Java Application │

└─────────┘

JDBC API ————————

┌─────────┐

│JDBC Manager │

└─────────┘

——————————————

┌─────┐┌───-─--─┐┌─────┐┌───────-┐

DJBC │JDBC-Net ││JDBC-ODBC | │Native-API││Native-Protocol |

Drivers│ Driver ││Bridge Driver││ Driver│ | Driver |

└─────┘└───-──--┘└─────┘└─────-──┘

↓↓↓↓

┌───────────────────────────────────┐

│D B M S |

───────────────────────────────────-┘

图11.3 JDBC Drivers

JDBC的Driver可分为以下四种类型:

(1)JDBC-ODBC Bridge和ODBC Driver

这种驱动器器通过ODBC驱动器提供数据库连接。使用这种驱动器,要求每一台客户机都装入ODBC的驱动器。

(2)Native-API partly-Java Driver

这种驱动器将JDBC指令转化成所连接使用的DBMS的操作形式。各客户机使用的数据库可能是Oracle,可能是Sybase,也可能是Access,都需要在客户机上装有相应DBMS的驱动程序。

(3)JDBC-Net All-Java Driver

这种驱动器将JDBC指令转化成独立于DBMS的网络协议形式,再由服务器转化为特定DBMS的协议形式。有关DBMS的协议由各数据库厂商决定。这种驱动器可以联接到不同的数据库上,最为灵活。目前一些厂商已经开始添加JDBC的这种驱动器到他们已有的数据库中介产品中。要注意的是,为了支持广域网存取,需要增加有关安全性的措施,如防火墙等等。

(4)Native-protocol All-Java Driver

这种驱动器将JDBC指令转化成网络协议后不再转换,由DBMS直接使用。相当于客户机直接与服务器联系,对局域网适用。

在这四种驱动器中,后两类“纯Java”(All-Java)的驱动器效率更高,也更具有通用性。但目前第一、第二类驱动器比较容易获得,使用也较普遍。

16.3 JDBC编程

11.3.1 程序基本结构

一般的JDBC程序都完成三项功能:与数据库建立连接;传送SQL 声明以及对返回结果进行处理。下面我们通过一个具体例子说明这三项功能的实现过程。

例11.1 Creage.java给出了一个简单的JDBC程序,此程序执行后创建一张名为testTable的表,表中包括两个域,域名分别为id和name。

1: import java.net.URL;

2: import java.sql.*;

3:

4: class Create{

5: public static void main (String[] args){

6: String url="jdbc:odbc:demo";

7: String query="CREATE TABLE testTable" + "(id INT,name CHAR(10))";

8:

9: try{

10: //下载jdbc-odbc bridge 驱动器

11: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");//关于此句参见下面Cyclone的注释

//与驱动器建立连接 ,这里可使用不同的特定驱动,如jdbcfororacle或jdbcforsqlserver

14: Connection con=DriverManager.getConnection(url,"user","password");

15:

16: //创建一个Statement对象

17: Statement stmt=con.createStatement();

18:

19: //执行SQL声明

20: stmt.executeUpdate(query);

21: System.out.println("Create successfully!");

22:

23: //关闭 stm

24: stmt.close();

25:

26: //关闭连接

27: con.close();

28: }catch(SQLException ex){

//SQL异常信息

29:System.out.println("\n***SQLException caught ***\n");

30: while(ex!=null){

31:System.out.println("SQLState:"+ex.getSQLState());

32:System.out.println("Message:"+ex.getMessage());

33:System.out.println("Vendor:"+ex.getErrorCode());

34:ex=ex.getNextException();

35:System.out.println("");

}

36:}catch(java.lang.Exception ex){

37:ex.printStackTrace();

38:}

39:}

40:}

11.3.2 Statement类及其子类

1)Statement接口

Statement stmt=con.createStatement();

//执行SQL声明

int count1=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(1,'wu')");

int count2=stmt.executeUpdate("INSERT INTO testTable(id,name) VALUES(2,'wang')");

2)PreparedStatement接口

String data[][]={{"5","xu"},{"6","yan"}};

PreparedStatement pstmt=con.prepareStatement("INSERT INTO testTable (id,name) VALUES(?,?)");

//参数赋值,执行SQL声明

for (int i=0;i<data.length;i++){

pstmt.setInt(1,Integer.parseInt(data[i][0]));

pstmt.setString(2,data[i][1]);

pstmt.executeUpdate();

}

3)CallableStatement接口

CallableStatement cstmt=con.prepareCall("{call Search(?)}");

//参数赋值

cstmt.setInt(1,934678);

//执行储存过程。

cstmt.execute();

11.3.3 结果集ResultSet及ResultSetMetaData

String query = "SELECT * FROM testTable";

Statement stmt=con.createStatement();

//发出查询要求,获得结果集

ResultSet rs=stmt.executeQuery(query);

//显示结果集各行各列

System.out.println("The detail of testTable is:");

ResultSetMetaData rsmd=rs.getMetaData();

//获得结果集列数

in numCols=rsmd.getColumnCount();

//显示列标题

for(int i=1;i<=numCols;i++)

{

if(i>1) System.out.print(",");

System.out.print(rsmd.getColumnLabel(i));

}

System.out.println("");

//显示结果集信息

while(rs.next())

{

//显示一行

for(int i=1;i<=numCols;i++)

{

if(i>1) System.out.print(",");

System.out.print(rs.getString(i));

}

11.3.4 DatabaseMetaData

DatabaseMetaData dma = con.getMetaData();

//驱动器和URL信息

System.out.println("\nConnected to" + dma.getURL());

System.out.println("Driver" + dma.getDriverName());

System.out.println("Version" + dma.getDriverVersion());

//数据库信息

System.out.println("\nDataBase name:" + dma.getDatabaseProductName()+dma.getDatabaseProductVersion());

System.out.println("DataBase supports SQL keywords:\n\t" + dma.getSQLKeywords());

//数据库功能信息函数

dma.supportsANSI92EntryLevelSQL()

dma.supportsANSI92FullSQL()

dma.supportsStoredProcedures()

11.3.5 JDBC数据类型及类型转换

一、JDBC的数据类型

JDBC的sql包中除了与数据库连接有关的抽象接口及与驱动器有关的DriverManager、DriverPropertyInfo等类型外,还定义了若干数据类,用以代表数据库中可能用到的SQL类型。下面我们就对它们逐一进行简略介绍。

1、sql.Date

sql包中的日期类Date是util包中Date类的子类,实际上也是util.Date类的子集。它只处理年月日,而忽略小时和分秒,用以代表SQL的DATE信息。

Date类的构造方法为:

public Date(int year, int mouth, int day)

其中参数格式同util.Date类的构造方法一样,年参数为所需设定的年份减去1900所得的整数值,月参数为0至11,日参数为1至31。如1998年1月23日所对应创建日期类的方法调用为:

Date d=new Date(98,0,23);

Date类还提供两个与String类互相转换的方法,分别是:

public static Date valueOf(String s)

将字符串类参数转换为日期类对象。其中String类参数S的格式为“年-月-日”,加“1997-04-12”。

public String toString()

将日期类对象转换为String类对象表示,同样采用“年-月-日”的格式。

2、sql.Time

该类是util.Date类的子类,也是它的一个子集。在Time类里,只处理小时和分秒,代表SQL的TIME类型。它与sql.Date合起来才表示完整的util.Date类信息。

Time类的构造方法为:

public Time(int hour,int minute,int second)

其中小时参数值为0至23,分秒参数取值均为0至59。

与sql.Date一样,Time类也定义了两个与String类互相转换的函数ValueOf和String。不同的是String类对象的格式为“小时:分:秒”,如“12:26:06”。

3、sql.Timestamp

这个类也是util.Date类的子类,其中除了包含年月日、小时和分秒和信息之外,还加入了纳秒信息(nanosecond),1纳秒即1毫微秒。Timestamp类用来代表SQL时间戳(Timestamp)类型信息。

Timestamp类的构造方法为:

public Timestamp(int year, int mouth, int date, int hour, int minute, int second, int nano)其中纳秒参数的取值从0至999,999,999,其余各参数同前。

Timestamp类特别定义了设置和获得纳秒信息的方法,分别是

public getnanos()

获取时间戳的纳秒部分

public void setNanos(int n)

以给定数值设置时间戳的纳秒部分

4、sql.Types

Types类是Object类的直接子类。在这个类中以静态常量的形式定义了可使用的SQL的数值类型。所有这些类型常量都以前缀

public final static int

的形式标明是公有静态整数,且不可改动。具体的类型名和含义如表11.1所示。其中OTHER用来代表数据库定义的特殊数据,可以用getObject或setObject方法将其映射为一个Java的Object对象。

表11.1 Types中定义的SQL类型

类型名

含义

BIGINT

长整型数

BINARY

二进制数

BIT

比特数

CHAR

字符型

DATE

日期型

DECIMAL

十进制数

DOUBLE

双精度数

FLOAT

浮点数

INTEGER

整数

LONGVARBINARY

可变长型二进制数

LONGVARCHAR

可变长型字符

NULL

空类型

NUMERIC

数值型

OTHER

其他类型

REAL

实数

SMALLINT

短整型

TIME

时间类型

TIMESTAMP

时间戳类型

TINYINT

微整型

VARBINARY

可变二进制数

VARCHAR

可变字符型

二、SQL与Java

由于SQL数据类型与Java的数据类型不一致,因而在使用Java类型的应用程序与使用SQL类型的数据库之间,需要某种读写类型转换机制。实际上我们前面介绍的ResultSet类的“get”系列方法,Statement及其子类的“set“系列方法和registerOutParameter方法,都是这一转换机制的组成部分。

需要进行的读写转换包括三种情况:

第一种情况是从数据库中读取数值后,存放在ResultSet对象中的是SQL类型的数据。而调用“get”系列方法时,JDBC才将SQL类型转换为指定的Java类型。在一般情形下,SQL类型相对应的Java类型如表11-2所示。

表11.2 SQL类型一般所对应的Java类型

SQL type

Java type

CHAR

java.lang.String

VARCHAR

java.lang.String

LONGVARCHAR

java.lang.String

NUMERIC

java.lang.Bignum

DECIMAL

java.lang.Bignum

BIT

boolean

TINYINT

byte

SMALLINT

short

INTEGER

int

BIGINT

long

REAL

float

FLOAT

double

DOUBLE

double

BINARY

byte[]

VARBINARY

byte[]

LONGVARBINARY

byte[]

DATE

java.sql.Date

TIME

java.sql.Time

TIMESTAMP

java.sql.Timestamp

当然,在使用时用户可以指定将SQL类型转换为某个需要的特定类型而不遵循表11.2。例如在结果集中的某个FLOAT型数值,依标准转换应用使用getDouble方法获取,但实际上按用户的不同需求也可以使用getFloat,getInt,甚至gefByte方法获取,但只是有可能影响数值精确度。表11.3列出了对每一SQL类型可用以获取“get”方法的清单,其中“+”表示可以使用该方法,“*”表示最好使用该方法。

表11.3 获取SQL类型使用的“get”方法

\SQL

\ 类型名

get方法名\

T

I

N

Y

I

N

T

S

M

A

L

L

I

N

T

I

N

T

E

G

E

R

B

I

G

I

N

T

R

E

A

L

F

L

O

A

T

D

O

U

B

L

E

D

E

C

I

M

A

L

N

U

M

E

R

I

C

B

I

T

C

H

A

R

V

A

R

C

H

A

R

L

O

N

G

V

A

R

C

H

A

R

B

I

N

A

R

Y

V

A

R

B

I

N

A

R

Y

L

O

N

G

V

A

R

B

I

N

A

R

Y

D

A

T

E

T

I

M

E

T

I

M

E

S

T

A

M

P

getByte

*

+

+

+

+

+

+

+

+

+

+

+

+

getShort

+

*

+

+

+

+

+

+

+

+

+

+

+

getInt

+

+

*

+

+

+

+

+

+

+

+

+

+

getLong

+

+

+

*

+

+

+

+

+

+

+

+

+

getFloat

+

+

+

+

*

+

+

+

+

+

+

+

+

getDouble

+

+

+

+

+

*

*

+

+

+

+

+

+

getBignum

+

+

+

+

+

+

+

*

*

+

+

+

+

getBoolean

+

+

+

+

+

+

+

+

+

*

+

+

+

getString

+

+

+

+

+

+

+

+

+

+

*

*

+

+

+

+

+

+

+

getBytes

*

*

+

getDate

+

+

+

*

+

getTime

+

+

+

*

+

getTimestamp

+

+

+

+

*

getAsciiStream

+

+

*

+

+

+

getUnicodeStream

+

+

*

+

+

+

getBinaryStream

+

+

*

getObject

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

+

注: “+”表示允许使用;“*”表示推荐使用

第二种情形是当用户发出的SQL操作通过PrepareStatement和CallableStatement执行,带有向数据库输入的参数时,需使用这些声明类的“set”系列方法。例如对PrepareStatement类对象pstmt ,调用方法

pstmt.setLong(1,2222222)

驱动器将自动把2222222转换为SQL的BIGINT类型数据,发往数据库。表16-4给出在这一情形下Java数据转换为SQL数据的标准。

表11.4 Java类型所对应转换的SQL类型

Java type

SQL type

java.lang.String

VARCHAR or LONGVARCHAR

java.lang.Bignum

NUMERIC

boolean

BIT

byte

TINYINT

short

SMALLINT

int

INTEGER

long

BIGINT

float

REAL

double

DOUBLE

byte[]

VARBINARY or LONGVARBINARY

java.sql.Date

DATE

java.sql.Time

TIME

java.sql.Timestamp

TIMESTAMP

最后一种情形是在使用CallableStatement时,用户可能用到INOUT参数。这时的转换过程最为复杂。首先,使用“set”系列方法给这些参数赋值,驱动器使用表11.4所示的标准映射将Java类型数据换为SQL类型,再发往数据库。其次,使用CallableStatement的registerOutParameter方法,为每一个参数登记其作为返回值时的SQL类型。这一登记类型必须是在sql.Types中定义的名称,如表11.1所示。最后在执行完毕后使用“get”方法取回参数的结果值。

下面的程序片段给出了一次INOUT参数从赋值到取值的完整转换过程,其中cstmt是CallableStatement的实例对象,有两个INOUT参数:

cstmt.setByte(1,25);//参数赋值

cstmt.setLong(2,4678935);

cstmt.registerOutParameter(1,java.sql.Types.TINYINT);

cstmt.registerOutParameter(2,java.sql.Type.BIGINT);//返回类型类型登记

ResultSet rs=cstmt.executeUpdate();

while(rs.next()){...//处理结果}

byte x=cstmt.getByte(1);//取回参数输出值;

long l=cstmt.getLong(2);

我们可以写出这两个INOUT参数的类型转换流程如下所示:

setByteexecuteUpdategetByte

↓↓↓

参数1 byte———→TINYINT————→TINYINT———→byte

setLongexecuteUpdategetLong

↓↓↓

参数2 long———→BIGINT————→BIGINT———→long

图11.12参数转换流程图

最后要提醒用户注意的是,INOUT参数的“get”方法和“set”方法的类型应当是一致的,如例中参数1的setByte和getByte方法。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有