JDBC(Java数据库连接)

1. JDBC 编程步骤

1.1. 加载驱动程序:

Class.forName(driverClass)
//加载MySql驱动
Class.forName("com.mysql.jdbc.Driver")
//加载Oracle驱动
Class.forName("oracle.jdbc.driver.OracleDriver")

原理

  • 利用加载class文件执行static代码
  • 所有的java.sql.Driver实现类,都提供了static块,块内的代码就是把自己的对象传给DriverManager.registerDriver(对象)方法来注册.
  • jdbc4.0后每个驱动jar包中,在META-INF/services目录下提供了一个名为java.sql.Driver的文件,内容就是该接口的实现类名称.会自动获取该实现类并加载,所以可以省略第一步.

1.2. 获得数据库连接:

//方法Connection   getConnection(URL,username,password)
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb3", "root", "root");
//MySQL的URL格式
jdbc:mysql://主机名或IP地址:端口号/数据库名?useUnicode=true&characterEncoding =UTF-8

1.3. 创建Statement/PreparedStatement对象:

//创建Statement
Statement stat = con.createStatement();
//发送DML或者DDL的sql语句
String sql ="INSERT INTO stu VALUES('ITCAST_003','zhangsan',88,'male')"  //不加;
int r = stat.executeUpdate(sql)  //r为影响的行数
//发送DQL语句
ResultSet rs = stmt.executeQuery("SELECT * FROM stu");  
//解析ResultSet
while(rs.next()) {//把光标向下移动一行,并判断下一行是否存在(初始在beforeFirst)
    int empno = rs.getInt(1);//通过列编号来获取该列的值!
    String ename = rs.getString("ename");//通过列名称来获取该列的值
    double sal = rs.getDouble("sal");
    System.out.println(empno +  ", " + ename + ", " + sal);
}

1.4. 关闭资源:

rs.close();
stmt.close();
con.close();

1.5. 例子

public void fun3() throws Exception {
    Connection con = null;// 定义引用
    Statement stmt = null;
    ResultSet rs = null;
    try {
        /*
         * 一、得到连接
         */
        String driverClassName = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/exam";
        String username = "root";
        String password = "123";

        Class.forName(driverClassName);
        con = DriverManager.getConnection(url, username, password);// 实例化

        /*
         * 二、创建Statement
         */
        stmt = con.createStatement();
        String sql = "select * from emp";
        rs = stmt.executeQuery(sql);

        rs.last();// 把光标移动到最后一行
        System.out.println(rs.getRow());
        rs.beforeFirst();

        /*
         * 三、循环遍历rs,打印其中数据
         * 
         * getString()和getObject()是通用的!
         */
        // while(rs.next()) {
        // System.out.println(rs.getObject(1) + ", "
        // + rs.getString("ename") + ", " + rs.getDouble("sal"));
        // }

        int count = rs.getMetaData().getColumnCount();
        while (rs.next()) {// 遍历行
            for (int i = 1; i <= count; i++) {// 遍历列
                System.out.print(rs.getString(i));
                if (i < count) {
                    System.out.print(", ");
                }
            }
            System.out.println();
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {
        // 关闭
        if (rs != null)
            rs.close();
        if (stmt != null)
            stmt.close();
        if (con != null)
            con.close();
    }
}

2. JDBC对象

2.1. DriverManager类

方法:

Class.forName(“com.mysql.jdbc.Driver”);//注册驱动
String url = “jdbc:mysql://localhost:3306/mydb1”;
String username = “root”;
String password =123;
Connection con = DriverManager.getConnection(url, username, password);

注意,上面代码可能出现的两种异常:

  • ClassNotFoundException:在第1句上出现,原因可能有两个:
    • 你没有给出mysql的jar包;
    • 你把类名称打错了,查看类名是不是com.mysql.jdbc.Driver。
  • SQLException:出现在第5句,出现这个异常就是三个参数的问题,往往username和password一般不是出错,所以需要认真查看url是否打错。

对于DriverManager.registerDriver()方法了解即可,因为我们今后注册驱动只会Class.forName(),而不会使用这个方法。

2.2. Connection接口

方法:

Statement stmt = con.createStatement();  获取Statement
Statement stmt = con.createStatement(int,int);

2.3. Statement接口

方法:

  • int executeUpdate(String sql):执行更新操作,即执行insert、update、delete语句,其实这个方法也可以执行create table、alter table,以及drop table等语句,但我们很少会使用JDBC来执行这些语句;
  • ResultSet executeQuery(String sql):执行查询操作,执行查询操作会返回ResultSet,即结果集。
  • boolean execute() 这个方法可以用来执行增、删、改、查所有SQL语句。该方法返回的是boolean类型,表示SQL语句是否有结果集。
    • 如果使用execute()方法执行的是更新语句,那么还要调用int getUpdateCount()来获取insert、update、delete语句所影响的行数。
    • 如果使用execute()方法执行的是查询语句,那么还要调用ResultSet getResultSet()来获取select语句的查询结果。

2.4. ResultSet接口

ResultSet表示结果集,它是一个二维的表格。ResultSet内部维护一个行光标(游标)

游标控制判断方法

void beforeFirst():把光标放到第一行的前面,这也是光标默认的位置;
void afterLast():把光标放到最后一行的后面;
boolean first():把光标放到第一行的位置上,返回值表示调控光标是否成功;
boolean last():把光标放到最后一行的位置上;

boolean isBeforeFirst():当前光标位置是否在第一行前面;
boolean isAfterLast():当前光标位置是否在最后一行的后面;
boolean isFirst():当前光标位置是否在第一行上;
boolean isLast():当前光标位置是否在最后一行上;

boolean previous():把光标向上挪一行;
boolean next():把光标向下挪一行;
boolean relative(int row):相对位移,当row为正数时,表示向下移动row行,为负数时表示向上移动row行;
boolean absolute(int row):绝对位移,把光标移动到指定的行上,正数第一行(1)开始,负数最后一行(-1)开始,超出范围分别为afterLast和beforeFirst,这两者返回false
int getRow():返回当前光标所在行。beforeFirst为0

获取结果集元数据

  • 得到元数据:rs.getMetaData(),返回值为ResultSetMetaData,其方法有:
    • 获取结果集列数:int getColumnCount()
    • 获取指定列的列名:String getColumnName(int colIndex) 第一列为1

结果集特性

con.createStatement():生成的结果集:不滚动、不敏感、不可更新!(mysql默认可滚动) Statement createStatement(int resultSetType, int resultSetConcurrency) 第一个参数:

  • ResultSet.TYPE_FORWARD_ONLY:不滚动结果集;
  • ResultSet.TYPE_SCROLL_INSENSITIVE:滚动结果集,但结果集数据不会再跟随数据库而变化;
  • ResultSet.TYPE_SCROLL_SENSITIVE:滚动结果集,但结果集数据不会再跟随数据库而变化;

第二个参数:

  • CONCUR_READ_ONLY:结果集是只读的,不能通过修改结果集而反向影响数据库;
  • CONCUR_UPDATABLE:结果集是可更新的,对结果集的更新可以反向影响数据库。

上面方法分为两类,一类用来判断游标位置的,另一类是用来移动游标的。如果结果集是不可滚动的,那么只能使用next()方法来移动游标,而beforeFirst()、afterLast()、first()、last()、previous()、relative()方法都不能使用。

获取列数据方法

可以通过next()方法使ResultSet的游标向下移动,当游标移动到你需要的行时,就需要来获取该行的数据了,ResultSet提供了一系列的获取列数据的方法:

String getString(int columnIndex):获取指定列的String类型数据;
int getInt(int columnIndex):获取指定列的int类型数据;
double getDouble(int columnIndex):获取指定列的double类型数据;
boolean getBoolean(int columnIndex):获取指定列的boolean类型数据;
Object getObject(int columnIndex):获取指定列的Object类型的数据,不确定列类型时

上面方法中,参数columnIndex表示列的索引,列索引从1开始,而不是0 ResultSet还提供了一套通过列名称来获取列数据的方法:

String getString(String columnName):获取指定名称的列的String数据;
int getInt(String columnName):获取指定名称的列的int数据;
double getDouble(String columnName):获取指定名称的列的double数据;
boolean getBoolean(String columnName):获取指定名称的列的boolean数据;
Object getObject(String columnName):获取指定名称的列的Object数据;

2.5. PreparedStatement预编译声明

它是Statement接口的子接口。

优点:1,防SQL攻击 2,提高代码的可读性、可维护性 3,提高效率

获得PreparedStatement对象:

  • 给出SQL模板,即有“?”的SQL语句

  • 调用Connection的PreparedStatement prepareStatement(String sql模板);

  • 调用pstmt的setXxx(int index,值)给sql模板中的第index个?赋值

  • 调用pstmt的executeUpdate()或executeQuery(),但它们的方法都没有参数。

预处理的原理

服务器的工作:
  • 校验sql语句的语法
  • 编译:一个与函数相似的东西
  • 执行:调用函数
PreparedStatement:
  • 前提:连接的数据库必须支持预处理,几乎没有不支持的。
  • 每个pstmt都与一个sql模板绑定在一起,先把sql模板给数据库,数据库先进行校验,再进行编译。执行时只是把参数传递过去而已。
  • 若二次执行时,就不用再次校验语法,也不用再次编译,直接执行。

防止SQL攻击

  • 过滤用户输入的数据中是否包含非法字符。
  • 分步交验!先使用用户名来查询用户,如果查找到了,再比较密码。
  • 使用PreparedStatement。

MySQL默认预处理关闭,需在URL里添加下列参数?属性1&属性2

  • useServerPrepStmts=true 开启预编译功能
  • cachePrepStmts=true 缓存编译后函数的key,避免不同pstmt执行相同sql语句产生的二次编译
  • rewriteBatchedStatements=true MySQL的批处理也需要通过参数来打开

3. 批处理

3.1. Statement批处理

批处理就是把几条sql存在一批里,一次性执行,批处理只针对更新(增、删、改)语句,批处理没有查询

方法:

void addBatch(String sql):添加一条语句到“批”中;
int[] executeBatch():执行“批”中所有语句。返回值表示每条语句所影响的行数据;
void clearBatch():清空“批”中的所有语句。

注意:当执行了executeBatch()之后,“批”中的SQL语句就会被清空。还可以在执行“批”之前,调用Statement的clearBatch()方法来清空“批”。

3.2. PreparedStatement批处理

PreparedStatement的批处理有所不同,因为每个PreparedStatement对象都绑定一条SQL模板。所以向PreparedStatement中添加的不是SQL语句,而是给“?”赋值。

方法:

void addBatch():添加一组?值到“批”中;
int[] executeBatch():执行“批”中所有语句。返回值表示每条语句所影响的行数据;

4. JdbcUtils工具类

4.1. JdbcUtils的作用

连接数据库的四大参数是:驱动类、url、用户名,以及密码。这些参数都与特定数据库关联,如果将来想更改数据库,那么就要去修改这四大参数,那么为了不去修改代码,写一个JdbcUtils类,让它从配置文件中读取配置参数,然后创建连接对象。

JdbcUtils.java

public class JdbcUtils {
    private static final String dbconfig = "dbconfig.properties";
    private static Properties prop = new Properties();
    static {
        try {
            InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(dbconfig);
            prop.load(in);
            Class.forName(prop.getProperty("driverClassName"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("username"),
                    prop.getProperty("password"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

dbconfig.properties文件

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb1?useUnicode=true&characterEncoding=UTF8
username=root
password=123

5. 连接池

5.1. tomcat的conf文件夹下context.xml文件<context>里

<Resource name="jdbc/suo"   //数据源的JNDI名称
          auth="Container"     //连接池管理权属性,容器管理:tomcat
          type="javax.sql.DataSource"  
          driverClassName="com.mysql.jdbc.Driver"  
          url="jdbc:mysql://localhost:3306/suo"  
          username="root"  
          password="123456"  
          maxActive="50"  //最大活动连接数
          maxIdle="30"      //即使没有数据库连接时依然可以保持30空闲的连接
          maxWait="10000" />   //最大等待时间(毫秒)

5.2. 数据源(DataSource)

javax.sql.DataSource接口

Context ct = new InitialContext();//构造一个初始上下文。 得到一个上下文对象
DataSource d = (DataSource)ct.lookup("java:comp/env/jdbc/suo");
//检索指定的对象。 得到一个数据源对象,参数为JNDI地址:java:comp/env/JNDI名称
conn=d.getConnection();

results matching ""

    No results matching ""