JDBC学习笔记

JDBC简介

数据库驱动

数据库厂商为了方便开发人员从程序中操作数据库而提供的一套jar包,导入这个jar包就可以调用其中的方法操作数据库,这样的jar包就叫做数据库驱动。

JDBC

SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范,称之为JDBC。
JDBC全称为:Java DataBase Connectivity(java数据库连接),它主要由接口组成。
组成JDBC的2个包(均包含在J2SE里):
java.sql
javax.sql
开发JDBC应用除了需要以上2个包的支持外,还需要导入相应JDBC的数据库实现(即数据库驱动)。

jdbc

JDBC快速入门

首先利用cmd命令建立一个数据库,新建一个user表并写入三条数据。代码如下:

create table user(
    id int primary key auto_increment,
    name varchar(40),
    password varchar(40),
    email varchar(60),
    birthday date
)character set utf8 collate utf8_general_ci;

insert into user(name,password,email,birthday) values('zs','123456','zs@sina.com','1980-12-04');
insert into user(name,password,email,birthday) values('lisi','123456','lisi@sina.com','1981-12-04');
insert into user(name,password,email,birthday) values('wangwu','123456','wangwu@sina.com','1979-12-04');

建好数据库和表之后,再新建一个Java工程,工程建好后需要新建一个lib文件夹导入MySQL的驱动包mysql-connector-java-5.0.8-bin.jar,并添加到BuildPath中。

准备工作完成以后,新建一个类JDBCDemo1.java,用来实现对user表的查询。基本的代码如下:

package me.zipstream.jdbc;

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

import com.mysql.jdbc.Driver;

public class JDBCDemo1 {

    public static void main(String[] args) throws SQLException {
        //1,注册数据库驱动
        DriverManager.registerDriver(new Driver());

        //2,获取数据库连接
        Connection conn = 
            DriverManager.getConnection("jdbc:mysql://localhost:3306/day10", "root", "root");

        //3,获取传输器对象
        Statement statement = conn.createStatement();

        //4,利用传输器对象传输SQL语句到数据库中执行,获取结果集对象
        ResultSet rs = statement.executeQuery("select * from user");

        //5,遍历结果集获取查询结果
        while (rs.next()){
            String name = rs.getString("name");
            System.out.println(name);
        }

        //6,关闭资源
        rs.close();
        statement.close();
        conn.close();
    }
}

程序的细节和优化

DriverManager

JDBC程序中的DriverManager用于加载驱动,并创建与数据库的连接,这个API的常用方法有:

DriverManager.registerDriver(new Driver());
DriverManager.getConnection(String url, String user, String password);

注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
1、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
2、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。

推荐方式:

Class.forName(“com.mysql.jdbc.Driver”);

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

同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的connection对象。正所谓面向接口编程,在导包的时候尽量不要导入与具体数据库有关的实现类,而是要导入接口。

附:Driver的源代码

package com.mysql.jdbc;

import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

数据库URL

URL用于标识数据库的位置,程序员通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:
jdbc:mysql:[]//localhost:3306/test ?参数名:参数值
常用数据库URL地址的写法:
Oracle写法:jdbc:oracle:thin:@localhost:1521:sid
SqlServer写法:jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
MySql写法:jdbc:mysql://localhost:3306/sid
Mysql的url地址的简写形式: jdbc:mysql:///sid
url可以接的参数
userpassword
useUnicode=true&characterEncoding=UTF-8

Connection

JDBC程序中的Connection,代表数据库的连接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过Connection对象完成的,这个对象的常用方法有:

  • createStatement():创建向数据库发送sql的statement对象。
  • prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
  • prepareCall(sql):创建执行存储过程的callableStatement对象。
  • setAutoCommit(boolean.autoCommit):设置事务是否自动提交。
  • commit():在链接上提交事务。
  • rollback():在此链接上回滚事务。

Statement

JDBC程序中的Statement对象用于向数据库发送SQL语句,Statement对象常用方法有:
- executeQuery(String.sql):用于向数据发送查询语句。
- executeUpdate(String.sql):用于向数据库发送insert、update或delete语句
- execute(String sql):用于向数据库发送任意sql语句
- addBatch(String.sql):把多条sql语句放到一个批处理中。
- executeBatch():向数据库发送一批sql语句执行。

ResultSet

JDBC程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next()方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
ResultSet用于封装执行结果,所以该对象提供的都是用于获取数据的get方法:
获取任意类型的数据:
getObject(int index)
getObject(string columnName)
其实以上两个方法主要是给框架设计者使用的。

获取指定类型的数据,例如:
getString(int index)
getString(String columnName)

常用的数据类型转换表:
转换表

ResultSet还提供了对结果集进行滚动的方法(操作游标):
- next():移动到下一行
- Previous():移动到前一行
- absolute(int row):移动到指定行
- beforeFirst():移动resultSet的最前面。
- afterLast():移动到resultSet的最后面。

释放资源

JDBC程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。

特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。

为确保资源释放代码能运行,资源释放代码一定要放在finally语句中。

释放时后创建的先释放。

最终优化后的代码

package me.zipstream.jdbc;

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

public class JDBCDemo1 {

    public static void main(String[] args) {

        Connection conn = null;
        Statement statement = null;
        ResultSet rs = null;

        try{
            //1,注册数据库驱动
            Class.forName("com.mysql.jdbc.Driver");

            //2,获取数据库连接
            conn = 
                DriverManager.getConnection("jdbc:mysql:///day10?user=root&password=root");

            //3,获取传输器对象
            statement = conn.createStatement();

            //4,利用传输器对象传输SQL语句到数据库中执行,获取结果集对象
            rs = statement.executeQuery("select * from user");

            //5,遍历结果集获取查询结果
            while (rs.next()){
                String name = rs.getString("name");
                System.out.println(name);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            //6,关闭资源
            if (rs != null){
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } finally{
                    rs = null;
                }
            }

            if (statement != null){
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } finally{
                    statement = null;
                }
            }

            if (conn != null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } finally{
                    conn = null;
                }
            }           
        }       
    }
}

使用JDBC对数据库进行CRUD

准备工作

为了提高程序的复用性,减少书写重复代码,提高代码的灵活性。新建一个配置文件config.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///day10
user=root
password=root

再新建一个工具类JDBCUtils.java,实现获取连接和关闭资源的功能。

package me.zipstream.utils;

import java.io.FileReader;
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 Properties prop = null;

    private JDBCUtils(){}

    static{
        prop = new Properties();
        try {
            prop.load(new FileReader(JDBCUtils.class.getClassLoader().getResource("config.properties").getPath()));
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取连接
     * @return 对数据库的连接
     * @throws ClassNotFoundException
     * @throws SQLException
     */
    public static Connection getConn() throws ClassNotFoundException, SQLException{
        Class.forName(prop.getProperty("driver"));
        return DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"), prop.getProperty("password"));
    }

    /**
     * 关闭资源
     * @param rs
     * @param st
     * @param conn
     */
    public static void close(ResultSet rs, Statement st, Connection conn){

        if (rs != null){
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally{
                rs = null;
            }
        }

        if (st != null){
            try {
                st.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally{
                st = null;
            }
        }

        if (conn != null){
            try {
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally{
                conn = null;
            }
        }
    }
}

CRUD操作-create

想user表中增加一个记录。代码如下:

@Test
public void add(){
    Connection conn = null;
    Statement st = null;

    try {
        conn = JDBCUtils.getConn();
        st = conn.createStatement();
        int count = 
            st.executeUpdate("insert into user values(null,'zhaoliu','123456','zhaoliu@qq.com','1999-09-09')");

        if (count > 0){
            System.out.println("操作成功!影响到"+ count +"行数据!");
        } else{
            System.out.println("操作失败!");
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally{
        JDBCUtils.close(null, st, conn);
    }
}

CRUD操作-updata

@Test
public void update(){
    Connection conn = null;
    Statement st = null;

    try{
        conn = JDBCUtils.getConn();
        st = conn.createStatement();
        st.executeUpdate("update user set password=999 where name='zhaoliu'");
    } catch (Exception e) {
        e.printStackTrace();
    } finally{
        JDBCUtils.close(null, st, conn);
    }
}

CRUD操作-read

@Test
public void find(){
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;

    try{
        conn = JDBCUtils.getConn();
        st = conn.createStatement();
        rs = st.executeQuery("select * from user where name='zhaoliu'");

        while (rs.next()){
            String name = rs.getString("name");
            String password = rs.getString("password");
            System.out.println(name + ": " + password);
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally{
        JDBCUtils.close(rs, st, conn);
    }
}

CRUD操作-delete

@Test
public void delete(){
    Connection conn = null;
    Statement st = null;

    try{
        conn = JDBCUtils.getConn();
        st = conn.createStatement();
        st.executeUpdate("delete from user where name='zhaoliu'");
    } catch (Exception e) {
        e.printStackTrace();
    } finally{
        JDBCUtils.close(null, st, conn);
    }
}

PreparedStatement

SQL注入攻击

SQL 注入是用户利用某些系统没有对输入数据进行充分的检查,从而进行恶意破坏的行为。由于dao中执行的SQL语句是拼接出来的,其中有一部分内容是由用户从客户端传入,所以当用户传入的数据中包含sql关键字时,就有可能通过这些关键字改变sql语句的语义,从而执行一些特殊的操作,这样的攻击方式就叫做sql注入攻击。典型的手段例如:
username' or '1=1
username'#
就可以不输入密码而登录了。

PreparedStatement的使用

PreparedStatementStatement的孩子,不同的是,PreparedStatement使用预编译机制,在创建PreparedStatement对象时就需要将sql语句传入,传入的过程中参数要用?替代,这个过程回导致传入的sql被进行预编译,然后再调用PreparedStatementsetXXX将参数设置上去,由于sql语句已经经过了预编译,再传入特殊值也不会起作用了。从而有效防止SQL注入攻击。

代码样例:

package me.zipstream.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import me.zipstream.utils.JDBCUtils;

public class JDBCDemo3 {

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;

        try{
            conn = JDBCUtils.getConn();
            ps = conn.prepareStatement("select * from user where name=? and password=?");
            ps.setString(1, "zs");
            ps.setString(2, "123456");
            rs = ps.executeQuery();

            while (rs.next()){
                System.out.println(rs.getString("email"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            JDBCUtils.close(rs, ps, conn);
        }
    }
}

使用JDBC处理大数据

在实际开发中,程序需要把大文本Text或二进制数据Blob保存到数据库。
注:Text是mysql叫法,Oracle中叫Clob

基本概念:大数据也称之为LOB(Large Objects),LOB又分为:
Clob和Blob
Clob用于存储大文本。
Blob用于存储二进制数据,例如图像、声音、二进制文等。

对MySQL而言只有Blob,而没有Clob,mysql存储大文本采用的是Text
Text分为:
TINYTEXT(255)、TEXT(64k)、MEDIUMTEXT(16M)和LONGTEXT(4G)

Blob分为:
TINYBLOB(255)、BLOB(64k)、MEDIUMBLOB(16M)和LONGBLOB(4G)

使用JDBC处理大文本

首先在数据库中新建一个表,用来存储一个10.3M的文本数据。代码如下:

create table textdemo(
    id int primary key auto_increment,
    name varchar(100),
    content MEDIUMTEXT
);

这里还需要修改执行程序虚拟机的最大和最小内存以及MySQL数据库存储文件的大小。在IDE的Run Dialog窗口的Argument选项卡中添加:
-Xms64m
-Xmx256m

来修改虚拟机内存。
在MySQL的my.ini文件的[mysqld]下添加:
max_allowed_packet=64M
修改MySQL存储文件的大小。

大文本的存储

@Test
public void addText(){
    Connection conn = null;
    PreparedStatement ps = null;

    try{
        conn = JDBCUtils.getConn();
        ps = conn.prepareStatement("insert into textdemo values(null, ?, ?)");
        ps.setString(1, "藏地密码");
        File file = new File("1.txt");
        ps.setCharacterStream(2, new FileReader(file), (int) file.length());

        ps.executeUpdate();
    } catch (Exception e){
        e.printStackTrace();
    } finally{
        JDBCUtils.close(null, ps, conn);
    }
}

大文本的查询

@Test
public void findText(){
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;

    try{
        conn = JDBCUtils.getConn();
        ps = conn.prepareStatement("select * from textdemo where id=?");
        ps.setInt(1, 1);
        rs = ps.executeQuery();

        while (rs.next()){
            String filename = rs.getString("name");
            Reader reader = rs.getCharacterStream("content");
            Writer writer = new FileWriter(filename);

            char[] cs = new char[1024];
            int i = 0;
            while ((i=reader.read(cs))!=-1){
                writer.write(cs, 0, i);
            }

            reader.close();
            writer.close();
        }
    } catch (Exception e){
        e.printStackTrace();
    } finally{
        JDBCUtils.close(rs, ps, conn);
    }
}

使用JDBC处理大二进制数据

方法与处理大文本并无两样。只要把setCharacterStreamgetCharacterStream替换为setBinaryStreamgetBinaryStream就可以了。

使用JDBC进行批处理

业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。

Statement方式执行批处理

优点:可以执行多条不同结构的sql语句
缺点:没有使用预编译机制,效率低下,如果要执行多条结构相同仅仅参数不同的sql时,仍然需要写多次sql语句的主干
例子:一段代码完成创建数据库新建表和插入数据的操作

package me.zipstream.batch;

import java.sql.Connection;
import java.sql.Statement;

import me.zipstream.utils.JDBCUtils;

public class StatementBatch {

    public static void main(String[] args) {
        Connection conn = null;
        Statement st = null;

        try{
            conn = JDBCUtils.getConn();
            st = conn.createStatement();

            st.addBatch("create database day10batch");
            st.addBatch("use day10batch");
            st.addBatch("create table batchDemo("+
                    "id int primary key auto_increment,"+
                    "name varchar(20)"+
                    ")");
            st.addBatch("insert into batchDemo values(null,'aaaa')");
            st.addBatch("insert into batchDemo values(null,'bbb')");
            st.addBatch("insert into batchDemo values(null,'cc')");
            st.addBatch("insert into batchDemo values(null,'d')");

            st.executeBatch();
        } catch (Exception e){
            e.printStackTrace();
        } finally{
            JDBCUtils.close(null, st, conn);
        }
    }
}

prparedStatement方式执行批处理

优点:有预编译机制,效率比较高.执行多条结构相同,参数不同的sql时,不需要重复写sql的主干
缺点:只能执行主干相同参数不同的sql,没有办法在一个批中加入结构不同的sql
例子:向psbatch表中存储10000条数据。

package me.zipstream.batch;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;

import me.zipstream.utils.JDBCUtils;

public class PreparedStatementBatch {

    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement ps = null;

        try{
            conn = JDBCUtils.getConn();
            ps = conn.prepareStatement("insert into psbatch values(null,?)");

            for (int i=1; i<=10000; i++){
                ps.setString(1, "name"+i);
                ps.addBatch();

                if (i%1000 == 0){
                    ps.executeBatch();
                    ps.clearBatch();
                }
            }

            ps.executeBatch();

        } catch (Exception e){
            e.printStackTrace();
        } finally{
            JDBCUtils.close(null, ps, conn);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值