Java Persistence with MyBatis 3(中文版) 第五章 与Spring集成

13 篇文章 1 订阅

    MyBatis-Spring是MyBatis框架的子模块,用来提供与当前流行的依赖注入框架Spring的无缝集成。

    Spring框架是一个基于依赖注入(Dependency Injection)和面向切面编程(Aspect Oriented Programming,AOP)的Java框架,鼓励使用基于POJO的编程模型。另外,Spring提供了声明式和编程式的事务管理能力,可以很大程度上简化应用程序的数据访问层(data access layer)的实现。在本章中,我们将看到在基于Spring的应用程序中使用MyBatis并且使用Spring的基于注解的事务管理机制。

 

本章将包含以下话题:

  • 在Spring应用程序中配置MyBatis

           安装

           配置MyBatis Beans

  • 使用SqlSession
  • 使用映射器
  • 使用Spring进行事务管理

5.1 在Spring应用程序中配置MyBatis

本节将讨论如何在基于Spring的应用程序中安装和配置MyBatis。

5.1.1 安装

如果你正在使用Maven构建工具,你可以配置MyBatis的spring依赖如下:

[html]  view plain  copy
 print ?
  1. <dependency>  
  2.   <groupId>org.mybatis</groupId>  
  3.   <artifactId>mybatis-spring</artifactId>  
  4.   <version>1.2.0</version>  
  5. </dependency>  
  6. <dependency>  
  7.   <groupId>org.springframework</groupId>  
  8.   <artifactId>spring-context-support</artifactId>  
  9.   <version>3.1.3.RELEASE</version>  
  10.   <exclusions>  
  11.     <exclusion>  
  12.       <groupId>commons-logging</groupId>  
  13.       <artifactId>commons-logging</artifactId>  
  14.     </exclusion>  
  15.   </exclusions>  
  16. </dependency>  
  17. <dependency>  
  18.   <groupId>org.springframework</groupId>  
  19.   <artifactId>spring-jdbc</artifactId>  
  20.   <version>3.1.3.RELEASE</version>  
  21. </dependency>  
  22. <dependency>  
  23.   <groupId>org.springframework</groupId>  
  24.   <artifactId>spring-test</artifactId>  
  25.   <version>3.1.3.RELEASE</version>  
  26.   <scope>test</scope>  
  27. </dependency>  
  28. <dependency>  
  29.   <groupId>org.aspectj</groupId>  
  30.   <artifactId>aspectjrt</artifactId>  
  31.   <version>1.6.8</version>  
  32. </dependency>  
  33. <dependency>  
  34.   <groupId>org.aspectj</groupId>  
  35.   <artifactId>aspectjweaver</artifactId>  
  36.   <version>1.6.8</version>  
  37. </dependency>  
  38. <dependency>  
  39.   <groupId>cglib</groupId>  
  40.   <artifactId>cglib-nodep</artifactId>  
  41.   <version>2.2</version>  
  42. </dependency>  
  43. <dependency>  
  44.   <groupId>commons-dbcp</groupId>  
  45.   <artifactId>commons-dbcp</artifactId>  
  46.   <version>1.4</version>  
  47. </dependency>  

    如果你没有使用Maven,你可以从http://code.google.com/p/mybatis/上下载mybatis-spring-1.2.0-boundle.zip。将其加入,将mybatis-1.2.0.jar包添加到classpath中。

    你可以从http://www.springsource.org/download/community/上下载Spring框架包spring-framework-3.1.3.RELEASE.zip。将其内所有jar包添加到classpath中。

    如果你只使用MyBatis而没有使用Spring,在每一个方法中,我们需要手动创建SqlSessionFactory对象,并且从SqlSessionFactory对象中创建SqlSession。而且我们还要负责提交或者回滚事务、关闭SqlSession对象。

    通过使用MyBatis-Spring模块,我们可以在Spring的应用上下文ApplicationContext中配置MyBatis Beans,Spring会负责实例化SqlSessionFactory对象以及创建SqlSession对象,并将其注入到DAO或者Service类中。并且,你可以使用Spring的基于注解的事务管理功能,不用自己在数据访问层中书写事务处理代码了。

5.1.2 配置MyBatis Beans

为了让Spring来实例化MyBatis组件如SqlSessionFactory、SqlSession、以及映射器Mapper对象,我们需要在Spring的bean配置文件中配置它们,假设在applicationContext.xml中,配配置如下:

[html]  view plain  copy
 print ?
  1. <beans>  
  2.   <bean id="dataSource" class="org.springframework.jdbc.datasource. DriverManagerDataSource">  
  3.     <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
  4.     <property name="url" value="jdbc:mysql://localhost:3306/elearning" />  
  5.     <property name="username" value="root" />  
  6.     <property name="password" value="admin" />  
  7.   </bean>  
  8.   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  9.     <property name="dataSource" ref="dataSource" />  
  10.     <property name="typeAliases" value="com.mybatis3.domain.Student, com.mybatis3.domain.Tutor" />  
  11.     <property name="typeAliasesPackage" value="com.mybatis3.domain" />  
  12.     <property name="typeHandlers" value="com.mybatis3.typehandlers.PhoneTypeHandler" />  
  13.     <property name="typeHandlersPackage" value="com.mybatis3.typehandlers" />  
  14.     <property name="mapperLocations" value="classpath*:com/mybatis3/**/*.xml" />  
  15.     <property name="configLocation" value="WEB-INF/mybatisconfig.xml" />  
  16.   </bean>  
  17. </beans>  

使用上述的bean定义,Spring会使用如下配置属性创建一个SqlSessionFactory对象:

  •         ž dataSource:它引用了dataSource bean
  •         ž typeAliases:它指定了一系列的完全限定名的类名列表,用逗号隔开,这些别名将通过默认的别名规则创建(将首字母小写的非无完全限定类名作为别名)。
  •         ž typeAliasesPackage:它指定了一系列包名列表,用逗号隔开,包内含有需要创建别名的JavaBeans。
  •         ž typeHandlers:它指定了一系列的类型处理器类的完全限定名的类名列表,用逗号隔开。
  •         ž typeHandlersPackage: 它指定了一系列包名列表,用逗号隔开,包内含有需要被注册的类型处理器类。
  •         ž mapperLocations:它指定了SQL映射器Mapper XML配置文件的位置
  •          configLocation:它指定了MyBatisSqlSessionFactory配置文件所在的位置。

5.2 使用SqlSession

一旦SqlSessionFactory bean被配置,我们需要配置SqlSessionTemplate bean,SqlSessionTemplatebean 是一个线程安全的Spring bean,我们可以从中获取到线程安全的SqlSession对象。由于SqlSessionTemplate提供线程安全的SqlSession对象,你可以在多个Spring bean实体对象中共享SqlSessionTemplate对象。从概念上看,SqlSessionTemplate和Spring的DAO模块中的JdbcTemplate非常相似。

[html]  view plain  copy
 print ?
  1. <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">  
  2.   <constructor-arg index="0" ref="sqlSessionFactory" />  
  3. </bean>  

现在我肯可以将SqlSessionbean实体对象注射到任意的Springbean实体中,然后使用SqlSession对象调用SQL映射语句。

[java]  view plain  copy
 print ?
  1. public class StudentDaoImpl implements StudentDao  
  2. {  
  3.     private SqlSession sqlSession;  
  4.     public void setSqlSession(SqlSession session)  
  5.     {  
  6.         this.sqlSession = session;  
  7.     }  
  8.     public void createStudent(Student student)  
  9.     {  
  10.         StudentMapper mapper =  
  11.             this.sqlSession.getMapper(StudentMapper.class);  
  12.         mapper.insertStudent(student);  
  13.     }  
  14. }  
如果你正在使用基于XML来配置Spring beans,你可以将SqlSessionbean实体对象注射到StudenDaoImpl bean 实体对象中,如下:

[html]  view plain  copy
 print ?
  1. <bean id="studentDao" class="com.mybatis3.dao.StudentDaoImpl">  
  2.   <property name="sqlSession" ref="sqlSession" />  
  3. </bean>  
如果你使用基于注解的方式配置Spring beans,你如下将SqlSession bean实体对象注入到StudentDaoImplbean实体对象中:

[java]  view plain  copy
 print ?
  1. @Repository  
  2. public class StudentDaoImpl implements StudentDao  
  3. {  
  4.     private SqlSession sqlSession;  
  5.     @Autowired  
  6.     public void setSqlSession(SqlSession session)  
  7.     {  
  8.         this.sqlSession = session;  
  9.     }  
  10.     public void createStudent(Student student)  
  11.     {  
  12.         StudentMapper mapper =  
  13.             this.sqlSession.getMapper(StudentMapper.class);  
  14.         mapper.insertStudent(student);  
  15.     }  
  16. }  

还有另外一种注入Sqlsession对象的方法,即,通过拓展继承SqlSessionDaoSupport。这种方式让我们可以在执行映射语句时,加入任何自定义的逻辑。

[java]  view plain  copy
 print ?
  1. public class StudentMapperImpl extends SqlSessionDaoSupport implements  
  2.     StudentMapper  
  3. {  
  4.     public void createStudent(Student student)  
  5.     {  
  6.         StudentMapper mapper =  
  7.             getSqlSession().getMapper(StudentMapper.class);  
  8.         mapper.insertAddress(student.getAddress());  
  9.         //Custom logic  
  10.         mapper.insertStudent(student);  
  11.     }  
  12. }  

[html]  view plain  copy
 print ?
  1. <bean id="studentMapper" class="com.mybatis3.dao.StudentMapperImpl">  
  2.   <property name="sqlSessionFactory" ref="sqlSessionFactory" />  
  3. </bean>  

在以上的这些方式中,我们注入了SqlSession对象,获取Mapper实例,然后执行映射语句。这里Spring会为我们提供一个线程安全的SqlSession对象,以及当方法结束后关闭SqlSession对象。

然而,MyBatis-Spring模块提供了更好的方式,我们可以不通过SqlSession获取映射器Mapper,直接注射Sql映射器Mapper bean。我们下节将讨论它。

5.3 使用映射器

我们可以使用MapperFactoryBean将映射器Mapper接口配置成Spring bean实体。如下所示:

[java]  view plain  copy
 print ?
  1. public interface StudentMapper  
  2. {  
  3.     @Select("select stud_id as studId, name, email, phone from  
  4.             students where stud_id=#{id}")  
  5.     Student findStudentById(Integer id);  
  6. }  
[html]  view plain  copy
 print ?
  1. <bean id="studentMapper" class="org.mybatis.spring.mapper. MapperFactoryBean">  
  2.   <property name="mapperInterface" value="com.mybatis3.mappers. StudentMapper" />  
  3.   <property name="sqlSessionFactory" ref="sqlSessionFactory" />  
  4. </bean>  
现在StudentMapper bean实体对象可以被注入到任意的Spring bean实体对象中,并调用映射语句方法,如下所示:
[java]  view plain  copy
 print ?
  1. public class StudentService  
  2. {  
  3.     private StudentMapper studentMapper;  
  4.     public void setStudentMapper (StudentMapperstudentMapper)  
  5.     {  
  6.         this. studentMapper = studentMapper;  
  7.     }  
  8.     public void createStudent(Student student)  
  9.     {  
  10.         this.studentMapper.insertStudent(student);  
  11.     }  
  12. }  
[html]  view plain  copy
 print ?
  1. <bean id="studentService" class="com.mybatis3.services. StudentService">  
  2.   <property name="studentMapper" ref="studentMapper" />  
  3. </bean>  

分别配置每一个映射器Mapper接口是一个非常单调的过程。我们可以使用MapperScannerConfigurer来扫描包(package)中的映射器Mapper接口,并自动地注册。

[html]  view plain  copy
 print ?
  1. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  2.   <property name="basePackage" value="com.mybatis3.mappers" />  
  3. </bean>  

如果映射器Mapper接口在不同的包(package)中,你可以为basePackage属性指定一个以逗号分隔的包名列表。

MyBatis-Spring-1.2.0介绍了两种新的扫描映射器Mapper接口的方法:

  • l   使用<mybatis:scan/>元素
  • l   使用@MapperScan注解(需Spring3.1+版本)

5.3.1 <mybatis:scan />

<mybatis:scan>元素将在特定的以逗号分隔的包名列表中搜索映射器Mapper接口。使用这个新的MyBatis-Spring名空间你需要添加以下的schema声明:

[html]  view plain  copy
 print ?
  1. <beans xmlns="http://www.springframework.org/schema/beans"  
  2.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
  3.     xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"  
  4.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  5.         http://www.springframework.org/schema/beans/spring-beans.xsd  
  6.         http://mybatis.org/schema/mybatis-spring   
  7.         http://mybatis.org/schema/mybatis-spring.xsd">  
  8.     <mybatis:scan base-package="com.mybatis3.mappers" />  
  9. </beans>  

<mybatis:scan>元素提供了下列的属性来自定义扫描过程:

  •         annotation: 扫描器将注册所有的在base-package包内并且匹配指定注解的映射器Mapper接口。
  •         factory-ref:当Spring上下文中有多个SqlSessionFactory实例时,需要指定某一特定的SqlSessionFactory来创建映射器Mapper接口。正常情况下,只有应用程序中有一个以上的数据源才会使用。
  •         ž marker-interface: 扫描器将注册在base-package包中的并且继承了特定的接口类的映射器Mapper接口
  •         ž template-ref: 当Spring上下文中有多个SqlSessionTemplate实例时,需要指定某一特定的SqlSessionTemplate来创建映射器Mapper接口。正常情况下,只有应用程序中有一个以上的数据源才会使用。
  •          name-generator:BeannameGenerator类的完全限定类名,用来命名检测到的组件。

5.3.2 MapperScan

Spring 框架3.x+版本支持使用@Configuration和@Bean 注解来提供基于Java 的配置。如果你倾向于使用基于Java的配置,你可以使用@MapperScan注解来扫描映射器Mapper接口。@MapperScan和<mybatis:scan/>工作方式相同,并且也提供了对应的自定义选项。

[java]  view plain  copy
 print ?
  1. @Configuration  
  2. @MapperScan("com.mybatis3.mappers")  
  3. public class AppConfig  
  4. {  
  5.     @Bean  
  6.     public DataSource dataSource()  
  7.     {  
  8.         return new PooledDataSource("com.mysql.jdbc.Driver",  
  9.                                     "jdbc:mysql://localhost:3306/elearning""root""admin");  
  10.     }  
  11.     @Bean  
  12.     public SqlSessionFactory sqlSessionFactory() throws Exception  
  13.     {  
  14.         SqlSessionFactoryBeansessionFactory = new  
  15.         SqlSessionFactoryBean();  
  16.         sessionFactory.setDataSource(dataSource());  
  17.         return sessionFactory.getObject();  
  18.     }  
  19. }  

@MapperScan注解有以下属性供自定义扫描过程使用:

      ž annotationClass: 扫描器将注册所有的在base-package包内并且匹配指定注解的映射器Mapper接口。

      ž markerInterface: 扫描器将注册在base-package包中的并且继承了特定的接口类的映射器Mapper接口

      ž sqlSessionFactoryRef:当Spring 上下文中有一个以上的SqlSesssionFactory时,用来指定特定SqlSessionFactory

      ž sqlSessionTemplateRef: 当Spring 上下文中有一个以上的sqlSessionTemplate时,用来指定特定sqlSessionTemplate

      ž nameGenerator:BeanNameGenerator类用来命名在Spring容器内检测到的组件。

      ž basePackageClasses:basePackages()的类型安全的替代品。包内的每一个类都会被扫描。

      ž basePackages:扫描器扫描的基包,扫描器会扫描内部的Mapper接口。注意包内的至少有一个方法声明的才会被注册。具体类将会被忽略。



5.4 使用Spring进行事务管理

只使用MyBatis,你需要写事务控制相关代码,如提交或者回退数据库操作。

[java]  view plain  copy
 print ?
  1. public Student createStudent(Student student)  
  2. {  
  3.     SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().  
  4.                             openSession();  
  5.     try  
  6.     {  
  7.         StudentMapper mapper =  
  8.             sqlSession.getMapper(StudentMapper.class);  
  9.         mapper.insertAddress(student.getAddress());  
  10.         mapper.insertStudent(student);  
  11.         sqlSession.commit();  
  12.         return student;  
  13.     }  
  14.     catch (Exception e)  
  15.     {  
  16.         sqlSession.rollback();  
  17.         throw new RuntimeException(e);  
  18.     }  
  19.     finally  
  20.     {  
  21.         sqlSession.close();  
  22.     }  
  23. }  

我们可以使用Spring的基于注解的事务处理机制来避免书写上述的每个方法中控制事务的冗余代码。

为了能使用Spring的事务管理功能,我们需要在Spring应用上下文中配置TransactionManagerbean实体对象:

[html]  view plain  copy
 print ?
  1. <bean id="transactionManager"   
  2.     class="org.springframework.jdbc. datasource.DataSourceTransactionManager">  
  3.   <property name="dataSource" ref="dataSource" />  
  4. </bean>  

事务管理器引用的dataSource和SqlSessionFactory bean使用的dataSource相同。

在Spring中使用基于注解的事务管理特性,如下:

[html]  view plain  copy
 print ?
  1. <tx:annotation-driven transaction-manager="transactionManager"/>  
现在你可以在Spring service bean上使用@Transactional注解,表示在此service中的每一个方法都应该在一个事务中运行。如果方法成功运行完毕,Spring会提交操作。如果有运行期异常发生,则会执行回滚操作。另外,Spring会将MyBatis 的异常转换成合适的DataAccessExceptions,这样会为特定错误上提供额外的信息。

[java]  view plain  copy
 print ?
  1. @Service  
  2. @Transactional  
  3. public class StudentService  
  4. {  
  5.     @Autowired  
  6.     private StudentMapper studentMapper;  
  7.     public Student createStudent(Student student)  
  8.     {  
  9.         studentMapper.insertAddress(student.getAddress());  
  10.         if(student.getName().equalsIgnoreCase(""))  
  11.         {  
  12.             throw new RuntimeException("Student name should not be  
  13.                                        empty.");  
  14.         }  
  15.         studentMapper.insertStudent(student);  
  16.         return student;  
  17.     }  
  18. }  

下面是一个Spring的applicationContext.xml完成配置:

[html]  view plain  copy
 print ?
  1. <beans>  
  2.   <context:annotation-config />  
  3.   <context:component-scan base-package="com.mybatis3" />  
  4.   <context:property-placeholder location="classpath:application.properties" />  
  5.   <tx:annotation-driven transaction-manager="transactionManager" />  
  6.   <bean id="transactionManager"   
  7. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  8.     <property name="dataSource" ref="dataSource" />  
  9.   </bean>  
  10.   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
  11.     <property name="basePackage" value="com.mybatis3.mappers" />  
  12.   </bean>  
  13.   <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">  
  14.     <constructor-arg index="0" ref="sqlSessionFactory" />  
  15.   </bean>  
  16.   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
  17.     <property name="dataSource" ref="dataSource" />  
  18.     <property name="typeAliases"   
  19. value="com.mybatis3.domain.Student,com.mybatis3.domain.Tutor" />  
  20.     <property name="typeAliasesPackage" value="com.mybatis3.domain" />  
  21.     <property name="typeHandlers" value="com.mybatis3.typehandlers.PhoneTypeHandler" />  
  22.     <property name="typeHandlersPackage" value="com.mybatis3.typehandlers" />  
  23.     <property name="mapperLocations" value="classpath*:com/mybatis3/**/*.xml" />  
  24.   </bean>  
  25.   <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
  26.     <property name="driverClassName" value="${jdbc.driverClassName}"></property>  
  27.     <property name="url" value="${jdbc.url}"></property>  
  28.     <property name="username" value="${jdbc.username}"></property>  
  29.     <property name="password" value="${jdbc.password}"></property>  
  30.   </bean>  
  31. </beans>  
现在让我们写一个独立的测试客户端来测试StudentService,如下:

[java]  view plain  copy
 print ?
  1. @RunWith(SpringJUnit4ClassRunner.class)  
  2. @ContextConfiguration(locations = "classpath:applicationContext.xml"  
  3.                      )  
  4. public class StudentServiceTest  
  5. {  
  6.     @Autowired  
  7.     private StudentService studentService;  
  8.     @Test  
  9.     public void testCreateStudent()  
  10.     {  
  11.         Address address = new Address(0, "Quaker Ridge  
  12.                                       Rd.", "Bethel", "Brooklyn", "06801", "USA");  
  13.         Student stud = new Student();  
  14.         long ts = System.currentTimeMillis();  
  15.         stud.setName("stud_" + ts);  
  16.         stud.setEmail("stud_" + ts + "@gmail.com");  
  17.         stud.setAddress(address);  
  18.         Student student = studentService.createStudent(stud);  
  19.         assertNotNull(student);  
  20.         assertEquals("stud_" + ts, student.getName());  
  21.         assertEquals("stud_" + ts + "@gmail.com", student.getEmail());  
  22.         System.err.println("CreatedStudent: " + student);  
  23.     }  
  24.     @Test(expected = DataAccessException.class)  
  25.     public void testCreateStudentForException()  
  26.     {  
  27.         Address address = new Address(0, "Quaker Ridge  
  28.                                       Rd.", "Bethel", "Brooklyn", "06801", "USA");  
  29.         Student stud = new Student();  
  30.         long ts = System.currentTimeMillis();  
  31.         stud.setName("Timothy");  
  32.         stud.setEmail("stud_" + ts + "@gmail.com");  
  33.         stud.setAddress(address);  
  34.         studentService.createStudent(stud);  
  35.         fail("You should not reach here");  
  36.     }  
  37. }  

这里在testCreateStudent()方法中,我们为Address和Student赋上了合适的数据,所以Address和Student会被分别插入到表ADDRESSES和STUDENTS中。在testCreateStudentForException()方法我们设置了名字为Timothy,该名称在数据库中已经存在了,所以当你尝试将此student记录插入到数据库中,MySQL会抛出一个UNIQUE KEY 冲突的异常,Spring会将此异常转换成DataAccessException异常,并且将插入ADDRESSES表中的数据回滚(rollback)掉。

5.5 总结

在本章中我们学习了怎样将MyBatis与Spring框架集成。我们还学习了怎样安装Spring类库并且在Spring 的应用上下文ApplicationContext上注册MyBatis bean实体对象。我们还看到怎样配置和注入SqlSession和Mapper bean实体对象以及调用映射语句。我们还学习了利用Spring基于注解的事务处理机制来使用MyBatis。

 

你已经读完本书,祝贺你!现在,你应该知道怎样高效地使用MyBatis与数据库工作。你学会了怎样发挥你的Java和SQL技巧的优势使MyBatis更富有成效。你知道了怎样以更清晰的方式使用MyBatis写出数据持久化代码,不用管被MyBatis框架处理的所有底层细节。另外,你学会了怎样在最流行的依赖注入框架-Spring中使用MyBatis。

 

MyBatis框架非常易于使用,但它提供了强大的特性,因此它对于基于Java的项目而言,是一个非常好的数据库持久化解决方案。MyBatis也提供了一些工具如MyBatis Generator(http://www.mybatis.org/generator/),可以被用来从已经存在的数据库schema中,产生持久化代码如数据库实体(databaseentities),映射器Mapper 接口,MapperXML配置文件,使MyBatis入门非常方便。另外,MyBatis还有它的姊妹项目如MyBatis.NET和MyBatis-Scala,分别为.NET 和Scala编程语言提供了一样强大的特性。

 

MyBatis随着每一个版本的发布,增加了一些特性,正变得越来越好。想了解更多的新特性,你可以访问MyBatis官方网站https://code.google.com/p/mybatis/.订阅MyBatis 的使用者邮件列表是一个不错的想法。我们祝你一切顺利,编码快乐!(We wish you all the best, and happy coding!)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值