Java开发【Spring之IOC详解第二篇(注解开发、JdbcTemplatem模板、Junit整合)】

一、IOC相关注解

1、注解引入

<?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"
       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">
    
    <context:component-scan base-package="packageName"/>
<beans/>    
# 在spring配置文件中启动注解扫描,加载类中配置的注解项
<context:component-scan base-package="packageName"/>

- 说明:
在进行包所扫描时,会对配置的包及其子包中所有文件进行扫描
扫描过程是以文件夹递归迭代的形式进行的
扫描过程仅读取合法的java文件
扫描时仅读取spring可识别的注解
扫描结束后会将可识别的有效注解转化为spring对应的资源加入IoC容器

- 注意:
无论是注解格式还是XML配置格式,最终都是将资源加载到IoC容器中,差别是数据读取方式不同。
从加载效率上来说注解优于XML配置文件

2、bean实例化注解

# 这些注解使用在类上,用于创建被修饰的类对象,并将创建好的对象存放到IOC容器中
    @Component	: 组件
    @Controller	: 控制器
    @Service	: 服务
    @Repository	: 仓库
    
# 说明
@Component : (Component组件)  在类上使用该注解,把资源让spring来管理。
	作用:  相当于bean标签,创建当前类对象并存放到IOC容器中
	value属性: 指定bean的id,默认bean的id是当前类的类名。首字母小写。
    
@Controller,@Service,@Repository:
	作用、使用、属性:  与@Component的作用和属性是一模一样的
	他们只是提供了更加明确的语义化(见名知意),精确指出是哪一层的对象,但不是强制要求的
	@Controller:一般用于表现层的注解。 将web层的类创建对象存放到IOC容器中
    @Service:一般用于业务层的注解。    将serice层的类创建对象存放到IOC容器中
    @Repository:一般用于持久层的注解。 将dao层的类创建对象存放到IOC容器中
    
# 以上注解相当于下面得到bean标签:
<bean id="userService" class="com.itheima.service.impl.UserServiceImpl"></bean>

代码演示:service层

//@Component
//@Controller
@Service
//@Repository
public class AccountServiceImpl implements AccountService {
    @Override
    public void add() {
        System.out.println("add 方法执行了...");
    }
}

3、数据注入的注解

`这些注解使用在属性上,用于给对象中的属性进行赋值:
@Autowired
@Qualifier
@Resource
@Value

`相当于xml中的set注入
<property name="userDao" ref="userDao" ></property> 
<property name="userDao" value="基本类型数据或String"></property> 
1)注入基本类型和String
@Value
`@Value: 
	作用:用于注入基本类型和String的注入
	value属性: 指定数据的内容,它可以支持Spring的EL表达式(SPEL),写法:${表达式}

`SpEL表达式:
	作用: 从IOC容器中获取对应的值
	格式: ${key}
		属性值来自于properties配置文件
		
`前提:
# 载properties配置文件中的信息存放到容器中
<context:property-placeholder 
location="classpath:jdbc.properties"></context:property-placeholder>
	
`注意: 
当我们使用注解注入数据时,构造器和set方法不是必须的了。
代码演示
xml配置

applicationContext.xml

     <!--
注释: 写个程序员看的,当程序员看到注释后就明白了代码的含义
注解: 写给虚拟机看的,当虚拟机在编译代码时,根据不同的注解将代码编译成指定的格式
     -->
    <!-- 开启组件扫描: 告诉Spring框架,它的注解在哪个包中的类上 -->
	<context:component-scan base-package="com.example"></context:component-scan>
    <!-- 解析Properties配置文件,将配置文件中的数据加载到IOC容器中 -->
	<context:property-placeholder 
             location="classpath:jdbc.properties" 
             file-encoding="utf-8">
    </context:property-placeholder>
Properties配置
jdbc.username=root
jdbc.password=1234
jdbc.port=3306
service层
@Service("AccountService")
public class AccountServiceImpl1 implements AccountService {
    /**
     * @Value: 用于注入基本类型和String类型的数据
     *      Value注解可以使用SPEL表达式获取IOC容器中的数据  ${变量名称} 
     */
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.port}")
    private int port;

    @Override
    public void add() {
        System.out.println("注入基本类型和String类型的数据.....");
        System.out.println(username+" : "+password+" : "+port);
    }
}

测试
package com.example.web;

public class AccountClient1 {
    @Test
    public void test01(){
        //1.解析配置文件,得到ApplicationContext对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.调用应用上下文的API从IOC容器中获取bean对象
        AccountService service = ac.getBean("AccountService", AccountService.class);
        service.add();
    }
}
2)注入bean类型
@Autowired
@Autowired: 默认按照bean的类型注入数据,如果类型相同,则按名称注入
`原理:
【被注入的对象,来自IOC容器】
(如果IOC容器中只有一个此类型的对象)-->直接注入
(如果IOC容器中有多个此类型的对象)-->按照IOC容器中对象的名称注入,如果名称不匹配错。
`属性:
required: 
	true:  此对象必须注入成功,若不成功则报错. 默认值
	false: 可以注入不成功
@Qualifier
`作用:
    与@Autowired注解一起使用,指定在按照bean类型注入的基础上,再按照bean的名称注入
`属性:
	 value:指定bean的名称
@Resource

此注解是JDK提供的,作用相当于 @Autowired+@Qualifier

`作用:
	默认按照bean的名称注入数据,如果同一个接口有多个实现,可以通过指定属性进行注入
`属性:
	name:指定bean的名称注入数据
	type:指定bean的类型注入数据
`注意细节:
	如果没有对应的名称与之匹配,则按照类型注入
代码演示
service层
package com.example.service.impl;

@Service("AccountService2")
public class AccountServiceImpl2 implements AccountService {
    /**
     * IOC容器:
     *      AccountDao:
     *           AccountDaoImpl  name: accountDaoImpl
     *           AccountDaoImpl2 name: accountDaoImpl2
     *           				 type: AccountDao.class
     * @Autowired: 将IOC容器中的对象注入到属性上
     *          默认按照类型注入,当只有一个该类型对象时,直接注入
     *          如果发现多个同类型的对象,则按照变量名称注入
     *          如果没有一样的变量名则报错
     *     required属性: 是否必须注入成功(当前属性需要结合@Qualifier使用)
     *          true: 必须注入成功,否则报错 (默认值)
     *          false: 可以注入不成功,如果注入不成功,则为null
     *
     * @Qualifier: 指定注入的对象的名称
     *
     * @Resource:
     *      默认按照名称注入,如果IOC容器中没有对应名称的对象,则按照类型注入
     *      type: 指定类型注入
     *      name: 指定名称注入
     */
    
    //@Autowired(required = false)
    //@Qualifier("accountDaoImpl2")
    @Resource(type = AccountDao.class,name = "accountDaoImpl")
    private AccountDao accountDao;

    @Override
    public void add() {
        System.out.println("33333333333...");
        accountDao.add();
    }
}

dao层
package com.example.dao;

public interface AccountDao {
    void add();
}
----------------------------------
package com.example.dao.impl;
import com.example.dao.AccountDao;

@Repository
public class AccountDaoImpl1 implements AccountDao {
    @Override
    public void add() {
        System.out.println("AccountDaoImpl11111中的方法执行了");
    }
}
-----------------------------------
package com.example.dao.impl;

import com.itheima.dao.AccountDao;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl2 implements AccountDao {
    @Override
    public void add() {
        System.out.println("AccountDaoImpl22222中的方法执行了");
    }
}

测试类
package com.example.web;

public class AccountClient2 {
    @Test
    public void test01(){
        //1.解析配置文件,得到ApplicationContext对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.调用应用上下文的API从IOC容器中获取bean对象
        AccountService service = ac.getBean("AccountService2", AccountService.class);
        service.add();
    }
}

4、bean作用范围与生命周期注解

1)作用范围注解
@Scope
@Scope: 
	作用:     用于调整bean的作用范围,相当于bean标签的scope属性
	使用位置:  被创建的类上
	value属性: 指定作用范围的取值。取值是固定的5个,和XML的配置取值是一样的。
			singleton: 单实例 默认值
			prototype: 多实例
2)生命周期注解
@PostConstruct
@PostConstruct : 热加载数据
	作用:     指定初始化方法,相当于init-method
	使用位置: 初始化的方法上
@PreDestroy
@PreDestroy : 	资源回收
	作用:    指定销毁方法,相当于destroy-method
	使用位置: 销毁的方法上
3)代码演示
service层
package com.example.service.impl;

/**
 * 测试作用范围
 * @Scope:
 *      singleton: 单实例
 *      prototype: 多实例
 * 生命周期:
 *      @PostConstruct: 设置初始化调用的方法
 *      @PreDestroy: 设置销毁前调用的方法
 */
@Service("AccountService3")
@Scope("prototype")
public class AccountServiceImpl3 implements AccountService {

    @Override
    public void add() {
        System.out.println("AccountServiceImpl3 中的add方法执行了");
    }

    @PostConstruct
    public void initMethod(){
        System.out.println("对象初始化了...");
    }
    @PreDestroy
    public void destoryMethod(){
        System.out.println("对象销毁了...");
    }
}

测试
package com.example.web;

import com.example.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AccountClient3 {
    @Test
    public void test01(){
        //1.解析配置文件,得到ApplicationContext对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.调用应用上下文的API从IOC容器中获取bean对象
        for (int i = 0; i < 10; i++) {
            AccountService service = ac.getBean("AccountService3", AccountService.class);
            System.out.println(service);
        }
    }

    @Test
    public void test02(){
        //1.解析配置文件,得到ApplicationContext对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.调用应用上下文的API从IOC容器中获取bean对象
        AccountService service = ac.getBean("AccountService3", AccountService.class);
        System.out.println(service);
        ((ClassPathXmlApplicationContext) ac).close();
    }
}

5、纯注解案例演示

使用配置类代替配置文件,将以下注解写在配置类上

@Configuration: 声明该类为Spring的配置文件类
@ComponentScan(basePackages = "com.example") : 指定要扫描的包
@PropertySource(value = "classpath:jdbc.properties") : 将配置文件交给Spring容器管理
@Bean() : 将方法返回的对象存放到IOC容器中,
    	  将返回值存放到IOC容器中,一般将第三方提供的类对加载到IOC容器
    	  
1、@Configuration:声明配置类,代替applicationContext.xml的作用
2、@ComponentScan:配置注解扫描 ,这样注解才可以被识别
3、@Bean:将方法的返回值装配到IOC容器中
4、@PropertySource:加载外部资源文件
5、@Import:引入其他配置类

加载配置类,初始化IOC容器,获取工厂
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);

@ComponentScan("com.example")
相当于
@ComponentScans(
{  @ComponentScan("com.example.web"),
   @ComponentScan("com.example.service")
})

对比下面的applicationContext.xml配置

<!--相当于@ComponentScan  扫描包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>  

<!-- 相当于@PropertySource  读取外部根目录下资源配置文件-->
    <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>

 <!--配置出druidDataSource数据源-->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${dataSource.driverClassName}"></property>
        <property name="url" value="${dataSource.url}"></property>
        <property name="username" value="${dataSource.username}"></property>
        <property name="password" value="${dataSource.password}"></property>
    </bean>

<!--配置jdbc查询模板,注入druidDataSource数据源-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
SpringConfig类
package com.example.config;

@Configuration
@ComponentScan("com.example")
@PropertySource(value = "classpath:data.properties",encoding = "utf-8")
public class SpringConfig {
//@Bean("存放到IOC容器时的名称")【如果不设置名称,默认为当前方法的名称】
//将方法的返回值存放到IOC容器中
    
    //@Bean("account")
    @Bean
    public Account getAccount(){
        return new Account(1,"景甜",100f);
    }
}
测试类
package com.example.web;

import com.example.config.SpringConfig;
import com.example.pojo.Account;
import com.example.service.AccountService;

public class AccountClient {
    @Test
    public void test01(){
        //1.解析配置类,得到ApplicationContext对象
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
        //2.调用应用上下文的API从IOC容器中获取bean对象
        AccountService service = ac.getBean("AccountService", AccountService.class);
        service.add();
    }

    @Test
    public void test02(){
        //1.解析配置类,得到ApplicationContext对象
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
        //2.调用应用上下文的API从IOC容器中获取bean对象
        Account account = ac.getBean("getAccount",Account.class);
        System.out.println(account);
    }
}

6、注解与xml对照表

注解xml说明
@Component父注解
@Controller:用于表现层的注解
@Service:用于业务层的注解
@Repository:一般用于持久层的注解
< bean id="" class="">声明bean交于springIOC管理
@Scopescope=“singleton/prototype”生命周期
@PostConstructinit-method初始化方法
@PreDestroydestroy-method销毁方法
@Autowired、@Qualifier
@Resource
ref=“自定义类型”依赖注入
@Valuevalue=“基础数据类型”基本数据类型注入

二、JdbcTemplate【了解】

1、原生JDBC开发基本路程

package com.example.jdbc;

public class JdbcDemo {
    public static void main(String[] args) throws Exception {
        // 查询: 根据id查询,查询id为1的账户信息
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        String url = "jdbc:mysql://localhost:3306/itheima118_spring";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager
            .getConnection(url, username, password);
        //3.编写sql语句
        String sql = "select * from account where id in (?,?) ";
        //4.创建预编译语句执行者对象
        PreparedStatement pst = conn.prepareStatement(sql);
        //5.设置真实值
        // 第一个值为sql语句中第几个 ?
        // 第二个值为当前?的真实值
        pst.setInt(1,1);
        pst.setInt(2,3);
        //6.执行sql并返回结果
        ResultSet rs = pst.executeQuery();
        //7.处理结果
        while(rs.next()){
            int id = rs.getInt("id");
            String name = rs.getString("name");
            float money = rs.getFloat("money");
            System.out.println(id+" : "+name+" : "+money);
        }
        //8.关闭资源
        rs.close();
        pst.close();
        conn.close();
    }
}

2、JdbcTemplate使用步骤

JdbcTemplate是Spring对JDBC的封装

操作数据库:
		`增删改:
				当执行增删改操作时,会对数据库原有数据产生影响,返回的是影响的行数
		`查询: 
				单行单列: 基本数据类型
					需求: 查询账户表总条数  查询id为1的账户名称
        		单行多列: map  java实体
					需求: 查询id为1的账户详情
        		多行单列: List
					需求: 查询账户表所有用户名称
        		多行多列: List<Map>  List<java实体>
					需求: 查找账户表的所有信息
1.导入jar包坐标
	<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.35</version>
    </dependency>
    
2.创建JdbcTemplate对象 
	public JdbcTemplate(DataSource dataSource)
	在创建对象时,传入数据源(连接池对象),JdbcTemplate会自动从连接池对象中获取连接
    
3.编写sql
	String sql = "insert into 表名 values(null,?,?) ";

4.处理返回结果

3、代码演示

package com.itheima.jdbc;

public class Demo02_JdbcTemplate {
    public static void main(String[] args) {
        // 新增
        //1.创建JdbcTemplate对象
        JdbcTemplate template = new JdbcTemplate();
        // 创建连接池对象
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///spring");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        // 给JdbcTemplate设置连接池对象 set方法
        template.setDataSource(dataSource);
        //2.编写sql语句
        String sql = "insert into account values(null,?,?) ";
        //3.调用JdbcTemplate的API执行添加
        int i = template.update(sql, "景甜", 500f);
        if(i>0){
            System.out.println("添加成功...");
        }
    }
}

4、JdbcTemplate相关API

1)增删改的方法
public int update(final String sql,Object... args)
    用于执行INSERT、UPDATE、DELETE等DML语句
    	参数1 sql: 要执行的sql语句
    	参数2 args: sql执行时需要的参数
代码演示
package com.example.template;

public class Demo2 {

    /**
     * JdbcTemplate: 增删改
     *      int update(String sql,Object args...);
     *          update执行完毕后返回对数据库影响的行数
     *          参数1: 要执行的sql语句
     *          参数2: sql语句中?对应的真实参数
     */

    private JdbcTemplate template;
    @Before
    public void init(){
        // 创建连接池
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        template = new JdbcTemplate(dataSource);
    }

    @Test
    public void testInsert(){
        // 编写sql
        String sql = "insert into account values(null,?,?) ";
        // 调用API执行
        int i = template.update(sql, "刘亦菲", 500f);
        if (i>0){
            System.out.println("添加成功");
        }
    }

    @Test
    public void testUpdate(){
        // 编写sql
        String sql = "update account set name=?,money=? where id=? ";
        // 调用API执行
        int i = template.update(sql, "杨幂", 50f,4);
        if (i>0){
            System.out.println("修改成功");
        }
    }

    @Test
    public void testDelete(){
        // 编写sql
        String sql = "delete from account where id=? ";
        // 调用API执行
        int i = template.update(sql, 4);
        if (i>0){
            System.out.println("删除成功");
        }
    }
}

2)查询的方法
 // 查询聚合函数 返回指定类型的一个数据
public <T> T queryForObject(String sql, Class<T> requiredType, Object... args) ★★★ 作用: 用于执行聚合函数
sql: 要执行的sql
requiredType: 返回的类型的字节码对象
args: sql所需要的参数

// 查询聚合函数  返回的结果封装到实体中
public <T> T queryForObject(String sql, RowMapper rm, Object... args)  
★★★  作用: 用于查询一条记录并封装到实体中(注意,若是查询不到结果jdbcTemplate会报异常)
RowMapper接口:  用于手动封装结果数据
mapRow方法,封装一条记录的
我们使用该方法查询一条记录并封装到实体中 BeanPropertyRowMapper<T>(T.class)

// 查询单行多列  将一条查询结果封装到map中返回  
public Map<String, Object> queryForMap(String sql, Object... args)
sql: 要执行的sql
args: sql所需要的参数

// 查询单列多行
List<T> queryForList(String sql,T.class, Object... args)

// 查询多行多列  将每条记录放map中,再将每个map放入list中
List<Map<String, Object>> queryForList(String sql, Object... args)
sql: 要执行的sql
args: sql所需要的参数

 // 返回一个List集合,List中存放的是RowMapper指定类型的数据
public <T> List<T> query(String sql, RowMapper<T> rowMapper) 
★★
返回一条记录: rowMapper(需要自己封装结果集)
返回值: BeanPropertyRowMapper
JDBCTemplate的 query方法用于执行SQL语句,简化JDBC的代码。
同时还可以在SQL语句中使用"?"占位,
Object... args 可变参数中传入对应的参数,可变参数可以传数组.
返回结果:
	单行单列: 基本类型或String接收结果即可
		select name from 表名 where id = 1;
		select count(*) from 表名;
	单行多列: Map<key,value> key为字段名称,value为查询到的结果
		select * from 表名 where id = 1;
	多行单列: List<T>
		select name from 表名;
	多行多列: List<Map<key,value>>
		select * from 表名;
    ----------常用★★★	-----------
   	将一条查询结果封装成一个对象: User
        select * from User where username="tom" and password="123";
	查询所有账户信息: List<Account>
        select * from account;
	查询总记录数:
		select count(*) from 表名;
代码演示
package com.example.template;

import com.example.pojo.Account;

public class Demo3 {
    /**
     * JdbcTemplate: 查询
     *      单行单列:
     *          T queryForObject(sql,T.class,Object args...)
     *      单行多列:
     *      多行单列:
     *      多行多列:
     */
    private JdbcTemplate template;
    
    @Before
    public void init(){
        // 创建连接池
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        template = new JdbcTemplate(dataSource);
    }

    /**
     * 需求: 查询id为5的账户名称
     * 结果: 单行单列
     * API:
     *      T queryForObject(sql,T.class,Object args...);
     */
    @Test
    public void test01(){
        String sql = "select name from account where id = ?";
        // 调用API
        String name = template.queryForObject(sql, String.class, 5);
        System.out.println(name);
    }

    /**
     * 需求: 查询id为5的账户详情?
     * 结果: 单行多列
     * API:
     *      Map<String,Object> queryForMap(sql,Object... args)
     *          map的key: 查询结果的字段名称
     *          map的value: 查询结果数据
     */
    @Test
    public void test02(){
        String sql = "select * from account where id = ? ";
        // 调用API
        Map<String, Object> map = template.queryForMap(sql, 5);
        for(String key:map.keySet()){
            System.out.println(key+" : "+map.get(key));
        }
    }

    /**
     * 需求: 查询id为5的账户详情?
     * 结果: 单行多列
     * API:
     *      queryForObject(sql,rowMapper接口的实现类,Object... args);
     */
    @Test
    public void test03(){
        String sql = "select * from account where id = ? ";
        // 调用API
        Account account = template.queryForObject(sql, new RowMapper<Account>() {
            // 每查询一条记录RowMapper的mapRow方法就会执行一次
            // rs: 本行记录信息
            // i: 索引
            @Override
            public Account mapRow(ResultSet rs, int i) throws SQLException {
                System.out.println(i);
                int id = rs.getInt("id");
                String name = rs.getString("name");
                float money = rs.getFloat("money");
                Account account = new Account(id, name, money);
                return account;
            }
        }, 5);
        System.out.println(account);
    }
    
    /**
     * 需求: 查询id为5的账户详情?
     * 结果: 单行多列
     * API:
     *      queryForObject(sql,BeanPropertyRowMapper<Account>,Object... args);
     *      数据库字段要和实体属性名保持一致.
     */
    @Test
    public void test04(){
        String sql = "select * from account where id = ? ";
        // 调用API
        Account account = template.queryForObject(sql,
                new BeanPropertyRowMapper<Account>(Account.class), 5);
        System.out.println(account);
    }
    
        /**
     * 需求: 查询账户表所有的账户名称
     * 结果: 多行单列
     * API:
     *      List<T> queryForList(sql,T.class,Object... args);
     */
    @Test
   public void test05(){
        String sql = "select name from account where id in(?,?,?) ";
        //API
        List<String> list = template.queryForList(sql, String.class, 1, 2, 3);
        System.out.println(list);
    }

    /**
     * 需求: 查询账户表所有的账户详情
     * 结果: 多行多列
     * API:
     *      List<Map<String,Object>> queryForList(sql,Object... args);
     */
    @Test
    public void test06(){
        String sql = "select * from account where id in(?,?,?) ";
        //API
        List<Map<String, Object>> list = template.queryForList(sql, 1, 2, 3);
        System.out.println(list);
    }

    /**
     * 需求: 查询账户表所有的账户详情
     * 结果: 多行多列
     * API:
     *      query();
     */
    @Test
    public void test07(){
        String sql = "select * from account where id in(?,?,?) ";
        //API
        // 可变参可以传数组
        Object params[] = {1,2,3};
        List<Account> list = template.query(sql, new BeanPropertyRowMapper<Account>(Account.class), params);
        System.out.println(list);
    }

    @Test
    public void test08(){
        String sql = "select * from account where id in(?,?,?) ";
        //API
        // 可变参可以传数组
        Object params[] = {1,2,3};
        List<Account> list = template.query(sql, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet rs, int i) throws SQLException {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                float money = rs.getFloat("money");
                Account account = new Account(id, name, money);
                return account;
            }
        }, params);
        System.out.println(list);
    }

}

5、IOC+JDBCTemplate案例

案例需求:"select * from account " 查询所有账户信息
分别使用三种方式完成:
xml方式
xml+注解方式
注解方式
1)xml方式
xml配置

applicationContext.xm

<!-- 解析Properties配置文件,将数据加载到IOC容器中 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

	<!--       ---------递进引用----------      -->  

    <!-- 一、创建AccountService对象 -->
    <bean id="AccountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="AccountDao"></property>
    </bean>

    <!-- 二、创建AccountDao对象 -->
    <bean id="AccountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="template" ref="JdbcTemplate"></property>
    </bean>

    <!-- 三、创建JdbcTemplate对象,存放到IOC容器中 -->
    <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 四、创建连接池对象 -->
    
    <!-- 1、Spring自带的连接池 -->
   <!-- <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>-->
    
    <!-- 2、c3p0的连接池 -->
    <!--<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>-->
    
    <!-- 3、druid连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>
properties配置

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
dao层
-----------接口---------------
package com.example.dao;

import com.example.pojo.Account;

import java.util.List;

public interface AccountDao {
    List<Account> findAll();
}

-----------实现类---------------
package com.example.dao.impl;

import com.example.dao.AccountDao;
import com.example.pojo.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class AccountDaoImpl implements AccountDao {

    private JdbcTemplate template;
    @Override
    public List<Account> findAll() {
        String sql = "select * from account ";
        List<Account> list = template.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
        return list;
    }

    public void setTemplate(JdbcTemplate template) {
        this.template = template;
    }
}
service层
-----------接口---------------
package com.example.service;

import com.example.pojo.Account;

import java.util.List;

public interface AccountService {
    List<Account> findAll();

}

-----------实现类---------------
package com.example.service.impl;

import com.example.dao.AccountDao;
import com.example.pojo.Account;
import com.example.service.AccountService;
import java.util.List;

public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;
    @Override
    public List<Account> findAll() {
        System.out.println("service层执行了");
        return accountDao.findAll();
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}
测试类
package com.example.web;

import com.example.pojo.Account;
import com.example.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class AccountClient {

    public static void main(String[] args) {
        // 获取spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 从IOC中获取service对象
        AccountService service = ac.getBean("AccountService", AccountService.class);
        // 
        List<Account> list = service.findAll();
        System.out.println(list);
    }
}

2)xml+注解方式

​ 配置xml:第三方用xml,jar包提供的类用xml,交给spring容器管理

​ 配置注解:自己的类用注解

xml配置

applicationContext.xm

    <!-- TODO:开启注解扫描 -->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!-- 解析properties配置文件,加载数据到IOC -->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!-- 创建JdbcTemplate对象,存放到IOC容器中 -->
    <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- druid连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>
</beans>
dao层
package com.example.dao;

import com.example.pojo.Account;

public interface AccountDao {
    List<Account> findAll();
}

-----------实现类---------------
package com.example.dao.impl;

import com.example.dao.AccountDao;
import com.example.pojo.Account;

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate template;
    
    @Override
    public List<Account> findAll() {
        String sql = "select * from account ";
        List<Account> list = template.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
        return list;
    }

    public void setTemplate(JdbcTemplate template) {
        this.template = template;
    }
}

service层
-----------接口---------------
package com.example.service;
import com.example.pojo.Account;

public interface AccountService {
    List<Account> findAll();

}

-----------实现类---------------
package com.example.service.impl;

@Service("AccountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
    
    @Override
    public List<Account> findAll() {
        System.out.println("service层执行了");
        return accountDao.findAll();
    }
}
测试类
package com.example.web;

import com.example.pojo.Account;
import com.example.service.AccountService;

public class AccountClient {
    
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取service的对象
        AccountService service = ac.getBean("AccountService", AccountService.class);
        List<Account> list = service.findAll();
        System.out.println(list);
    }
}

3)注解方式

其他环境同上

Spring核心配置类
package com.example.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration // 声明配置类
@ComponentScan("com.itheima") // 开启包扫描
@PropertySource("classpath:jdbc.properties") // 解析properties配置文件
public class SpringConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    // @Bean("template")
    // public JdbcTemplate getJdbcTemplate(){
    //     JdbcTemplate template = new JdbcTemplate();
    //     template.setDataSource(getDataSource());
    //     return template;
    // }

    @Bean("template")
    public JdbcTemplate getJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
        JdbcTemplate template = new JdbcTemplate();
        template.setDataSource(dataSource);
        return template;
    }

    @Bean("dataSource")
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

测试类
package com.example.web;

import com.example.config.SpringConfig;
import com.example.pojo.Account;
import com.example.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class AccountClient {

    public static void main(String[] args) {
        // 获取容器IOC
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
        // 从容器中获取service的对象
        AccountService service = ac.getBean("AccountService", AccountService.class);
        // 调用方法
        List<Account> list = service.findAll();
        System.out.println(list);
    }
}
4) JdbcTemplate整合不同的数据源(连接池)
properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
Spring自带数据源
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>
C3P0数据源
<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>
------------------------------
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
Druid数据源
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.13</version>
</dependency>
-----------------------------------
<!-- druid连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
    </bean>

三、Spring整合junit

作用: 简化单元测试代码      使用junit 4.12以上版本
// 例如省略:从容器中获取service的对象
AccountService service = ac.getBean("AccountService", AccountService.class);

1、条件

spring-test.jar
<!-- 引入单元测试的jar包 4.12以上 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- 导入Spring整合junit的jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
@RunWith
@ContextConfiguration
// 在测试类上添加以下两个注解

// 声明spring提供的单元测试环境
@RunWith(SpringJUnit4ClassRunner.class)
// 声明spring的配置信息
@ContextConfiguration(
        locations="classpath:applicationContext.xml")(配置路径)
或者 @ContextConfiguration(classes=SpringConfig.class) (配置类)
locations属性:加载xml配置文件
classes属性:加载配置类的字节码

2、Junit整合代码

其他环境同上

1)整合xml方式的测试
package com.example.web;

import com.example.pojo.Account;
import com.example.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class AccountTest {
    @Autowired
    private AccountService service;

    @Test
    public void testFindAll(){
        List<Account> list = service.findAll();
        for (Account account : list) {
            System.out.println(account);
        }
    }
}
2)整合注解方式的测试
package com.example.web;

import com.example.config.SpringConfig;
import com.example.pojo.Account;
import com.example.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * SpringJUnit4ClassRunner: 解析配置文件或配置类,并创建ApplicationContext对象
 * @ContextConfiguration: 设置配置文件或配置类
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountTest {
    @Autowired
    private AccountService service;
    @Test
    public void testFindAll(){
        List<Account> list = service.findAll();
        for (Account account : list) {
            System.out.println(account);
        }
    }
}

四、proxy与cglib动态代理

`方法增强: 在不改变源码的基础上对方法进行增强
    继承
    装饰模式
    动态代理
`AOP: 在不改变源码的基础上对方法进行增强
	 底层动态代理:
		proxy: jdk提供的,基于接口
        cglib: 基于子类,生成被代理类的子类

1、继承、装饰模式、动态代理分析

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2、基于接口的动态代理(JDK)

作用: 方法增强
要求: 被代理类至少实现一个接口
代码演示
Proxy: 动态代理 有目标对象

需求: 在不改变源码的基础上对run方法进行增强

-------------接口-----------------
public interface Car {
    void color();
    void run();
    String jiaYou(String you);
}
-------------被代理类---------------
public class QQ implements Car {
    public void color() {
        System.out.println("绿色的");
    }
    public void run() {
        System.out.println("QQ车以每秒1米的速度在挪动....");
    }
    public String jiaYou(String you) {
        return "QQ车正在加"+you;
    }
}
-------------动态代理生成的代理类---------
public class TestProxy {
    public static void main(String[] args) {
        // 被代理类对象
        QQ qq = new QQ();
        // 生成代理类对象
        // proxy: 生成的代理的类对象
        Car proxy = (Car) Proxy.newProxyInstance(
                TestProxy.class.getClassLoader(),//类加载器
                qq.getClass().getInterfaces(),//被代理类实现的接口字节码数组(代理类需要实现这些接口)
                //new Class[]{Car.class},//被代理类实现的接口字节码数组
                new InvocationHandler() { // 对需要增强的方法进行增强,对不需要增强的方法调用原来的逻辑
                    /**
                     * 每次调用代理类的方法时,此方法都会执行
                     * @param proxy : 生成的代理类对象(慎用)
                     * @param method : 当前所执行的方法的字节码对象
                     * @param args : 当前执行的方法所传递的参数
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        String name = method.getName();
                        System.out.println("当前执行的方法名称: "+name);
                        if("run".equals(name)){
                            System.out.println("QQ车以每秒1千米的速度在飞奔...");
                            return null;
                        }
                        // 反射执行方法(调用原来的逻辑)
                        return method.invoke(qq,args);
                        // java中返回值分为2类.
                        // 第一: 有返回值
                        // 第二: 无返回值
                        // 动态代理说,如果执行的方法没有返回值则返回 null
                        // 如果执行的方法有返回值,则直接将返回值给返回
                    }
                }
        );
        // 调用代理类的相关方法
        proxy.color();
        proxy.run();
        String you = proxy.jiaYou("97#");
        System.out.println(you);

    }
}
Proxy: 动态代理 无目标对象 直接生成接口实现类

扩展-生成接口的实现类

public class TestProxy1 {
    public static void main(String[] args) {
        // 能不能使用动态代理生成接口的代理类
        // 生成接口的实现类
        // 生成代理类对象
        // proxy: 生成的代理的类对象
        Car proxy = (Car) Proxy.newProxyInstance(
                TestProxy1.class.getClassLoader(),//类加载器
                new Class[]{Car.class},//被代理类实现的接口字节码数组
                new InvocationHandler() { // 对需要增强的方法进行增强,对不需要增强的方法调用原来的逻辑
                    /**
                     * 每次调用代理类的方法时,此方法都会执行
                     * @param proxy : 生成的代理类对象(慎用)
                     * @param method : 当前所执行的方法的字节码对象
                     * @param args : 当前执行的方法所传递的参数
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        String name = method.getName();
                        System.out.println(name);
                        Object result = null;
                        if("run".equals(name)){
                            System.out.println("QQ车以每秒1千米的速度在飞奔...");
                        }else if("color".equals(name)){
                            System.out.println("黄色的");

                        }else if ("jiaYou".equals(name)){
                            result = "QQ车正在加"+args[0];
                        }
                        return result;
                    }
                }
        );
        // 调用代理类的相关方法
        proxy.color();
        proxy.run();
        String you = proxy.jiaYou("97#");
        System.out.println(you);
    }
}

3、基于子类的动态代理(cglib第三方)

作用: 方法增强
要求: 导入cglib的jar包,由于cglib已经被Spring整合了,
所以导入Spring-context包即可
代码演示

被代理类

public class UserService{
    
    public void demo01(){
      System.out.println("demo01-----");
    }
    
    public void demo01(){
      System.out.println("demo02-----");
    }
    
}

测试

public static void main(String[] args) {
    // 被代理对象
    final UserService userService = new UserService();
    // Enhance动态代理
    // 基于子类的,动态生成一个类的子类,在生成的子类中对方法进行增强
    UserService proxy = (UserService)Enhancer.create(
            userService.getClass(),// 参数1:被代理对象的字节码
            new MethodInterceptor() { // 参数2: 接口,在原方法执行前对方法进行拦截处理
                /**
                 * 拦截吃了
                 * @param proxy : 生成的代理类对象(被代理类的子类)
                 * @param method : 当前执行的方法的字节码对象
                 * @param args : 当前执行的方法携带的参数数组
                 * @param methodProxy : 当前执行的方法的代理对象(不用管)
                 * @return
                 * @throws Throwable
                 */
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    Object result = null;
                    System.out.println("111111111111111111111");
                    result = method.invoke(userService,args);
                    System.out.println("222222222222222222222");
                    return result;
                }
            }
    );
    
    proxy.demo1();
    proxy.demo2();
}

总结

`封装:
	在java中提供了各式各样的容器,这些容器可以用于封装不同类型的数据.

`IOC注解开发:
	1.创建对象并将对象存放到IOC容器
		@Component
		@Controller
		@Service("accountService")
		@Repository("accountDao")如果value属性值不写,默认为当前类的类名称,首字母小写
		@Configuration
    2.用于注入数据: 注入 基本类型和String 其他bean对象
        【被注入的数据都来自IOC容器】
		@Autowired : 按照类型自动注入
		@Qualifier : 指定注入的对象名称
		@Resource : 按照name注入,也可以按照类型注入
            name: 需要注入的对象的名称
            type: 需要注入的数据的类型
            @Resource(name = "accountDao2",type=AccountDao.class)
                如果name和type属性都不写,则按照名称注入
                如果写了name,就将指定名称的对象注入进来
                如果写了type,就按照类型注入
                如果两个属性都写了,那么就必须满足所有的属性
            
		@Value("值"):
			值: 直接赋值
			值: SpEL表达式,从容器中获取参数
				解析properties配置文件中的参数,并存放到IOC容器中
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
	  3.生命周期和作用范围:
			@Scope("singleton或者prototype") (写在类上)
			@PostConstruct : 初始化时调用的方法 (写在方法上)
			@PreDestory : 销毁时调用的方法 (写在方法上)
	  4.纯注解:
			使用配置类代替配置文件
			@Configuration: 声明该类为Spring的配置文件类
            @ComponentScan(basePackages = "com.example") : 指定要扫描的包
            @PropertySource(value = "classpath:jdbc.properties") : 将配置文件交给Spring容器管理
            @Import({其他配置类.class})
            @Bean() : 将返回值存放到IOC容器中
               
`JDBCTemplate:
	Spring提供的,对JDBC进行封装的工具
  API:
	增删改: 执行增删改返回影响的行数
		★int update(sql,args...); 
	查询:
		★T queryForObject(sql,T.class,args...); 
			主要用于查询聚合函数
		★T queryForObject(sql,new BeanPropertyRowMapper<T>(T.class),args...);
			查询一条记录,并将其封装到对应的实体中(T代表实体)
            注意: 实体中属性的名称要和数据库表字段名称保持一致
            	 当查询不到结果时,JdbcTemplate会抛异常
            	 
        Map<key,value> queryForMap(sql,args...);
			查询一条记录,并将这条记录存放到Map集合中
			可以用于多表查询,返回一条记录
        List<T> queryForList(sql,T.class,args...);
			查询多行单列的结果,并将结果封装到List集合中
		List<Map<key,value>> queryForList(sql,args...);
			查询多行多列,将每一行封装成map,多个map存放到list中
        ★List<T> query(sql,new BeanPropertyRowMapper<T>(T.class),args...);
			查询多行多列,每一个行封装成一个实体对象,多个实体对象存放到list集合中

`动态代理:              
Proxy基于接口 : JDK
	被代理类对象至少实现一个接口
	代理类和被代理类为兄弟关系
cglib基于子类 : cglib
	导入jar包坐标
	Enhancer  
	代理和被代理类为父子关系
Spring会自动抉择
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值