java castor_Castor功能与应用参考四

索引

1.Castor JDO概述

Castor JDO是一个纯Java的对象关系(O/R)映射和数据绑定框架。Castor使用用户定义的映射信息在关系数据库和Java对象之间建立起映射转换关系。从另一个层面说,Castor JDO为Java对象提供了一个数据持久层。

Castor JDO对于Java对象的操作被自动转换为对数据库的查询,更新,插入和删除操作。在事务边界中,对于Java对象的修改,在执行提交(commit)操作时自动提交给数据源。因此, Java开发人员在构建了数据库表和映射信息后,视角的重心从数据库操作层面转移到Java对象的操作上。Castor JDO将数据的序列化和业务逻辑分离为更好的进行面向对象的设计提供了支持,这在大型项目中特别重要。

Castor JDO采用类似于Castor XML样式的Mapping映射文件。下文我们将详述Castor JDO的概念与应用。

2.功能特性

ØCastor JDO是一个事务框架。当数据对象被Castor加载后,Castor会对该数据对象进行锁定并对其他事务隔离。Castor通过xa.Synchronzation支持完整的两阶段事务提交。Castor提供数据对象的“shared”、“exclusive”、“database locked”和“read-only”等多种数据读写策略。

n共享读(shared),Castor默认的数据锁定策略,在多个事务查询同一数据对象,更新操作作用于各自的对象拷贝时适用。

n排斥读(exclusive),Castor提供一个内存锁,多个事务在同一数据对象上执行更新操作时,以竞争的方式获取内存锁来完成对象的更新操作。

n数据库锁(dabase locked),此策略应用于多个应用程序同时访问数据库数据的场景,如果其他应用程序使用Castor以外的其他方式访问数据库,则考虑使用该策略。

n只读(read-only),不对数据做任何锁定。使用该策略的所有数据对象都不加锁,这些对象也不参与事务提交和回滚。

Ø支持“长时间事务(long transactions)”,通过内置的数据“脏检查(dirty-checking)”防止用户在初始事务中对数据变更后被重复修改,从而允许用户在一个事务内读取对象,修改更新,然后在第二个事务中提交。通过自动的数据“脏检查(dirty-checking)”和死锁侦测,Castor可以确保数据的一致性并减少不必要的数据库更新操作。

Ø支持OMG(Object Management Group)3.0对象查询语言规范OQL的一个子集用来与数据库进行交互。OQL与SQL类似,但其操作作用于Java对象而不是数据库表。

Ø实现了一个数据缓存池来减少数据库的频繁访问,同时提供了几种可选的LRU类型的缓存策略。

Ø支持一对一,一对多,多对多等多种数据库关系,支持对象和数据库记录的继承。Castor区分对象数据生命周期中的关联(如联合)和依赖(如聚合)关系,在各自对象的生命周期中,自动创建和删除依赖的对象数据。

Ø支持数据库表多主键,并提供多种主键生成器。

Ø自动处理包含Java集合类型的对象的存储,包括Vector,Hashtable,Collection,Set和Map。通过“延迟加载”减少不必要的数据库访问。延迟加载可以在每一个集合类型的字段上设定。

Ø支持Java对象类型与数据库类型间的自动转换。

ØCastor JDO在同一个应用中使用多个类加载器,使得其可以在EJB容器或Servlet容器中使用。

ØCastor XML框架和Castor JDO可以无缝集成使用,同时处理O/R映射和Java对象-XML 数据绑定。

ØCastor JDO支持以下数据库系统:

nDB2

nDerby

nGeneric DBMS

nHypersonic SQL

nInformix

nInstantDB

nInterbase

nMySQL

nOracle (8.1.x,9.x,10g)

nPostgreSQL

nProgress

nSAP DB / MaxDB

nSQLServer

nSybase

用户可以针对特定数据库提供自己的JDBC驱动接口实现插入到Castor中,前提是该数据库支持JDBC2.0,多结果集(ResultSet)和事务。

3.简单的示例

下面我们以一个简单的示例来展示Castor JDO使用过程中的一些关键要素。类似于Castor XML,Castor JDO中用户需要提供数据实体的Java类定义,Java类模型与数据库表结构的映射信息。此外,在多数情况下用户还要提供数据库配置文件。在Castor XML中,用户使用Marshaller和Unmarshaller编组和解编对象/XML数据,在Castor JDO中,用户使用JDOManager持久化和读取Java对象/关系数据。

首先,我们定义两个Java类:产品分类Catalogue和产品Product。每一产品分类可以存在多种产品。

public class Catalogue {

private long id;

private List products = new ArrayList();

public long getId() { ... }

public void setId(long id) { ... }

public String getProducts() { ... }

public void setProducts(List products) { ... }

}

public class Product {

private long id;

private String description;

public long getId() { ... }

public void setId(long id) { ... }

public String getDescription() { ... }

public void setDescription(String description) { ... }

}

为了实现上述Java对象的持久化操作,如查询产品,从一种产品分类中删除产品等,我们需要定义Java类与数据库表及列之间的映射关系。Castor JDO Mapping文件与Castor XMLMapping文件具有相同的结构,如下所示:

定义了Castor JDO Mapping文件后,我们即可以使用Castor JDOManager实现Java对象与数据库表记录之间的互操作。在此之前我们先看一下Castor JDO Configuraton文件,在此文件中定义了数据库连接配置信息。Castor JDO配置文件不是必须的,Castor JDO提供了使用API直接设定数据连接参数。

在下述代码中,Castor JDO读取数据库配置信息并构造一个名为“sample”的JDOManager实例;然后通过getDatabase()方法获取与数据库的连接,从数据库中获取一个产品分类的ID(主键)值为“1”的记录并加载成一个产品分类对象,最后提交并关闭与数据库的连接。

JDOManager.loadConfiguration("jdo-conf.xml");

JDOManager jdoManager = JDOmanager.createInstance("sample");

Database database = jdoManager.getDatabase();

database.begin();

Catalogue catalogue = database.load(catalogue.class, new Long(1));

database.commit();

database.close();

4.JDO操作详述

4.1连接数据库

Castor JDO支持独立应用和J2EE应用两种运行环境。独立应用需要配置数据库连接并显式管理事务,J2EE应用使用JNDI获取一个预先配置的数据库连接并使用UserTransaction或容器控制的事务(CMT)来处理事务。

4.1.1JDO用于独立应用

在独立的Java应用中,用户需要自己定义JDO数据配置信息,控制事务及其调用。JDO数据库配置信息使用一个独立的XML配置文件定义,并在其中指明映射文件。用户也可以使用org.exolab.castor.jdo.util.JDOConfFactory类在程序代码中设定数据库配置。关于JDO数据库的详细配置请参考后续文档。

org.exolab.castor.jdo.JDOManager设定要构造数据库实例名称及其属性,用来与数据库建立连接。JDOManager的构造过程分为两步:

Ø使用JDOManager的静态方法loadConfiguration()加载数据库配置信息。

Ø使用静态方法createInstance(java.lang.String)方法构造一个JDOManager实例,createInstance()方法接收一个String类型的参数,用于指明第一步加载的数据库配置信息中定义的数据库名称。

Castor JDO使用org.exolab.castor.jdo.Database对象表示与数据库的一个连接。Database实例是非线程安全的,用户不应该在并发线程中使用。同时构造多个Database对象实例的开销很小,Castor JDO在每一个开放的事务中获取一个连接。

下面的代码为名称为“mydb”数据库构造一个JDOManager实例, 打开一个数据库连接,开始一个事务,然后关闭数据库连接。实际使用中,用户应该进行必要的异常处理。

JDOManager jdoManager;

Database db;

//加载一个JDO数据库配置文件,并为名称为'mydb'的数据库构造一个JDOManager实例

JDOManager.loadConfiguration("jdo-conf.xml");

jdoManager = JDOManager.createInstance("mydb");

//获取一个数据库连接

Database db = jdoManager.getDatabase();

//开始一个事务

db.begin();

//处理某些操作

. . .

//提交事务并关闭数据库连接

db.commit();

db.close();

4.1.2JDO用于J2EE Application

J2EE应用程序依赖于J2EE容器来配置数据库连接并使用JNDI查找配置。Castor JDO在J2EE应用系统中不希望直接构造JDOManager实例用JNDI对其进行查找,而是推荐用户将JDOManager实例添加到java:comp/enb/jdo名称空间下,以遵循查找JDBC资源的惯例。下述代码展示了用JNDI查找一个Database实例并使用JTA UserTransaction接口来控制处理事务:

InitialContext ctx;

UserTransaction ut;

Database        db;

// Lookup databse in JNDI

ctx = new InitialContext();

db = (Database) ctx.lookup( "java:comp/env/jdo/mydb" );

// Begin a transaction

ut = (UserTransaction) ctx.lookup( "java:comp/UserTransaction" );

ut.begin();

// Do something

. . .

// Commit the transaction, close database

ut.commit();

db.close();

对于EJB实体Bean,如果事务是由容器控制控制处理,则没有必要显式的调用begin/commit方法,这一切都由应用服务器来控制开始一个事务,并在合适的时候执行commit/rollback方法。下面的代码展示了容器控制下的事务:

InitialContext ctx;

UserTransaction ut;

Database        db;

// Lookup databse in JNDI

ctx = new InitialContext();

db = (Database) ctx.lookup( "java:comp/env/jdo/mydb" );

// Do something

. . .

// Close the database

db.close();

4.2使用JDO Database执行持久化操作

4.2.1暂存对象与对持久化象

所有的JDO操作都在一个事务上下文之内。JDO将数据从数据库加载到内存对象,允许对对象进行修改,然后在事务提交时将对象的新状态持久化到数据库。所有的对象可以分为两种状态:暂存(transient)和持久化(persistent)。

Ø暂存(transient):当事务提交时,不需要将对象信息持久化到数据库中的对象。对暂存对象的修改不会在数据库中体现。

Ø持久化(persistent):当事务提交时,需要将对象信息持久化到数据库中的对象。对持久化对象的修改将会在数据库中体现。

在两种情况下,一个对象要成为可持久化的:该对象是一次查询的结果(而且该查询不是在read-only策略下执行的)或者该对象是使用JDO Database实例的create(java.lang.Object)或update(java.lang.Object)方法添加到数据库的。所有不可持久化的对象都是暂存对象。当事务提交或回滚后,所有可持久化对象都变为暂存对象。

在独立Java应用中,使用begin(),commit()和rollback()方法来控制事务。如果一个持久化对象在事务内部被修改,在提交阶段所有的修改会更新到数据库。如果事务回滚,对数据库数据所做的所有更改将撤销。当事务完成后,该对象由持久化转入暂存状态,因此,如果在两个不同的事务中使用同一个对象,必须再一次执行查询。

对象的持久化与暂存状态是相对于事务所属的JDO Database实例来说的。如果一个对象在某一JDO Database实例中是可持久化的,在另一个JDO Database实例中调用方法isPersistent(java.lang.Object)时返回false。将一个对象持久化到两个数据库中可以通过在一个数据库中查询并在另一个数据库中重新构建来实现。

4.2.2执行OQL查询

OQL查询用于从数据库中查询对象。OQL查询类似于SQL查询,但在查询语句中不是使用SQL名称而是对象名称,而且OQL不需要联合查询(join)语句。例如,如果对象以TestObject类型加载,则OQL查询使用“FROM TestObject”语句而不用关心该对象所对应的数据库表名称为”test”,”test_object”或其他。在SQL中,如果需要查找关联表必须使用SQL的关联查询来实现;在OQL中,加载关联对象时Castor会自动完成关联查询。下述代码使用OQL从产品(Product)数据表中查询属于某一组的所有对象。注意:产品和产品组是关联对象,使用JDBC查询时需要使用join联合查询:

OQLQuery     oql;

QueryResults results;

// Explicitly begin transaction

db.begin();

// Construct a new query and bind its parameters

oql = db.getOQLQuery("SELECT p FROM Product p WHERE Group=$1");

oql.bind(groupId);

// Retrieve results and print each one

results = oql.execute();

while (results.hasMore()) {

System.out.println(results.next());

}

// Explicitly close the QueryResults

results.close();

// Explicitly close the OQLQuery

oql.close();

// Explicitly commit transaction

db.commit();

db.close();

下述代码使用上面代码的查询获取所有的产品,并修改这些产品的价格减少到原有价格的25%然后重新写入数据库。

OQLQuery     oql;

QueryResults results;

// Explicitly begin transaction

db.begin();

// Construct a new query and bind its parameters

oql = db.getOQLQuery("SELECT p FROM Product p WHERE Group=$1");

oql.bind(groupId);

// Retrieve results and mark up each one by 25%

Product prod;

while (results.hasMore()) {

prod = (Product) results.next();

prod.markDown(0.25);

prod.setOnSale(true);

}

// Explicitly close the QueryResults

results.close();

// Explicitly close the OQLQuery

oql.close();

// Explicitly commit transaction

db.commit();

db.close();

正如上述代码所示,在Castor JDO中,一次查询分为三个步骤。首先使用OQL语句从数据库中查询对象;然后,如果存在任何查询参数,第二步是绑定这些参数,有限的参数按照其顺序依次绑定(如$1为第一个参数,$2为第二个参数,依次类推)。第三步是执行查询并获得类型为org.exolab.castor.jdo.QueryResult的查询结果。

一个查询在创建后可以执行多次。每次执行后,该查询所绑定的参数将会丢失必须再次进行绑定。查询的结果可以在执行新一次查询后继续使用。Castor JDO也支持调用数据库存储过程:

oql = db.getOQLQuery("CALL sp_something($) AS myapp.Product");

上面的语句中sp_something是一个存储过程并返回一个或多个ResultSets,这些结果集的返回顺序与Castor生成的SELECT语句“SELECT p FROM myapp.Product p”相同。对于没有关系的对象,返回顺序为:主键,然后按Mapping映射文件中指定的字段顺序排序。

4.2.3创建持久化对象

JDO Database类中的create(java.lang.Object)实例方法用于在数据库中构建一个新的对象,以JDO术语来说是将暂存对象持久化。使用create()方法构造的对象在事务提交后仍存在于数据库中;如果事务回滚则该对象将从数据库中删除。如果在数据库中创建具有相同主键的对象将会抛出异常。下述代码使用前述查询的产品组创建一个新的产品:

Database db = ...;

db.begin();

//load product group

ProductGroup furnitures = db.load(...);

// Create the Product object

Product prod;

prod = new Product();

prod.setSku(5678);

prod.setName("Plastic Chair");

prod.setPrice(55.0 );

prod.setGroup(furnitures);

// Make it persistent

db.create(prod);

db.commit();

4.2.4删除持久化对象

JDO Database类中的remove(java.lang.Object)方法从数据库中删除持久化数据对象。如果一个对象被删除,则该对象对所有的事务都不可见。如果事务提交,该对象将从数据库中彻底删除;如果事务回滚,则该对象仍然存在于数据库中。当尝试删除一个非持久化的对象时将抛出异常。下面的代码删除前述构建的产品实例:

Database db = ...;

db.begin();

// load the Product instance with sku = 5678

Product prod = db.load (Product.class, new Integer(5678);

// delete the Product instance

db.create(prod);

db.commit();

4.2.5更新持久化对象

在org.exolab.castor.jdo.Database类中没有特定的方法用于更新现有的持久化对象。更新持久化对象的过程可以通过加载需要更新的对象,修改其属性然后提交事务实现。Castor JDO会自动确定要提交的对象是否存在变更,如果存在则将变更持久化到数据库。下述代码加载前述创建的产品实例,然后变更其产品描述信息,最后提交事务将变更持久化回数据库。

Database db = ...;

db.begin();

// load the Product instance with sku = 5678

Product prod = db.load (Product.class, new Integer(5678);

// change the object properties

prod.setDescription("New plastic chair");

//commit the transaction

db.commit();

4.3同时使用JDO和XML

Castor JDO与Castor XML的结合可以使XML作为数据库数据的输入和输出形式。下面的代码用持久化和暂存对象描述了一次账户转存操作。在这次金融操作中存在两个账户对象,操作将一个账户的金额部分转入另一个账户。示例中使用Transfer描述一个暂存的对象,该对象在数据库中并没有对应的记录。操作完成后使用Castor XML Marshaller将这次账户转存操作的描述用XML文档输出。

Transfer trans;

Account from;

Account to;

OQLQuery oql;

trans = new Transfer();

// Construct a query and load the two accounts

oql = db.getOQLQuery("SELECT a FROM Account a WHERE Id=___FCKpd___169quot;);

oql.bind(fromId);

from = oql.execute().nextElement();

oql.bind(toId);

to = oql.execute().nextElement();

// Move money from one account to the other

if (from.getBalance() >= amount) {

from.decBalance(amount);

to.incBalance(amount);

trans.setStatus(Transfer.COMPLETE);

trans.setAccount(from);

trans.setAmount(amount);

} else {

// Report an overdraft

trans.setStatus( Transfer.OVERDRAFT );

}

// Produce an XML describing the transfer

Marshaller.marshal(trans, outputStream);

上述代码生成的XML文档如下所示:

Completed

5.JDO数据库配置文件

默认方式下,Castor JDO访问一个特定的数据库时使用数据库配置文件获取配置信息。该配置文件详细描述了从某一数据库服务器上获取一个连接的信息以及在该数据库服务器上的数据表与Java类之间的映射信息。用户也可以使用JDOConfFactory来编程设定数据库配置。

5.1JDO数据库配置Schema定义

Castor JDO数据库配置XML Schema定义如下:

targetNamespace="http://castor.org/JDO"

xmlns="http://castor.org/JDO">

Castor名称空间 URI:http://castor.org/。用户如果希望校验JDO数据库配置文件,使用如下设定:

/p>

"http://castor.org/jdo-conf.xsd">

Castor JDO数据库配置XML文档举例如下:

J2EE配置方式举例:

如上例所示,Castor JDO使用数据库配置文件访问特定数据库,在配置中节点的name属性指定了访问该数据库的名称,其子节点指定了该数据库上的对象关系映射信息.engine属性指定了数据库服务的类型。不同的数据库系统Castor为engine属性定义了不同的值,Castor支持的数据类型的engine名称如下表所示:

Engine名称

数据库类型

Engine名称

数据库类型

db2

DB2

mysql

MySQL

derby

Derby

oracle

Oracle7-Oracle 10g

generic

Generic JDBC Support

postgresql

PostgreSQL

hsql

Hypersonic SQL

sapdb

SAP DB/MaxDB

informix

Informix

Sql-server

Microsoft SQL Server

instantdb

InstantDB

sybase

Sybase 11

interbase

Interbase

pointbase

Borland Pointbase

progress

Progress RDBMS

注意:Castor不支持Sun的JDBC-ODBC桥,因此不支持MS Access。

5.2JDO数据库配置概述

JDO数据库配置中存在三种方式设定数据库连接:设定JDBC驱动的URL;设定JDBC DataSource数据源;以JNDI方式查找JDBC数据源。Castor应用在J2EE应用服务器中时,推荐使用JNDI查找(参考jndi节点),让应用服务器管理连接池和分布式事务。

同一database配置中允许指明多个数据映射定义文件,两个数据库配置可以共享同一个数据映射定义文件。考虑到并发和数据一致性的原因,两个数据库配置不应该使用重叠的映射文件。理想情况是每一个数据库服务器使用一个数据库配置。

数据映射定义文件使用URL指定路径,通常情况是使用file:URL。如果数据库配置文件与映射定义文件在同一个目录下,则可以使用相对路径URL。相对路径URL在数据库配置文件和数据映射定义文件从JAR文件获取且位于相同的类路径(ClassPath)下时也可以使用。

节点指定从数据库服务器获取一个新连接所使用的JDBC驱动类型(class-name)和位置(url),其他属性设定使用节点。

下面是Oracle8i JDBC驱动(type 4)的配置示例:

url="jdbc:oracle:thin:@host:port:SID">

...

...

节点指以JDBC 数据源的方式获取数据库连接。DataSource在JDBC2.0规范中定义。class-name属性指定JDBC数据源的实现类全名称。数据源的属性配置使用类似Java Bean的getter/setter方法由节点配置设定。下面是PostgreSQL 7.1 DataSource配置示例:

...

...

节点指定使用JNDI查找方式获取JDBC数据源实例。下面是J2EE数据源设定的示例:

...

5.3事务声明

Castor在使用JDO操作数据库数据时,使用JDO配置文件配置事务声明。节点的mode属性值可以设置为local或者global,分别代表着两种事务处理模式。

5.3.1本地事务(Local Mode)

当Castor JDO在独立的Java应用中使用时,需要Castor控制事务的声明,节点的配置如下:

5.3.2全局事务(Global Mode)

当Castor运行于J2EE应用服务器时,如果使用全局事务(XA事务),节点的配置如下:

在此模式下, 节点用于确定J2EE容器用于控制这些事务的事务管理器。Castor支持的事务管理器如下表所示:

管理器名称

jndi

TM looked up in the JNDI ENC

websphere

IBM WebSphere 4 and previous releases

websphere5

IBM WebSphere 5

websphere51

IBM WebSphere 5.1

jotm

JOTM

除了设定事务管理其名称外,可以在节点内添加子节点,用于设置事务管理器的名称/键值对属性。注意:目前只有JNDI事务管理器支持此种属性设定。在次上下文中,使用jndiEnc属性可以指定使用那种JNDI ENC来查找事务管理器。配置示例如下:

5.4不同数据库的配置示例

...

...

...

...

...

url="jdbc:oracle:thin:@host:post:SID">

...

...

url="jdbc:mysql:/localhost:2206/test">

...

...

url="jdbc:idb:C:\\castor-1.0\\db\\test\\test.prp">

...

5.5以编程方式配置Castor JDO

很多应用系统使用不同的用户账户来连接数据库,为实现此种需求,Castor提供了帮助类JDOConfFactory和JDOManager.loadConfiguration(org.exolab.castor.jdo.conf.JdoConf)方法。下面的代码示例展示了在不使用数据库XML配置文件的情况下设定JDO数据库配置:

private static final String DRIVER = "oracle.jdbc.driver.OracleDriver";

private static final String CONNECT = "jdbc:oracle:thin:localhost:1521:SID";

private static final String USERNAME = "scott";

private static final String PASSWORD = "tiger";

private static final String MAPPING = "mapping.xml";

private static final String DATABASE = "mydb";

private static final String ENGINE = "oracle";

// create driver configuration

org.castor.jdo.conf.Driver driverConf =

JDOConfFactory.createDriver(DRIVER, CONNECT, USERNAME, PASSWORD);

// create mapping configuration

org.castor.jdo.conf.Mapping mappingConf =

JDOConfFactory.createMapping(getClass().getResource(MAPPING).toString());

// create database configuration

org.castor.jdo.conf.Database dbConf =

JDOConfFactory.createDatabase(DATABASE, ENGINE, driverConf, mappingConf);

// create and load jdo configuration

JDOManager.loadConfiguration(JDOConfFactory.createJdoConf(dbConf));

// Construct a new JDOManager for the database

jdoManager = JDOManager.createInstance(DATABASE);

// Obtain a new database

Database db = jdoManager.getDatabase();

下面代码以另一种方式通过使用org.exolab.castor.jdo.conf.JdoConf来配置JDBC 2.0 DataSource:

private static final String DS = "oracle.jdbc.pool.OracleConnectionCacheImpl";

private static final String CONNECT = "jdbc:oracle:thin:localhost:1521:SID";

private static final String USERNAME = "scott";

private static final String PASSWORD = "tiger";

private static final String MAPPING = "mapping.xml";

private static final String DATABASE = "mydb";

private static final String ENGINE = "oracle";

// setup properties for datasource configuration

Properties props = new Properties();

props.put("URL", CONNECT);

props.put("user", USERNAME);

props.put("password", PASSWORD);

// create datasource configuration

org.castor.jdo.conf.DataSource dsConf =

JDOConfFactory.createDataSource(DS, props);

// create mapping configuration

org.castor.jdo.conf.Mapping mappingConf =

JDOConfFactory.createMapping(getClass().getResource(MAPPING).toString());

// create database configuration

org.castor.jdo.conf.Database dbConf =

JDOConfFactory.createDatabase(DATABASE, ENGINE, dsConf, mappingConf);

// create and load jdo configuration

JDOManager.loadConfiguration(JDOConfFactory.createJdoConf(dbConf));

// Construct a new JDOManager for the database

jdoManager = JDOManager.createInstance(DATABASE);

// Obtain a new database

Database db = jdoManager.getDatabase();

posted on 2007-11-28 11:46 IT扁担 阅读(569) 评论(0)  编辑  收藏 所属分类: 开源框架

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值