hibernate笔记


9:56 2012/3/29
引入
一、模型不匹配(阻抗不匹配)
java面向对象语言,对象模型,其主要概念有:继承、关联、多态等;数据库是关系模型,其主要概念有:表、主键、外键等。
二、解决办法
1、使用JDBC手工转换
2、使用ORM(Object Relation Mapping对象关系映射)框架来解决,主流的ORM框架有Hibernate、TopLink、OJB。
******************************************************************************************************************

10:01 2012/3/29
安装配置
1、下载地址http://www.hibernate.org,本教程使用3.2.5
2、将下载目录/hibernate3.jar和/lib下的hibernate运行时必须的包加入classpath中:
antlr.jar,cglib.jar,asm.jar,commons-collections.jar,commons-logging.jar,jta.jar,dom4j.jar
3、配置文件hibernate.cfg.xml和hibernate.properties,xml和properties两种,这两个文件的作用一样,提供一个即可,推荐XML格式,下载目录/etc下是示例配置文件。
可以再配置文件制定:数据库的URL、用户名、密码、JDBC驱动类、方言等。
4、映射文件(hbm.xml,对象模型和关系模型的映射)。在/eg目录下有完整的hibernate示例。
5、快速开始小例子。
******************************************************************************************************************

16:13 2012/3/29
hibernate例子:
1、创建一个hibernate项目
2、把hibernate包导入,包括hibernate3.jar包及sqljdbc4.jar驱动包
3、创建一个cn.flame.hibernate.domain包并在包中创建一个U_User实体类,内容为:
------------------------------------------------------------------------
package cn.flame.hibernate.domain;

import java.util.Date;

public class U_User {
    private int id;
    private String name;
    private Date birthday;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    

}

------------------------------------------------------------------------
4、在cn.flame.hibernate.domain中创建一个名为U_User.hbm.xml的文件,内容为:
------------------------------------------------------------------------
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping
    package="cn.flame.hibernate.domain">

    <class name="U_User" >
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
        <property name="birthday"/>
    </class>
    
</hibernate-mapping>
------------------------------------------------------------------------
5、在src目录下创建一个名为hibernate.cfg.xml的文件,内容为:
------------------------------------------------------------------------
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
        <property name="connection.url">jdbc:sqlserver://localhost:1433;databaseName=test</property>
        <property name="connection.username">sa</property>
        <property name="connection.password">123456</property>
        <property name="hibernate.show_sql">true</property>
        <property name="dialect">org.hibernate.dialect.SQLServerDialect</property>
        <property name="hbm2ddl.auto">create</property>
        <mapping resource="cn/flame/hibernate/domain/U_User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
------------------------------------------------------------------------
7、在cn.flame.hibernate包中创建一个Base类,内容为:
------------------------------------------------------------------------
public static void main(String[] args) {
        Configuration cfg = new Configuration();
        cfg.configure();
        SessionFactory sf = cfg.buildSessionFactory();
        Session s = sf.openSession();
        Transaction t = s.beginTransaction();
        U_User u = new U_User();
        u.setId(1);
        u.setName("flame");
        u.setBirthday(new Date());
        s.save(u);
        t.commit();
        s.close();
        System.out.println("end");
    }
------------------------------------------------------------------------
******************************************************************************************************************

16:30 2012/3/29
基本概念和CURD
一、开发流程
1、有Domain object ->mapping ->db.(官方推荐)
2、由DB开始,用工具生成mapping和Domain object。(使用较多)
3、由映射文件开始。
二、Domain object限制
1、默认的构造方法(必须的)
2、有无意义的标识符id(主键)(可选)
3、非final的,对懒加载有影响(可选)
******************************************************************************************************************

17:15 2012/3/29
U_User.hbm.xml文件讲解
---------------------------------------------------------------
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping
    package="cn.flame.hibernate.domain">

    <class name="U_User" >
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
        <property name="birthday"/>
    </class>
</hibernate-mapping>
---------------------------------------------------------------
package属性用来指定映射文件位置。
class标签代表一个类,用name属性来指定类名;table用来指定表名称(缺省表示类名与标明一致);
id标签,用于映射主键,代表数据库里的主键。name属性指定类的属性;column属性指定列名。
generator标签指定主键生成器,class指定主键生成器类型。
property标签指定类中属性。column属性用来指定类中列名。

******************************************************************************************************************

11:30 2012/3/30
基本概念和CURD
一、Session的几个主要方法
1、save,persist保存数据,persist在事务外不会长生insert语句。
2、delete,删除对象
3、update,更新对象,如果数据库中没有记录,会出现异常。
4、get,根据ID查询,会立刻访问数据库。
5、Load,根据ID查询,(返回的是代理,不会立即访问数据库)。
6。saveOrUpdate,merge(根据ID和version的值来确定是save或update),调用merge时对象还是托管的。
7、lock(把对象变成持久对象,但不会同步对象的状态)。

******************************************************************************************************************

13:39 2012/3/30
对象状态
1、瞬间(transient):数据库中没有数据与之对应,超过作用于会被JVM垃圾回收器回收,一把是new出来且与session没有关联的对象。
2、持久(persistent):数据库中有数据与之对应,当前与session有关联,并且相关联的session没有关闭,事务没有提交;持久对象状态发生改变,在事务提交时会影响到数据库(hibernate能检测到)。
3、脱管(deached):数据库中有数据与之对应,但当前没有session与之关联;脱管对象状态发生改变,hibernate不能检测到。
注意:可以通过对session与datetable的关系来判断对象状态。
******************************************************************************************************************

16:01 2012/3/30
HQL和Criteria
HQL(Hibernate Query Language)
面向对象的查询语言,与SQL不同,HQL中的对象名是区分大小写的(除了JAVA累和属性其他部分不区分大小写);HQL中查的是对象而不是集合表,并且支持多态;HQL主要通过Query来操作,Query的创建方式:
Query q = session.createQuery(hql);
1、from Person
2、from User user where user.name=:name
3、from User user where user.name=:name and user.birthday <: birthday

-------------------------------------------------------------------------

Criteria
Criteria是一种比HQL更面向对象的查询方式;
Criteria的创建方式:
Criteria crit = session.createCriteria(DomainClass.class);
简单属性条件如:criteria.add(estrictions.eq(propertyName,value)),
criteria.add(Restrictions.eqProperty(propertyName,otherPropertyName));
******************************************************************************************************************

17:46 2012/3/30
HQL例子
private static void test2(String name){
        Session s = null;
        try{
            s = HibernateUtil.getSession();
            String hql = "from U_User as u_user where u_user.name=?";
            Query query = s.createQuery(hql);
            query.setString(0, name);
            /*List<U_User> list = query.list();
            for(U_User user : list){
                System.out.println(user.getId());
                System.out.println(user.getName());
                System.out.println(user.getBirthday());
            }*/
            Object obj = query.uniqueResult();
            System.out.println(obj);
            U_User u = (U_User) s.get(U_User.class, 1);
            if(u != null){
                System.out.println(u.getName());
            }
            
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            if(s!=null)
                s.close();
        }
    }
******************************************************************************************************************


9:30 2012/3/31
Query查询实现分页
query.setFirstResult(0);表示从哪一条数据开始去。
query.setMaxResult(10);去第一条数据开始,去多少条。
******************************************************************************************************************

10:19 2012/3/31
Criteria条件查询接口

******************************************************************************************************************

10:40 2012/3/31
基本功能练习
实现UserDao
public interface UserDao{
    public void saveUser(User user);
    public User findUserById(int id);
    public User findUserByName(String name);
    public void updateUser(User user);
    public void remove(User user);
}
******************************************************************************************************************

13:50 2012/3/31
关联映射
1、多对一(Employee - Department)
映射文件<many-to-one name="depart" column="depart_id" />
---------------------------------------------------------------
2、一对多(Department - Employee)
映射文件
<set name="emps">
    <key column="depart_id" />
    <one-to-many class="Employee" />
</set>
---------------------------------------------------------------
3、一对一(Person - IdCard)
1)、基于主键的one-to-one(person的映射文件)
<di name="id">
    <generator class="foreign">
        <param name="property">idCard</param>
    </generator>
</id>
<one-to-one name="idCard" constrained="true" />
2)基于外键的one-to-one,可以描述为多对一,家unique="true"约束
<one-to-one name="idCard" property-ref="person" />
<many-to-one name="person" column="person_id" unique="true" not-null="true" />
注:hibernate依赖于映射工作。
---------------------------------------------------------------
4、多对多(teacher - student)
在操作和性能方面都不太理想,所以多对多的映射使用较少,实际使用中最好转换成一对多的对象模型;
hibernate会为我们创建中间关联表,转换成两个一对多。
<set name="teacher" table="teacher_student" >
    <key column="teacher_id" />
    <many-to-many class="Student" column="student_id" />
</set>
---------------------------------------------------------------
5、组件映射(User - Name)
关联的属性是一个复杂类型的持久化类,但不是实例即:数据库中没有表与该属性对应,但该类的属性要持久保存的。
<component name="name" class="com.test.hibernate.domain.Name" >
    <property name="initial" />
    <property name="first" />
    <property name="last" />
</component>
当组件的属性不能喝表中的字段简单对应的时候可以选择实现:
org.hibernate.usertype.UserType 或
org.hibernate.usertype.CompositeUserType
---------------------------------------------------------------
6、集合映射(set,list,map,bag)
<set name="employees" >
    <key column="depart_id">
    <one-to-many class="Employee" />
    <!-- <element type="string" column="name" /> -->
    <!--
        <composite-element class="YourClass">
            <property name="prop1" />
            <property name="prop2" />
        </composite-element>
    -->
</set>
注:用list集合,hibernate会在数据表中增加一列,用来保存顺序。
bag映射与list是对应的,如果用的是bag映射,那么集合类必须采用list。
1、这些集合都是hibernate实现的类和java中的集合类不完全一样,set,list,map分别和java中的set,list,map接口对应,bag映射成java的list;这些集合的使用和java集合中对应的接口基本一致;在java的实体类中集合只能定义成接口不能定义成具体类,因为集合会在运时被替换成hibernate的实现类。
2、集合的简单使用原则:大部分情况下用set,需要保证集合中的顺序用list,想用java.util.List又不需要保证顺序时用bag。
---------------------------------------------------------------
private Map<String,Employee> emps;

<map name="emps" >
    <key column="depart_id" />
    <map-key type="string" column="name" />
    <ont-to-many class="Employee" />
</map>
---------------------------------------------------------------
private List<Employee> emps;

<bag name="emps" />
    <key column="depart_id />
    <one-to-many class="Employee" />
</bag>
---------------------------------------------------------------
private List<Employee> emps;

<list name="emps">
    <key column="depart_id" />
    <list-index column="order_col" />
    <one-to-many class="Employee" />
</list>
---------------------------------------------------------------
private Set<Employee> emps;

<set name="emps" >
    <key column="depart_id" />
    <one-to-many class="Employee" />
</set>
---------------------------------------------------------------
级联和关系维护
7、inverse和cascade(Employee - Department)
1、Casade用来说明当对主对象进行某种操作时是否对其关联的从对象也作类似的操作,常用的cascade;
none,all,save-update,delete,lock,refresh,evict,replicate,persist,merge,delete-orphan(one-to-many)。
一般对于many-to-one,many-to-many不设置级联,在<one-to-one>和<one-to-many>中设置级联。
2、inverse表是“是否放弃维护关联关系”(在java里两个对象产生关联时,对数据库表的影响),在one-to-many和many-to-many的集合定义中使用,inverse="true"表示该对象不维护关联关系;该属性的值一般在使用有序集合时设置成false(注意hibernate的缺省值是false)。one-to-many维护关联关系就是更新外键。
many-to-many维护关联关系就是在中间表增减记录。
注:配置成one-to-one的对象不维护关联关系。

******************************************************************************************************************

1:18 2012/4/3
传智播客提醒:大家在学习hibernate如何处理对象之间的关联关系的底层细节hi,可以从两方面去思考:
一是如何将对象之间的关联关系保存到数据库中;
二是如何检索出关联的对象。
******************************************************************************************************************

11:49 2012/4/3
继承关系
<class name="Employee" >
    <id name="id">
        <generator class="native" />
    </id>
    <discriminator clumn="type" type="string" />
    <property name="name" unique="true" />
    <many-to-one name="depart" column="depart_id" />
    <subclass name="Skiller" >
        <property name="skill" />
    </subclass>
    <subclass name="Sales" >
        <property name="sell" />
    </subclass>
</class>
上述映射代码简述了一个员工表(Employee)被两个对象继承(Skiller,Sales)的映射关系。type字段用于区分员工类型。
这样会导致一个表内有很多空字段,解决方案是把公共部分放在一个表中,其余的子类放在各自的表中,子表继承主表。
这是后hibernate的映射关系代码为:
<class name="Employee" >
    <id name="id">
        <generator class="native" />
    </id>
    <discriminator clumn="type" type="string" />
    <property name="name" unique="true" />
    <many-to-one name="depart" column="depart_id" />
    <joined-subclass name="Skiller" table="skiller">
        <key column="emp_id" />
        <property name="skill" />
    </joined-subclass>
    <joined-subclass name="Sales" table="sales">
        <key column="emp_id" />
        <property name="sell" />
    </joined-subclass>
</class>
******************************************************************************************************************

12:47 2012/4/3
鉴别器
指定列的字段类型,如:
<subclass name="Skiller" discriminator-value="1" >
合并表
<union-subclass name="Skiller" table="skiller" >
    <property name="skill" />
</union-subclass>
******************************************************************************************************************

14:57 2012/4/3
hibernate的load方法和懒加载。
在调用hibernate的load方法时,hibernate会生成一个类并继承原有的实体类,将该类装饰为懒加载模式。
有load方法时不能判断返回代码是否为null,该策略是无效的。
load方法只有在访问对象内容时load才回去访问数据库,故需要执行Hibernate.initialize(user);的方法来初始化。
---------------------------------------------------------
通过asm和cglib两个包实现;Domain是非final的。
1、session.load懒加载。
2、one-to-one(元素)懒加载;
    必须同时满足下面三个条件时才能实现懒加载
    (主表不能有constrained=true,所以主表没有懒加载)
    1)、lazy!=false
    2)、constrained=true
    3)、fetch=select
3、one-to-many(元素)懒加载
    1)、lazy!=false
    2)、fetch=select
4、many-to-one(元素):
    1)、lazy!=false
    2)、fetch=select
5、many-to-many(元素):
    1)、lazy!=false
    2)、fetch=select
6、能够懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象(代理对象)的属性(getId和getClass除外)hibernate会初始化这些代理,或用Hibernate.initialize(proxy)来初始化代理对象;当相关联的session关闭后,再访问懒加载的对象将出现异常。
******************************************************************************************************************

19:54 2012/4/3
缓存
缓存的作用主要用来提高性能,可以简单的理解成一个Map;
使用缓存设计到三个操作:把数据放入缓存、从缓存中获取数据、删除缓存中的无效数据。

一级缓存,Session级共享。
save,update,saveOrUpdate,load,get,list,iterate,lock这些方法都会将对象放在一级缓存中,一级缓存不能控制缓存的数量,所以要注意大批量数据操作时可能造成内存溢出;
可以用evict.clear方法清除缓存中的内容。

二级缓存,SessionFactory级共享。
1、实现为可插拔,通过修改cache.provider_class参数来改变;
hibernate内置了对EhCach,OSCache,TreeCache,SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入Hibernate不支持的缓存实现。
2、在hibernate.cfg.xml中加入:
<class-cache class="className" usage="read-only" />
或在映射文件的class元素加入子元素:
<cache usage="read-write" />
其中usage:read-only,read-write,nonstrict-read-write,transactional
3、Session的:save(这个方法适合native生成方式的主键),update,saveOrUpdate,list,iterator,get,load,以及Query,Criteria都会填充二级缓存,但只有(没打开查询缓存时)Session的iterator.get,load会从二级缓存中去数据(iterator可能存在N+1次查询)。
4、Query,Criteria(查询缓存)由于命中率较低,所以hibernate缺省是关闭;
修改cache.use_query_cache为true打开对查询的缓存,并且调用query.setCacheable(true)或criteria.setCacheable(true)。
5、SessionFactory中提供了evictXXX()方法用来清除缓存中的内容。
6、统计信息打开generate_statistics,用sessionFactory.getSatistics()获取统计信息。
******************************************************************************************************************

22:28 2012/4/3
分布式缓存和中央缓存
一、使用缓存的条件
1、读取大于修改
2、数据量不能超过内存容量
3、对数据要有独享的控制
4、可以容忍出现无效数据
******************************************************************************************************************

23:00 2012/4/3
事务
JDBCTransaction
单个数据库(yige1SessionFactory对应一个数据库),由JDBC实现。
Session session = null;
Transaction tx = null;
try{
    session = sessionFactory.openSession();
    tx = session.beginTransaction();
    //process
    tx.commit();
}catch(HibernateException e){
    if(tx!=null)tx.rollback();throw e;
}finally{
    if(session!=null)session.close();
}
connection.setAutoCommit(false);
connection.commit();
conn.rollback();
}
******************************************************************************************************************

23:05 2012/4/3
分布式事务
JTATransaction
可以简单的理解成跨数据库的事物,由应用JTA容器实现;使用JTATransaction需要配置hibernate.transaction.factory_class参数,该参数缺省值是org.hibernate.transaction.JDBCTransactionFactory,当使用JTATransaction时需要将该参数改成
org.hibernate.transaction.JTATransactionFactory,并配置jta.UserTransaction参数JNDI名(Hibernate在启动JTATransaction时需要用该值到JNDI的上下文Context中去找javax.transaction.UserTransaction)。
javax.transaction.UserTransaction tx = context.lookup("jndiName");
try{
    tx.begin();
    //多个数据库的session操作;
    //session1...
    //session2...
    tx.commit();
}catch(Exception e){
    tx.rollback;
    throw e;
}

******************************************************************************************************************

23:30 2012/4/3
session context 和事务边界
用current_session_context_class属性来定义context(用sessionFactory.getCurrentSession()来获得session),其值为:
1、thread:ThreadLocal来管理Session实现多个操作共享一个Session,避免反复获取Session,并控制事务边界,此时session不能调用close,当commit或rollback的时候session会自动关闭(connection.release_mode:after_transaction)。
Open session in view:在生成(渲染)页面时保持session打开。
2、jta:由JTA事物管理器来管理事务(connection.release_mode:after_statement)。
******************************************************************************************************************

23:36 2012/4/3
悲观锁和乐观锁
悲观锁有数据库来实现;
乐观锁hibernate用version和timestamp来实现
******************************************************************************************************************

1:16 2012/4/4
其他问题
一、数据类型:
1、<property name="name" type="java.lang.String" />
type可以使hibernate、java类型或者你自己定义的类型(需要实现hibernate的一个接口)。
2、基本类型一般不需要再映射文件(hbm.xml)中说明,只有在一个java类型和多个数据库数据类型相对应时并且你想要的和hibernate缺省映射不一致,需要在映射文件中指定类型(如:java.util.Date,数据库DATE,TIME,DATATIME,TIMESTAMP,hibernate缺省会把java.util.Date映射成DATATIME类型,而如果你想映射成TIME,则你必须在映射文件中指定类型)。
3、Session是非线程安全的,生命周期较短,代表一个和数据库的连接,在B/S系统中一般不会超过一个请求;内部维护一级缓存和数据库连接,如果session长时间打开,会长时间占用内存和数据库连接。
4、SessionFactory是线程安全的,一个数据库对应一个SessionFactory,声明周期长,一般在整个系统生命周期内有效;SessionFactory保存着和数据库连接的相关信息(user,password,url)和映射信息,以及hibernate运行时要用到的一些信息。
5、flush时将一级缓存与数据库同步
6、大批处理
大量操作数据时可能造成内存溢出,解决方法如下:
1)、清除session中的数据
for(int i=0;i<100000;i++)session.save(obj);
for(int i=0;i<100000;i++){
    session.save(obj);
    if(i%50 == 0){
        session.flush();
        session.clear();
    }
}
2)、用StatelessSession接口:它不和一级缓存、二级缓存交互,也不触发任何事件、监听器、拦截器,通过该接口的操作会立即发送该数据库,与JDBC的功能一样。
StatelessSession s = sessionFactory.openStatelessSession();该接口的方法与session类似。
3)、Query.executeUpdate()执行批量更新,会清除相关联二级缓存
(sessionFactory.evict(class)),也可能会造成级联,和乐观锁定出现问题

******************************************************************************************************************

1:39 2012/4/4
HQL
1、查询多个对象select art,user form Article art ,User user where art.author.id=user.id and art.id=:id这种方式返回的是object[],Object[0]:article,Object[1]:user,
2、分页query.setFirstResult,query.setMaxResults查询记录总数query.iterate("selectcount(*) from Person").next()
3、批量更新query.executeUpdate()可能造成二级缓存有无效数据。
******************************************************************************************************************

1:42 2012/4/4
Criteria
1、排序Criteria.addOrder(Order.desc(propertyName));
2、关联查询criteria.setFetchMode("propertyName",FetchMode.SELECT)与映射文件中关联关系的fetch作用一致。
3、投影Projections.rowCount(),max(propertyName),avg,groupProperty...
4、分页Projections.rowCount(),criteria.setFirstResult(),criteria.setMaxResult()
5、DetachedCriteria可在session外创建(在其他层创建比如在service中创建)然后用getExecutableCriteria(session)方法创建Criteria对象来完成查询。
6、Example查询,Example.create(obj);criteria.add(example)。
******************************************************************************************************************

1:50 2012/4/4
一、N+1查询和懒加载
1、用Query.iterator可能会有N+1次查询。
2、懒加载时获取关联对象
3、如果打开对查询的缓存即使用list也可能有N+1次查询。
二、拦截器与事件
拦截器与事件都是hibernate的扩展机制,Interceptor接口是老的实现机制,现在改成事件监听机制;他们都是hibernate的回调接口,hibernate在save,delete,update...等会回调这些类。
4、SQL和命名查询
5、用Map替换Domain对象;将对象转换为XML。

******************************************************************************************************************

0:13 2012/4/5
监听器
监听数据保存(save方法)。
实现接口SaveOrUpdateEventListener

在hibernate.cfg.xml中配置
<event type="save" >
    <listener class="cn.flame.hibernate.SaveListener" />
    <listener class="org.hibernate.event.def.DefaultSaveOrUpdateEventListener" />
</event>
******************************************************************************************************************

0:24 2012/4/5
映射文件中HQL的使用
<query name="getUserByBirthday" >
    <![CDATA[from User where birthday="birthday]]>
</query>
获取方式
Query q = s.getNamedQuery("getUserByBithday");
q.setDate("birthday",new Date());
******************************************************************************************************************

0:30 2012/4/5
Hibernate不适合的场景
1、不适合OLAP(On-Line Analytical Processing联机分析处理),以查询分析数据为主的系统;
适合OLTP(On-line transaction processing联机事务处理)。
2、对于一些关系模型设计不合理的老系统,也不能发挥hibernate优势。
3、数据量巨大,性能要求苛刻的系统,hibernate也很难达到要求,批量操作数据的效率也不高。
******************************************************************************************************************
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值