Java框架之ORM

写在前面

本文看下Java操作数据库相关的内容。

1:JDBC

我们知道关系型数据库不止一种,像MySQL,Oracle,db2,PostgreSQL,sql server等,为了规范对这些不同数据的连接,数据的CRUD操作, Java官方便设计了一套接口,如DriverManager,Connection,Statement,ResultSet,DriverManager等,用来规范这些操作,这个规范就是JDBC在,rt.jar包的中的java.sql包下,如下图:

在这里插入图片描述

JDBC和应用程序,以及各种数据库之间的关系如下图:

在这里插入图片描述

那么最终如何连接到数据库并操作数据库呢,这就需要不同的数据库厂商按照JDBC的规范来提供一个具体的实现,这个具体的实现我们叫做是数据库驱动,如MySQL的驱动包mysql-connector-java-xxx.jar,oracle的驱动包ojdbc-xxx.jar,接下来我们也分别以MySQL和Oracle为例子看下如何操作数据库。

1.1:MySQL

源码
首先引入pom:

<dependencies>
    <dependency>
<!--
        <groupId>mysql</groupId>
        <artifactId>mysql</artifactId>
        <version>5</version>
        <systemPath>${project.basedir}/jar/mysql-connector-java-5.1.47.jar</systemPath>
-->
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

接着写测试程序大家自己创建表a0707,然后创建两个字段录数据就行:

public class MySqlMain {
    public static void main(String[] args) throws Exception {
        //1、导入驱动jar包
        //2、注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //3、获取数据库的连接对象
        Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3366/test", "root", "root");
        //4、定义sql语句
        String sql = "SELECT * FROM a0707 ";
        //5、获取执行sql语句的对象
        Statement stat = con.createStatement();
        //6、执行获取结果
        ResultSet rs = stat.executeQuery(sql);
        while(rs.next()){
            String one = rs.getString(1);
            String two = rs.getString(2);
            System.out.println("one is: " + one + ", two is: " + two);
        }
    }
}

运行:

one is: 1, two is: xxx
one is: 2, two is: d7df5f01-fdc7-11ec-b06f-00ff2dadabf5
one is: 3, two is: d7df7a50-fdc7-11ec-b06f-00ff2dadabf5
one is: 4, two is: d7df7ad2-fdc7-11ec-b06f-00ff2dadabf5
...

这样我们就通过MySQL的JDBC驱动程序成功操作MySQL数据库了。

1.2:Oracle

这里 下载驱动包。

首先配置驱动包:

<dependency>
    <groupId>oracle</groupId>
    <artifactId>oracle</artifactId>
    <version>5</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/jar/ojdbc-10g.jar</systemPath>
</dependency>

这里的systemPath根据本地环境自行修改,当然如果有线上资源的话,直接用也行。

接着写测试程序大家自己创建表AREA,然后创建两个字段录数据就行:

public class OracleMain {
    public static void main(String[] args) throws Exception {
        //1、导入驱动jar包
        //2、注册驱动
        Class.forName("oracle.jdbc.driver.OracleDriver");
        //3、获取数据库的连接对象
        Connection con = DriverManager.getConnection("jdbc:oracle:thin:@192.168.10.251:1521/orcl", "jc6_jcs", "jinher.2023");
        //4、定义sql语句
        String sql = "SELECT * FROM AREA ";
        //5、获取执行sql语句的对象
        Statement stat = con.createStatement();
        //6、执行获取结果
        ResultSet rs = stat.executeQuery(sql);
        while(rs.next()){
            String one = rs.getString(1);
            String two = rs.getString(2);
            System.out.println("one is: " + one + ", two is: " + two);
        }
    }
}

运行:

one is: 337, two is: 繁峙县
one is: 338, two is: 宁武县
one is: 339, two is: 静乐县
...

这样我们就通过oracle的JDBC驱动程序成功操作oracle数据库了。

可以看到,虽然操作了完全不同的数据库,但是我们只需要引入对应的驱动包,然后修改驱动类,以及数据库地址信息就行了,核心的数据操作逻辑完全不用动,这就是抽象的好处,规范的好处。

2:数据库连接池

我们知道线程资源比较珍贵,而且创建的成本很高,所以我们就有了连接池技术 ,数据库连接也同样如此,所以,我们也同样需要数据库连接的一种池化的技术,也就是数据库连接池,数据库连接池构建于JDBC之上,通过JDBC获取一组Connection,并按照一定的策略对其进行维护,如最大连接数,最小连接数,空闲多久回收等,如下图:

在这里插入图片描述

主要的数据库连接池实现如下:

C3P0:目前用到不多
DBCP:apatch common pool
Druid:阿里大牛开发,提供了sql审计功能,目前有一定市场
hikari:海卡里,日语“光”的意思,目前性能最好的数据库连接池,用的比较多

3:orm

源码

orm全称是object relation mapping,即对象关系映射,描述的是Java中的对象和数据库表的一种映射关系。数据库的表和Java的对象是差一层的,通过Java的对象不能直接映射到表,因此这就需要做一个转换,而这个转换的工作也势必是需要有一种规范的,因此SUN公司就是提出了JPA,Java persistence API,是由一组接口和抽象类组成的Java类到数据库表映射的一个规范,如下图:

在这里插入图片描述

实现了JPA规范的orm框架事实上的标准就是hibernate,其他的框架还有toplink,openJpa等,应用程序使用JPA规范,底层是具体实现了JPA规范的orm框架,如下图:

在这里插入图片描述

实际上,各种orm框架底层和数据库的交互还是通过JDBC完成的,因此可以认为JPA是在JDBC基础上以面向对象操作数据库的方式进行了进一步的封装,如下图:

在这里插入图片描述

除了hibernate,我们常用的orm框架还有mybatis,但是不同于hibernate,mybatis并没有遵循JPA规范,而是直接基于JDBC开发的,允许开发人员以直接写sql语句,并通过resultmap的方式映射到对象,接下来我们分别看下hibernate和mybatis的用法。

3.1:hibernate

首先我们创建表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `user_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_account` (`user_name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

接着引入依赖:

<dependencies>
    <!-- junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>
        <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <!-- servlet -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>${servlet.version}</version>
        <scope>provided</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <!-- Mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
    <!-- jstl -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>${jstl.version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/taglibs/standard -->
    <!-- taglibs -->
    <dependency>
        <groupId>taglibs</groupId>
        <artifactId>standard</artifactId>
        <version>${taglibs.version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jsp-api -->
    <!-- tomcat -->
    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-jsp-api</artifactId>
        <version>${tomcat.version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core -->
    <!-- hibernate -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>${hibernate.version}</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>
</dependencies>

创建映射表的Java对象:

@Getter
@Setter
@AllArgsConstructor
public class User implements Serializable{
	private Integer id;
	private String userName;
}

创建映射java对象和表的配置文件User.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- 实体类映射文件 -->
<hibernate-mapping>
    <!--
        name:实体类全路径名
        table:实体类对于的数据库表名称
     -->
    <class name="dongshi.daddy.User" table="user">
        <!--
            id:用于设置数据库表结构中主键列的生成方式
            name:实体类中属性名称
            type:Jave的数据类型
            column:数据库表字段名称
         -->
        <id name="id" type="java.lang.Integer" column="id">
            <!--
                class:定义主键列生成的方式:hibernate管理、数据库管理、开发者管理
                increment,identity,sequcene,native,assgine
             -->
            <generator class="increment"></generator>
        </id>
        <!-- 与实体类相匹配 -->
        <property name="userName" type="java.lang.String" column="user_name"/>
    </class>
</hibernate-mapping>

配置hibernate的配置文件hibernate.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 数据库相关配置 -->
        <!-- connection.username|connection.password|connection.url|connection.driver_class|dialect -->
        <!-- 连接数据库账户名称 -->
        <property name="connection.username">root</property>
        <!-- 连接数据库密码(我的数据库没有登录密码,直接不用写) -->
        <property name="connection.password">root</property>
        <!-- 连接的绝对路径(使用&需要解译&amp;) -->
        <property name="connection.url">
            jdbc:mysql://localhost:3366/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;userSSL=false
        </property>
        <!-- 驱动的绝对路径 -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <!-- 数据库方言配置 -->
        <property name="dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <!-- 配置本地事务 -->
        <property name="hibernate.current_session_context_class">thread</property>
        <!-- 调试相关配置 -->
        <!-- hibernate运行过程是否展示sql命令代码(自动生成) -->
        <property name="show_sql">true</property>
        <!-- 是否规范输出sql代码 -->
        <property name="format_sql">true</property>
        <!-- 实体映射相关配置 -->
        <mapping resource="hbm/User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

测试类:

public class HibernateJpaMain {
    public static void main(String[] args) {
        //创建Hibernate核心类
        Configuration cfg=new Configuration();
        //读取核心配置文件
        cfg.configure("hibernate.cfg.xml");
        //创建session工厂
        SessionFactory sf = cfg.buildSessionFactory();
        //获取session
        Session session = sf.openSession();
        //开启事务
        Transaction ts= session.beginTransaction();
        System.out.println("-------------增加-------------------");
        //新增
		User user=new User(111, "阿三同学");
		session.save(user);
        ts.commit();
    }
}

运行:

INFO: HHH000182: No default (no-argument) constructor for class: dongshi.daddy.User (class must be instantiated by Interceptor)
-------------增加-------------------
Hibernate: 
    select
        max(id) 
    from
        user
Hibernate: 
    insert 
    into
        user
        (user_name, id) 
    values
        (?, ?)

查看表:
在这里插入图片描述

3.2:mybatis

首先我们创建表:

CREATE TABLE test_mybatis (
   id INT(32) PRIMARY KEY AUTO_INCREMENT,
   full_name VARCHAR(64) DEFAULT NULL,
   age INT(32)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

创建实体类:

@Getter
@Setter
@AllArgsConstructor
public class TestMybatis {
    private Integer id;
    private String fullName;
    private Integer age;
}

创建mapper接口:

public interface TestMyBatisMapper {
    void insertTestMybatis(TestMybatis testMybatis);
}

创建mapper xml my-mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 注意:这里namespace必须使用mapper接口的类全限定名称,不然无法自动生成代理 -->
<mapper namespace="dongshi.daddy.mapper.TestMyBatisMapper">
    <insert id="insertTestMybatis" parameterType="dongshi.daddy.TestMybatis">
        INSERT INTO test_mybatis (
            full_name,
            age
        )
        VALUES
        (
            #{fullName},
            #{age}
        );
    </insert>
</mapper>

上述的namespace和parameterType注意改成自己的。

创建全局配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!-- 配置mybatis自动转换为驼峰式命名 -->
    <!-- 环境,可以配置多个,default:指定采用哪个环境 -->
    <environments default="test">
        <!-- id:唯一标识 -->
        <environment id="test">
            <!-- 事务管理器,JDBC类型的事务管理器 -->
            <transactionManager type="JDBC" />
            <!-- 数据源,池类型的数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://127.0.0.1:3366/test" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="dongshi/daddy/mapper/my-mapper.xml"/>
    </mappers>
</configuration>

测试代码:


public class TestMyBatisMapperTest {
    private TestMyBatisMapper testMyBatisMapper;
    private SqlSession sqlSession;

    @Before
    public void setUp() throws Exception {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        this.sqlSession = sqlSession;
        this.testMyBatisMapper = sqlSession.getMapper(TestMyBatisMapper.class);
    }

    @Test
    public void insertTestMyBatis() {
        TestMybatis newTestMyBatis = new TestMybatis(1212, "别吵吵,只有中国人解放军才能保卫台湾", 33);
//        newTestMyBatis.setFullName(UUID.randomUUID().toString());
//        newTestMyBatis.setAge(new Random().nextInt(100));
        testMyBatisMapper.insertTestMybatis(newTestMyBatis);
        this.sqlSession.commit();
    }
}

运行后:
在这里插入图片描述

截止到这里我们看下JDBC,JPA,ORM框架之间的关系:

在这里插入图片描述

另外,spring针对常见数据存储组件提供了spring-data 项目 ,如下图:

在这里插入图片描述

我们来看下spring-data-jpa是如何操作数据库的

3.3:spring data jpa

spring data jpa 进一步进一步封装了hibernate,其和JDBC,ORM等关系如下图:

在这里插入图片描述
spring data jpa支持如下三种查询方式:

1:通过方法名查询:将查询语句写到接口的名称中;
2:通过 Example 对象查询:构造一个模板对象,使用 findAll 方法来查询;
3:自定义查询:通过 Query 注解自定义复杂查询语句。

基于方法名称
在这里插入图片描述
基于Example:
在这里插入图片描述
自定义查询
在这里插入图片描述
下面我们详细看下。

  • 创建表
CREATE TABLE `jpa_user` (
  `id` bigint(64) NOT NULL AUTO_INCREMENT,
  `name` varchar(63) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4
  • xml
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.2.10.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--spring-data-jpa-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!--druid连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.23</version>
    </dependency>
    <!--oracle桥接器-->
<!--
    <dependency>
        <groupId>com.oracle.ojdbc</groupId>
        <artifactId>ojdbc8</artifactId>
        <scope>runtime</scope>
    </dependency>
-->
    <dependency>
        <!--
                    <groupId>mysql</groupId>
                    <artifactId>mysql</artifactId>
                    <version>5</version>
                    <systemPath>${project.basedir}/jar/mysql-connector-java-5.1.47.jar</systemPath>
        -->
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

</dependencies>
  • 表对应实体
@Data
@Entity
@Table(name = "jpa_user")
//@EntityListeners(AuditingEntityListener.class)
public class JpaUser {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "JPA_USER_S")
    @SequenceGenerator(sequenceName = "JPA_USER_S", name = "JPA_USER_S", allocationSize = 1)
    private Long id;

    @Column(name = "name")
    private String name;
}
  • yml配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://localhost:3366/test
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update #自动更新
    show-sql: true  #日志中显示sql语句
  application:
    name: spring-data-jpa-demo
server:
  port: 2333 #端口号
  • repository service controller
public interface JpaUserRepository extends JpaRepository<JpaUser, Long> {
}
public interface JpaUserService {
    /**
     * 新增用户
     * @param user 用户对象
     */
    JpaUser insertUser(JpaUser user);
}
@Service
public class JpaUserServiceImpl implements JpaUserService {
    @Resource
    private JpaUserRepository jpaUserRepository;

    @Override
    public JpaUser insertUser(JpaUser user) {
        return jpaUserRepository.save(user);
    }
}
@RestController
@RequestMapping("/user")
public class JpaUserController {
    @Resource
    private JpaUserService jpaUserService;

    @PostMapping("/addUser")
    public JpaUser addUser(@RequestBody JpaUser user){
        return jpaUserService.insertUser(user);
    }

}
  • main
/**
 * 启动类
 */
//@EnableJpaAuditing
@SpringBootApplication
public class SpringContextApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringContextApplication.class, args);
    }
}
  • 启动访问
    在这里插入图片描述

console输出:

2023-07-05 16:52:57.115  INFO 14556 --- [nio-2333-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms
Hibernate: select jpauser0_.id as id1_0_0_, jpauser0_.name as name2_0_0_ from jpa_user jpauser0_ where jpauser0_.id=?
Hibernate: select next_val as id_val from jpa_user_s for update
Hibernate: update jpa_user_s set next_val= ? where next_val=?
Hibernate: insert into jpa_user (name, id) values (?, ?)

在这里插入图片描述

4:其他

4.1:spring jdbc

封装了JDBC,提供了简单的数据库操作,我们经常用到的JdbcTemplate就在此功能内,如下图:

在这里插入图片描述

看下其如何使用。

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `user_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_account` (`user_name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
  • pom
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.2.10.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

</dependencies>
  • dao
@Repository
public class UserDao {
    @Resource
    private JdbcTemplate jdbcTemplate;

    public void addUser() {
        String insertSql = "INSERT INTO `user`(id, `user_name`) VALUES (?, ?) ";
        jdbcTemplate.update(insertSql, new Object[]{ 999, "精确治理是个啥?" });
    }
}
  • main

注意实现了ApplicationRunner接口,我们在其run方法中直接插入数据了。

@SpringBootApplication
public class SpringJdbcApplication implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(SpringJdbcApplication.class, args);
    }

    @Resource
    private UserDao userDao;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        userDao.addUser();
    }
}

运行:
在这里插入图片描述

4.2:事务

针对事务管理,spring定义了接口PlatformTransactionManager,针对不同的操作数据库的方式提供了不同的事务管理类,如对JDBC提供了DataSourceTransactionManager,Hibernate提供了HibernateTransactionManager,是通过AOP的方式来实现的。

写在后面

参考文章列表

JDBC连接Mysql数据库详解

JDBC连接Mysql数据库详解

学习笔记之JPA连接数据库

JPA、Hibernate、Spring data jpa 之间的关系,终于明白了

Java获取类、方法、属性上的注解

mybatis学习文章系列(偏源码)

最详细的Spring-data-jpa入门(一)

Spring Boot(三): 操作数据库-Spring JDBC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值