Java之JDBC、连接池

JDBC

Java对数据库进行操作(增删改查)的技术

1、JDBC的基本使用

使用流程(增删改):

  1. 导入驱动包
  2. 获取连接对象
  3. 编写sql语句并由statement对象提交执行
  4. 关闭资源
//导入驱动包
Class.forName("com.mysql.cj.jdbc.Driver");

//获取连接对象
String url = "jdbc:mysql://localhost:3306/java2402?characterEncoding=utf8&serverTimezone=UTC";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, user, password);

//获取statement对象
Statement statement = connection.createStatement();

//编写sql语句+执行sql命令 -> 返回受影响的行数
String sql = "insert into student values (8,'李虹霖','女',20,8000,'Python', 1,2,3);";
//String sql = "delete from student where id = 8";
//String sql = "update student set salary = 1000 where id = 4";
int i = statement.executeUpdate(sql);
System.out.println("Affected rows: " + i);

//关闭连接对象
statement.close();
connection.close();

如果是查询:

  1. 导入驱动包
  2. 获取连接对象
  3. 编写sql语句并由statement对象提交执行,获得resultSet对象 -> 获取查询结果
  4. 关闭资源
//导入驱动包
Class.forName("com.mysql.cj.jdbc.Driver");

//获取连接对象
String url = "jdbc:mysql://localhost:3306/java2402?characterEncoding=utf8&serverTimezone=UTC";
String user = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, user, password);

//获取statement对象
Statement statement = connection.createStatement();

//编写sql语句+执行sql命令 -> 返回受影响的行数
String sql = "select * from student";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
    int id = resultSet.getInt("id");
    String name = resultSet.getString("name");
    double salary = resultSet.getDouble("salary");
    String course = resultSet.getString("course");
    int tId = resultSet.getInt("t_id");
    int courseId = resultSet.getInt("course_id");
    int classId = resultSet.getInt("class_id");
    System.out.println(id + "-" + name + "-" + salary + "-" + course + "-" + courseId + "-" + classId + "-" + tId);
}

//关闭连接对象
resultSet.close();
statement.close();
connection.close();

2、结合反射查询数据并生成对象

假设一张表里面存储了学生的信息,在通过JDBC查询后生成一个对应的对象,那么就可以使用反射来实现

    @Test
    public void test04() throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {

        String sql = "select * from student where course = ?";
        List<Student> list = DBManager.query(Student.class, sql, "JAVA");//其中在DBManager中封装了查询的主要逻辑
        for (Student student : list) {
            System.out.println(student);
        }
    }
//...

3、SQL注入

SQL注入问题:所谓SQL注入式攻击,就是攻击者把SQL命令插入到Web表单的输入域或页面请求的查询字符串,欺骗服务器执行恶意的SQL命令

SELECT * FROM user where userName = '' or 1 = 1 # AND password = 

上述的SQL注入命令就是'' or 1 = 1 #,欺骗了服务器,从而达到注入的效果

解决方案:使用预编译

在生成statement对象时,采用connection.perpareStatement(sql语句)的方式,然后通过statement.setObject(inedx, value)的方式对sql语句进行预编译处理,能够防止sql注入问题

Connection connection = DriverManager.getConnection();
String sql = "select * from student where name =?";//使用 ? 进行占位
PreparStatement statement = connection.preparStatement(sql);
statement.setString(1, "刘亦菲");//下标是从1开始,参数的类型按照实际情况使用对应的类型
resultSet results = statement.executeQuery();
if(result.next()){
    String name = result.getString("name");
    int age = result.getInt("age");
    SYstem.out.println(name + age);
}
//关闭资源
result.close();
statement.close();
connection.close();

4、主键回传

主键回传是指在查询后返回的结果为表的主键,用于后续操作,查询过程与普通查询一致,只需要在获取statement对象时加上Statement.RETURN_GENERATED_KEYS参数

preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

5、事务

事务是数据库操作的一个基本执行单元

//开启事务
connection.setAutoCommit(false);

//提交事务
connection.commit();

//事务回滚
connection.rollback();

在sql语句中开启事务、提交、回滚

start transaction;-- 开启事务
commit;-- 提交事务
rollback;-- 事务回滚

6、批处理

当执行重复性操作时,考虑批处理

Connection connection = DriverManager.getConnection();
String sql = "insert into student (name, age) values(?,21)";
PreparStatement statement = connection.preparStatement(sql);
for(int i=0;i<1000;i++){
    statement.setString(1,"刘亦菲" +i);
    statement.addBatch();
    
    if(i%100 == 0 ){
        statement.executeBatch();
        statement.clearBatch();
    }
}

//关闭资源...

7、面试题

  1. 数据库的特性(ACID)?

    • 原子性:事务是数据库操作执行单元,要么全部执行成功,要么全部执行失败;对于一个事务来说,不可能只执行其中一部分sql操作,这就是事物的原子性
    • 一致性:数据库总是从一个一致性状态到另一个一致性状态。
    • 隔离性:通常来说,一个事务所做的修改在提交以前,其他事务是不可见的,不同的事务之间互不干扰。
    • 持久性:一旦事务提交,所做的修改就会永久保存到数据库中,即使系统崩溃,修改的数据也不会丢失。
  2. 并发时出现的问题?

    • 脏读:一个事务读取到了另一个事务中未提交的数据
    • 不可重复读:一个事务先后读取同一条记录,而数据在事务两次读取之间被其他事务所修改,所以两次读取到的数据不一致
    • 虚读:一个事务先后按照相同的查询条件重新读取以前检索过的数据,发现其他事务插入了满足查询条件的数据

    脏读与不可重复读的区别:脏读读取到的是另一个事务未提交的数据,而不可重复读读取的是已经提交的数据,只是前后读取的数据被其他事务修改了

    不可重复读与虚读的区别:不可重复读指的是同一条数据项,而虚读针对的是一批数据整体(比如个数变化)

  3. 隔离级别?

    • READ UNCOMMITED:脏读、不可重复读、虚读都可能发生

    • READ COMMITED:避免了脏读,不可重复读、虚读可能发生

    • REPEATABLE READ:避免了脏读和不可重复读,虚读可能发生

    • SERIALIZABLE:避免了脏读、不可重复读、虚读(一般来说,上一个隔离级别虚读基本就能避免虚读的问题)

      //设置隔离级别(可重复读)
      connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
      
  4. 最高隔离级别存在的问题?

    ​ SERIALIZABLE:当一个事务提交之前,其他事务不能执行(类似于多线程的排他),不适用于高并发情况

8、连接池

连接池的优点:

  1. 减少了创建链接和关闭连接的时间和空间代价
  2. 使项目中的连接数可控
  3. 提高了连接的复用性

自定义连接池

思路:

  1. 创建一个连接池类,存放多个连接
  • int 最大连接数
  • LinkedList<Connection> list 容器
  • String 驱动路径
  • String 连接url
  • String 用户名
  • String 密码
  • Connection getConnection() 获取连接对象 -> 获取单个连接
  1. 包装connection,修改close() 方法
  2. 测试

连接池

/**
 * 自定义连接池
 */
@SuppressWarnings({"all"})
public class MyConnectionPool implements DataSource {

    //驱动
    private String driverName;
    //url
    private String url;
    //username
    private String name;
    //password
    private String password;
    //容器
    private LinkedList<Connection> list;
    //最大连接数
    private int maxActive;

    /**
     * 初始化连接池
     */
    private void init() throws SQLException, IOException {
        //初始化容器
        list = new LinkedList<>();
        try {
            //加载驱动
            Class.forName(driverName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        //向容器中初始化maxActive个连接
        for (int i = 0; i < maxActive; i++) {
            //获取mysql的connection对象
            Connection connection = DriverManager.getConnection(url, name, password);
            //将connection包装 -> 修改close()方法
            MyConnectionWrapper wrapper = new MyConnectionWrapper(list, connection);
            list.add(wrapper);
        }
    }
    public void setDriverName(String driverName) {
        this.driverName = driverName;
    }

    public void setUrl(String url) {
        this.url = url;
    }

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

    public void setPassword(String password) {
        this.password = password;
    }

    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }


    public String toString() {
        return "MyConnectionPool{driverName = " + driverName + ", url = " + url + ", name = " + name + ", password = " + password + ", list = " + list + "}";
    }

    @Override
    public Connection getConnection() throws SQLException {
        //如果list中还没有连接,则初始化
        if (list == null) {//这里不使用 list.isEmpty() 是因为 init() 中才初始化 LinkedList
            try {
                init();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        //如果请求连接数为空,则等待
        if(list.isEmpty()){
            System.out.println("连接池已经没有空闲Connection,请等待");
            return null;
        }

        return list.removeFirst();
    }

    @Override
    ...//其他需要重写的方法
}

包装类

/**
 * 包装connection,修改connection的close()方法
 * 返回的是被包装过后的 wrapper(类型仍然是 Connection)
 */
public class MyConnectionWrapper implements Connection {

    private LinkedList<Connection> list;
    private Connection connection;


    public MyConnectionWrapper(LinkedList<Connection> list, Connection connection) {
        this.list = list;
        this.connection = connection;
    }
    
    //。。。其他重写方法
    
    @Override
    public void close() throws SQLException {
        //修改默认 close() 方法 
        list.add(this);
    }
}

测试

public static void main(String[] args) throws SQLException {
        MyConnectionPool pool = new MyConnectionPool();
        //添加配置
        pool.setDriverName("com.mysql.cj.jdbc.Driver");
        pool.setUrl("jdbc:mysql://localhost:3306/xxxx?characterEncoding=utf8&serverTimezone=UTC");
        pool.setName("root");
        pool.setPassword("xxxx");
        pool.setMaxActive(20);

        Connection connection = null;
        PreparedStatement statement;
        ResultSet res;
        String sql = "select * from student where id < 5";
        try {
            connection = pool.getConnection();
            statement = connection.prepareStatement(sql);
            res = statement.executeQuery();
            while (res.next()) {
                int id = res.getInt("id");
                String name = res.getString("name");
                String sex = res.getString("sex");
                System.out.println(id + "--" + name + "--" + sex);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            connection.close();
        }
    }

druid连接池

druid连接池有两种方式可以实现

  1. 创建druidDataSource对象,再通过setXxx()方法配置连接
  2. 通过DruidDataSourceFactory.createDataSource(properties)加载配置文件自动生成
//这里是第二种方法
//DBManager中的静态加载部分
Properties properties = new Properties();
properties.load(DBManager.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
DataSource dataSource = dataSource = DruidDataSourceFactory.createDataSource(properties);

//DBManager是一个工具类,自定义封装了很多方法,包括查询query()、getConnection()等
//DBManager中的getConnection()方法
public static Connection getConnection() throws SQLException {
    Connection connection = threadLocal.get();
    if (connection == null) {
        connection = dataSource.getConnection();
        threadLocal.set(connection);
    }
    return connection;
}

//---------------------------------------------------------
String sql = "select * from student where id = ?";
List<Student> query = DBManager.query(Student.class, sql,1);
query.forEach(System.out::println);

DBConfig.properties

driverName = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/xxx?characterEncoding=utf8&serverTimezone=UTC
username = root
password = xxx
maxActive = 20

其他连接池

其他常见的连接池还包括

  • C3P0:稳定性好
  • DBPC:速度快

其中druid连接池是使用最多、效果最好的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

拖把湛屎,戳谁谁死

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

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

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

打赏作者

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

抵扣说明:

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

余额充值