Java Web学习总结(11)JDBC

一,简介

JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序

二,编写JDBC程序

首先在mysql中创建一个库,并创建user表和插入表的数据,SQL脚本如下:

CREATE DATABASE jdbcLibrary CHARACTER SET utf8 COLLATE utf8_general_ci;
 
USE jdbcLibrary;
 
CREATE TABLE users(
    id INT PRIMARY KEY,
    uname VARCHAR(40),
    upwd VARCHAR(40)
);
INSERT INTO users(id,uname,upwd) VALUES(1,'Zender','123456');
INSERT INTO users(id,uname,upwd) VALUES(2,'张三','123456');
INSERT INTO users(id,uname,upwd) VALUES(3,'李四','123456');

项目中导入驱动:

JDBCDemo代码如下:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
 
public class JDBCDemo {
    public static void main(String[] args) throws Exception {
        //要连接的数据库URL
        String url = "jdbc:mysql://localhost:3306/jdbcLibrary";
        //用户名
        String username = "root";
        //密码
        String password = "123456";
        
        //1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取与数据库的链接
        Connection conn = DriverManager.getConnection(url, username, password);
        
        //3.获取用于向数据库发送sql语句的statement
        Statement st = conn.createStatement();
        
        String sql = "select id,uname,upwd from users";
        //4.向数据库发sql,并获取代表结果集的resultset
        ResultSet rs = st.executeQuery(sql);
        
        //5.取出结果集的数据
        while(rs.next()){
            System.out.println("id=" + rs.getObject("id"));
            System.out.println("uname=" + rs.getObject("uname"));
            System.out.println("upwd=" + rs.getObject("upwd"));
        }
        
        //6.关闭链接,释放资源
        rs.close();
        st.close();
        conn.close();
    }
}

运行结果:

1,DriverManager类

DriverManager用于加载驱动,并创建与数据库的链接。

API的常用方法:

DriverManager.registerDriver(new Driver())

DriverManager.getConnection(url, user, password)

注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:

1、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。

2、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。

推荐方式:Class.forName("com.mysql.jdbc.Driver")

采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。

2,Connection类

用于代表数据库的链接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的。

常用方法:

createStatement()

创建向数据库发送sql的statement对象。

prepareStatement(sql)

创建向数据库发送预编译sql的PrepareSatement对象。

prepareCall(sql)

创建执行存储过程的callableStatement对象。

setAutoCommit(boolean autoCommit)

设置事务是否自动提交。

commit()

在链接上提交事务。

rollback()

在此链接上回滚事务。

3,Statement类

用于向数据库发送SQL语句

常用方法:

executeQuery(String sql)

用于向数据发送查询语句。

executeUpdate(String sql)

用于向数据库发送insert、update或delete语句

execute(String sql)

用于向数据库发送任意sql语句

addBatch(String sql)

把多条sql语句放到一个批处理中。

executeBatch()

向数据库发送一批sql语句执行。

4,ResultSet类

该类代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。

常用方法:

获取任意类型的数据:

    getObject(int index)

    getObject(string columnName)

获取指定类型的数据:

    getString(int index)

    getString(String columnName)

next()

移动到下一行

Previous()

移动到前一行

absolute(int row)

移动到指定行

beforeFirst()

移动resultSet的最前面。

afterLast()

移动到resultSet的最后面。

二,数据库的CRUD

创建一个db.properties文件用于存放MySQL数据库的连接信息,代码如下所示:

driver=com.mysql.jdbc.Driver

url=jdbc:mysql://localhost:3306/jdbcLibrary

username=root

password=123456

编写一个JdbcUtils工具类,用于连接数据库,获取数据库连接和释放数据库连接,代码如下:

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
 
public class JdbcUtils {
    private static String driver = null;
    private static String url = null;
    private static String username = null;
    private static String password = null;
    
    static{
        try{
            //读取db.properties文件中的数据库连接信息
            InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
            Properties prop = new Properties();
            prop.load(in);
            
            //获取数据库连接驱动
            driver = prop.getProperty("driver");
            //获取数据库连接URL地址
            url = prop.getProperty("url");
            //获取数据库连接用户名
            username = prop.getProperty("username");
            //获取数据库连接密码
            password = prop.getProperty("password");
            
            //加载数据库驱动
            Class.forName(driver);
            
        }catch (Exception e) {
            throw new ExceptionInInitializerError(e);
        }
    }
    
    /**
     * 
     * @方法名: getConnection
     * @描述: 获取数据库连接对象
     * @return
     * @throws SQLException
     * @创建人 Zender
     */
    public static Connection getConnection() throws SQLException{
        return DriverManager.getConnection(url, username,password);
    }
    
    /**
     * 
     * @方法名: release
     * @描述: 释放资源
     * @param conn
     * @param st
     * @param rs
     * @创建人 Zender
     */
    public static void release(Connection conn,Statement st,ResultSet rs){
        try{
            if(rs!=null){
                rs.close();
                rs = null;
            }
            if(st!=null){
                st.close();
                st = null;
            }
            if(conn!=null){
               conn.close();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JDBCDemo代码如下:

package com.zender;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
 
import org.junit.Test;
 
import com.zender.util.JdbcUtils;
 
public class JDBCDemo {
    
    private Connection conn = null;
    private PreparedStatement st = null;
    private ResultSet rs = null;
    
    @Test
    public void insertUser(){
        try{
            //获取一个数据库连接
            conn = JdbcUtils.getConnection();
            //构建执行的SQL,SQL中的参数使用?作为占位符
            String sql = "INSERT INTO users(id,uname,upwd) VALUES(?,?,?);";
            
            //获取prepareStatement对象
            st = conn.prepareStatement(sql);
            st.setInt(1, 6);
            st.setString(2, "Zender");
            st.setString(3, "123456");
            //执行插入操作,executeUpdate方法返回成功的条数
            int num = st.executeUpdate();
            if(num>0){
                System.out.println("插入成功!!");
            }
            
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            //SQL执行完成之后释放相关资源
            JdbcUtils.release(conn, st, rs);
        }
    }
    
    @Test
    public void deleteUser(){
        try{
            conn = JdbcUtils.getConnection();
            String sql = "delete from users where id=4";
            st = conn.prepareStatement(sql);
            int num = st.executeUpdate();
            if(num>0){
                System.out.println("删除成功!!");
            }
        }catch (Exception e) {
            e.printStackTrace();
            
        }finally{
            JdbcUtils.release(conn, st, rs);
        }
    }
    
    @Test
    public void deleteUpdate(){
        try{
            conn = JdbcUtils.getConnection();
            String sql = "update users set uname='修改的Name', upwd='123' where id=1";
            st = conn.prepareStatement(sql);
            int num = st.executeUpdate();
            if(num>0){
                System.out.println("更新成功!!");
            }
        }catch (Exception e) {
            e.printStackTrace();
            
        }finally{
            JdbcUtils.release(conn, st, rs);
        }
    }
    
    @Test
    public void findUser(){
        try{
            conn = JdbcUtils.getConnection();
            String sql = "select * from users where id=3";
            st = conn.prepareStatement(sql);
            rs = st.executeQuery();
            if(rs.next()){
                System.out.println(rs.getString("id"));
                System.out.println(rs.getString("uname"));
                System.out.println(rs.getString("upwd"));
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            JdbcUtils.release(conn, st, rs);
        }
    }
}

这里使用了PreperedStatement,该类是Statement的子类,PreperedStatement可以避免SQL注入的问题,并且PreperedStatement对于sql中的参数,允许使用占位符的形式进行替换。

三,事务

事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。

例如:模拟银行转账,User1向User2转账

数据库中创建名字为account的table:

JDBCDemo代码如下:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
 
import org.junit.Test;
 
import com.zender.util.JdbcUtils;
 
public class JDBCDemo {
    
    private Connection conn = null;
    private PreparedStatement st = null;
    private ResultSet rs = null;
    
    @Test
    public void TransactionDemo(){
        try{
            //获取一个数据库连接
            conn = JdbcUtils.getConnection();
            //通知数据库开启事务
            conn.setAutoCommit(false);
            //构建执行的SQL
            String sql = "update account set money=money-100 where id=1";
            //获取prepareStatement对象
            st = conn.prepareStatement(sql);
            //执行修改操作
            st.executeUpdate();
            sql = "update account set money=money+100 where id=2";
            st = conn.prepareStatement(sql);
            st.executeUpdate();
            //提交事务
            conn.commit();
        }catch (Exception e) {
            //出现异常回滚事务
            e.printStackTrace();
        }finally{
            //释放资源
            JdbcUtils.release(conn, st, rs);
        }
    }
}

运行以上代码,数据库数据如下:

现在修改JDBCDemo代码,让代码在执行中途出错,导致有一部分SQL执行失败后,让数据库自动回滚事务,代码如下:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
 
import org.junit.Test;
 
import com.zender.util.JdbcUtils;
 
public class JDBCDemo {
    
    private Connection conn = null;
    private PreparedStatement st = null;
    private ResultSet rs = null;
    
    @Test
    public void TransactionDemo(){
        try{
            //获取一个数据库连接
            conn = JdbcUtils.getConnection();
            //通知数据库开启事务
            conn.setAutoCommit(false);
            //构建执行的SQL
            String sql = "update account set money=money-100 where id=1";
            //获取prepareStatement对象
            st = conn.prepareStatement(sql);
            //执行修改操作
            st.executeUpdate();
            //出错代码
            int x = 1/0;
            sql = "update account set money=money+100 where id=2";
            st = conn.prepareStatement(sql);
            st.executeUpdate();
            //提交事务
            conn.commit();
        }catch (Exception e) {
            //出现异常回滚事务
            e.printStackTrace();
        }finally{
            //释放资源
            JdbcUtils.release(conn, st, rs);
        }
    }
}

运行以上代码,控制台报错:

数据库数据如下:

四,事务的四大特性

原子性(Atomicity)

原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败。

一致性(Consistency)

事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,user1向user2转账,假设转账之前这两个用户的钱加起来总共是200,那么user1向user2转账之后,不管这两个账户怎么转,user1用户的钱和user2用户的钱加起来的总额还是200,这个就是事务的一致性。

隔离性(Isolation)

事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

持久性(Durability)

持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

事务的四大特性中最麻烦的是隔离性,下面重点介绍一下事务的隔离级别

五、事务的隔离级别

作用:

在多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。

不考事务的虑隔离性,可能出现以下问题:

1,脏读

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

例如:

假设user1向user2转帐100元,对应sql语句如下所示:

    SQL1:update account set money=money+100 where name='user2'

    SQL2:update account set money=money-100 where name='user1'

 当SQL1执行完,SQL2还没执行(user1未提交时),如果此时user2查询自己的帐户,就会发现自己多了100元钱。如果user1等user2走后再回滚,user2回来再次查询时候就会少100元。 

2,不可重复读

不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。

例如:

1,首先银行查询用户user1的余额,第一次查询结果为100元。

2,user1来到银行查询自己余额,查询的余额为100元,然后user1向账户中存入了500元并提交。

3,银行接着又进行了一次查询,此时A帐户为600元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。

3,虚读(幻读)

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

例如:

user1去银行存款500元(事务没有提交),这时候银行做总存款统计,所有用户存款为1000元,然后user1这个时候提交事务,这时银行再统计发现帐户钱多了500元,造成虚读。可能就会很困惑,不知道哪次查询是准的。

posted @ 2017-11-09 20:00  Zender  阅读(353)  评论(0编辑  收藏  举报