JDBC

JDBC理解

概念

  • Java Database Connectivity Java连接数据库的技术 独立于数据库系统、通用的存取数据的一组接口(API)

本质

  • 独立于数据库系统、通用的存取数据的一组接口(API)

作用

  • JDBC为连接不同数据库提供 统一路径,为开发者屏蔽了细节问题
  • 开发者只需要面向这一组API(接口)即可,提高开发效率
  • 连接不同的数据库,需要不同的数据库厂商提供不同的驱动即可

JDBC快速入门

涉及的API:(java.sql.* 或 javax.sql.*)

  • DriverManager类 管理不同的驱动
  • Connection 接口 应用和数据库的连接
  • Statement 接口 用于执行sql语句
  • ResultSet 接口 保存查询的结果

增删改查使用

  • 注册驱动(加载驱动)

    • Cliass.forName("com.mysql.jdbc.Driver");
  • 获取连接对象

    • Connection con = DriverManager.getConnection("jdbc:mysql://IP地址:端口号/库名", "用户名", "密码");
    • url:要连接数据的地址 如果你连接地址是本地并且端口是默认的3306 此时ip+端口可以省略
  • 创建命令对象(创建可执行对象)

    • Statement st = con.createStatement();
  • 执行并处理结果

    • ResultSet rs = st.executeQuery(sql);
      查询
    • int row = st.executeUpdate(sql);
      增删改
  • 释放资源(先开后关原则)

    • rs.close();
    • st.close();
    • con.close();

junit单元测试

概述

  • 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进行自动测试了。

junit使用

  • 把junit4.x的测试jar,添加到该项目中来
  • 定义一个测试类,测试类的名字:xxxTest
  • 注意:方法必须是public修饰,无返回值的,不能被static修饰,该方法上贴有@Test注解

常见注解

  • @Before:在每个测试方法执行之前需要执行的方法
  • @After:在每个测试方法执行之后需要执行的方法
  • @BeforeClass:在所有的测试方法执行之前最先执行并且只执行一次
    被测试的方法必须使用static来修饰
  • @AfterClass:在所有的测试方法执行之后最后执行并且只执行一次
    被测试的方法必须使用static来修饰
  • @Test:要执行的测试方法

JDBC优化之连接池

连接池简介

  • 数据库连接是一种关键的有限的昂贵的资源,传统数据库连接每发出一个请求都要创建一个连接对象,使用完直接关闭不能重复利用;

  • 关闭资源需要手动完成,一旦忘记会造成内存溢出;
    请求过于频繁的时候,创建连接极其消耗内存;
    而且一旦高并发访问数据库,有可能会造成系统崩溃。
    为了解决这些问题,我们可以使用连接池。

  • 连接池原理

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

  • 连接池优势

    • 程序启动时提前创建好连接,免去用户请求时创建,给服务器减轻压力
    • 连接关闭时不会直接销毁connection,能够重复利用
    • 如果超时设定的连接数量但还没有达到最大值,那么可以再创建
    • 空闲了,会默认销毁(释放)一些连接,使系统性能达到最优
  • 常见开源连接池

    • DBCP:是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在bug,hibernate3已不再提供支持
    • C3P0:是一个开源组织提供一个数据库连接池,速度相对较慢,稳定性还可以
    • Druid:是阿里提供的数据库连接池,集合DBCP、C3P0优点于一身的高性能的数据库连接池

C3P0连接池

  • 导入jar包和配置文件
  • 创建连接池
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

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

public class C3P0Demo {
 //创建连接池对象
 ComboPooledDataSource dataSource = new ComboPooledDataSource();
 @Test
 public void findAll() throws SQLException {
      //通过连接池获取连接对象
     Connection con = dataSource.getConnection();
      //创建可执行对象
     Statement st = con.createStatement();
     //执行并获取结果
     String sql = "select * from user";
     ResultSet rs = st.executeQuery(sql);

     while (rs.next()) {
         //获取当前行数据
         int uid = rs.getInt("uid");
         String name = rs.getString("name");
         String sex = rs.getString("sex");
         double money = rs.getDouble("money");
         String address = rs.getString("address");
    
         System.out.println(uid + "\t" + name + "\t" + sex + "\t" + money + "\t" + address);
     }
     //释放资源
     rs.close();
     st.close();
     //如果从连接池中获取的连接 调用close()方法会将此连接归还到连接池
     con.close();
 }
}

Druid连接池

  • 导入jar包和配置文件
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

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

public class DruidDemo {
 //定义连接池对象
 private static DataSource dataSource;
 //最先加载并且只加载一次
 static{
     try {
         //创建持久化属性集合
         Properties pro = new Properties();
         //{driverClassName=com.mysql.jdbc.Driver,url=jdbc:mysql:///day3,username=root,password=root}
         //通过类加载器加载的durid.properties文件 因为这个类加载默认是从src路径下去找资源所以不用写src/路径
         InputStream fis =DruidDemo.class.getClassLoader().getResourceAsStream("durid.properties");
         //pro.load(new FileInputStream("src/durid.properties"));
          pro.load(fis);
         //通过工厂创建连接池对象
         dataSource = DruidDataSourceFactory.createDataSource(pro);
     } catch (Exception e) {
         e.printStackTrace();
     }
 }
 @Test
 public void deleteUser() throws SQLException {
     //从连接池中获取连接
     Connection con = dataSource.getConnection();
     //获取可执行对象
     Statement st = con.createStatement();

     String sql = "delete from user where uid =3";
     //执行并获取结果
     System.out.println(st.executeUpdate(sql) > 0 ? "删除成功" : "删除失败");
    
     //释放资源
     st.close();
     con.close();
 }

}

封装JDBCUtils工具类

工具类封装

import com.mchange.v2.c3p0.ComboPooledDataSource;

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

public class JDBCUtils {
 static ComboPooledDataSource dataSource = new ComboPooledDataSource();
 /*
     从连接池获取连接的方法
  */
 public static Connection getConnection() {
     try {
         return  dataSource.getConnection();
     } catch (SQLException e) {
         e.printStackTrace();
     }
     return null;
 }

 /*
    *  释放资源
    * */
    public static void coloseResouces(ResultSet rs, Statement st, Connection con) {

        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        if (st!=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        if (con!=null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}
import com.alibaba.druid.util.JdbcUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import javax.sql.rowset.JdbcRowSet;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/*
    增删改查
 */
public class JDBC_CRUD {
    Connection con;
    Statement st;
    ResultSet rs;
    @Before
    public void before() throws SQLException {
        //通过工具类获取连接对象
         con = JDBCUtils.getConnection();
        //创建可执行对象
         st = con.createStatement();
    }
    @After
    public void after() {
        JDBCUtils.coloseResouces(rs,st,con);
    }
    @Test
    public void findAll() throws SQLException {
        //执行并获取结果
        rs = st.executeQuery("select * from user ");
        while (rs.next()) {
            int uid = rs.getInt(1);
            String name = rs.getString(2);
            String sex = rs.getString(3);
            double money = rs.getDouble(4);
            String address = rs.getString(5);
            System.out.println(uid + "\t" + name + "\t" + sex + "\t" + money + "\t" + address);
        }

    }
    @Test
    public void insertUser() throws SQLException {
        //执行并获取结果
        System.out.println(st.executeUpdate("insert into user values(4,'金莲妹妹','女',9000,'王婆家')") > 0 ? "添加成功" : "添加失败");
    }
    @Test
    public void deleteUser() throws SQLException {
        System.out.println(st.executeUpdate("delete from user where uid =4") > 0 ? "删除成功" : "删除失败");
    }
    @Test
    public void updateUser() throws SQLException {
        System.out.println(st.executeUpdate("update user set name ='西门庆' where uid =3") > 0 ? "修改成功" : "修改失败");
    }
}

线程安全问题

  • 多线程共享同一资源(同一个连接对象)引发线程安全问题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自己的变量,而不会影响其他线程的变量。
  • 使用

    • ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
    • ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
    • ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
import com.mchange.v2.c3p0.ComboPooledDataSource;

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

public class JDBCUtils {
 static ThreadLocal<Connection> tl = new ThreadLocal<>();
 static ComboPooledDataSource dataSource = new ComboPooledDataSource();
 /*
     从连接池获取连接的方法
  */
 public static Connection getConnection() throws SQLException {
       //从当前线程中获取到连接对象
     Connection con = tl.get();

     if (con==null) {
         //线程中没有绑定连接对象
          con = dataSource.getConnection();
          //绑定连接到当前线程对象中
          tl.set(con);
     }
     return  con;
 }

 /*
    *  释放资源
    * */
    public static void coloseResouces(ResultSet rs, Statement st, Connection con) {

        if (rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        if (st!=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        if (con!=null) {
            try {
                //从当前线程中解绑
                tl.remove();
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

SQL注入命令优化

造成sql注入原因

  • sql语句中包含特殊符号,将整个sql语句改变

解决

  • 将Statement替换成PreparedStatment预编译命令对象

原理

  • sql语句先编译 使用?作为占位符,将传递的参数如果出现特殊符号,自动转义

PreparedStatment好处

  • 避免了字符串和变量的拼接,屏蔽细节问题
  • 解决了sql注入问题
  • sql语句可以复用
import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCUtils {

    private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
    
    public static Connection getConnection() throws SQLException {
    
        return dataSource.getConnection();
    }


    public static void closeResources(ResultSet rs, Statement st,Connection con) {


        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        if (st!=null) {
            try {
                st.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (con!=null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

事务

事务的概念

  • 事务:一组预编译的sql要么全部执行成功,要么都失败

事务特性(ACID)

  • 原子性(Atomicity):多个sql构成了一个整体单元、在这个单元内部要么全部执行成功、要么全部执行失败
  • 一致性(Consistency):事务执行前和执行的后状态一致的。
  • 隔离性(Isolation):多个事务、相互隔离互不干扰
  • 持久性(Durability):事务一旦提交、数据将永久的发送改变、不受外界因素干扰

管理自定义事务语句

  • mysql

    • begin开启事务 start transaction 开始事务
    • commit;提交事务
    • rollback;回滚事务
  • jdbc

    • Connection:连接对象
    • con.setAutoCommit(false)
      false:手动提交事务
      true:默认 自动提交事务
    • con.commit() 提交事务
    • con.rollback() 回滚事务
import com.ujiuye.utils.JDBCUtils;

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

public class AccountDemo {

 public static void main(String[] args){

     Scanner sc = new Scanner(System.in);
     System.out.println("请输入你要转出的金额");
     double money = sc.nextDouble();
    
     Account(money);

 }

 private static void Account(double money)  {

     String sql = "update account set money =money-? where name =?";
    
     Connection con = null;
     PreparedStatement ps1=null;
     PreparedStatement ps2=null;
     try {
         con = JDBCUtils.getConnection();
         //开启事务 true自动提交 false 手动提交
         con.setAutoCommit(false);
         //创建可执行对象
         ps1 = con.prepareStatement(sql);
         ps1.setDouble(1,money);
         ps1.setString(2,"pgone");
    
         int row = ps1.executeUpdate();
         if (row > 0) {
             System.out.println("转出成功");
         } else {
             System.out.println("转出失败");
         }
         //银行断电
         //System.out.println(1/0);
    
         sql = "update account set money =money+? where name =?";
    
         ps2 = con.prepareStatement(sql);
    
         ps2.setDouble(1,money);
         ps2.setString(2,"lxl");
         //执行并获取结果
         int row1= ps2.executeUpdate();
    
         if (row1 > 0) {
             System.out.println("转入成功");
         }else{
             System.out.println("转入失败");
         }
        //提交事务
         con.commit();
     } catch (Exception e) {
         //回滚事务
         try {
             con.rollback();
         } catch (SQLException ex) {
             ex.printStackTrace();
         }
         e.printStackTrace();
     }finally {
         //异常总执行
         try {
             ps2.close();
             ps1.close();
             con.close();
         } catch (SQLException e) {
             e.printStackTrace();
         }
    
     }


 }
}

批量处理

概述

  • JDBC操作数据库的时候,需要一次性插入大量的数据的时候,如果每次只执行一条SQL语句,效率可能会比较低。这时可以使用batch操作,每次批量执行SQL语句,调高效率。
import com.ujiuye.utils.JDBCUtils;

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

/*
 批量插入
*/
public class InsertOffcn_Demo02 {

 public static void main(String[] args) throws SQLException {
     //获取连接
     Connection con = JDBCUtils.getConnection();
     //创建可执行对象
     String s = "insert into offcn values(?,?,?,?)";
     PreparedStatement ps = con.prepareStatement(s);
     long start = System.currentTimeMillis();
     for (int i = 1; i <= 100000; i++) {

         ps.setInt(1,i);
         ps.setString(2,"zg"+i);
         ps.setInt(3,i);
         ps.setString(4,"zg"+i+"@qq.com");
         //积攒
         ps.addBatch();
         if (i%2000==0) {
             //执行一次
             ps.executeBatch();//批量插入
             System.out.println("插入了"+i+"条");
         }
     }
     long end = System.currentTimeMillis();
    
     System.out.println("花费了"+(end-start)+"毫秒");
    
     //清除批量插入
     ps.clearBatch();
     //释放资源
     ps.close();
     con.close();
 }
}
posted @ 2021-08-14 15:23  Lucky_龍  阅读(46)  评论(0)    收藏  举报