spring 提供的缓存管理器

spring 提供了几个缓存管理器

1. SimpleCacheManager

2. ConcurrentMapCacheManager

3. CompositeCacheManager

4.还有redis的RedisCacheManager

目前先介绍这几类。

需要的maven依赖

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

SimpleCacheManager 配置

<bean id="cacheManager" class = "org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                    <property name="name" value="tzwCache"></property>
                 </bean>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCache">
                    <constructor-arg value="tzwCache1"></constructor-arg>
                </bean>
            </set>
        </property>
    </bean>

属性caches,可以有多个cache类, ConcurrentMapCache这个类是已经实现了Cache,所以可以直接拿来用。

而ConcurrentMapCacheFactoryBean,是由于这个FactorBean就是生成ConcurrentMapCache的类,所以可以跟ConcurrentMapCache一样

当然我们也可以自定义Cache类,只要实现了Cache的类,当然自定义实现。

ConcurrentMapCacheManager配置

<bean id="cacheManager1" class = "org.springframework.cache.concurrent.ConcurrentMapCacheManager">
        <property name="cacheNames" value="tzwCache2"/>
    </bean>

如果使用ConcurrentMapCacheManager缓存管理器,只需要配置cacheNames属性即可。这个缓存管理器默认使用了ConcurrentMapCache缓存实现

CompositeCacheManager配置

<bean id = "comCacheManager" class="org.springframework.cache.support.CompositeCacheManager">
        <property name="cacheManagers">
            <list>
                <ref bean="cacheManager"></ref>
                <ref bean="cacheManager1"></ref>
            </list>
        </property>
    </bean>

这个缓存管理器可以管理多个缓存管理器。他会迭代查询每个缓存管理器,直到找到缓存条目。

缓存注解

<!--开启缓存注解,这里如果只有一个缓存管理器,可以省去cache-manager="cacheManager"-->
    <cache:annotation-driven  cache-manager="comCacheManager"/>

开启缓存注解,如果只配置了一个缓存管理器的话,可以省略 cache-manager配置。

如果配置了多个缓存管理器的话,可以用cache-manager指定。

 

 

redis缓存管理器

<!--redis-->
    <context:property-placeholder location="classpath:redis.properties"/>
    <!-- redis连接池配置 -->
    <bean id="poolConfig1" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}"></property>
        <property name="maxTotal" value="${redis.maxActive}"></property>
        <property name="maxWaitMillis" value="${redis.maxWait}"></property>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"></property>
    </bean>

    <!-- Jedis ConnectionFactory连接配置 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="poolConfig" ref="poolConfig1"></property>
        <!--<property name="password" value="${redis.pass}"></property>-->
        <property name="hostName" value="${redis.host}"></property>
        <property name="port" value="${redis.port}"></property>
        <!--配置redis集群-->       
 <!--<constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg>-->
        <!-- <constructor-arg name="clusterConfig" ref="sentinelConfiguration"></constructor-arg>-->
    </bean>


    <!-- redis模板类,提供了对缓存的增删改查 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <!--<property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
        </property>-->
        <property name="valueSerializer" >
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>

    </bean>

    <!--redisCacheManager-->
    <bean id="cacheManage5" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg  name="redisOperations" ref="redisTemplate"></constructor-arg>
        <property name="cacheNames">
            <set>
                <value>cacheone</value>
                <value>cacheone1</value>
            </set>
        </property>
        <!--缓存名称作为前缀-->
        <property name="usePrefix" value="true"></property>
        <property name="defaultExpiration" value="300000"/>
        <!--开启redis事务配置-->
        <property name="transactionAware" value = "true"/>
    </bean>

redis配置要素

jedis连接池配置 

JedisPoolConfig

需要配置的属性:

redis.maxIdle
redis.maxActive
redis.maxWait
redis.testOnBorrow

jedis连接配置

JedisConnectionFactory 依赖 JedisPoolConfigre

需要配置的属性:

poolConfig
password
hostName
port
还有redis集群
<!--<constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg>-->
<!-- <constructor-arg name="clusterConfig" ref="sentinelConfiguration"></constructor-arg>-->

RedisTemplate操作redis数据库模板


redisTemplate 依赖 JedisConnectionFactory
 

需要配置的属性:

JedisConnectionFactory 
keySerializer 
valueSerializer

RedisCacheManager redis缓存管理器

redisCacheManager 依赖 redisTemplate

<property name="cacheNames">
    <set>
        <value>cacheone</value>
        <value>cacheone1</value>
    </set>
</property>
<!--缓存名称作为前缀-->
<property name="usePrefix" value="true"></property>
<property name="defaultExpiration" value="300000"/>
<property name="transactionAware" value = "true"/>

 

 

以上的介绍都是缓存管理。

缓存呢,一种是缓存在本地内存中,其实就是缓存在map中,比如ConcurrentMapCacheManager这个管理器就是直接缓存在内存中的。

另一种就是缓存在缓存数据库中的。比如RedisCacheManager管理器,他就是缓存在redis数据库中的。

当然其他的缓存管理器也能缓存在redis中,比如SimpleCacheManager,可以自定义cache类,自己时间redis缓存操作。

package com.tzw.cache;


import org.springframework.cache.Cache;

import java.util.concurrent.Callable;
public class TzwCache implements Cache {


    public String myName;


    @Override
    public String getName() {
        System.out.println("getName");
        return this.myName;
    }

    @Override
    public Object getNativeCache() {
        System.out.println("getNativeCache");
        return null;
    }

    @Override
    public ValueWrapper get(Object o) {
        System.out.println("get");
        return null;
    }

    @Override
    public <T> T get(Object o, Class<T> aClass) {
        System.out.println("get1");
        return null;
    }

    @Override
    public <T> T get(Object o, Callable<T> callable) {
        System.out.println("get2");
        return null;
    }

    @Override
    public void put(Object o, Object o1) {
        System.out.println("put");
    }

    @Override
    public ValueWrapper putIfAbsent(Object o, Object o1) {
        System.out.println("putIfAbsent");
        return null;
    }

    @Override
    public void evict(Object o) {
        System.out.println("evict");
    }

    @Override
    public void clear() {

    }

    public void setMyName(String myName) {
        myName = this.myName;
    }
}

缓存注解

缓存注解

@Cacheable

@Cacheput

@Cacheing

 

@Cacheable(value = "tzwCache2")
    public EcifClient getClientById(String clientId){

        System.out.println("进入方法中,查询数据库");
        EcifClient ecifClient = new EcifClient();
        ecifClient.setClientId("0000000001");
        EcifClient ecifClient1 = ecifClientMapper.selectByPrimaryKey(ecifClient);
        System.out.println(ecifClient1.toString()+"-------");
        return ecifClient1;
    }

 

 

 

 @Cacheable

@Cacheable可以标记在一个方法上,也可以标记在一个类上。标记方法上表示该方法支持缓存,标记类上表示该类下所有方法都支持缓存。
对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。
spring以键值对形式缓存,key可以自定义设置,未设置则为默认值,value则是方法返回结果。@Cacheable可以指定三个属性:value、key和condition。
@Cacheable 常用与查询操作。

1.1.1 value指定cache缓存空间
value指定当前结果缓存在哪个cache下,value值可以是一个cache,或者多个cache

@Cacheable("goods")
public Goods find(Integer goodsId) {
  return null;
}
 
@Cacheable({"goods", "channel:goods"})
public Goods find(Integer goodsId) {
  return null;
}

1.1.2 指定cache的key值
设置缓存数据的key值,未设置则为默认值。

默认策略
默认的key生成策略是通过KeyGenerator生成的,其默认策略如下:
如果方法没有参数,则使用0作为key。
如果只有一个参数的话则使用该参数作为key。
如果参数多余一个的话则使用所有参数的hashCode作为key

自定义key

@Cacheable(value="goods", key="goodsId")
public Goods find(Integer goodsId) {
  return null;
}
@Cacheable(value="goods", key="goods.goodsId")
public Goods find(Goods goods) {
  return null;
}


前言caching配置中

 

@Override
public KeyGenerator keyGenerator() {
    System.out.println("RedisCacheConfig.keyGenerator()");
    return new KeyGenerator(){
             @Override
             public Object generate(Object o, Method method, Object... objects ) {
            StringBuilder sb = new StringBuilder("Cache:BaseService");
            sb.append(":" + o.getClass().getName().toString());
            sb.append(":" + method.getName().toString());
 
 
            StringBuilder s = new StringBuilder();
            for (Object obj : objects) {
                if (Objects.nonNull(obj)){
                    s.append(obj.toString());
                }
            }
 
 
            StringBuilder testss = sb;
            sb.append(":" + StringEncryptionUtils.StringToMd5(s.toString()));
            return sb.toString();
        }
    };
}


1.1.3 condition 设置缓存条件
指定缓存方法上,添加缓存条件,当满足该条件时候,结果才进行缓存。
@Cacheable(value="goods", key="goods.goodsId", condition="goods.goodsId == 123456")
public Goods find(Goods goods) {
  return null;
}


1.2 @CachePut


@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CachePut也可以标注在类上和方法上。使用@CachePut时我们可以指定的属性跟@Cacheable是一样的(这边就不重复阐述)。
@CachePut常用与更新操作。

@CachePut(value="goods", key="goods.goodsId", condition="goods.goodsId == 123456")
public Goods update(Goods goods) {
  return null;
}


1.3@CacheEvict


@CacheEvict可以标记在一个方法上,也可以标记在一个类上。标记方法上标识方法执行时候触发清楚缓存操作,当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。
@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。
其中value、key和condition的语义与@Cacheable对应的属性类似。主要阐述新出现的两个属性allEntries和beforeInvocation。
@CacheEvict常用删除操作。

1.3.1  allEntries属性
allEntries是boolean类型,表示是否需要清除缓存中的所有元素,默认为false。
当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。

@CacheEvict(value="goods", key="goods.goodsId", condition="goods.goodsId == 123456")
public void deleteGoods(Goods goods) {
}
 
 
@CacheEvict(value="goods", allEntries=true)
public void deleteAllGoods() {
}


1.3.2  beforeInvocation属性
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。
使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(value="goods", beforeInvocation=true)
public void deleteGoods(Goods goods) {
}


1.4 @Caching


@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
@Caching(cacheable = @Cacheable("goods"), evict = { @CacheEvict("goods1"),
@CacheEvict(value = "goods2", allEntries = true) })
public Goods find(Integer goodsId) {
  return null;
}

 

 

 

完整配置,里面涉及事务管理、mybatis配置、缓存管理、数据库配置,自己选取查看哦。、

<?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:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:cache="http://www.springframework.org/schema/cache" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/jdbc
       http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">




    <context:component-scan base-package="com.tzw"></context:component-scan>

    <!--数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
        <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"></property>
        <property name="username" value="tzw"></property>
        <property name="password" value="tzw"></property>
    </bean>


    <!--原生jdbc模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!--事务管理器-->
    <bean id="transactionManager"  class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>


    <!--事务管理模板,一般用于简化编程式事务-->
    <bean id="transactionTemplate" class ="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>


    <!--方式1: 基于TransactionProxyFactoryBean实现,底层是通过AOP的方式实现的-->
    <bean  id="transactionProxy"  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <!--注入事务管理器-->
        <property name="transactionManager" ref="transactionManager"></property>
        <!--配置目标对象-->
        <property name="target" ref="coreService"></property>
        
        <!--注入事务的属性-->
        <property name="transactionAttributes">
            <props>
                <prop key="submit*">PROPAGATION_REQUIRED,+java.lang.ArithmeticException</prop>

                <!-- 格式:
                    <prop key="方法">参数</prop>
					* PROPAGATION	:事务的传播行为
					* ISOLATION		:事务的隔离级别
					* readOnly		:只读.(不可以进行修改、插入、删除)
					* -Exception	:发生哪些异常回滚事务
					* +Exception	:发生哪些异常事务不回滚
					<prop key="insert*">PROPAGATION_REQUIRED</prop>
					<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
				-->
                <!--<prop key="transfer">PROPAGATION_REQUIRED</prop> -->

            </props>
        </property>
    </bean>

    <!--方式2: 通过aspectj配置事务管理器-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="updateClient11111" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.tzw.service.TzwService.*(..))"></aop:pointcut>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut" />
    </aop:config>


    <!--方式3: 开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"/>


    <!--以上是spring的事务管理器,与下面的 数据库框架没有任何关系-->

    <!--以下是 mybatis 框架的配置,一共有两步,1,sqlSessionFactory    2. 自动扫描对象映射关系 org.mybatis 和 tk.mybatis-->

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis.xml"></property>
        <property name="mapperLocations">
            <array>
                <value>classpath*:mapper/clientMapper.xml</value>
            </array>
        </property>
    </bean>

    <!--自动扫描对象关系映射 -->
    <!--<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        &lt;!&ndash;指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 &ndash;&gt;
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        &lt;!&ndash; 指定要自动扫描接口的基础包,实现接口 &ndash;&gt;
        <property name="basePackage" value="com.tzw.mapper"></property>
    </bean>-->

    <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
        <!-- 指定要自动扫描接口的基础包,实现接口 -->
        <property name="basePackage" value="com.tzw.mapper"></property>

        <property name="properties">
            <value>mappers=tk.mybatis.mapper.common.Mapper</value>
        </property>
    </bean>




    <!--spring 对缓存的支持  这里介绍了两种缓存管理器,一种是SimpleCacheManager,另一个是ConcurrentMapCacheManager-->
    <bean id="cacheManager" class = "org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                    <property name="name" value="tzwCache"></property>
                 </bean>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCache">
                    <constructor-arg value="tzwCache1"></constructor-arg>
                </bean>
            </set>
        </property>
    </bean>

    <bean id="cacheManager1" class = "org.springframework.cache.concurrent.ConcurrentMapCacheManager">
        <property name="cacheNames" value="tzwCache2"/>
    </bean>


    <!--CompositeCacheManager 可以用多个缓存管理器来配置,查找时CompositeCacheManager管理器换遍历所有的缓存管理
    器去查询缓存条目-->
    <bean id = "comCacheManager" class="org.springframework.cache.support.CompositeCacheManager">
        <property name="cacheManagers">
            <list>
                <ref bean="cacheManager"></ref>
                <ref bean="cacheManager1"></ref>
            </list>
        </property>
    </bean>

    <!--开启缓存注解,这里如果只有一个缓存管理器,可以省去cache-manager="cacheManager"-->
    <cache:annotation-driven  cache-manager="comCacheManager"/>





    <!--redis-->
    <context:property-placeholder location="classpath:redis.properties"/>
    <!-- redis数据源 -->
    <bean id="poolConfig1" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="${redis.maxIdle}"></property>
        <property name="maxTotal" value="${redis.maxActive}"></property>
        <property name="maxWaitMillis" value="${redis.maxWait}"></property>
        <property name="testOnBorrow" value="${redis.testOnBorrow}"></property>
    </bean>

    <!-- Jedis ConnectionFactory连接配置 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="poolConfig" ref="poolConfig1"></property>
        <!--<property name="password" value="${redis.pass}"></property>-->
        <property name="hostName" value="${redis.host}"></property>
        <property name="port" value="${redis.port}"></property>
        <!--<constructor-arg name="sentinelConfig" ref="sentinelConfiguration"></constructor-arg>-->
        <!-- <constructor-arg name="clusterConfig" ref="sentinelConfiguration"></constructor-arg>-->
    </bean>


    <!-- redis模板类,提供了对缓存的增删改查 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
        <property name="connectionFactory" ref="jedisConnectionFactory" />
        <property name="keySerializer">
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
        </property>
        <!--<property name="valueSerializer">
            <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
        </property>-->
        <property name="valueSerializer" >
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
        </property>

    </bean>

    <!--redisCacheManager-->
    <bean id="cacheManage5" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg  name="redisOperations" ref="redisTemplate"></constructor-arg>
        <property name="cacheNames">
            <set>
                <value>cacheone</value>
                <value>cacheone1</value>
            </set>
        </property>
        <!--缓存名称作为前缀-->
        <property name="usePrefix" value="true"></property>
        <property name="defaultExpiration" value="300000"/>
        <property name="transactionAware" value = "true"/>
    </bean>

    <!--redis集群-->
    <!-- <bean id="sentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
         <property name="master">
             <bean class="org.springframework.data.redis.connection.RedisNode">
                 &lt;!&ndash; 集群的名称 &ndash;&gt;
                 &lt;!&ndash;<property name="name" value="${redis.master}"></property>&ndash;&gt;
                 <constructor-arg name="host" value="${sentinel1.ip}"></constructor-arg>
                 <constructor-arg name="port" value="${sentinel1.port}"></constructor-arg>
             </bean>
         </property>
         <property name="sentinels">
             <set>
                 <bean id="redisNode" class="org.springframework.data.redis.connection.RedisNode">
                     <constructor-arg name="host" value="${sentinel1.ip}"></constructor-arg>
                     <constructor-arg name="port" value="${sentinel1.port}"></constructor-arg>
                 </bean>
                 &lt;!&ndash;<bean class="org.springframework.data.redis.connection.RedisNode">
                     <constructor-arg name="host" value="${sentinel2.ip}"></constructor-arg>
                     <constructor-arg name="port" value="${sentinel2.port}"></constructor-arg>
                 </bean>
                 <bean class="org.springframework.data.redis.connection.RedisNode">
                     <constructor-arg name="host" value="${sentinel3.ip}"></constructor-arg>
                     <constructor-arg name="port" value="${sentinel3.port}"></constructor-arg>
                 </bean>&ndash;&gt;
             </set>
         </property>
     </bean>-->

</beans>

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值