说明:

全称:Java Data Base Connectivity

jar包位置:java.sql、javax.sql

MySQL驱动jar包:mysql-connector-java-x.x.x.jar

Oracle驱动jar包:ojdbcxx.jar

工作流程:
应用程序 —> JDBC —> MySQL驱动或Oracle驱动 —> MySQL数据库或Oracle数据库

一、JDBC操作数据库(增删改查):

1.加载驱动:

方式1:

/**
* 1加载驱动
*/
static {
    try {
        // 不推荐,JVM虚似机内存中会产生两个一样的Driver对象。com.mysql.cj.jdbc.Driver
        DriverManager.registerDriver(new Driver());
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

方式2:

/**
* 1加载驱动
*/
static {
    try {
        // 推荐这种方式,不会对具体的驱动类产生依赖。
        Class.forName("com.mysql.cj.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

2.建立连接:

(1)URL格式说明:
协议:子协议://主机:端口/数据库名?serverTimezone=UTC
MySQL的url:jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC
Oracle的url:jdbc:oracle:thin:@localhost:1521:数据库名?serverTimezone=UTC
SQLServer的url:jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=数据库名?serverTimezone=UTC

(2)建立连接:

方式1:

/**
* 2.建立连接
* 
* @param url      数据库url
* @param username 数据库登录用户名
* @param password 数据库登录密码
*/
public Connection getDBConnection(String url, String username, String password) throws SQLException {
    return DriverManager.getConnection(url + "?" + "user=" + username + "&&password=" + password);
}

方式2:

/**
* 2.建立连接
* 
* @param url      数据库url
* @param username 数据库登录用户名
* @param password 数据库登录密码
*/
public Connection getDBConnection(String url, String username, String password) throws SQLException {
    return DriverManager.getConnection(url, username, password);
}

3.创建PreparedStatement对象:

(1)采用Statement实现,有被SQL注入的危险,如:('or 1=1 or name=') ,且可能造成数据库缓冲区溢出:

// 创建Statement
Statement st = conn.createStatement();

(2)采用PreparedStatement实现,此类安全,可对SQL进行预编译:

/**
* 3.创建PreparedStatement对象
* 
* @param conn Connection连接对象
* @param sql  sql语句
*/
public PreparedStatement getPreparedStatement(Connection conn, String sql) throws SQLException {
    // 创建PreparedStatement
    //PreparedStatement st = conn.prepareStatement(sql);
    PreparedStatement st = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); // 或,参数2的常量表示生成的主键可以获取
    return st;
}

4.设置?占位符数据:

(1)int类型:

String sql = "insert into user(id,name,birthday,mytext,myblob) values(?,?,?,?,?)";
PreparedStatement st = getPreparedStatement(conn, sql);
st.setInt(1, 1); // 参数表示?位置,从1开始,参数2为数据,对应第1个?号,int类型

(2)String类型:

PreparedStatement st = null;
st.setString(2, "杨先生"); // 对应第2个?号,String类型

(3)日期类型:

PreparedStatement st = null;
st.setDate(3, new java.sql.Date(new java.util.Date().getTime())); // 对应第3个?号,日期存入数据库时要转成sql类型的Date

(4)大文本类型:

/*
* 对应第4个?号,大文本类型(文件路径:工程/WebContent/WEB-INF/file/text.txt)
*/
String textFileName = "text.txt";
String textFilePath = context.getRealPath("WEB-INF/file/" + textFileName);
File textFile = new File(textFilePath);
st.setCharacterStream(4, new FileReader(textFile), (int) textFile.length());

(5)二进制数据类型:

PreparedStatement st = null;
/*
* 对应第5个?号,二进制数据类型(文件路径:工程/WebContent/WEB-INF/file/text.txt)
*/
String blobFileName = "blob.png";
String blobFilePath = context.getRealPath("WEB-INF/file/" + blobFileName);
File blobFile = new File(blobFilePath);
st.setBinaryStream(5, new FileInputStream(blobFile), (int) blobFile.length());

5.执行语句:

PreparedStatement st = null;
ResultSet rs = st.getGeneratedKeys();  //得到插入行的主键,只对insert有用
boolean result = st.execute();         //任意操作使用
ResultSet rs = st.executeQuery();      //查询时使用
int result = st.executeUpdate();       //增删改时使用

6.增删改查:

通用关闭方法:

/**
* 关闭数据库连接
*/
public void close(Connection conn, PreparedStatement st, ResultSet rs) {
    try {
        if (rs != null) {
            rs.close();
        }
        if (st != null) {
            st.close();
        }
        if (conn != null) {
            conn.close();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

(1)插入数据:

/**
* 4.1插入数据
* 
* @param conn Connection连接对象
*/
public void insert(ServletContext context, Connection conn) {
    PreparedStatement st = null;
    try {
        // 1.创建sql语句,id为int类型、name为String类型、birthday为java.sql.Date类型、mytext为大文本类型、myblob为二进制数据类型
        String sql = "insert into user(id,name,birthday,mytext,myblob) values(?,?,?,?,?)";

        // 2.获取PreparedStatement
        st = getPreparedStatement(conn, sql);

        // 3.给占位符设置数据
        st.setInt(1, 1); // (1)对应第1个?号,int类型

        st.setString(2, "杨先生"); // (2)对应第2个?号,String类型

        st.setDate(3, new java.sql.Date(new java.util.Date().getTime())); // (3)对应第3个?号,日期存入数据库时要转成sql类型的Date

        /*
        * (4) 对应第4个?号,大文本类型(文件路径:工程/WebContent/WEB-INF/file/text.txt)
        */
        String textFileName = "text.txt";
        String textFilePath = context.getRealPath("WEB-INF/file/" + textFileName);
        File textFile = new File(textFilePath);
        st.setCharacterStream(4, new FileReader(textFile), (int) textFile.length());

        /*
        * (5)对应第5个?号,二进制数据类型(文件路径:工程/WebContent/WEB-INF/file/text.txt)
        */
        String blobFileName = "blob.png";
        String blobFilePath = context.getRealPath("WEB-INF/file/" + blobFileName);
        File blobFile = new File(blobFilePath);
        st.setBinaryStream(5, new FileInputStream(blobFile), (int) blobFile.length());

        // 4.执行语句
        ResultSet rs = st.getGeneratedKeys();// 得到插入行的主键,只对insert有用
        //int result = st.executeUpdate(); // 增删改时,可使用此方法
        boolean result = st.execute(); // 增删改查,都可使用此方法
        System.out.println("insert result: " + result);
    } catch (SQLException | FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        close(conn, st, null);
    }
}

(2)更新数据:

/**
* 4.2更新数据
* 
* @param conn Connection连接对象
*/
public void update(Connection conn) {
    PreparedStatement st = null;
    try {
        // 1.创建sql语句,name为String类型
        String sql = "update user set name=? where name=?";

        // 2.获取PreparedStatement
        st = getPreparedStatement(conn, sql);

        // 3.给占位符设置数据
        st.setString(1, "刘先生"); // (1)对应第1个?号,String类型
        st.setString(2, "杨先生"); // (2)对应第2个?号,String类型

        // 4.执行语句
        //boolean result = st.execute(); // 增删改查,都可使用此方法
        int result = st.executeUpdate(); // 增删改时,可使用此方法
        System.out.println("update result: " + result);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(conn, st, null);
    }
}

(3)查询数据:

移动ResultSet指针:

//移动ResultSet指针
rs.previous();    // 移动到前一行
rs.absolute(1);   // 移动到指定行
rs.beforeFirst(); // 移动ResultSet的最前面(表头)
rs.afterLast();   // 移动到ResultSet的最后面(表尾)
rs.next();        // 指向第一条,下次执行指向第二行

取对应列的数据(可使用列名或列position值,从1开始):

// 第一种方法,得到的是个Object对象
rs.getObject("列名"); // 取出该行该列的值
rs.getObject(1); // 取出该行该列的值

// 第二种方法,得到的是对应类型的值
rs.getBoolean("列名");
rs.getByte("列名");
rs.getShort("列名");
rs.getInt("列名");
rs.getLong("列名");
rs.getString("列名");

rs.getDate("列名");	   // DATE,得到的是java.sql.Date类型
rs.getTime("列名");	   // TIME,得到的是java.sql.Time类型
rs.getTimestamp("列名"); // TIMESTAMP,得到的是java.sql.Timestamp类型

// 大文本类型Text,longText
Reader reader = rs.getCharacterStream("列名");
// Reader reader = rs.getClob("列名").getCharacterStream();
char buffer[] = new char[1024];
int len = 0;
FileWriter writer = new FileWriter("保存文件路径");
while ((len = reader.read(buffer)) > 0) {
    writer.write(buffer, 0, len);
}

// 二进制数据类型 //Blob,LongBlob
InputStream in = rs.getBinaryStream("列名");
//InputStream in = rs.getBlob("二进制文件存放列名或列号").getBinaryStream();
byte buffer[] = new byte[1024];
int len = 0;
FileOutputStream out = new FileOutputStream("保存文件路径");
while ((len = in.read(buffer)) > 0) {
    out.write(buffer, 0, len);
}

例子:

/**
* 4.3查询数据
* 
* @param conn Connection连接对象
*/
public void query(Connection conn) {
    PreparedStatement st = null;
    ResultSet rs = null;
    try {
        // 1.创建sql语句
        String sql = "select * from user";

        // 2.获取PreparedStatement
        st = getPreparedStatement(conn, sql);

        // 3.执行语句
        rs = st.executeQuery();// 查询时使用executeQuery

        if (rs.next()) {// 指向第一条,下次执行指向第二行
            String name = rs.getString("name"); // 获取name列的值
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(conn, st, rs);
    }
}

(4)删除数据:

/**
* 44删除数据
* 
* @param conn Connection连接对象
*/
public void delete(Connection conn) {
    PreparedStatement st = null;
    try {
        // 1.创建sql语句,name为String类型 DELETE FROM 表名称 WHERE 列名称 = 值
        String sql = "delete from user where name=?";

        // 2.获取PreparedStatement
        st = getPreparedStatement(conn, sql);

        // 3.给占位符设置数据
        st.setString(1, "刘先生"); // (1)对应第1个?号,String类型

        // 4.执行语句
        boolean result = st.execute(); // 增删改查,都可使用此方法
        System.out.println("insert result: " + result);
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(conn, st, null);
    }
}

7.批处理sql语句:

(1)采用Statement批量插入:

优点:可以向数据库发送多条不同的sql语句。
缺点:sql语句没有预编译

String sql = "";
//可实现批处理sql语句
Statement.addBatch(sql)

(2)采用PreparedStatement批量插入:

优点:发送的是预编译后的SQL语句,执行效率高。
缺点:只能应用在sql语句相同,但参数不同的批处理中。

/**
* 批量插入
* 
* @param conn Connection连接对象
*/
public void batchInsert(Connection conn) {
    PreparedStatement st = null;
    try {
        String sql = "insert into user(id,name) values(?,?)";
        st = conn.prepareStatement(sql);
        for (int i = 2; i < 10000; i++) {
            st.setInt(1, i);
            st.setString(2, "杨先生" + i);
            // 程序执行到此,st对象里面已经是一条完整的sql,可以加到 batch里面去了
            st.addBatch();
            if (i % 1000 == 0) {// batch最大可容1000条sql语句
                st.executeBatch();// 执行1次batch里的所有sql,
                st.clearBatch();// 清空batch里的所有sql
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(conn, st, null);
    }
}

8.在Oracle数据库中实现存二进制文件(注意:这些操作需开启事务):
(1)向列中存一个指针:

/**
* 向列中存一个指针
*/
public void insertEmptyBlob(Connection conn) {
    PreparedStatement st = null;
    try {
        String sql = "insert into user(id,image) values(1,empty_blob())";
        st = conn.prepareStatement(sql);
        st.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(conn, st, null);
    }
}

(2)把指针查询出来,并将图片二进制数据存入数据库:

/**
* 把指针查询出来,并将图片二进制数据存入数据库
*/
public void queryEmptyBlob(ServletContext context, Connection conn) {
    PreparedStatement st = null;
    ResultSet rs = null;
    try {
        String sql = "select image from user where id=1 for update"; // for update是为了锁定这一行,为了不产生并发冲突
        st = conn.prepareStatement(sql);
        rs = st.executeQuery();
        if (rs.next()) {
            // 把指针查询出来
            BLOB blob = (BLOB) rs.getBlob("image");
            // 获取image列的输出流
            OutputStream out = blob.getBinaryOutputStream();

            // 获取图片文件输入流
            String fileName = "header.jpg";
            String filePath = context.getRealPath("WEB-INF/files/" + fileName);
            File file = new File(filePath);
            FileInputStream in = new FileInputStream(file);

            // 将图片写入数据库image列中
            byte buffer[] = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
        }
        st.executeBatch();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        close(conn, st, null);
    }
}

二、JDBC调用存储过程:

1.得到CallableStatement,并调用存储过程:

Connection conn = null;
CallableStatement cs = conn.prepareCall("{call 方法(?, ?)}");

2.设置参数,注册返回值,得到输出:

//注册返回值
cs.registerOutParameter(2, Types.VARCHAR);
//设置参数
cs.setString(1, "值");
//执行
cs.execute();
//得到输出
cs.getString("列名");

三、事务:

1.概念:

(1)事务指逻辑上的一组操作,组成这组操作的各个子操作,要不全部成功,要不全部不成功。

(2)特性ACID:

原子性(Atomicity):事务中包含的代码逻辑块不可分割。

一致性(Consistency):事务执行前后,数据完整性。

隔离性(Isolation):多个用户并发访问数据库时,数据库为每一个用户开启的事务,不被其他事务的操作影响,多个并发事务之间要相互隔离。

持久性(Durability):事务执行成功,数据持久保存到磁盘上的数据库。

(3)隔离性:

脏读:指一个事务读取了另外一个事务未提交的数据。

不可重复读:在一个事物内读取表中的某一行数据,多次读取结果不同。
和脏读的区别是:脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。

虚读:是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

2.控制事务:

(1)数据库开启事务命令:

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

(2)JDBC控制事务:

//conn为Connection类
Connection conn = null;

//关闭JDBC默认提交方式,让多条SQL在一个事务中执行
conn.setAutoCommit(false);  
//设置事务回滚点
Savepoint sp = conn.setSavepoint();

//此处进行SQL增删改操作
//...

//提交,回滚后必须要提交
conn.commit();

//Exception中执行回滚,回到上面设置的回滚点
conn.rollback(sp);

3.设置隔离性:

数据库四种隔离级别:
serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)
read committed:可避免脏读情况发生(读已提交)。
read uncommitted:最低级别,以上情况均无法保证。(读未提交)

(1)数据库设置隔离性命令:

//设置事务隔离级别
set session transaction isolation level repeatable read;
//查询当前事务隔离级别
select @@tx_isolation;

(2)JDBC设置隔离级别,此操作要在开启事务之前:

//conn为Connection类
Connection conn = null;
//设置隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);     //串行化
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);  //可重复读
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);   //读已提交
conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); //读未提交

四、元数据-DataBaseMetaData:

1.返回数据库的定义信息:

try {
    Connection conn = null;
    DatabaseMetaData db = conn.getMetaData(); // 返回数据库元数据对象
    db.getURL();// 返回一个String类对象,代表数据库的URL。
    db.getUserName(); // 返回连接当前数据库管理系统的用户名。
    db.getDatabaseProductName();// 返回数据库的产品名称。
    db.getDatabaseProductVersion();// 返回数据库的版本号。
    db.getDriverName();// 返回驱动驱动程序的名称。
    db.getDriverVersion();// 返回驱动程序的版本号。
    db.isReadOnly();// 返回一个boolean值,指示数据库是否只允许读操作。
} catch (SQLException e) {
    e.printStackTrace();
}

2.返回表的定义信息:

try {
    PreparedStatement st = null;
    ParameterMetaData pd = st.getParameterMetaData();// 返回代表PreparedStatement对象的元数据
    pd.getParameterCount();// 获得PreparedStatement对象传递的参数个数
    pd.getParameterType(1);// 获得PreparedStatement对象传递的参数中第1个参数的sql类型
} catch (SQLException e) {
    e.printStackTrace();
}

3.返回列的定义信息:

try {
    ResultSet rs = null;
    ResultSetMetaData rd = rs.getMetaData();// 获得代表ResultSet对象的元数据对象
    rd.getColumnCount();// 返回resultset对象的列数
    rd.getColumnName(1);// 获得第1列的列名
    rd.getColumnTypeName(1);// 获得第1列的列数据的类型 
} catch (SQLException e) {
    e.printStackTrace();
}

五、CachedRowSet(离线操作数据库):

1.创建CachedRowSet:

CachedRowSetImpl cache = new CachedRowSetImpl();

2.填充CachedRowSet:
//对查出的结果集提供分页功能
cache.setPageSize(10);  //取10行数据
cache.populate(rs,2);  //从第2行开始取,起始值是1

3.更新CachedRowSet
调用update***()方法后,调用updateRow方法
更新至数据库,调用acceptChanges方法