讨论:在DAO中对Hibernate的封装

我在现在的项目中在DAO层中对Hiberante做了如下封装:
用一个HibernateDAO封装一些共同的操作:

代码
  1. package org.skyinn.commons.dao;   
  2.   
  3. import net.sf.hibernate.HibernateException;   
  4. import net.sf.hibernate.Session;   
  5. import net.sf.hibernate.Transaction;   
  6.   
  7. /**   
  8.  * <p>HibernateDAO.</p>  
  9.  *    
  10.  * <b>Description:</b>  
  11.  * 封装Hibernate的会话、事务的通用操作,以简化相应DAO中的相关操作。   
  12.  *   
  13.  * 具体实现DAO的示例代码如下:   
  14.  * <code><pre>  
  15.  * public Account register(final Account account) throws DAOException {   
  16.  *     try {   
  17.  *         //事务开始   
  18.  *         beginTransaction();   
  19.  *         //具体操作   
  20.  *         //注册帐号   
  21.  *         //getSession().save(account);   
  22.  *         //结束事务   
  23.  *         endTransaction(true);   
  24.  *     } catch (HibernateException he) {   
  25.  *         //但发生异常时事务回滚   
  26.  *         rollback();   
  27.  *         log.error(he);   
  28.  *         //向调用者抛出异常   
  29.  *         throw new DAOException(he);   
  30.  *     } finally {   
  31.  *         //结束会话   
  32.  *         closeSession();   
  33.  *     } //end try...catch...finally....   
  34.  *     return account;   
  35.  * } //end register()   
  36.  * </pre></code>  
  37.  *   
  38.  * @author HanQing   
  39.  * @version $Revision: 1.3 $ $Date: 2003/08/08 11:44:31 $   
  40.  */   
  41. public abstract class HibernateDAO implements DAO {   
  42.   
  43.     protected Session session = null;   
  44.     protected Transaction transaction = null;   
  45.        
  46.     /**   
  47.      * Returns the session.   
  48.      * @return Session   
  49.      */   
  50.     public Session getSession() {   
  51.         return session;   
  52.     }   
  53.   
  54.     /**   
  55.      * Returns the transaction.   
  56.      * @return Transaction   
  57.      */   
  58.     public Transaction getTransaction() {   
  59.         return transaction;   
  60.     }   
  61.   
  62.     /**   
  63.      * 开始事务。   
  64.      *   
  65.      * @throws HibernateException 创建事务时的异常   
  66.      */   
  67.     public void beginTransaction() throws HibernateException {   
  68.         //创建对话   
  69.         session = HibernateSessionFactory.openSession();   
  70.         //事务开始   
  71.         transaction = session.beginTransaction();   
  72.     }//end beginTransaction()   
  73.   
  74.     /**   
  75.      * 结束事务。   
  76.      *   
  77.      * @param commit 是否提交,但只是读取操作时不需要事务提交,故设为false以提高相应速度   
  78.      * @throws HibernateException  结束事务时的Hibernate异常   
  79.      * @throws DAOException 结束会话时的DAO异常   
  80.      */   
  81.     public void endTransaction(boolean commit)   
  82.         throws HibernateException,DAOException {   
  83.                
  84.         if (commit) {   
  85.             transaction.commit();   
  86.         } else {   
  87.             // Don't commit the transaction, can be faster for read-only operations   
  88.             transaction.rollback();   
  89.         }   
  90.         closeSession();   
  91.     }//end endTransaction()   
  92.   
  93.     /**   
  94.      * 事务回滚,但事务出错时回滚当前操作。   
  95.      *   
  96.      * @throws DAOException 回滚中出现的异常   
  97.      */   
  98.     public void rollback() throws DAOException{   
  99.         if(null != transaction){   
  100.             try {   
  101.                 transaction.rollback();   
  102.             } catch (HibernateException e) {   
  103.                 throw new DAOException(e);   
  104.             }   
  105.         }   
  106.     }//end rollback()   
  107.   
  108.     /**   
  109.      * 结束会话。   
  110.      *   
  111.      * @throws DAOException 结束会话时出现的异常   
  112.      */   
  113.     public void closeSession() throws DAOException{   
  114.         try {   
  115.             session.close();   
  116.         } catch (HibernateException e) {   
  117.             throw new DAOException(e);   
  118.         }   
  119.     }//end closeSession()   
  120. }//EOC HibernateDAO   
<script type="text/javascript">render_code();</script>

 

在具体的DAO中,用有个DAO接口来定义方法(如AccountDAO),
从HiberanteDAO继承并实现AccountDAO,做具体的持久层操作:

代码
  1. package org.skyinn.test.user.dao;   
  2.   
  3. import org.skyinn.commons.dao.DAOException;   
  4. import org.skyinn.commons.dao.HibernateDAO;   
  5. import org.skyinn.commons.test.user.bean.Account;   
  6. import net.sf.hibernate.HibernateException;   
  7. import net.sf.hibernate.Query;   
  8. import org.apache.commons.logging.Log;   
  9. import org.apache.commons.logging.LogFactory;   
  10.   
  11. import java.util.Iterator;   
  12.   
  13. /**   
  14.  * <p>AccountDAO的Hibernate实现。</p>  
  15.  *   
  16.  * @version $Revision: 1.6 $ $Date: 2003/08/09 09:41:33 $   
  17.  * @author $Author: hanqing $   
  18.  */   
  19. public final class AccountDAOHibImpl   
  20.     extends HibernateDAO   
  21.     implements AccountDAO {   
  22.   
  23.     /**日志。*/   
  24.     private static Log log = LogFactory.getLog(AccountDAOHibImpl.class);   
  25.   
  26.     //~ Methods ================================================================   
  27.   
  28.     public Account findAccount(String accountName) throws DAOException{   
  29.         Account account = null;   
  30.         final String queryString =   
  31.             "select account from Account as account "  
  32.                 + " where account.accountName = :accountName";   
  33.         try {   
  34.             //验证帐号   
  35.             beginTransaction();   
  36.             final Query query = getSession().createQuery(queryString);   
  37.             //设置参数   
  38.             query.setString("accountName", accountName);   
  39.             //Execute Query...   
  40.             final Iterator it = query.iterate();   
  41.             if (it.hasNext()) {   
  42.                 account = (Account) it.next();   
  43.             }   
  44.             //结束事务,false表示不提交   
  45.             endTransaction(false);   
  46.         } catch (HibernateException he) {   
  47.             //但发生异常时事务回滚   
  48.             rollback();   
  49.             log.error(he);   
  50.             throw new DAOException(he);   
  51.         } finally {   
  52.             //结束会话   
  53.             closeSession();   
  54.         } //end try...catch...finally....   
  55.         return account;   
  56.     }//end findAccount()   
  57.   
  58.     public Account findAccount(long accountId) {   
  59.         return null;   
  60.     }   
  61.   
  62.     /**   
  63.      * @see AccountDAO#register   
  64.      */   
  65.     public Account register(final Account account) throws DAOException {   
  66.         try {   
  67.             //事务开始   
  68.             beginTransaction();   
  69.             //创建帐号   
  70.             session.save(account);   
  71.             //结束事务   
  72.             endTransaction(true);   
  73.         } catch (HibernateException he) {   
  74.             //但发生异常时事务回滚   
  75.             rollback();   
  76.             log.error(he);   
  77.             throw new DAOException(he);   
  78.         } finally {   
  79.             //结束会话   
  80.             closeSession();   
  81.         } //end try...catch...finally....   
  82.         return account;   
  83.     } //end register()   
  84.   
  85.     /**   
  86.      * @see AccountDAO#login   
  87.      */   
  88.     public Account login(final Account account) throws DAOException {   
  89.         Account _account = null;   
  90.         final String queryString =   
  91.             "select account from Account as account "  
  92.                 + " where account.accountName = :accountName"   
  93.                 + " and account.password = :password";   
  94.         try {   
  95.             //验证帐号   
  96.             beginTransaction();   
  97.             final Query query = getSession().createQuery(queryString);   
  98.             //设置参数   
  99.             query.setString("accountName", account.getAccountName());   
  100.             query.setString("password", account.getPassword());   
  101.             //Execute Query...   
  102.             final Iterator it = query.iterate();   
  103.             if (it.hasNext()) {   
  104.                 _account = (Account) it.next();   
  105.             }   
  106.             //结束事务,false表示不提交   
  107.             endTransaction(false);   
  108.         } catch (HibernateException he) {   
  109.             //但发生异常时事务回滚   
  110.             rollback();   
  111.             log.error(he);   
  112.             throw new DAOException(he);   
  113.         } finally {   
  114.             //结束会话   
  115.             closeSession();   
  116.         } //end try...catch...finally....   
  117.         return _account;   
  118.     } //end login()   
  119. } //EOC AccountDAOHibImpl   
<script type="text/javascript">render_code();</script>

 

在业务逻辑层中通过DAO工厂来取这些DAO并执行之,这里有几个问题不是很确信:
1:在DAO中每个方法都需要创建一个session和tx,这在一般应用中问题不大,我考虑的是但业务逻辑层有如果需要调用多个DAO的方法时,岂不需要多个session和tx,这样其实也就美了事务的的操作了,所以想请教有没有好的解决方案;)

2:昨天才想到,其实对对象的CRUD来说,CUD方法非常的类似,
而通过Hiberante一封装,变得更抽象了,比如CREATE方法都可以抽象成:

代码
  1. ...   
  2. //事务开始   
  3. beginTransaction();   
  4. //创建对象   
  5. session.save(obj);   
  6. //结束事务   
  7. endTransaction(true);   
  8. ...   
<script type="text/javascript">render_code();</script>

 

但还没做更进一步的研究和试验,不知道是否可以实现,如果可以的话,把这三个方法再往上一抽,剩下的DAO实现中就只要完成一些查询等就够了,HOHO,哪岂不很爽》?呵呵:)回头试试去:)

嗯,还有很多问题,大家一起来讨论下:)

20:28  |   永久链接  |   浏览 (50653)  |   评论 (62)  |    收藏  |   进入论坛  |    
评论    共 62 条 发表评论
justfly     2004-07-20 12:49

一个简单的设计,UML图标不是很标准,DAO的一些方法也都没有写出来。还有返回值什么的都没有斟酌

tommyhero     2004-07-24 14:56

 

kk_kkk 写道
哎,讨论了这么长时间,也没有人牵个头把总结一下Dao封装hibernate的一个合理的可行的方案并整理出代码大家分享一下啊。对那些已经很熟悉hibernate的朋友来说,这个该不是很难的事情吧。讨论技术上的问题是正确的,但是如果有相关的代码作为辅助说明的话,大家理解起来才不会吃力吧?!不知大家怎么认为?

强烈支持!这样对新手很有帮助d!

 

LuluWang     2004-07-24 18:08

Spring对Hibernate进行了封装,
其中的

代码
  1. org.springframework.orm.hibernate.support.HibernateDaoSupport  
<script type="text/javascript">render_code();</script> 比较符合讨论的主题。

 

还有:

代码
  1. org.springframework.orm.hibernate.LocalSessionFactoryBean   
  2. org.springframework.orm.hibernate.HibernateTransactionManager  
<script type="text/javascript">render_code();</script>
看名字就能猜出它们的作用了。

 

DAO中的代码大概是这个样子:

代码
  1. public class EmployerHibernateDAO extends HibernateDaoSupport   
  2.         implements  IEmployerDAO {   
  3.   
  4.     public EmployerHibernateDAO() {   
  5.         super();   
  6.     }   
  7.   
  8.     public Employer saveOrUpdateEmployer(final Employer employer) {   
  9.         getHibernateTemplate().saveOrUpdate(employer);   
  10.         return employer;   
  11.     }   
  12. // ...   
  13. }   
<script type="text/javascript">render_code();</script>

 

Service中的代码大概是这个样子:

代码
  1. public class EmpServiceSpringImpl implements IEmpService {   
  2.   
  3.     private IEmployeeDAO employeeDAO;   
  4.     private IEmployerDAO employerDAO;   
  5.     private IEmploymentDAO employmentDAO;   
  6.        
  7.        
  8.     public EmpServiceSpringImpl() {   
  9.         super();   
  10.     }   
  11.   
  12.     public Employer addEmployer(Employer employer) throws EmpException {   
  13.         try{   
  14.             getEmployerDAO().saveOrUpdateEmployer(employer);   
  15.         }   
  16.         catch (RuntimeException e) {   
  17.             throw new EmpException("Could not addEmployer " + e.toString());   
  18.         }   
  19.         return employer;   
  20.     }   
  21. //...   
  22. }   
<script type="text/javascript">render_code();</script>

 

什么连接、关闭、提交、回滚。。。统统地不要写了,在xml中说明一下就行了。

对于Spring+Hibernate,我也是初学乍练。有同道中人吗?切磋一下。

btw:
1、在这里提及Spring,不知坛主是否有意见?
2、论坛的时钟好像快了10多分钟,按照这个时间下班。。。

sinx     2004-07-26 18:32

 

Haiqing 写道
robbin 写道
我其实不是很同意你的封装方法,当然意见可以有分歧,不过你的思路很不错,所以放进精华文章。

 

面向对象应该根据业务逻辑来封装对象,而不是按照使用的API来封装,我的面向对象的设计思路都写在 《面向对象的思维方法》

http://hibernate.fankai.com/viewtopic.php?t=38

里面。我考虑封装的出发点和你不太一样的。

 

引用
1:在DAO中每个方法都需要创建一个session和tx,这在一般应用中问题不大,我考虑的是但业务逻辑层有如果需要调用多个DAO的方法时,岂不需要多个session和tx,这样其实也就美了事务的的操作了,所以想请教有没有好的解决方案;)

 

可以用ThreadLocal 来管理Session,论坛有这方面的几个帖子,你用论坛搜索功能搜一下。

 

我没看过hibernate源码,但感觉hibernate已经封装了domain store,完成了底层dao的持久化,所以完全可以把映射类直接参与业务逻辑transparently,而不必再次把hibernate类封装在dao模式中,当然会有诸如session.update()等难以避免的操作。我才接触hibernate,望多多指教。

 

l_walker     2004-07-27 10:56

哇,这么都回复了啊;)
上半年被项目整死,现在有点空了,好好看看先:)

现在我更倾向于使用Spring来处理业务,见我的blog:
http://www.skyinn.org/wiki/Wiki.jsp?page=Java_blogentry_270704_1

目前还在学习Spring中,故而很多东西还不知道

l_walker     2004-07-27 11:17

现在回过头来看看当初自己的这个设计,确实存在太多的弊端,
在我们的项目中,最大的问题不是哪里去获得session等,而是事务的处理,
对于业务层,他并不知道后台persistence层到底是hibernate还是纯jdbc,因为都是面向接口的,透明处理了,然而当需要处理事务的时候就出现麻烦了,
因为如果将session放在业务层,那么势必增加业务层和持久层间的耦合(至少得传session)。。。

而现在我们的做法是使用spring来管理事务等,见上面我的回复,

唉,如果当初就了解点spring的话。。。。。

mikecool     2004-08-10 01:27

是应该去用Spring,很方便也很透明,对Hibernate的支持也没话说

不过你以前的思路还是给了我很多启发,我也遇到过这个问题,就是事务的判断,如果自己写是需要一些技巧和方法

mikecool     2004-08-11 19:30

我的写的代码是

代码
  1. /*  
  2.  * Created on 2004-8-9  
  3.  */  
  4. package com.genewoo.hibernate;   
  5.   
  6. import java.io.Serializable;   
  7. import java.util.Iterator;   
  8. import java.util.List;   
  9.   
  10. import net.sf.hibernate.HibernateException;   
  11. import net.sf.hibernate.LockMode;   
  12. import net.sf.hibernate.Session;   
  13. import net.sf.hibernate.Transaction;   
  14.   
  15. /**  
  16.  * @author jian_wu  
  17.  */  
  18. public abstract class BasicDAO implements IBasicDAO {   
  19.   
  20.     protected static Session session;   
  21.     private static int tranLevel = 0;   
  22.   
  23.     private static Transaction transaction;   
  24.        
  25.     static{   
  26.         try {   
  27.             session = HibernateSessionFactory.currentSession();   
  28.         } catch (DataAccessException e) {   
  29.             e.printStackTrace();   
  30.         }   
  31.     }   
  32.        
  33.     /**  
  34.      * @throws DataAccessException  
  35.      *    
  36.      */  
  37.     public BasicDAO() throws DataAccessException {   
  38.         super();   
  39.     }   
  40.   
  41.     protected static void beginCurrentTransaction() throws HibernateException {   
  42.         tranLevel++;   
  43.         if (transaction == null) {   
  44.             transaction = session.beginTransaction();   
  45.         }   
  46.     }   
  47.   
  48.     protected static void endCurrentTransaction() {   
  49.         endCurrentTransaction(true);   
  50.     }   
  51.   
  52.     protected static void endCurrentTransaction(boolean isCommit) {   
  53.         try {   
  54.             if (transaction != null) {   
  55.                 if (isCommit) {   
  56.                     tranLevel--;   
  57.                     if (tranLevel == 0) {   
  58.                         transaction.commit();   
  59.                         transaction = null;   
  60.                     }   
  61.                 } else {   
  62.                     tranLevel = 0;   
  63.                     transaction.rollback();   
  64.                     transaction = null;   
  65.                 }   
  66.             }   
  67.         } catch (HibernateException e) {   
  68.             transaction = null;   
  69.         }   
  70.   
  71.     }   
  72.   
  73.     public void insertObjectList(List list) throws DataAccessException {   
  74.         updateList(list, IBasicDAO.ACTION_INSERT);   
  75.     }   
  76.   
  77.     public void insertSingleObject(Object object) throws DataAccessException {   
  78.         updateObject(object, IBasicDAO.ACTION_INSERT);   
  79.     }   
  80.   
  81.     public void removeObject(Object object) throws DataAccessException {   
  82.         updateObject(object, IBasicDAO.ACTION_REMOVE);   
  83.     }   
  84.   
  85.     public void removeObjectList(List list) throws DataAccessException {   
  86.         updateList(list, IBasicDAO.ACTION_REMOVE);   
  87.     }   
  88.   
  89.     private void updateList(List list, String action) throws DataAccessException {   
  90.         try {   
  91.             beginCurrentTransaction();   
  92.             for (Iterator iter = list.iterator(); iter.hasNext();) {   
  93.                 Object vo = (Object) iter.next();   
  94.                 if (action.equalsIgnoreCase(IBasicDAO.ACTION_UPDATE)) {   
  95.                     session.saveOrUpdate(vo);   
  96.                     session.flush();   
  97.                     session.lock(vo, LockMode.UPGRADE);   
  98.                 } else {   
  99.                     session.delete(vo);   
  100.                 }   
  101.             }   
  102.             endCurrentTransaction();   
  103.         } catch (HibernateException e) {   
  104.             e.printStackTrace();   
  105.             throw new DataAccessException("ERROR IN " + action + " List");   
  106.         }   
  107.     }   
  108.   
  109.     private void updateObject(Object vo, String action)   
  110.             throws DataAccessException {   
  111.         try {   
  112.   
  113.             beginCurrentTransaction();   
  114.             if (action.equalsIgnoreCase(IBasicDAO.ACTION_UPDATE)) {   
  115.                 session.saveOrUpdate(vo);   
  116.                 session.flush();   
  117.                 session.lock(vo, LockMode.UPGRADE);   
  118.             } else {   
  119.                 session.delete(vo);   
  120.             }   
  121.             endCurrentTransaction();   
  122.         } catch (HibernateException e) {   
  123.             e.printStackTrace();   
  124.             throw new DataAccessException("ERROR IN " + action + " Object");   
  125.         }   
  126.     }   
  127.        
  128.   
  129.     public Object selectSingleObjectByPK(Class c, Serializable pk)   
  130.             throws DataAccessException {   
  131.         try {   
  132.             return session.get(c, pk);   
  133.         } catch (HibernateException e) {   
  134.             e.printStackTrace();   
  135.             throw new DataAccessException("Error get object");   
  136.         }   
  137.     }   
  138.   
  139.     public void updateObjectList(List list) throws DataAccessException {   
  140.         updateList(list, IBasicDAO.ACTION_UPDATE);   
  141.     }   
  142.   
  143.     public void updateSingleObject(Object object) throws DataAccessException {   
  144.         updateObject(object, IBasicDAO.ACTION_UPDATE);   
  145.   
  146.     }   
  147. }   
<script type="text/javascript">render_code();</script>
现在跑起来是没问题的,主要的疑问在于static方面的使用,我在这方面总有些缺乏经验
担心Session和Transaction是非线程安全,如果多线程读写会出现很大的问题,也在考虑是否把对数据的操作都做成静态方法,不知道好不好

 

willmac     2004-08-14 16:48

 

robbin 写道
我很早以前就说过n次的话题了。搜索一下最早的帖子。

 

简单的来说,如果你用EJB,那么用容器管理事务;如果不用EJB,那么用ThreadLocal来管理Session和Transaction。Transaction的提交和Session的关闭在ServletFilter里面完成。如果需要在一次用户请求过程中,完成n次Transaction的话(实际上很少发生这样的需求),就在DAOImpl里面提交Transaction。

总之,不管用不用EJB,你所有的Hibernate的代码都是封装在DAOImpl里面的,业务层不需要也不应该出现Hibernate的代码。


这里有误导,即使使用ejb如果事务没有移交到ejb容器的话,也是一定要做threadlocal的,注意以下选择根据需求发生变化,如果并发很大,没有写入问题
光是查询的话也可以不做threadlocal,反之用threadlocal来管理session试必不可少的

 

mgjava     2004-09-03 17:27

为啥不用Spring+Hibernate 那?

只要在DAO操作如下就可以了:

 

代码
  1. import org.springframework.orm.hibernate.support.HibernateDaoSupport;   
  2.   
  3. public class ReportingHibernateDAO   
  4.     extends HibernateDaoSupport   
  5.     implements IReportingDAO   
  6. {   
  7.     /**  
  8.      *   
  9.      */  
  10.     public ReportingHibernateDAO()   
  11.     {   
  12.         super();   
  13.         // TODO Auto-generated constructor stub   
  14.     }   
  15.     /**  
  16.     *   * @return List  
  17.     */  
  18.     public List getAngetCountbyRoot() throws Exception   
  19.     {   
  20.         List angetcount = new ArrayList();   
  21.         String hql =   
  22.             "select agent.id from Agent as agent where agent.uaid is NULL";   
  23.         try  
  24.         {   
  25.             angetcount = getHibernateTemplate().find(hql);   
  26.         }   
  27.         catch (Exception e)   
  28.         {   
  29.             // TODO Auto-generated catch block   
  30.             e.printStackTrace();   
  31.         }   
  32.         return angetcount;   
  33.     }   
  34.   
  35. }  
<script type="text/javascript">render_code();</script>

 

因为Spring已经支持了Hibernate的DAO操作,把事务都封装在了配置文件中:

 

代码
  1. <bean id="iReportingService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">      
  2.         <property name="transactionManager"><ref local="myTransactionManager"/></property>  
  3.         <property name="target"><ref local="iReportingTarget"/></property>  
  4.         <property name="transactionAttributes">  
  5.             <props>  
  6.                 <prop key="getReporting*">PROPAGATION_REQUIRED,readOnly,-OfficeException</prop>  
  7.                 <prop key="save*">PROPAGATION_REQUIRED,-OfficeException</prop>  
  8.                 <prop key="edit*">PROPAGATION_REQUIRED,-OfficeException</prop>  
  9.                 <prop key="del*">PROPAGATION_REQUIRED,-OfficeException</prop>  
  10.                 <prop key="getAllReporting">PROPAGATION_REQUIRED,-OfficeException</prop>  
  11.             </props>  
  12.         </property>  
  13.     </bean>  
  14.        
  15.     <!-- ADD iReportingTarget primary business object implementation -->  
  16.                                                                            
  17.     <bean id="iReportingTarget" class="com.uplus.service.spring.ReportingServiceSpringImpl">  
  18.         <property name="reportingDAO"><ref local="reportingDAO"/></property>  
  19.     </bean>  
  20.        
  21.     <!-- DAO object: Hibernate implementation -->  
  22.     <bean id="reportingDAO" class="com.uplus.service.dao.hibernate.ReportingHibernateDAO">  
  23.         <property name="sessionFactory"><ref local="mySessionFactory"/></property>  
  24.     </bean>           
  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值