Mybatis(一):JDBC传统连接数据库详解

1、配置连接数据库依赖包


    mysql
    mysql-connector-java
    5.1.39

否则加载JDBC驱动会报错:java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

Mybatis(一):JDBC传统连接数据库详解_第1张图片

 

2、加载JDBC驱动程序

通过java.lang.Class类的静态方法forName(String className)实现,反射成功加载后,会将Dirver类的实例注册到DriverManager类中

try {
    // 加载MySql的驱动类 
    Class.forName("com.mysql.jdbc.Driver");    
} catch (ClassNotFoundException e) {
    System.out.println("找不到驱动程序类 ,加载驱动失败!"); 
    e.printStackTrace();
}

为什么通过反射加载JDBC驱动后,DriverManager就能直接通过getConnection获取数据库连接?

1、DriverManager类的源码中静态代码块中loadInitialDrivers会加载初始化的JDBC驱动通过系统设置的属性。

Mybatis(一):JDBC传统连接数据库详解_第2张图片

2、拿到系统的driver,加载Driver.class类

Mybatis(一):JDBC传统连接数据库详解_第3张图片

 

3、拼接JDBC连接URL

JDBC连接URL格式:协议:子协议:数据源标识

mysql URL格式如下:jdbc:mysql://[host:port],[host:port].../[database][?参数key1=value1][&参数key2=value2]...

例如:(MySql的连接URL)

url = jdbc:mysql://localhost:3306/test_db?useUnicode=true&characterEncoding=gbk

useUnicode=true:表示使用Unicode字符集,如果characterEncoding设置为gb2312或GBK,本参数必须设置为true。

characterEncoding=gbk:字符编码方式。

 

4、创建数据库连接

使用DriverManager获取连接

// url数据库JDBC连接url
// username 数据库登录用户
// passward 登录密码
try {
    Connection con = DriverManager.getConnection(url, username, password);
} catch (SQLException ex) {
    System.out.println("数据库连接失败!");  
    ex.printStackTrace();
}

 

5、创建statement

要执行SQL语句,必须获得java.sql.Statement实例,Statement实例分为以下3种类型:

  • 执行静态SQL语句,通常通过Statement实例实现。 Statement stmt = con.createStatement();
  • 执行动态SQL语句,通常通过PreparedStatement实例实现。PreparedStatement pstmt = con.prepareStatement(sql);
  • 执行数据库存储过程,通常通过CallableStatement实例实现。CallableStatement cstmt = con.prepareCall("{CALL demoSp(? , ?)}");

通过PreparedStatement向数据库插入中文字符时,数据库出现'??'乱码的问题:

Mybatis(一):JDBC传统连接数据库详解_第4张图片

解决:JDBC连接的URL加上指定编码 jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8

 

6、执行SQL语句

Statement接口提供了三种执行SQL语句的方法:executeQuery 、executeUpdate、execute;

1、ResultSet executeQuery(String sqlString):

执行查询数据库的SQL语句,返回一个结果集(ResultSet)对象。

2、int executeUpdate(String sqlString):

用于执行INSERT、UPDATE或DELETE语句以及SQL DDL语句,如:CREATE TABLE和DROP TABLE等

3、execute(sqlString):

用于执行返回多个结果集、多个更新计数或二者组合的语句。

ResultSet rs = stmt.executeQuery("SELECT * FROM ...");
int rows = stmt.executeUpdate("INSERT INTO ...");   
boolean flag = stmt.execute(String sql);

 

7、处理SQL返回的结果

两种情况:

1、执行更新返回的是本次操作影响到的记录数。

2、执行查询返回的结果是一个ResultSet对象

ResultSet包含符合SQL语句中条件的所有行,并且它通过一套get方法提供了对这些行中数据的访问。

使用结果集(ResultSet)对象的访问方法获取数据:

while(rs.next()) {
    // String columnLabel,通过列名获取列数据
    String name = rs.getString("name");
    // int columnIndex,通过第几列位置获取列数据,此方法比较高效
    String pass = rs.getString(1);
}    

 

8、关闭JDBC连接

// 关闭记录集 
if (rs != null) {
    try {
        rs.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }    
}
  
// 关闭声明  
if (stmt != null) {
    try {
        stmt.close();
    } catch(SQLException e) {
        e.printStackTrace();
    }    
 }
 
 // 关闭连接对象
 if (conn != null) {
     try {
         conn.close();
     } catch(SQLException e) {
         e.printStackTrace();
     }    
} 

 

使用JDBC的缺点:

  • jdbc底层没有连接池、操作数据库需要频繁的创建和关闭连接,资源消耗很大
  • 写原生的jdbc代码在java中,一旦我们修改sql,java需要整体编译,不利于系统维护。
  • 使用PreparedStatement预编译需要对参数设置需要123数字,序号不利于维护
  • 返回result结果集也需要硬编码

 

可使用Apache Commons的DBUtils工具包:

https://mvnrepository.com/search?q=DBUtils


    commons-dbutils
    commons-dbutils
    1.7

 

手写完整代码示例:

DBUtil.java工具类

package edward.com;

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

public class DBUtil {

    private static String driver;    // 连接数据库的驱动
    private static String url;       // 连接URL
    private static String username;  // 数据库用户
    private static String password;  // 数据库密码

    static {
        driver="com.mysql.jdbc.Driver";
        // 2、拼接JDBC连接URL
        url="jdbc:mysql://localhost:3306/test_db?characterEncoding=utf8";
        username="root";
        password="123456";
    }

    public static Connection open() {
        try {
            // 1、加载JDBC驱动程序
            Class.forName(driver);
            
            // 3、创建数据库连接
            return (Connection) DriverManager.getConnection(url, username, password);
        } catch (Exception ex) {
            System.out.println("数据库连接失败");
            ex.printStackTrace();
        }
        return null;
    }

    public static void close (Connection conn) {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException ex) {
                System.out.println("数据库关闭失败");
                ex.printStackTrace();
            }
        }
    }
}

Jdbc.java

package edward.com;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Jdbc {
    // 入口
    public static void main (String[] args) {
        StuScore stuScore = new StuScore();
        stuScore.setName("edward");
        stuScore.setScore(new BigDecimal("88"));
        stuScore.setSubject("美术");

        // 插入
        insertData(stuScore);
    }

    public static void insertData(StuScore stuScore) {
        String sql = "insert into stu_score (name, score, subject) values (?,?,?)";

        Connection conn = DBUtil.open();
        try {
            // 4、创建statement
            PreparedStatement pstmt = (PreparedStatement) conn.prepareStatement(sql);
            pstmt.setString(1, stuScore.getName());
            pstmt.setBigDecimal(2, stuScore.getScore());
            pstmt.setString(3, stuScore.getSubject());

            // 5、执行SQL语句
            pstmt.executeUpdate();
        } catch (SQLException ex) {
            ex.printStackTrace();
        } finally {
            // 7、关闭JDBC对象
            DBUtil.close(conn);
        }
    }

    public static StuScore queryData (Long id) {
        String sql = "select * from stu_score where id = ?";
        Connection conn = DBUtil.open();
        try {
            PreparedStatement preStatement = (PreparedStatement) conn.prepareStatement(sql);
            preStatement.setLong(1, id);
            ResultSet res = preStatement.executeQuery();

            // 6、处理SQL返回的结果
            if (res.next()) {
                String name = res.getString("name");
                BigDecimal score = res.getBigDecimal("score");
                String subject = res.getString("subject");

                StuScore stuScore = new StuScore();
                stuScore.setName(name);
                stuScore.setScore(score);
                stuScore.setSubject(subject);
                return stuScore;
            }
        } catch (SQLException ex) {
            ex.printStackTrace();
        } finally {
            DBUtil.close(conn);
        }
        return null;
    }
}

 

PreparedStatement和Statement区别

  • PreparedStatement在conn.prepareStatement(sql)时会把sql语句发给数据库做预编译处理并缓存,等到执行execute()方法时,再把查询参数发给数据库直接执行,如果只执行一次SQL语句Statement效率更好,如果SQL批量处理PreparedStatement性能更好。预处理语句查询比普通查询快。
  • PrepareStatement可以写动态查询,代码可读性和可维护性,PreparedStatement好。
  • PrepareStatement可以阻止常见的SQL注入,安全性更高。

// Statement操作, SQL不支持参数化查询,多个参数时需要进行字符串拼接
public static void queryByStatement(Long id) {
    String sql = "select * from stu_score where id = " + id;
    Connection conn = DBUtil.open();
    try {
        // 创建Statement对象
        Statement stmt = conn.createStatement();
        // SQL完整语句发送给数据库进行SQL编译+SQL执行
        ResultSet res = stmt.executeQuery(sql);
        while (res.next()) {
            String name = res.getString("name");
            BigDecimal score = res.getBigDecimal("score");
            System.out.println(name+score);
        }
    } catch (SQLException ex) {
        ex.printStackTrace();
    } finally {
        DBUtil.close(conn);
    }
}

// PreparedStatement,先对SQL语句进行预编译,在进行参数传入,执行已存储的编译过程
public static void queryByPreparedStatement(Long id) {
    String sql = "select * from stu_score where id = ?";
    Connection conn = DBUtil.open();
    try {
        // 预编译SQL语句
        PreparedStatement pst = conn.prepareStatement(sql);
        // 参数传入
        pst.setLong(1, id);
        // 执行SQL,发送参数
        ResultSet res = pst.executeQuery();
        while (res.next()) {
            String name = res.getString("name");
            BigDecimal score = res.getBigDecimal("score");
            System.out.println(name+score);
        }
    } catch (SQLException ex) {
        ex.printStackTrace();
    } finally {
        DBUtil.close(conn);
    }
}

 

使用Statement的SQL注入问题

如下为查询id=1 & name = 'edward'的数据;但当我们传入参数为(1L, " 'anyOne' or 1=1 ")时,因为or 1=1会始终成立,所以无论name为多少,都能查找id=1的数据返回;同理,进行登录用户账号和密码匹配查询时,SQL注入使得任何密码都能成立;或着用户通过传入 " drop table tableName;"等语句可以随意修改数据库。所以使用Statement还得对这些操作进行过滤和验证。

使用PreparedStatement预编译sql语句,传入的参数不会和语句发送任何匹配关系,

// queryByStatement(1L, " 'anyOne' or 1=1 ")
public static void queryByStatement(Long id, String name) {
    String sql = "select * from stu_score where id = " + id + " and name = " + name;
    Connection conn = DBUtil.open();
    try {
        Statement stmt = conn.createStatement();
        ResultSet res = stmt.executeQuery(sql);
        while (res.next()) {
            String n = res.getString("name");
            BigDecimal score = res.getBigDecimal("score");
            System.out.println(name+score);
        }
    } catch (SQLException ex) {
        ex.printStackTrace();
    } finally {
        DBUtil.close(conn);
    }
}

 

Statement、PreparedStatement、PreparedStatement+Batech批处理效率测试

网上结论:批处理效率>PreparedStatement>Statement

根据自己实验结果有点懵逼。。。

测试表准备:

create table `insert_batch_test`(
	`id` bigint(20) NOT NULL AUTO_INCREMENT,
	`num` int(11) NOT NULL DEFAULT '0',
	PRIMARY KEY(`id`)
) ENGINE=InnoDB COMMENT='批处理插入测试表';

普通Statement插入1w和100W跳数据,效率测试:

public static void insertTest1() {
    try {
        Connection conn = DBUtil.open();
        conn.setAutoCommit(false);

        Long beginTime = System.currentTimeMillis();
        Statement stm = conn.createStatement();
        for (int i = 1; i <= 10000; i++) {
            String sql = "insert into insert_batch_test (num) values (" + i + ")";
            stm.executeUpdate(sql);
        }
        // 提交
        conn.commit();
        Long endTime = System.currentTimeMillis();

        System.out.println("----Statement用时: " + (endTime-beginTime)/1000 + "秒;");
        DBUtil.close(conn);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 1W数据结果
----Statement用时: 2371豪秒;
// 100W数据结果
----Statement用时: 124520豪秒;

PreparedStatement插入1W&100W条数据测试:

public static void insertTest2() {
    try {
        Connection conn = DBUtil.open();
        conn.setAutoCommit(false);

        Long beginTime = System.currentTimeMillis();
        String sql = "insert into insert_batch_test (num) values (?)";
        PreparedStatement pst = conn.prepareStatement(sql);
        for (int i = 1; i <= 10000; i++) {
            pst.setInt(1, i);
            pst.executeUpdate();
        }
        conn.commit();
        Long endTime = System.currentTimeMillis();

        System.out.println("----PreparedStatement用时: " + (endTime-beginTime) + "豪秒;");
        DBUtil.close(conn);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
// 1W数据结果
----PreparedStatement用时: 2316豪秒;
// 100W数据结果
----PreparedStatement用时: 139062豪秒;

PreparedStatement+batch批处理插入1W&100W条数据测试:

public static void insertTest3() {
    try {
        Connection conn = DBUtil.open();
        conn.setAutoCommit(false);

        Long beginTime = System.currentTimeMillis();
        String sql = "insert into insert_batch_test (num) values (?)";
        PreparedStatement pst = conn.prepareStatement(sql);
        for (int i = 1; i <= 10000; i++) {
            pst.setInt(1, i);
            pst.addBatch();
            if (i == 10000) {
                pst.executeBatch();
                conn.commit();
                pst.clearBatch();
            }
        }
        Long endTime = System.currentTimeMillis();

        System.out.println("----PreparedStatement+batch用时: " + (endTime-beginTime) + "豪秒;");
        DBUtil.close(conn);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
// 1W数据结果
----PreparedStatement+batch用时: 2135豪秒;
// 100W数据结果
----PreparedStatement+batch用时: 188619豪秒;

你可能感兴趣的:(Mybatis系列,mybatis,数据库)