JDBC学习笔记

一、JDBC快速入门

一、概念:
    Java Database Connectivity  Java连接数据库的技术 独立于数据库系统、通用的存取数据的一组接口(API)
二、本质:
    独立于数据库系统、通用的存取数据的一组接口(API)
三、作用:
    JDBC为连接不同的数据库 提供了统一路径,为开发者屏蔽了细节问题
    开发者只需要面向这一组API(接口)即可,提高开发效率
    连接不同的数据库,需要不同的数据库厂商提供不同的驱动即可
JDBC的快速入门:
            涉及的API(java.sql.* 或 javax.sql.*)
            1. DriverManager类  管理不同的驱动
            2. Connection 接口  应用和数据库的连接
            3. Statement 接口   用于执行sql语句
            4. ResultSet 接口   保存查询的结果
    1、注册加载驱动    初始化
    2、获取连接对象
    3、创建命令对象
    4、执行sql命令
            (1). int update = statement.executeUpdate(sql); // 执行增 删 改
            (2). ResultSet rs = statement.executeQuery(sql);  // 执行查询
    5、释放资源

1、1导包

1、2数据库表

 

1、3代码演示

测试类:增删改查

package com.jn.jdbc1;

import com.jn.jdbc1.bean.Tb1;

import java.sql.*;

public class UseJDBC {
    public static void main(String[] args) throws Exception {
            //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
            //Class.forName("com.mysql.jdbc.Driver()");8.0以下版本使用该路径
            Class.forName("com.mysql.cj.jdbc.Driver");

            //获取连接对象
        String url="jdbc:mysql://localhost:3306/shixun";
        String user ="root";
        String password="123456";
        Connection connection = DriverManager.getConnection(url,user,password);
        System.out.println(connection);

        //创建命令对象
        Statement statement = connection.createStatement();

        //执行sql命令,执行sql语句
        //添加
        String sql1 ="insert into tb_1(id,name,sex,age,aihao)" + " values(5,'马超','男',40,'插秧')";
        int add1 = statement.executeUpdate(sql1);
        //修改
        String sql2 = "update tb_1 set name='huangzhong' where id=4";
        int add2= statement.executeUpdate(sql2);
        //删除
        String sql3 = "delete from tb_1 where id =5";
        int add3 = statement.executeUpdate(sql3);
        System.out.println(add1+add2+add3);

        //查询操作
        String sql4 = "select * from tb_1 where id = 1";
        ResultSet resultSet = statement.executeQuery(sql4);
        if (resultSet.next()){//判断结果集指针所在的位置的下个位置是否有数据,有的话指针下移并返回true,否则返回false
            int id =resultSet.getInt("id");
            String  name =resultSet.getString("name");
            String  sex =resultSet.getString("sex");
            int age =resultSet.getInt("age");
            String  aihao =resultSet.getString("aihao");
            //System.out.println(id + name + sex + age + aihao );
            Tb1 tb1 = new Tb1(id,age,name,sex,aihao);
            System.out.println(tb1);

            //释放资源
            resultSet.close();
            statement.close();
            connection.close();
        }
    }
}

实体类: 

package com.jn.jdbc1.bean;

public class Tb1 {
    public Tb1(int id, int age, String name, String sex, String aihao) {
        this.id = id;
        this.age = age;
        this.name = name;
        this.sex = sex;
        this.aihao = aihao;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAihao() {
        return aihao;
    }

    public void setAihao(String aihao) {
        this.aihao = aihao;
    }

    @Override
    public String toString() {
        return "Tb1{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", aihao='" + aihao + '\'' +
                '}';
    }

    public Tb1() {
    }

    private int id;
    private int age;
    private String  name;
    private String sex;
    private String aihao;
}

二、Junit 

2、1 概述     

JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个。
JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
    JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)
    Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。
    Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。 

2、2 使用     

1. 把junit4.x的测试jar,添加到该项目中来;
2. 定义一个测试类 测试类的名字: XxxTest,如EmployeeDAOTest
3. 在EmployeeDAOTest中编写测试方法:如
@Test
public void testXxx() throws Exception {
}
注意:方法是public修饰的,无返回的,该方法上必须贴有@Test标签,XXX表示测试的功能名字
4. 选择某一个测试方法,鼠标右键选择 [run as junit],或则选中测试类,表示测试该类中所有的测试方法.

2、3 常见注释

1. @Test:要执行的测试方法
2. @Before:每次执行测试方法之前都会执行
3. @After: 每次执行测试方法之后都会执行
4. @BeforeClass:在所有的Before方法之前执行,只在最初执行一次. 只能修饰静态方法
5. @AfterClass:在所有的After方法之后执行,只在最后执行一次. 只能修饰静态方法

实体类:

package com.jn.junit.bean;

public class Tb1 {
    public Tb1(int id, int age, String name, String sex, String aihao) {
        this.id = id;
        this.age = age;
        this.name = name;
        this.sex = sex;
        this.aihao = aihao;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAihao() {
        return aihao;
    }

    public void setAihao(String aihao) {
        this.aihao = aihao;
    }

    @Override
    public String toString() {
        return "Tb1{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", aihao='" + aihao + '\'' +
                '}';
    }

    public Tb1() {
    }

    private int id;
    private int age;
    private String  name;
    private String sex;
    private String aihao;
}

Dao类:

package com.jn.junit.dao;
import com.jn.junit.bean.Tb1;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Date;

public class Tb1Dao {
    //查询bookbs表中的数据
    public Tb1 getBookByid(int id) {
        Tb1 tb1=null;
        Connection connection=null;
        Statement statement=null;
        ResultSet resultSet = null;
        try {
            //1.加载驱动
            String url="jdbc:mysql://localhost:3306/shixun";
            String user ="root";
            String password="123456";
            connection = DriverManager.getConnection(url,user,password);
            //3.得到Statement对象
            statement = connection.createStatement();
            String sql="select * from tb_1 where id="+id;
            //4.执行语句并返回结果集
            resultSet = statement.executeQuery(sql);
            if(resultSet.next()) {
                int id1 =resultSet.getInt("id");
                String  name =resultSet.getString("name");
                String  sex =resultSet.getString("sex");
                int age =resultSet.getInt("age");
                String  aihao =resultSet.getString("aihao");
                //System.out.println(id + name + sex + age + aihao );
                 tb1 = new Tb1(id1,age,name,sex,aihao);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if(resultSet!=null) {
                    resultSet.close();
                }
                if(statement!=null) {
                    statement.close();
                }
                if(connection!=null) {
                    connection.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return tb1;
    }
}

测试类: 

package com.jn.junit.test;

import com.jn.junit.bean.Tb1;
import com.jn.junit.dao.Tb1Dao;
import org.junit.Test;

import java.awt.print.Book;

public class Tb1Test {
    //测试Tb1Dao中数据查询的方法是否正确
    @Test
    public void testBookGetBookById() {
        System.out.println("=====testById======");
        Tb1Dao tb1Dao = new Tb1Dao();
        Tb1 tb1 = tb1Dao.getBookByid(1);
        System.out.println(tb1);
    }
}

三、连接池

3、1 概念

为什么使用连接池?
    数据库连接是一种关键的有限的昂贵的资源,传统数据库连接每发出一个请求都要创建一个连接对象,使用完直接关闭不能重复利用;
    关闭资源需要手动完成,一旦忘记会造成内存溢出;
    请求过于频繁的时候,创建连接极其消耗内存;
    而且一旦高并发访问数据库,有可能会造成系统崩溃。
    为了解决这些问题,我们可以使用连接池。

3、2  原理

数据库连接池负责分配、管理和释放数据库连接,它的核心思想就是连接复用,通过建立一个数据库连接池,这个池中有若干个连接对象,当用户想要连接数据库,
就要先从连接池中获取连接对象,然后操作数据库。一旦连接池中的连接对象被用完了,判断连接对象的个数是否已达上限,如果没有可以再创建新的连接对象,如
果已达上限,用户必须处于等待状态,等待其他用户释放连接对象,直到连接池中有被释放的连接对象了,这时候等待的用户才能获取连接对象,从而操作数据库。
这样就可以使连接池中的连接得到高效、安全的复用,避免了数据库连接频繁创建、关闭的开销。这项技术明显提高对数据库操作的性能。

3、3 优势 

(1)程序启动时提前创建好连接,不用用户请求时创建,给服务器减轻压力;
(2)连接关闭的时候不会直接销毁connection,这样能够重复利用;
(3)如果超过设定的连接数量但是还没有达到最大值,那么可以再创建;
(4)如果空闲了,会默认销毁(释放)一些连接,让系统性能达到最优;

3、4 常用的开源连接池

1.DBCP
       是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持
   2.C3P0
       是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
       Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
    BoneCP 是一个开源组织提供的数据库连接池,速度快
   3.Druid
       是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不知道   是否有BoneCP快

四、Druid连接池

4、1导包

4、2使用

 4、2、1实体类

package com.jn.jruidpool.bean;

public class Person {
    public Person(int id, String name, int age, String sex) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public Person() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }

    private int id;
    private String name;
    private int age;
    private  String sex;
}

4、2、2测试连接类

package com.jn.jruidpool;

import com.alibaba.druid.pool.DruidAbstractDataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

public class DuirdConnectionTest {
    @Test
    public void getDruidConnection()throws Exception{
        //读取配置文件,获取里面的配置信息
        InputStream inputStream = DuirdConnectionTest.class.getClassLoader().getResourceAsStream("druid.properties");
        Properties info =new Properties();
        info.load(inputStream);
        DataSource dataSource = DruidDataSourceFactory.createDataSource(info);
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }

}

4、2、3测试类

package com.jn.jruidpool.dao;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.jn.jdbc1.bean.Tb1;
import com.jn.jruidpool.DuirdConnectionTest;
import com.jn.jruidpool.bean.Person;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

//使用druid连接池获取连接,对表中的数据进行增删改查
public class PersonDao {
    private static DataSource dataSource;
    static {
        try {
            //读取配置文件,获取里面的配置信息
            InputStream inputStream = DuirdConnectionTest.class.getClassLoader().getResourceAsStream("druid.properties");
            Properties info =new Properties();
            info.load(inputStream);
            dataSource = DruidDataSourceFactory.createDataSource(info);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //获取连接的方法
    public Connection getConnection(){
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return connection;
    }
    @Test
    //查询性别为男的数据
    public void getStuudentBySex() throws SQLException {
        Connection connection = getConnection();
        //创建命令对象
        Statement statement = connection.createStatement();
        //查询操作
        String sql = "select * from Person where sex ='男'";
        ResultSet resultSet = statement.executeQuery(sql);
        while(resultSet.next()) {
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            String sex = resultSet.getString("sex");
            Person person = new Person(id, name, age, sex);
            System.out.println(person);

            resultSet.close();
            statement.close();
            connection.close();
        }
    }
    @Test
        //修改id=3的sex为nv
        public void UpdateStudent () throws SQLException {
            Connection connection = getConnection();
            //创建命令对象
            Statement statement = connection.createStatement();
            String sql = "update person set sex = '女' where id = 3";
            int add = statement.executeUpdate(sql);
            System.out.println(add);
            statement.close();
            connection.close();
    }
    @Test
        //添加一条数据
        public void AddStuudent () throws SQLException {
            Connection connection = getConnection();
            //创建命令对象
            Statement statement = connection.createStatement();
            String sql = "insert into person(id,name,age,sex)"+"values (7,'小乔',15,'女')";
            int add = statement.executeUpdate(sql);
            System.out.println(add);
            statement.close();
            connection.close();

        }

        @Test
        //删除id=5的数据
        public void DeleteStuudent () throws SQLException {
            Connection connection = getConnection();
            //创建命令对象
            Statement statement = connection.createStatement();
            String sql = "delete from person where id =5";
            int add = statement.executeUpdate(sql);
            System.out.println(add);
            statement.close();
            connection.close();

        }

    }

五、线程隔离

线程安全问题
        多个线程共享同一资源(同一个连接对象) 引发线程安全问题
    ThreadLocal 类
        ① 理解
            JDK 1.2 的版本中就提供 java.lang.ThreadLocal,ThreadLocal 为解决多线程程序的
            并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
        ② 原理
            ThreadLocal 用于保存某个线程共享变量,原因是在 Java 中,每一个线程中都有一个 ThreadLocalMap<ThreadLocal, Object>,其 key 就是一个 ThreadLocal,而
        Object 即为该线程的共享变量。而这个 map 是通过 ThreadLocal 的 set 和 get 方法操作的。对于同一个 static ThreadLocal,不同线程只能从中 get,set,remove 自己的变
        量,而不会影响其他线程的变量。
        ③ 使用
            1、ThreadLocal.get: 获取 ThreadLocal 中当前线程共享变量的值。
            2、ThreadLocal.set: 设置 ThreadLocal 中当前线程共享变量的值。
            3、ThreadLocal.remove: 移除 ThreadLocal 中当前线程共享变量的值。
package com.jn.threadlocal;

import java.sql.SQLException;

public class ThreadLocalTest {

        // 创建一个 ThreadLocal 变量,用于存储当前线程的用户名
        private static final ThreadLocal<String> usernameThreadLocal = new ThreadLocal<>();

        public static void main(String[] args) {
            // 线程 1
            new Thread(() -> {
                setUsername("Thread1");
                printUsername();
            }).start();

            // 线程 2
            new Thread(() -> {
                setUsername("Thread2");
                printUsername();
            }).start();
        }

        public static void setUsername(String username) {
            usernameThreadLocal.set(username);
        }

        public static void printUsername() {
            System.out.println("当前线程的用户名是:" + usernameThreadLocal.get());
        }
    }


 六、c3p0配置 

6、1包的导入 

 6、2 xml文件的配置

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
	<!-- This is default config! -->
	<default-config>
		<property name="initialPoolSize">10</property>
		<property name="maxIdleTime">30</property>
		<property name="maxPoolSize">100</property>
		<property name="minPoolSize">10</property>
		<property name="maxStatements">200</property>
	</default-config>

	<!-- This is my config for mysql-->
	<named-config name="mysql">
		<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/shixun?rewriteBatchedStatements=true </property>
		<!--MySQL账户密码-->
		<property name="user">root</property>
		<property name="password">123456</property>
		<!-- 初始化连接池中的连接数,取值应在minPoolSize与maxPoolSize之间,默认为3-->
		<property name="initialPoolSize">10</property>
		<!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。默认值: 0 -->
		<property name="maxIdleTime">30</property>
		<!--连接池中保留的最大连接数。默认值: 15 -->
		<property name="maxPoolSize">100</property>
		<!-- 连接池中保留的最小连接数,默认为:3-->
		<property name="minPoolSize">10</property>
		<!--c3p0全局的PreparedStatements缓存的大小。如果maxStatements与maxStatementsPerConnection均为0,则缓存不生效,只要有一个不为0,则语句的缓存就能生效。如果默认值: 0-->
		<property name="maxStatements">200</property>
		<!-- 当连接池连接耗尽时,客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待。单位毫秒。默认: 0 -->

		<!--这种写法也可以
        <property name="checkoutTimeout" value="3000"/>
        -->
		<property name="checkoutTimeout">3000</property>
		<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。默认值: 3 -->
		<!--
        <property name="acquireIncrement" value="2"/>
        -->
		<property name="acquireIncrement">2</property>
		<!--定义在从数据库获取新连接失败后重复尝试的次数。默认值: 30 ;小于等于0表示无限次-->
		<!--
        <property name="acquireRetryAttempts" value="0"/>
        -->
		<property name="acquireRetryAttempts">0</property>
		<!--重新尝试的时间间隔,默认为:1000毫秒-->
		<!--
        <property name="acquireRetryDelay" value="1000" />
        -->
		<property name="acquireRetryDelay">1000</property>
		<!--关闭连接时,是否提交未提交的事务,默认为false,即关闭连接,回滚未提交的事务 -->
		<property name="autoCommitOnClose">false</property>
		<!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。默认值: null -->
		<property name="automaticTestTable">Test</property>
		<!--如果为false,则获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常,但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。默认: false-->
		<property name="breakAfterAcquireFailure">false</property>
		<!--每60秒检查所有连接池中的空闲连接。默认值: 0,不检查 -->
		<property name="idleConnectionTestPeriod">60</property>
		<!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。默认值: 0 -->
		<property name="maxStatementsPerConnection">200</property>
	</named-config>


	<!-- This is my config for oracle
    <named-config name="oracle">
        <property name="driverClass">oracle.jdbc.driver.OracleDriver</property>
        <property name="jdbcUrl">jdbc:oracle:thin:@localhost:1521:orcl</property>
        <property name="user">scott</property>
        <property name="password">liang</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>
    </named-config>
    -->
</c3p0-config>

 6、3 连接数据库

package com.jn.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;

public class JDBCUtil {

	private static DataSource dataSource=null;
	static{
		dataSource=new ComboPooledDataSource("mysql");
	}

	/**
	 * 获取数据库连接
	 * @return
	 */
	public static Connection getConnection(){
		Connection conn=null;
		try {
			conn=dataSource.getConnection();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return conn;
	}


	/**
	 * 关闭数据库连接
	 * @param conn
	 */
	public static void closeConn(Connection conn){
		try {
			if(conn!=null && conn.isClosed()){
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

6、4测试连接

package com.jn.util;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UtilTest {

        public static void main(String[] args){
            Connection conn= com.jn.util.JDBCUtil.getConnection();
            System.out.println(conn);
            try {
                PreparedStatement stmt=conn.prepareStatement("select * from tb_1");
                ResultSet re=stmt.executeQuery();
                while(re.next()){
                    String name=re.getString(3);
                    System.out.println(name);
                }
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

七、SQL注入命令优化

1. 介绍
    SQL 注入即是指 web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在 web 应用程序中事先定义好的查询语句的结尾上添加额外的 SQL
    语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
2. 问题语句演示:
    SELECT * FROM users WHERE username='admin and pwd=' OR 1=1;
    String username=“’test ”; String pwd=“ ‘or 1=1 ”;
3.优化命令:将 Statement 替换成了 PreparedStatment 预编译命令对象
原因:
    sql语句中包含特殊符号 将整个sq1语句改变
    解诀:将Statement 替换成子接口 PreparedStatment预编译命令对象
    原理:sq1语句先编译 使用?作为占位符 将传递的参数如果出现特殊符号 自动转义
Preparedstatment好处:
    避免了字符串和变量的拼接 屏蔽了细节问题
    解决了sql注入问题
    sq1语句可以复用

7、1 User类

package com.jn.optimize.bean;

import java.util.Date;

public class User {
	private int id;
	private String username;
	private String pwd;
	private String sex;
	private String tel;
	private String home;
	public User() {
		super();
	}
	public User(int id, String username, String pwd, String sex, String tel, String home) {
		super();
		this.id = id;
		this.username = username;
		this.pwd = pwd;
		this.sex = sex;
		this.tel = tel;
		this.home = home;
	}

	public String getHome() {
		return home;
	}
	public void setHome(String home) {
		this.home = home;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPwd() {
		return pwd;
	}
	public void setPwd(String pwd) {
		this.pwd = pwd;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
	public String getTel() {
		return tel;
	}
	public void setTel(String tel) {
		this.tel = tel;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", username=" + username + ", pwd=" + pwd + ", sex=" + sex + ", tel=" + tel
				+ ", home=" + home + " ]";
	}
}

7、2 问题引入

package com.jn.optimize.dao;

import com.jn.optimize.bean.User;
import com.jn.util.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


public class UserDao {
	//问题方法
	public void login(String username,String password) throws SQLException {
		Connection connection = JDBCUtil.getConnection();
		Statement statement = connection.createStatement();
		String sql="select * from user where username="+username+" and pwd="+password;
		ResultSet resultSet = statement.executeQuery(sql);
		if(resultSet.next()) {
			int id = resultSet.getInt("id");
			String uname = resultSet.getString("username");
			String pwd = resultSet.getString("pwd");
			String sex = resultSet.getString("sex");
			String home = resultSet.getString("home");
			String tel = resultSet.getString("tel");
			User user=new User(id, uname, pwd, sex, tel, home);
			System.out.println(user);
		}
		resultSet.close();
		statement.close();
		JDBCUtil.closeConn(connection);
	}

	public static void main(String[] args) throws SQLException {
		UserDao userDao=new UserDao();
		userDao.login("'qq ","123s' or 1=1");
	}
}

7、3 解决问题 

        此时没有查询到数据

package com.jn.optimize.dao;

import com.jn.optimize.bean.User;
import com.jn.util.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class UserDao {
	//sql注入问题的解决
	public void login02(String username,String password) throws SQLException {
		Connection connection = JDBCUtil.getConnection();
		String sql="select * from user where username=? and pwd=?";
		//获取PreparedStatement对象,并预编译语句
		PreparedStatement prepareStatement = connection.prepareStatement(sql);
		//给占位符进行赋值
		prepareStatement.setString(1,username);
		prepareStatement.setString(2,password);
		ResultSet resultSet = prepareStatement.executeQuery();
		if(resultSet.next()) {
			int id = resultSet.getInt("id");
			String uname = resultSet.getString("username");
			String pwd = resultSet.getString("pwd");
			String sex = resultSet.getString("sex");
			String home = resultSet.getString("home");
			String tel = resultSet.getString("tel");
			User user=new User(id, uname, pwd, sex, tel, home);
			System.out.println(user);
		}
		resultSet.close();
		prepareStatement.close();
		JDBCUtil.closeConn(connection);
	}
	public static void main(String[] args) throws SQLException {
		UserDao userDao=new UserDao();
		userDao.login02("'qq","123s' or 1=1");
	}
}

八、事务

(一)事务的概念
    事务是应用程序中一个完整的业务逻辑,(包含多个小的单元,每一个小的单元分别对数据库中的数据进行 crud 操作。)我们通过事务保证所有的小单元,要么同时成功,
    要么同时失败。也就是说事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。
事务的特性(ACID):
    (1)原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
    (2)一致性(Consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    (3)隔离性(Isolation):隔离性是当多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,即多个并发事务之间互不影响。
    (4)持久性(Durability):持久性是指一个事务一旦被提交了,那么对数据库中数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事
    务的操作。
管理自定义事务的语句:
    1. 手动开启事务:connection.setAutoCommit(false);
    2. 成功提交:connection.commit();//
    失败回滚:connection.rallbock()
(二)事务的实现
   ① 手动开启事务
    // 开启事务: 将事务的自动提交关闭 并且手动开启一个事务
    connection.setAutoCommit(false);
    ② 成功 提交
    // 如果没问题 提交
    connection.commit();
    ③ 失败 回滚
    如果有问题 回滚
    try{
    connection.rollback();
    }catch(soLExceptione1){
    TOD0 Auto-generated catch block
    e1,printstackTrace;
    }

8、1转账失败的案例

package com.jn.matter;

import com.jn.util.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Matter {

    //模仿转账失败的场景
    public  void Method() throws SQLException {
        Connection connection=null;
        PreparedStatement slbprepareStatement=null;
        PreparedStatement syhprepareStatement=null;
        try{
        connection = JDBCUtil.getConnection();
        String slbsql="update matter set money=money-100 where name='孙莲奔'";
        String syhsql="update matter set money=money+100 where name='商运鹤'";
        //获取PreparedStatement对象,并预编译语句
        slbprepareStatement = connection.prepareStatement(slbsql);
        syhprepareStatement = connection.prepareStatement(syhsql);

        int zsStart = slbprepareStatement.executeUpdate();
        if(zsStart>0) {
            System.out.println("孙莲奔转钱成功");
        }
        int i=5/0;
        int lsStart = syhprepareStatement.executeUpdate();
        if(lsStart>0) {
            System.out.println("商运鹤收钱成功");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        //关闭资源
        slbprepareStatement.close();
        syhprepareStatement.close();
        JDBCUtil.closeConn(connection);
    }
    }



    public static void main(String[] args) {
        Matter matter = new Matter();
        try {
            matter.Method();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

 8、2事务转账

package com.jn.matter;

import com.jn.util.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Matter {


    //开启事务转账
    public  void Method01() throws SQLException {
        Connection connection=null;
        PreparedStatement slbprepareStatement=null;
        PreparedStatement syhprepareStatement=null;
        try{
            connection = JDBCUtil.getConnection();

            //把事务设为手动提交
            connection.setAutoCommit(false);

            String slbsql="update matter set money=money-100 where name='孙莲奔'";
            String syhsql="update matter set money=money+100 where name='商运鹤'";


            //获取PreparedStatement对象,并预编译语句
            slbprepareStatement = connection.prepareStatement(slbsql);
            syhprepareStatement = connection.prepareStatement(syhsql);

            int zsStart = slbprepareStatement.executeUpdate();
            if(zsStart>0) {
                System.out.println("孙莲奔转钱成功");
            }
            //int i=5/0;
            int lsStart = syhprepareStatement.executeUpdate();
            if(lsStart>0) {
                System.out.println("商运鹤收钱成功");
            }

            //程序没问题的话执行commit,提交事务
            connection.commit();

        } catch (Exception e) {

            //程序异常事务回滚
            connection.rollback();
            e.printStackTrace();
        }finally {
            //关闭资源
            slbprepareStatement.close();
            syhprepareStatement.close();
            JDBCUtil.closeConn(connection);
        }
    }


    public static void main(String[] args) {
        Matter matter = new Matter();
        try {
            matter.Method01();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

九、批量操作

9、1普通方法

        用这种方法添加数据时时间较慢

package com.jn.patch;

import com.jn.util.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class PatchOperater {

    //普通方式,循环添加数据
    public void addPerson() throws SQLException {
        //获取连接
        Connection connection = JDBCUtil.getConnection();
        //准备语句
        String sql="insert into matter (name,money) values(?,?)";
        PreparedStatement prepareStatement = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        for(int i=1;i<=2000;i++) {
            prepareStatement.setString(1,"test"+i);
            prepareStatement.setDouble(2,1+i);
            int update = prepareStatement.executeUpdate();
            if(i%500==0) {
                System.out.println("添加了---"+i);
            }
        }
        long end = System.currentTimeMillis();
        prepareStatement.close();
        connection.close();
        long time=end-start;
        System.out.println("用时--"+time);
    }//用时--7181

    public static void main(String[] args) throws SQLException {
        PatchOperater p=new PatchOperater();
        p.addPerson();
    }

}

9、2Patch批量操作

package com.jn.patch;

import com.jn.util.JDBCUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class PatchOperater {

    //批量数据的添加
    public void addPersonBatch() throws SQLException {
        //获取连接
        Connection connection = JDBCUtil.getConnection();
        //准备语句
        String sql="insert into matter (name,money) values(?,?)";
        PreparedStatement prepareStatement = connection.prepareStatement(sql);
        long start = System.currentTimeMillis();
        for(int i=1;i<=2000;i++) {
            prepareStatement.setString(1,"test"+i);
            prepareStatement.setDouble(2,1+i);
            //积攒语句
            prepareStatement.addBatch();
            if(i%500==0) {
                //积攒500条数据,添加一次
                prepareStatement.executeBatch();
                System.out.println("添加了---"+i);
            }
        }
        //清理
        prepareStatement.clearBatch();

        long end = System.currentTimeMillis();
        prepareStatement.close();
        connection.close();
        long time=end-start;
        System.out.println("用时--"+time);
    }//122ms

    public static void main(String[] args) throws SQLException {
        PatchOperater p=new PatchOperater();
        p.addPersonBatch();
    }

}

 十、dbutils

(一)简介
    commons-dbutils 是 Apache 组织提供的一个开源 JDBC 工具类库,它是对
    JDBC 的简单封装,学习成本极低,并且使用 dbutils 能极大简化 jdbc 编码的工作量,同时也不会影响程序的性能。
(二)作用
    DbUtils :提供如关闭连接、装载 JDBC 驱动程序等常规工作的工具类,里面的所有方法都是静态的。
    该包封装了 SQL 的执行,是线程安全的。
        (1)可以实现增、删、改、查、批处理、
        (2)考虑了事务处理需要共用 Connection。
        (3)该类最主要的就是简单化了 SQL 查询,它与 ResultSetHandler 组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
(三)常用的方法
    ① 操作:update()
        public int update(Connection conn, String sql, Object... params)
        throws SQLException:用来执行一个更新(插入、更新或删除)操作。
    ② 查询:query()
        public Object query(Connection conn, String sql, ResultSetHandlerrsh,Object... params) throws SQLException:执行一个查询操作,
        在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理PreparedStatement 和 ResultSet 的创建和关闭。
    注:
        该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
        ResultSetHandler 接口提供了一个单独的方法:Object handle(java.sql.ResultSet rs)该方法的返回值将作为 QueryRunner 类的 query()方法的返回值
    方法:
        ArrayHandler:把结果集中的第一行数据转成对象数组。
        ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到 List中。
        BeanHandler:将结果集中的第一行数据封装到一个对应的 JavaBean 实例中。
        BeanListHandler:将结果集中的每一行数据都封装到一个对应的 JavaBean 实例中,存放到 List 里。
        ColumnListHandler:将结果集中某一列的数据存放到 List 中。
        KeyedHandler(name):将结果集中的每一行数据都封装到一个 Map 里,再把这些 map 再存到一个 map 里,其 key 为指定的 key。
        MapHandler:将结果集中的第一行数据封装到一个 Map 里,key 是列名,value就是对应的值。
        MapListHandler:将结果集中的每一行数据都封装到一个 Map 里,然后再存放到List
(四)基本使用
    ① 下载导包 commons-dbutils-1.7.jar
    ② 使用
  1、创建核心对象
  2. 执行命令
    int update = qr.update(connection,sql,params); //
    执行增删改
    T t = qr.query(connection,sql,new BeanHandler,params);
    List t = qr.query(connection,sql,new BeanListHandler,params);
    Object t = qr.query(connection,sql,new ScalerHandler,params);

10、1导包

10、2增删改查

10、2、1Books类

package com.jn.dbutils.bean;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;

public class Books {
    public Books() {
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }



    @Override
    public String toString() {
        return "Books{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                ", creatTime=" + creatTime +
                '}';
    }

    private String title;
    private String author;
    private BigDecimal price;

    public LocalDateTime getCreatTime() {
        return creatTime;
    }

    public void setCreatTime(LocalDateTime creatTime) {
        this.creatTime = creatTime;
    }

    private LocalDateTime creatTime;
}

10、2、2测试

package com.jn.dbutils.dao;

import com.jn.dbutils.bean.Books;
import com.jn.util.JDBCUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;

import java.awt.print.Book;
import java.sql.Connection;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;

public class BooksThest {

    //往books里面添加一条数据
    @Test
    public void addMethord() throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        Connection connection = JDBCUtil.getConnection();
        String sql ="insert into books(title,author,price,creatTime) values(?,?,?,?)";
        Timestamp currentTime = new Timestamp(System.currentTimeMillis());
        Object[] objects = new Object[]{"百年孤独", "商运鹤", 15,currentTime };

        //update:可实现数据的增删改
        //update可以预编译语句,并且可以给语句中的占位符进行赋值,最后把语句运送到数据库的服务器进行执行
        int update = queryRunner.update(connection,sql,objects);
        System.out.println(update);
        JDBCUtil.closeConn(connection);
    }
    //删除books里面title为book1的数据
    @Test
    public void deleteMethord() throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        Connection connection = JDBCUtil.getConnection();
        String sql ="delete from books where title=?";
        Object[] objects = new Object[]{"book1"};
        int update = queryRunner.update(connection,sql,objects);
        System.out.println(update);
        JDBCUtil.closeConn(connection);
    }


    //修改books中title为Book3的作者为孙莲奔
    @Test
    public void upodateMethord() throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        Connection connection = JDBCUtil.getConnection();
        String sql ="update books set author=? where title=?";
        Object[] objects = new Object[]{"孙莲奔", "Book3"};
        int update = queryRunner.update(connection,sql,objects);
        System.out.println(update);
        JDBCUtil.closeConn(connection);
    }
    //查询books中title为百年孤独的信息
    @Test
    public void getBookById() throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        Connection connection = JDBCUtil.getConnection();
        String sql = "select * from books where title=?";
        BeanHandler<Books> bh = new BeanHandler<Books>(Books.class);
        Books books = queryRunner.query(connection,sql,bh, "百年孤独");
        System.out.println(books);
        JDBCUtil.closeConn(connection);
    }

    //查询books中的所有数据
    @Test
    public void getBookList() throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        Connection connection = JDBCUtil.getConnection();
        String sql = "select * from books";
        BeanListHandler<Books> bh = new BeanListHandler<Books>(Books.class);
        List<Books> booksList = queryRunner.query(connection,sql,bh);
        for (Books books : booksList) {
            System.out.println(books);
        }
        JDBCUtil.closeConn(connection);
    }


    //查询books中的数据量
    @Test
    public void getBookCount() throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        Connection connection = JDBCUtil.getConnection();
        String sql = "select count(*) from books";
        ScalarHandler handler = new ScalarHandler();
        Object object = queryRunner.query(connection,sql,handler);
        long count = (long) object;
        System.out.println(count);
        JDBCUtil.closeConn(connection);
    }


}

十一、dao的使用

11、1bean

package com.jn.userdao.bean;

import java.math.BigDecimal;
import java.time.LocalDateTime;

public class Books {
    public Books() {
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }



    @Override
    public String toString() {
        return "Books{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                ", creatTime=" + creatTime +
                '}';
    }

    private String title;
    private String author;
    private BigDecimal price;

    public LocalDateTime getCreatTime() {
        return creatTime;
    }

    public void setCreatTime(LocalDateTime creatTime) {
        this.creatTime = creatTime;
    }

    private LocalDateTime creatTime;
}

 

11、2dao类的封装

package com.jn.userdao.dao;

import com.jn.util.JDBCUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class Dao<T> {
    private QueryRunner queryRunner=new QueryRunner();
    //通用增删改
    public int update(String sql,Object...objects) {
        Connection connection = JDBCUtil.getConnection();
        int update=0;
        try {
            update = queryRunner.update(connection, sql, objects);
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.closeConn(connection);
        }
        return update;
    }
    //查询单条数据
    public T getBean(String sql,Class<T> cls,Object...objects) {
        Connection connection = JDBCUtil.getConnection();
        BeanHandler<T> bh=new BeanHandler<T>(cls);
        T t=null;
        try {
            t = queryRunner.query(connection, sql, bh, objects);
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.closeConn(connection);
        }
        return t;
    }
    //查询多条数据
    public List<T> getBeanList(String sql, Class<T> cls, Object...objects) {
        Connection connection = JDBCUtil.getConnection();
        BeanListHandler<T> bh=new BeanListHandler<T>(cls);
        List<T> list=null;
        try {
            list = queryRunner.query(connection, sql, bh, objects);
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.closeConn(connection);
        }
        return list;
    }
    //查询聚合数据
    public Object getScaleValue(String sql,Object...objects) {
        Connection connection = JDBCUtil.getConnection();
        ScalarHandler scalarHandler = new ScalarHandler();
        Object obj=null;
        try {
            obj = queryRunner.query(connection, sql, scalarHandler, objects);
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            JDBCUtil.closeConn(connection);
        }
        return obj;
    }
}

11、3测试

package com.jn.userdao.test;

import com.jn.optimize.bean.User;
import com.jn.userdao.bean.Books;
import com.jn.userdao.dao.Dao;

import java.util.List;

public class Test {
    Dao dao = new Dao();
    //增删改
    @org.junit.Test
    public void updateUser() {
        String sql="update books set title=? where author=?";
        int update = dao.update(sql, "老人与海","Author4");
        System.out.println(update);
    }

    //查询一条数据
    @org.junit.Test
    public void getUser() {
        String sql="select * from books where title=?";
        Books books = (Books) dao.getBean(sql, Books.class, "老人与海");
        System.out.println(books);
    }

    //查询表中的所有数据
    @org.junit.Test
    public void getUserList() {
        String sql="select * from books";
        List<Books> list = dao.getBeanList(sql,Books.class,null);
        for (Books books : list) {
            System.out.println(books);
        }
    }

    //查询表中的数据量
    @org.junit.Test
    public void getUserCount() {
        String sql="select count(*) from books";
        Object obj=dao.getScaleValue(sql, null);
        System.out.println(obj);
    }

}

一、概述: JDBC从物理结构上说就是Java语言访问数据库的一套接口集合。从本质上来说就是调用者(程序员)和实现者(数据库厂商)之间的协议。JDBC的实现由数据库厂商以驱动程序的形式提供。JDBC API 使得开发人员可以使用纯Java的方式来连接数据库,并进行操作。 ODBC:基于C语言的数据库访问接口。 JDBC也就是Java版的ODBC。 JDBC的特性:高度的一致性、简单性(常用的接口只有4、5个)。 1.在JDBC中包括了两个包:java.sql和javax.sql。 ① java.sql 基本功能。这个包中的类和接口主要针对基本的数据库编程服务,如生成连接、执行语句以及准备语句和运行批处理查询等。同时也有一些高级的处理,比如批处理更新、事务隔离和可滚动结果集等。 ② javax.sql 扩展功能。它主要为数据库方面的高级操作提供了接口和类。如为连接管理、分布式事务和旧有的连接提供了更好的抽象,它引入了容器管理的连接池、分布式事务和行集等。 注:除了标出的Class,其它均为接口。 API 说明 java.sql.Connection 与特定数据库的连接(会话)。能够通过getMetaData方法获得数据库提供的信息、所支持的SQL语法、存储过程和此连接的功能等信息。代表了数据库。 java.sql.Driver 每个驱动程序类必需实现的接口,同时,每个数据库驱动程序都应该提供一个实现Driver接口的类。 java.sql.DriverManager (Class) 管理一组JDBC驱动程序的基本服务。作为初始化的一部分,此接口会尝试加载在”jdbc.drivers”系统属性中引用的驱动程序。只是一个辅助类,是工具。 java.sql.Statement 用于执行静态SQL语句并返回其生成结果的对象。 java.sql.PreparedStatement 继承Statement接口,表示预编译的SQL语句的对象,SQL语句被预编译并且存储在PreparedStatement对象中。然后可以使用此对象高效地多次执行该语句。 java.sql.CallableStatement 用来访问数据库中的存储过程。它提供了一些方法来指定语句所使用的输入/输出参数。 java.sql.ResultSet 指的是查询返回的数据库结果集。 java.sql.ResultSetMetaData 可用于获取关于ResultSet对象中列的类型和属性信息的对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值