ibatis 使用文档 (下篇)

掌握iBATIS框架中的<iterate>标签 iBATIS框架,作为早期流行的ORM(对象关系映射)解决方案之一,为Java开发者提供了一种简便的方式来操作数据库。它的核心是通过XML文件或注解来映射SQL语句和Java对象,从而简化了传统JDBC操作的复杂性。动态SQL是指在执行时,SQL语句可以根据实际需要进行拼装和修改的SQL。它与静态SQL相对,后者是在编译期就已经确定,不会在运行时改变。动态SQL能够根据不同的条件和参数生成不同的SQL语句,使得代码更加灵活,适用于复杂的查询和更新操作。 阅读详情
====================================================================================
第5章 使用高级查询技术

一:用已映射语句关联对象


问题:如果你用过Hibernate或JPA,会想到entity(实体对象 -- 数据库对应JavaBean)之间可能存在关联关系。如一对一、多对多等。伴随就出现了关联获取技术,我们iBATIS如何做到关联获取呢?

使用iBATIS可以根据相关联的对象来定义数据模型,并且让iBATIS立即加载到它们。
例如:
假设你有一个数据库,其中Account记录和Order记录存在关联,建立起这些关联关系之后,请求Account记录时,就会同时获得所有关联的Order对象。

使用起来非常简单,大体通过以下步骤:
第一步:在我们的JavaBean中建立对应关系
public class Order implements Serializable {
	private static final long serialVersionUID = -7307764485708148107L;


	private Integer orderId;
	private String orderAddress;
...


public class Account implements Serializable {
	private static final long serialVersionUID = 3337778507086494766L;


	private Integer accountId;
	private String username;
	//建立了一个包含关联关系
	private List<Order> orders = new LinkedList<Order>();
...



第二步:创建表(我用的MySQL)
CREATE TABLE `orders` (
  `order_id` int(11) NOT NULL AUTO_INCREMENT,
  `order_address` varchar(20) DEFAULT NULL,
  `account_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


CREATE TABLE `account` (
  `account_id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`account_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


INSERT INTO `account` (`account_id`, `username`) VALUES 
  (1, 'hello'),
  (2, 'world');


INSERT INTO `orders` (`order_id`, `order_address`, `account_id`) VALUES 
  (1, 'hello1', 1),
  (2, 'hello2', 1);


第三步:在SqlMap中(Account.xml中)添加关联获取关系
主要在resultMap中做了手脚,实现方式很简单:
1.书写,我们关联获取时被调用的用于获取关联数据的<select>;被调取的用于获取关联数据的select中(也就是getOrderList中)参数使用#value#(固定格式)。
2.包含关联关系的resultMap标签中,除了添加自己本身表中对应的result,额外添加对应关联属性的result标签。
3.对应关联属性的result标签中,select属性值为被调取的用于获取关联数据的select标签的id。
4.对应关联属性的result标签中,column属性值为代码调取获取数据的<select>中“主表”的关联字段名称。
(<result property="orders" column="account_id" select="getOrderList" />)

<sqlMap namespace="Account">
	<typeAlias alias="account" type="com.partner4java.demo1.bean.Account" />
	<typeAlias alias="order" type="com.partner4java.demo1.bean.Order" />


	<!-- 简单关联方案 -->
	<resultMap class="account" id="accountMap">
		<result property="accountId" column="account_id" />
		<result property="username" column="username" />
		<!-- 通过select来获取的值放入orders;select需要一个关联参数,为本处column -->
		<result property="orders" column="account_id" select="getOrderList" />
	</resultMap>


	<resultMap class="order" id="orderMap">
		<result property="orderId" column="order_id" />
		<result property="orderAddress" column="order_address" />
	</resultMap>


	<select id="getAccount" resultMap="accountMap">
		select * from account
	</select>


	<select id="getOrderList" resultMap="orderMap">
		select * from orders where
		account_id = #value#
	</select>
</sqlMap>


第四步:执行代码
public static void main(String[] args) throws Exception {
	Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
	SqlMapClient sqlMapClient = SqlMapClientBuilder
			.buildSqlMapClient(reader);
	List<Account> accounts = sqlMapClient.queryForList("getAccount", null);
	for(Account account:accounts){
		System.out.println(account);
	}
}
打印:
Account [accountId=1, username=hello, orders=[Order [orderId=1, orderAddress=hello1], Order [orderId=2, orderAddress=hello2]]]
Account [accountId=2, username=world, orders=[]]

延迟加载:
要使用延迟加载,需要编辑SqlMapConfig.xml文件,通过在<setting>元素中将lazyLoadingEnabled属性改为true来启动它。
如果想要使用延迟加载的cglib增强版,则必须下载并将其添加到应用程序的类路径中,同时也必须将<setting>元素中的enhancementEnabled属性改为true。
必须注意的是,这是一个全局设置,因此如果启用了这些特性,SQL映射中所有的已映射语句都将会使用延迟加载。

问题:你是否会揣摩到上面的查询方式会出现“N+1”问题呢,那么如何解决“N+1”问题?
避免N+1问题:
<!-- N + 1查询方案,注意每个resultMap都添加了一个groupBy -->
<resultMap class="order" id="orderJMap" groupBy="orders.order_id">
	<result property="orderId" column="order_id" />
	<result property="orderAddress" column="order_address" />
</resultMap>


<resultMap class="account" id="accountJMap" groupBy="account.account_id">
	<result property="accountId" column="account_id" />
	<result property="username" column="username" />
	<!-- 这里的resultMap为我们上面对order的定义 -->
	<result property="orders" resultMap="Account.orderJMap" />
</resultMap>


<select id="getAccountJ" resultMap="accountJMap">
	select
	account.account_id,account.username,orders.order_id,orders.order_address
	from account join orders on account.account_id = orders.account_id
	order
	by account.account_id,orders.order_id
</select>

public static void main(String[] args) throws Exception {
	Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
	SqlMapClient sqlMapClient = SqlMapClientBuilder
			.buildSqlMapClient(reader);
	List<Account> accounts = sqlMapClient.queryForList("getAccountJ", null);
	for(Account account:accounts){
		System.out.println(account);
	}
}

但是这种方式会丢失不存在order的Account,可以使用left join。


问题:当我执行的不是一个简单insert、update时如何选择标签?
iBATIS框架的设计意图就是要灵活。当无法使用其他类型的已映射语句时,也许就可以使用<statement>已映射语句。
1.使用语句类型和DDL
<statement>类型的已映射语句有点奇怪,它和其他类型(例如<insert>)的已映射语句不同的地方就是,它没有对应的方法可以调用。
这就暗示了,我们不鼓励使用<statement>类型的已映射语句,只有在别无选择的情况下才可以考虑使用它。

<statement id="dropTableTest">
drop table test;
</statement>

sqlMapClient.update("dropTableTest", null);


==================================================================================================
第6章 事务

一:事务是什么


用最简单的话来说,事务就是一项通常包含若干步骤的工作单元,这些步骤必须作为整体来执行,无论成功还是失败。
也就是说,如果一个事务中的任何一个步骤失败了,那么所有其他步骤必须回滚,以保证数据仍处于一致的状态。

示例:就是常用的两个账户的转账的问题。

支持事务的数据库需要具备的特性通常称为ACID:原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。


二:自动事务


与JDBC还有所谓的“自动提交”模式不同,iBATIS处理的只有事务,它根本就没有在一个事务之工作的概念(除非你数据库不支持事务),因此它对“自动提交”的支持其实完全是间接的。
作为“自动提交”的替代,iBATIS支持所有的自动事务。
自动事务允许使用单个的方法调用来运行单个更新语句活查询语句,而完全不用关心如何划分事务。


三:局部事务
所谓局部事务,它仅仅涉及一个应用程序、一种资源(例如关系数据库),并且一次只能处理一个事务。

首先配置:
<transactionManager type="JDBC">
	<!-- SIMPLE是一个iBATIS内置事务管理器的名字 -->
	<dataSource type="SIMPLE">
...

public static void main(String[] args) throws Exception {
	Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
	SqlMapClient sqlMapClient = SqlMapClientBuilder
			.buildSqlMapClient(reader);
	
	//学过jdbc的,是不是很容易理解啊?
	sqlMapClient.startTransaction();
	
	long being = System.currentTimeMillis();
	try {
		sqlMapClient.startBatch();
		for(int i=0;i<1000;i++){
			Account account = new Account("en5" + i, "o5", "EMPLOYEE");
			sqlMapClient.insert("insertAccount", account);
		}
		sqlMapClient.executeBatch();
		sqlMapClient.commitTransaction();
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		sqlMapClient.endTransaction();
	}
	System.out.println("time:" + (System.currentTimeMillis() - being));
	
}

定制事务:
我们也可以获取一个conn,然后使用conn手工管理事务,不过我们建议在基于conn管理事务的基础上使用sqlMapClient来执行SQL操作。
public static void main(String[] args) throws Exception {
	Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
	SqlMapClient sqlMapClient = SqlMapClientBuilder.buildSqlMapClient(reader);
	Connection conn = null;
	SqlMapSession session = null;
	try {
		conn = sqlMapClient.getDataSource().getConnection();
		conn.setAutoCommit(false);
		session = sqlMapClient.openSession(conn);
		session.insert("insertAccount", new Account("hello", "123", "EMPLOYEE"));
		
		conn.commit();
	} catch (Exception e) {
		if(conn != null) conn.rollback();
		e.printStackTrace();
	}finally{
		if(session != null){
			session.close();
		}
		if(conn != null){
			conn.close();
		}
	}
}

(局部事务就不多说了,你自己打一遍就明白了)

==================================================================================================
第7章 使用动态SQL

一:先来个hello

<select id="queryAccount" resultClass="account" parameterClass="string">
	select * from user_account 
	<!-- 就多了一个这种标签,看这标签的起名是不是很像我们Junit里的断言啊? -->
	<isNotNull>
		where groupname = #value#
	</isNotNull>
</select>	

public static void main(String[] args) throws Exception {
	Reader reader = Resources.getResourceAsReader("sql-map-config.xml");
	SqlMapClient sqlMapClient = SqlMapClientBuilder
			.buildSqlMapClient(reader);
	List<Account> accounts = sqlMapClient
			.queryForList("queryAccount", "EMPLOYEE");
	for (Account account : accounts) {
		System.out.println(account);
	}
}


也可以:使用<dynamic>标签作为父标签
<dynamic>标签不像其他动态SQL标签,它不会去评测(evaluate)任何值或状态。它通常只是使用prepend属性,该属性值将作为前缀被加到<dynamic>标签的内容体之前。
如果<dynamic>标签的内容体经iBATIS处理后没有产生任何文本,那么prepend值将被忽略。
<select id="queryAccount" resultClass="account" parameterClass="string">
	select * from user_account 
	<dynamic>
		<isNotNull>
			where groupname = #value#
		</isNotNull>
	</dynamic>
</select>

二:熟悉动态标签

所有标签分为5类:
<dynamic>标签、二元标签、一元标签、参数标签以及<iterate>标签。

先看一下所有的动态SQL标签都有的属性和行为。
open、close:无条件地将其属性值放在标签的结果内容的开始处和结束处。
prepend:除了dynamic之外,prepend属性在所有其他标签中的功能也都是一样的。<dynamic>标签在内容体的结果SQL非空时,总是将其prepend属性值添加为该结果SQL的前缀。

只有当你想要向据诶过内容体使用open、close或prepend值时,才必须使用<dynamic>标签。


<dynamic>标签:
<dynamic>标签是最顶层标签;这意味着它不能被嵌套。该标签用来换分一个动态的SQL片段。
<dynamic>标签的属性:
prepend(可选的):该值用于作为前缀添加到标签的结果内容体前。但是当标签的结果内容体为空时,prepend值将不起作用。
open(可选的):该值用于作为前缀添加到标签的结果内容体前。但是如果结果内容体为空,open将不会附加到前面。open值将在prepend属性值被添加前缀先被添加前缀。
close(可选的):该值用于作为后缀附加到标签的结果内容体后。如果标签的结果内容体为空,close值将不起作用。

iBATIS二元标签:
二元标签(binary tag)用于将参数特性的某个值同另外一个值或者参数特性做比较。
如果比较结果为true,那么结果SQL中就包含其内容体。
所有的二元标签都共享compareProperty特性、compareValue属性。
property属性用于设置被比较的基本类型值,而compareProperty属性和compareValue属性则用于设置比较的参考值。
compareProperty属性会指定参数对象中的一个特性,该字段的取值会作为比较时的参数。
compareValue属性则会指定一个静态值,用于比较的基本类型值。
二元标签的属性:
property(必需的):参数对象中用于同compareProperty或者compareValue想比较的特性。
prepend(可选的):该值用于作为前缀附加到标签的结果内容体前。只有以下情况prepend值不会被加为前缀:当标签的结果内容体为空时;如果该标签是第一个产生内容体,且它被嵌套在一个removeFirstPrepend属性被设置为true的父标签中时。
open(可选的):该值用于作为前缀添加到标签的结果内容体前。如果标签的结果内容体为空,open值将不会被附加到其前面。open值将在prepend属性值被添加为前缀之前先被添加前缀。
close(可选的):该值用于作为后缀附加到标签结果内容体后。如果标签的结果内容体为空,则close值将不起作用。
removeFirstPrepend(可选的):该值用于确定第一个嵌套的内容生产标签是否移除其prepend值(prepend是可选属性)。
compareProperty(如果没有指定compareValue,则它是必需的):该值指定参数对象中的一个特性用来同property属性所指定的特性相比较。
compareValue(如果没有指定compareProperty,则它是必需的):该值指定一个静态比较值用于同property属性所指定的特性相比较。

二元动态标签:
<isEqual>:将property属性同compareProperty属性或compareValue属性相比较,确定他们是否相同。
<isNotEqual>:将property属性同compareProperty属性或compareValue属性相比较,确定他们是否不同。
<isGreaterThan>:确定property属性是否大于compareProperty属性或compareValue属性。
<isGreaterEqual>:确定property属性是否大于等于compareProperty属性或compareValue属性。
<isLessThan>:确定property属性是否小于compareProperty属性或compareValue属性。
<isLessEqual>:确定property属性是否小于等于compareProperty属性或compareValue属性。

iBATIS一元标签:
一元标签(unary tag)用于直接考察参数对象中某个bean特性的状态,而不是与其他进行比较。
如果参数对象的状态结果为真,那么结果SQL中就会包含其内容体。
所有的一元标签都共享property属性。一元标签的property属性用于指定参数对象上用来考察状态的特性。

一元标签属性:
property(必需的):参数对象中用于状态比较的特性。
prepend(可选的):该值用于作为前缀附加到标签的结果内容体前。只有以下情况prepend值不会被加为前缀:当标签的结果内容体为空时;如果该标签是第一个产生内容体,且它被嵌套在一个removeFirstPrepend属性被设置为true的父标签中时。
open(可选的):该值用于作为前缀,添加到结果内容体前。如果结果内容体为空,open值将不会被附加到其前面。open值将在prepend属性值被添加前缀之前先被添加前缀。
close(可选的):该值用于作为后缀附加到标签的结果内容体后。如果结果内容体为空,则close值将不起作用。
removeFirstPrepend(可选的):该属性值用于决定第一个产生内容的嵌套子标签是否移除其prepend值。

一元标签:
<isPropertyAvailable>:确定参数对象中是是否存在所指定的字段。对于bean,它寻找一个特性;而对于map,它寻找一个键。
<isNotPropertyAvailable>:确定参数中是否不存在所指定的字段。对于bean,它寻找一个特性;而对于map,它寻找一个键。
<isNull>:确定所指定的字段是否为空。对于bean,它寻找获取方法特性的返回值;而对于map,它寻找一个键,若这个键不存在,则返回true。
<isNotNull>:确定所指定的字段是否为非空。对于bean,它寻找获取方法特性的返回值;而对于map,它寻找一个键,若这个键不存在,则返回false。
<isEmpty>:确定所指定的字段是否为空、空字符串、空集合或者空的String.valueOf()。
<isNotEmpty>:确定所指定的字段是否为非空、非空字符串、非空集合或者非空的String.valueOf()。

iBATIS参数标签:
考虑到iBATIS允许定义没有参数的已映射语句。
参数标签(parameter tag)就是用来检查某个特定参数是否被传递给了已映射语句。
参数标签的属性和前面大同小异。
<isParameterPresent>:确定参数对象是否出现。
<isNotParameterPresent>:确定参数对象是否不存在。

iBATIS<iterate>标签:
<iterate>标签以一个集合或数组类型的特性作为其property属性值,iBATIS通过遍历这个集合(数组)来从一组值中重复产生某种SQL小片段。
这些小片段以conjunction属性值作为分隔符连接起来,从而形成一个有意义的SQL语句片段,open属性值将作为所呈现的值列表的前缀,close属性值将作为所呈现的值列表的后缀,最终形体形成一个完成合法的SQL。
<iterate>标签属性:
conjunction(可选的):该值用于连接iBATIS遍历集合(数组)时重复产生那些SQL小片段。
其它属性基本一致。

==================================================================================================
第8章 使用高速缓存提高性能

(导读:建议你看的同事自己动手练,有什么疑问自己用各种方式试出来)
(测试的建议:想看下是不是缓存,可在两次查询中做一个线程等待,然后去修改数据库,等待结束后,看数据是否改变,当然前提是你的缓存时间长于你的两次打印时间间隔,且修改工作在等待过程中完成)

一:一个简单的iBATIS高速缓存示例

iBATIS的强健且简单的高速缓存机制是完全基于配置的,因此避免了直接使用高速缓存的负担。
<!-- type="MEMORY",指定其高速缓存类型为MEMORY,把查询结果直接存储在内存中。 -->
<cacheModel type="MEMORY" id="categoryCache">
	<!-- flushOnExecute标签用于指定当某个特定高速缓存被访问时,其存储结果将被清除。 -->
	<flushOnExecute statement="insert" />
	<flushOnExecute statement="update" />
	<flushOnExecute statement="delete" />
	<!-- 每一种类型的高速缓存模型都有一些专用于它自己配置的特性,property标签就是用于完成这些专用特性的设置的。那么属性指定该高速缓存模型将要设置的特性的名称,value指定该预定义特性的值。 -->
	<property name="reference-type" value="WEAK" />
</cacheModel>


<select id="queryAccount" resultClass="account" parameterClass="string"
	cacheModel="categoryCache">
	select * from user_account
	<dynamic>
		<isNotEmpty>
			where groupname = #value#
		</isNotEmpty>
	</dynamic>
</select>

二:理解高速缓存模型

有关高速缓存模型,最简单的描述就是,它是一种高速缓存配置。或者更确切的说,它是定义所有的iBATIS高速缓存实现的基础。
高速缓存模型的配置是在SQL Map配置文件中,通过<cacheModel>标签来定义。
<cacheModel>标签属性:
id(必需的):该值用于指定一个唯一的ID,便于为需要使用此高速缓存模型所配置的高速缓存的查询已映射语句所引用。
Type(必需的):此属性用于指定高数缓存模型所配置的高速缓存的类型。其有效值包括MEMORY、LRU、FIFO和OSCACHE。该属性也可取值为某个自定义CacheController实现的全限定类名。
readOnly(可选的):将该值设置为true,就表示高速缓存将仅仅被用作只读高速缓存。
serialize(可选的):该属性值指定在读取高速缓存内容时是否要进行“深复制”

1.type属性 -- 内置的各种高速缓存模型类型:
MEMORY:这个模型简单的将高速缓存保存在内存中,直至垃圾收集器将它移除。
FIFO:这个模型中,高速缓存的数据量是固定的,使用“先进先出(first in first out)”算法来移除高速缓存中的数据。
LRU:这个模型中,高速缓存的数据量也是固定的,使用“最近最少使用(least recently used)”算法来移除高速缓存中的数据。
OSCACHE:这个模型使用OpenSymphony高速缓存

要使用何种高速缓存实现,是通过将它们相应的默认关键字(MEMORY、LRU、FIFO以及OSCACHE)添加到<cacheModel>标签的type属性来指定的。
也可以自己实现CacheController接口,然后在type属性中指定其全限定类名,以提供一套自己的高速缓存方案。

2.readOnly属性:
<cacheModel>标签提供了一个readOnly属性。
该属性仅仅是一个为高速缓存模型提供指令的指示器,用于告诉高速缓存模型应该如何检索和保存已告诉缓存对象。
该属性设置为true,并不能保证从高速缓存中检索出的对象其内容不被改变。
指定一个高速缓存为只读时,也就是告诉高速缓存模型允许其返回对存在于高速缓存中的某个对象的引用,因为该对象将不会被正在请求它的应用程序所改变。
如果readOnly属性设置为false,就可以确保不会出现多个用户同时访问某个已高速缓存的对象的同一引用实例的情况。

3.serialize属性:
serialize属性用于指示已高速缓存对象应该如何返回。
当该属性被设置为true时,高速缓存中所请求的每个对象都将作为一个深拷贝被返回。
这就意味着从高速缓存中检索出来的对象只具有相同的值,但并不是同一个实例。
这就可以确保存储在高速缓存中的实际数据永远不会被改变。
需要注意的是,此处所谓的串行化并不是我们所学的“序列化”,它并不是把数据串行化到硬盘上。
这里的串行化是基于内存的串行化,用于创建内存中已高速缓存对象的深拷贝。

4.联合使用readOnly属性和serialize属性:
它们看上去在功能上视乎存在一些重叠,事实上,它们需要紧密协同才能正常工作。
readOnly serialize结果 原因
true false好 可以最快速的检索出已高速缓存的对象。返回已高速缓存对象的一个共享实例,若使用不当可能会导致问题。
false true 好 能快速检索出已高速缓存对象。返回已高速缓存对象的一个深拷贝。
false false 警告 对于此种组合,高速缓存仅仅对调用线程的会话的声明周期有关,且不能被其他线程所使用。
true true 坏 这种组合同readOnly=false而serialize=true的组合作用一致,否则它在语义上没有任何意义。

以上两个属性的默认组合是readOnly=true和serizalize=false。


三:如何使用高速缓存模型中的标签
1.高速缓存的清除
<flushOnExecute> -- 定义查询已映射语句,其执行将引起相关高速缓存的清除
<flushInterval> -- 定义一个时间间隔,高速缓存将以此间隔定期清除

<flushOnExecute>标签:
只有一个属性statement,当某条已映射语句执行时,将触发相应高速缓存的清除。
当你在需要必须更新高速缓存以保证与数据库一致是尤其有用。

<flushInterval>标签:
除了时间之外它没有任何配置上的依赖性。
每个一定的时间间隔就会清除一次高速缓存。

2.设置高速缓存模型实现的特性:
<property>标签的属性name和value,都是必需的。
它们用来构建一个可传递给高速缓存模型组件以供其初始化的Properties对象。

四:高速缓存模型的类型
MEMORY:
MEMORY高速缓存是一种基于引用的高速缓存。
高速缓存中的每个对象都赋予一个引用类型。此引用类型为垃圾收集器提供了线索,指导它如何处理相应的对象。
引用类型:
WEAK -- 将很快的废弃高速缓存的对象。这种引用不会阻止对象被垃圾收集器收集。它仅仅提供一种方式来访问高速缓存中的对象,该对象在垃圾收集器的第一遍收集中就会被移除。(默认)如果因为某种原因GC比较频繁,那么会带来频繁访问数据库的问题。
SOFT -- 除非确定需要更多的内存,否则垃圾收集器始终不会收集对象。SOFT引用也确保不会超过内存限制,和WEAK相比,其数据库访问频率会低些。
STRONG -- 其中的已高速缓存对象永远不会被废弃,除非到达了指定清除时间间隔。
通过<property name="reference-type" value="WEAK" />来指定期望使用的引用类型。

LRU:
使用最近最少使用策略来管理高速缓存。
只有当高速缓存超过指定大小限制约束条件时,才会废弃最近最少被访问的对象。
大小限制定义了高速缓存中可以包含的对象数目。
<property name="size" value="200" />

FIFO:
才用先进先出的管理策略。
用法和收集点同上。
(<property name="size" value="200" />)

OSCACHE:
需要依赖OSCache的jar。并放入OSCache需要的配置文件。

==================================================================================================
第9章 结合Spring

先从配置文件下手,然后书写我们的service,最后调用测试

创建工程,我们Maven配置如下:
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.partner4java.spring</groupId>
	<artifactId>5spring</artifactId>
	<version>0.0.1-SNAPSHOT</version>


	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>3.1.1.RELEASE</spring.version>
		<ibatis.version>2.1.7.597</ibatis.version>
	</properties>


	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-aop</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>


		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-sqlmap</artifactId>
			<version>2.3.4.726</version>
		</dependency>
		<dependency>
			<groupId>org.apache.ibatis</groupId>
			<artifactId>ibatis-core</artifactId>
			<version>3.0</version>
		</dependency>


		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.21</version>
		</dependency>
		<dependency>
			<groupId>c3p0</groupId>
			<artifactId>c3p0</artifactId>
			<version>0.9.1.2</version>
		</dependency>


		<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>2.2.2</version>
		</dependency>


		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.6.9</version>
		</dependency>


		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.10</version>
			<scope>test</scope>
		</dependency>
	</dependencies>


</project>

第一步:创建数据库相关配置文件

在类路径下创建:jdbc-c3p0.properties
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/ibatis_test?useUnicode=true&characterEncoding=UTF-8
user=root
password=123456


#初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3
initialPoolSize=1


#连接池中保留的最小连接数
minPoolSize=1


#连接池中保留的最大连接数。Default: 15
maxPoolSize=300


#最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0
maxIdleTime=60


#当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3
acquireIncrement=5


#每60秒检查所有连接池中的空闲连接。Default: 0
idleConnectionTestPeriod=120


#定义在从数据库获取新连接失败后重复尝试的次数。Default: 30
#acquireRetryAttempts=30


#两次连接中间隔时间,单位毫秒。Default: 1000
#acquireRetryDelay=1000


#连接关闭时默认将所有未提交的操作回滚。Default: false
#autoCommitOnClose=false


#JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 
#  属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 
#  如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0
#maxStatements=100


#maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 
#maxStatementsPerConnection=


#c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 
# 通过多线程实现多个操作同时被执行。Default: 3
#numHelperThreads=3


#当用户调用getConnection()时使root用户成为去获取连接的用户。主要用于连接池连接非c3p0 
#  的数据源时。Default: null
#overrideDefaultUser=root


#与overrideDefaultUser参数对应使用的一个参数。Default: null
#overrideDefaultPassword=


#定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个一显著提高测试速度。注意: 
#  测试的表必须在初始数据源的时候就存在。Default: null
#preferredTestQuery=select id from test where id=1


#用户修改系统配置参数执行前最多等待300秒。Default: 300
#propertyCycle=300


#因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 
#  时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable 
#  等方法来提升连接测试的性能。Default: false
#testConnectionOnCheckout=false


#如果设为true那么在取得连接的同时将校验连接的有效性。Default: false
#testConnectionOnCheckin=true


#c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么 
#属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试 
#使用。Default: null
#automaticTestTable=Test


#获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 
#  保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试 
#  获取连接失败后该数据源将申明已断开并永久关闭。Default: false
#breakAfterAcquireFailure=false


#当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出 
#  SQLException,如设为0则无限期等待。单位毫秒。Default: 0
#checkoutTimeout=5000


#通过实现ConnectionTester或QueryConnectionTester的类来测试连接。类名需制定全路径。 
#  Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester
#connectionTesterClassName=


#指定c3p0 libraries的路径,如果(通常都是这样)在本地即可获得那么无需设置,默认null即可 
#  Default: null
#factoryClassLocation=


#maxConnectionAge=400

第二步:创建我们Spring的DataSource(不多说,不懂去查阅Spring相关资料)

创建文件datasource-c3p0.xml,放入类路径下的META-INF/spring中
<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
	
	<context:property-placeholder location="classpath:jdbc-c3p0.properties" />
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
		<property name="driverClass" value="${driverClass}"/>
		<property name="jdbcUrl" value="${jdbcUrl}"/>
		<property name="user" value="${user}"/>
		<property name="password" value="${password}"/>
		<property name="initialPoolSize" value="${initialPoolSize}"/>
		<property name="minPoolSize" value="${minPoolSize}"/>	
		<property name="maxPoolSize" value="${maxPoolSize}"/>
		<property name="maxIdleTime" value="${maxIdleTime}"/>	
		<property name="acquireIncrement" value="${acquireIncrement}"/>	
		<property name="idleConnectionTestPeriod" value="${idleConnectionTestPeriod}"/>
	</bean>
	
	
</beans>

 第三步:创建我们的JavaBean
 
public class Account implements Serializable {
	private static final long serialVersionUID = -6876471972027832806L;
	private int userid;
	private String username;
	private String password;
	private String groupname;
...

第四步:创建SqlMap
创建文件Account.xml放入类路径下的ibatis文件夹中
<?xml version="1.0" encoding="UTF-8" ?>


<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
    "http://ibatis.apache.org/dtd/sql-map-2.dtd">


<sqlMap namespace="Account">
	<typeAlias alias="account" type="com.partner4java.demo.entity.Account" />


	<select id="queryAccount" resultClass="account" parameterClass="string">
		select * from user_account
		<dynamic>
			<isNotEmpty>
				where groupname = #value#
			</isNotEmpty>
		</dynamic>
		order by userid desc
	</select>


	<insert id="insertAccount" parameterClass="account">
		insert into
		user_account(username,password,groupname)
		values(#username#,#password#,#groupname#)
	</insert>
</sqlMap>

第五步:创建我们iBATIS的sql-map-config.xml
创建文件sql-map-config.xml,放入类路径的ibatis文件中
<?xml version="1.0" encoding="UTF-8" ?>


<!DOCTYPE sqlMapConfig PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
    "http://ibatis.apache.org/dtd/sql-map-config-2.dtd">


<sqlMapConfig>


    <settings 
	    useStatementNamespaces="true" 
	    />
	
	<sqlMap resource="ibatis/Account.xml" />
	
</sqlMapConfig>

第六步:继续配置Spring配置文件 -- 从Spring中创建SqlMapClient
创建文件beans.xml放入类路径下的META-INF/spring中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.0.xsd">
	<aop:aspectj-autoproxy />
	<context:component-scan base-package="com.partner4java" />
	
	<import resource="classpath:META-INF/spring/datasource-c3p0.xml" />
	
	<!-- SqlMapClientFactoryBean是个Spring工厂Bean,用于生成SqlMapClient。
		iBATIS API的核心是SqlMapClient接口。
		SqlMapClient大致相当于Hibernate的Session或者JPA的EntityManager,用于执行全部的数据访问操作。 -->
	<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="configLocation" value="classpath:ibatis/sql-map-config.xml"/>
	</bean>
	
	<!-- SqlMapClientTemplate包裹了一个SqlMapClient来透明的打开和关闭会话,好捕获抛出的SQLException。 -->
	<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
		<property name="sqlMapClient" ref="sqlMapClient"/>
	</bean>
		
</beans>        

第七步:创建我们的Service
public interface AccountService {
	List<Account> query(String groupname);
	void add(Account account);
}


@Service
public class AccountServiceBean implements AccountService {
	@Autowired
	private SqlMapClientTemplate sqlMapClientTemplate;
	public void setSqlMapClientTemplate(
			SqlMapClientTemplate sqlMapClientTemplate) {
		this.sqlMapClientTemplate = sqlMapClientTemplate;
	}


	public List<Account> query(String groupname) {
		return sqlMapClientTemplate.queryForList("Account.queryAccount",
				groupname);
	}


	public void add(Account account) {
		sqlMapClientTemplate.insert("Account.insertAccount", account);
	}


}

最后一步:测试
public class AccountServiceBeanTest {
	ApplicationContext applicationContext;
	private AccountService accountService;
	@Before
	public void setUp() throws Exception {
		applicationContext = new ClassPathXmlApplicationContext(
				"META-INF/spring/beans.xml");
		accountService = applicationContext.getBean("accountServiceBean", AccountService.class);
	}


	@Test
	public void testQuery() {
		System.out.println(accountService.query(null));
	}


}


ibatis官方中文文档 ibatis官方中文文档.rar包含ibatis官方中文文档及使用说明 立即下载

相关推荐

ibatis-2.3.4 开发包 及 官方权威文档(开发指南+入门教程)

包括最新版本ibatis-2.3.4开发包,以及官方发布的最权威的开发指南+入门教程,让你轻松掌握ibatis知识,可迅速上手搭建自己的ibatis工程。

ibatis 使用文档 (上篇)

(代码下载地址:http://download.youkuaiyun.com/detail/partner4java/4760043) iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 创建于2002年,后很快加入了Apache,但是2010年又易主到google code,并改名为MyBatis。 本篇文章主要是基于iBATIS来进行展示,后面会再发

java相关技术(partner4java专栏)希望对您的学习有所帮助 4286

IBatis2.3.4帮助文档(官方)

IBatis2.3.4官方帮助文档 检索非常方便 官方下载,绝对专业

ibatis使用文档(下篇)

==================================================================================== 第5章 使用高级查询技术 一:用已映射语句关联对象 问题:如果你用过Hibernate或JPA,会想到entity(实体对象 -- 数据库对应JavaBean)之间可能存在关联关系。如一对一、多对多等。伴随就出现了关

Ltw518的专栏 1742

异步fifo的UVM验证(最终最终版)

更新了refmdl以及scb

qq_44674008的博客 2987

无线充电效率从75%到90%+:深入拆解LCC补偿网络如何成为电动汽车和智能车竞赛的‘稳压器’

本文深入探讨了LCC补偿网络如何显著提升无线充电效率至90%以上,成为电动汽车和智能车竞赛中的关键技术。通过分析LCC的三元件结构和双模式切换特性,揭示了其在负载突变和线圈错位情况下的稳定性优势,并展示了实际应用中的效率提升和产业化挑战。

weixin_42528191的博客 396

ibatis使用文档(上篇)

iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 创建于2002年,后很快加入了Apache,但是2010年又易主到google code,并改名为MyBatis。 本篇文章主要是基于iBATIS来进行展示,后面会再发一篇和MyBatis的对比。 (本文大多摘自《iBATIS in action》一书,若有时间学习此书,建议不要看本文--因

Ltw518的专栏 2797

Arduino跑通ssd1306_spi显示屏

代码首先,您需要下载两个库:● Adafruit GFX库,这是核心图形库,提供了一组通用的图形基元(点、线、圆等)。它需要与特定于硬件的库对应。● Adafruit SSD-1306 OLED显示库,这是基于SSD1306驱动程序的OLED显示器的库。解压缩这两个库并将它们添加到Arduino库文件夹中,然后运行Arduino IDE并从OLED库中打开SPI示例。

Leo ICer的博客 1493

ibatis 使用文档

(代码下载地址:http://download.youkuaiyun.com/detail/partner4java/4760043) iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 创建于2002年,后很快加入了Apache,但是2010年又易主到google code,并改名为MyBatis。 本篇文章主要是基于iBATIS来进行展示,后面会

Larry的博客 5849

【RT-DETR涨点改进】独家Conv改进篇 | 来自TIP 2024顶刊| 引入 DEConv细节增强卷积模块, 能够恢复更多细节信息,提升小目标检测精度、含多种不同模型改进点

本文提出了一种细节增强卷积(DEConv)模块,通过融合多种差异卷积(CDC、ADC、HDC、VDC)来增强图像特征表示能力,特别在边缘和纹理细节提取方面表现突出。该模块采用重参数化技术,在不增加计算成本的情况下,将先验信息融入常规卷积层,显著提升了RT-DETR模型在模糊或遮挡场景下的目标检测精度。实验验证了DEConv在多种骨干网络(ResNet18/50、HGNet等)中的有效性,为实时目标检测系统提供了轻量高效的改进方案。

Ai缝合怪、助力小伙伴高效发论文 449

一文吃透 ABAP CDS SELECT:语法全景、关联与去重、可运行示例与实战心法

摘要: 本文深入解析ABAP中CDS视图的SELECT语法,重点介绍两种等价写法、关联与路径表达式优化、DISTINCT去重逻辑及WHERE/GROUP BY/HAVING/UNION等子句的应用。通过可运行的业务伙伴角色去重、关联路径查询及多角色筛选示例,演示如何将CDS特性转化为项目实践。强调关联复用性优于传统JOIN,并指出DISTINCT的权限边界与误用场景,帮助开发者从行式思维转向图式数据建模。(150字) 关键词: ABAP CDS、SELECT语法、路径表达式、DISTINCT去重、关联复用

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 390

ibatis 框架介绍及使用详解

 iBATIS 是什么?这一节将描述 iBATIS 中的单独的 API,以及为什么您可能使用它们,并了解 iBATIS 优于其他数据库映射框架的优点。iBATIS 框架简言之,iBATIS 由两个单独的框架组成。可以将 Data Mapper 框架专门用于 OR 映射,OR 映射是 Java 域对象到数据库中关系表的映射。DAO 框架为应用程序提供了一个简洁一致的访问基

xiemeikj的专栏 1万+

uniapp - 解决真机调试报错 request:fail abort statusCode:-1 java.security.cert.CertPathValidatorException: Tr

uni-app,真机调试报错,app端平台,安卓Android,安卓App发布线上报错,打包后接口请求失效了,api接口报错,真机运行安卓app时接口不通且报错,request:failabortstatusCode:-1java.security.cert.CertPathValidatorException:Trustanchorforcertificationpathnotfound.,真机调试,苹果ios真机报错,Uniapp打包到app后报错,uni-app真机调试报错,服务器

🏆 前端领域优质创作者 1万+

IBatis使用浅析

ibatis 历史 Eight years ago in 2002, I created the iBATIS Data Mapper and introduced SQL Mapping as an approach to persistence layer development. Shortly thereafter, I donated the iBATIS name and code ...

liangwenrong的博客 1万+

BVH2SMPL 实战笔记

BVH2SMPL 实战笔记

jacke121的专栏 840

白话讲解帕累托前沿点(Pareto Frontier)基础知识与实例解析

排除所有“劣解”,缩小决策范围;量化目标间的“权衡关系”(如“每增加1千元成本,续航提升多少小时”);支持不同场景下的个性化选择,平衡冲突目标。无论是手机选购、汽车设计,还是城市规划,帕累托前沿都是从“混乱的多目标”到“清晰的权衡”的关键桥梁,是现代决策科学中不可或缺的工具。通过数学建模,工程很多问题都可以建模成帕累托前沿求解问题,通过线性规划CPLEX、Gurobi 等优化工具,可以方便进行求解。

2138

Ibatis帮助文档

Ibatis帮助文档

hongyabing110的专栏 1193

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

ibatis使用小文档

1、认识配置文件SqlMap.config:http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="SqlMapConfig.xsd"> ${d

li_yibin的专栏 694

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

IBATIS总结

iBATIS 学习总结 一、        了解iBATIS 1.什么是iBATIS “一个映射层,在对象和数据库间传递数据,并保持两者与映射层本身独立”  iBatis 不是直接在类和数据表或字段与列之间进行关联,而是把SQL语句的参数和返回结果映射至类。iBatis是出于类和数据表之间的一个中间层。这使得它在类和数据表之间进行映射时更加灵活。 iBATIS 是把SQL语句的参数和返回

duci2的专栏 2505

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

IBatis的使用

一、前言: 本文档主要介绍了使用Ibatis的数据访问和Log4Net的日志管理在开发项目中一些常用的操作,关于dll等的引入等暂不作介绍了 参考资料: Doc-DataMapper- 帮助文档 网上的部分资料 二、实战: 我们以一个例子来介绍和说明如何在项目中使用Ibatis中的数据访问 A.数据访问 我们以SQL...

weixin_33753845的博客 1172

IBATIS使用proxool连接池

在众多项目中使用过诸多连接池,如DBCP,PROXOOL等,发现还是PROXOOL好使,主要是兼容性好,性能稳定。 在某次项目中,由于历史版本问题,使用了两个连接池,一个是proxool,一个是IBATIS自由的SIMPLE,但是在连接一个专有数据库时,却发现proxool可以创建连接池,而IBATIS的SIMPLE却不能建立连接(报经典的错The network apdapter cou

3038

iBATIS入门简介

一、iBATIS教程之iBATIS介绍: iBATIS是什么呢?相对于Hibernate和Apache OJB等“一站式”ORM解决方案而言,iBATIS是一种“半自动化”的ORM实现。 这个框架将让你能够更好的在JAVA应用中设计和实现实体层。这个框架有两个主要的组成部分,一个是SQL Maps,另一个是Data Access Objects。另外还包括一些可能很有用的工具。

冷丶Feng 1万+

ibatis2mybatis 项目使用教程

ibatis2mybatis 项目使用教程 项目介绍 ibatis2mybatis 是一个开源工具,旨在帮助开发者将旧的 iBATIS 2.x sqlmap 文件转换为 MyBatis 3.x mapper 文件。由于 iBATIS 2.x 框架已经不再维护,MyBatis 提供了更加丰富的功能和更好的支持,因此将 iBATIS 升级到 MyBatis 对于仍在使用旧框架的项目来说是非常必要的。 ...

gitblog_00002的博客 718

什么是iBATIS

1、什么是iBATIS 1.1作者        Clinton Begin,很牛X的名字 1.2背景故事     Clinton Begin于2001年启动iBATIS项目,最先的焦点是发展密码软件解决方案。第一个iBATIS产品是Secrets,是一个象PGP的个人数据加密器和签名工具。Secrets完全用Java编写并在一个开源许可下发布。     在发布Secrets Java版

daojianhong的专栏 880

iBATIS总结

1.1作者       Clinton Begin,很牛X的名字1.2背景故事    Clinton Begin于2001年启动iBATIS项目,最先的焦点是发展密码软件解决方案。第一个iBATIS产品是Secrets,是一个象PGP的个人数据加密器和签名工具。Secrets完全用Java编写并在一个开源许可下发布。    在发布Secrets Java版后不久,iBATIS项目陷入困境转而关注W...

mno2330的博客 910
上一篇: ibatis 使用文档 (上篇)
下一篇: 《partner4java 讲述Spring入门》之第一步:Spring概述与Spring IoC
partner4java
博客等级 码龄17年 481粉丝 183原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值