# JDBC

# 简介

JDBC(Java Database Connectivity)提供了一种与平台无关,执行SQL的标准API,可方便实现多种关系数据库的统一操作,它由一组Java语言编写的类和接口组成。这些标准需各个数据库厂商实现,每个数据库厂商都会提供一个JDBC驱动程序,常见的JDBC分为四类:

  • JDBC-ODBC bridge

    SUN提供JDBC操作标准,利用微软的ODBC进行连接操作,此模式会把所有的JDBC调用传递给ODBC,让其调用数据库的本地代码。

    • 优点是几乎所有数据库厂商提供对应的ODBC,可访问所有的数据库。

    • 缺点是执行效率低,不适合大数据量访问,需客户端预装ODBC驱动。

      odbc1

      odbc2

  • Native-API driver

    JDBC API调用转为原生C/C++ API本地调用。

    • 优点是速度快,比上面桥接模式快。

    • 缺点是客户端需要装数据库厂商的驱动程序,且这些驱动程序不是通用的。

      nativeAPI1

      nativeAPI2

  • Network-Protocol driver (Middleware driver)

    JDBC把对数据库的访问请求传递给中间件服务器,中间服务器再调用数据库服务器。

    • 优点是不需要再客户端加载数据库厂商的驱动程序,可扩展性好,便于做监控负载均衡。

    • 缺点是需要配置中间层,降低速度。

      NetworkProtocolDriver1

      NetworkProtocolDriver2

  • Database-Protocol driver (Pure Java driver) or thin driver

    纯基于Java的驱动程序,通过Socket与数据库厂商数据库进行通信。

    • 优点是平台独立,不需要中间媒介,应用能同数据库服直接通信,访问速度快。

    • 缺点是几乎只有数据库厂商自己才提供这种驱动,需针对不同的数据库使用不同的驱动。

      DatabaseProtocolDriver1

      DatabaseProtocolDriver2

JDBC的类结构如下:

JDBCClass

这些类同数据库的关系如下:

JDBCClassDatabase

# 选择策略

  • 如果访问的是Oracle,且并不需要分布式架构时,可考虑Database-Protocol driver。
  • 如果Java需同时访问多个数据库,或者需要分布式架构时需使用Network-Protocol driver。
  • 在没有上面的驱动时使用Native-API driver。
  • 在学习环境中或者开发测试环境中可使用JDBC-ODBC桥接。

# 数据库操作

# 连接数据库

JDBC属于Java提供的服务标准,它的操作步骤如下:

  • 加载数据库驱动程序。一般为jar或zip包,需配置到CLASSPATH路径。
  • 连接数据库。
  • 操作数据库。
  • 关闭数据库连接。

我们使用maria作为数据库,需要先下载jdbc驱动程序:JDBC-maria

在Eclipse中找到项目->属性->JavaBuildPath->Libraries,添加下载的JDBC驱动。

下面是连接程序:

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

class jdbcOper {
    String driverName = "org.mariadb.jdbc.Driver";
    String url = "jdbc:mysql://localhost:6612/test";
    String username = "root";
    String password = "666666";
    Connection connector = null;

    public jdbcOper() {
        try {
            Class.forName(driverName);
            // DriverManager.registerDriver(new Driver());
            connector = DriverManager.getConnection(url, username, password);
            System.out.println(connector);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        connector.close();
    }

}

public class Main {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        jdbcOper jdbc = new jdbcOper();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# Statement

Statement是JDBC中操作数据库的接口,可实现数据的更新与查询的处理操作。注意数据查询时向数据发出select指令,查询的结果必须通过ResultSet接口来封装,ResultSet是一种可以保存任意查询结果的集合结构,所有查询结果通过ResultSet在内存中形成一张虚拟表,可根据数据行的索引,从每行依据数据类型来获取列数据。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;


class jdbcOper {
    String driverName = "org.mariadb.jdbc.Driver";
    String url = "jdbc:mysql://localhost:6612";
    String username = "root";
    String password = "666666";
    String defaultDatabase="test";
    Connection connector = null;
    Statement statement;

    public jdbcOper() {
        try {
            Class.forName(driverName);
            // DriverManager.registerDriver(new Driver());
            connector = DriverManager.getConnection(url, username, password);
            System.out.println(connector);
            statement = connector.createStatement();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void CreateDatabaseAndTable() {
        try {
            statement.execute("create database test;");
            UseDateBase();
            // statement.execute("create table student(learn_id int not null
            // auto_increment,name varchar(20) not null,score int,primary key(learn_id));");
            String createTableString = new StringBuilder()
                    .append(" create table student( ")
                    .append(" learn_id int not null auto_increment, ")
                    .append(" name varchar(20) not null, ")
                    .append(" score int, ")
                    .append(" date datetime, ")
                    .append(" primary key(learn_id)); ").toString();
            statement.execute(createTableString);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            System.out.println("创建数据库出错!");
        }
    }
    
    public void UseDateBase() {
        try {
            statement.execute("use "+defaultDatabase+";");
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void InsertStudentData(String name, float score, String dateString) {
        UseDateBase();
        String insertDataString = new StringBuilder()
                .append(" insert into student ")
                .append(" (name,score,date) ")
                .append(" values( ")
                .append(GetQuotedString(name)+ "," + GetQuotedString(score) + "," + GetQuotedString(getTime(dateString)))
                .append(" )")
                .toString();
        System.out.println("insert string:"+insertDataString);
        try {
            statement.execute(insertDataString);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public int updateStudentData() {
        String updateString = new StringBuilder()
                .append(" update student set ")
                .append(" score = "+GetQuotedString(90))
                .append(" where name="+GetQuotedString("xie"))
                .toString();
        int count=0;
        try {
            System.out.println("update string:"+updateString);
            count = statement.executeUpdate(updateString);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("更改了数据:"+count+"行");
        return count;
    }
    
    public int deleteStudentData() {
        String deleteString = new StringBuilder()
                .append(" delete from student ")
                .append(" where learn_id in (2,3); ")
                .toString();
        int count = 0;
        try {
            System.out.println("delete string:"+deleteString);
            count = statement.executeUpdate(deleteString);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("删除了数据:"+count+"行");
        return count;
    }
    
    public void SelectData() {
        UseDateBase();
        String selectString = new StringBuilder()
                .append(" select name,score,date ")
                .append(" from student ")
                .toString();
        try {
            ResultSet rs = statement.executeQuery(selectString);
            while(rs.next()) {
                System.out.println("name:"+rs.getNString(1)+" score:"+rs.getInt(2)+" date:"+rs.getDate(3)+" time:"+rs.getTime(3));
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public String GetQuotedString(Object field) {
        return "\""+field.toString()+"\"";
    }

    public Timestamp getTime(String dateString) {
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd kk:mm:ss", Locale.ENGLISH);
        dateFormat.setLenient(false);
        Date timeDate = null;
        try {
            if ("current".equalsIgnoreCase(dateString)) {
                timeDate = new Date();
            } else {
                timeDate = dateFormat.parse(dateString);
            }
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return new Timestamp(timeDate.getTime());
    }

    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        connector.close();
    }

}

public class Main {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        jdbcOper jdbc = new jdbcOper();
        jdbc.CreateDatabaseAndTable();
        jdbc.InsertStudentData("xie", 70, "current");
        jdbc.InsertStudentData("wang", 80, "current");
        jdbc.InsertStudentData("zhang", 90, "current");
        jdbc.InsertStudentData("li", 30, "2018-07-27 12:25:00");
        jdbc.updateStudentData();
        jdbc.deleteStudentData();
        jdbc.SelectData();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

# PreparedStatement

PreparedStatement是Statement的子接口,属于SQL预处理,与Statement不同的是PreparedStatement在操作时先在数据表中准备好一条待执行的SQL语句,后面再设置内容,这样的处理模型会让数据库操作更安全。

前面都是把变量拼凑到SQL中,当有大量的行要插入时,这样做效率既不高,也不方便维护,而且容易出错,这时就是PreparedStatement使用的场景了,它编写SQL用"?"进行占位符设计,Connection接口通过prepareStatement()方法来实例化PreparedStatement接口实例,在更新或查询时使用setXXX()方法依据占位符的索引顺序进行内容设置。

注意JDBC中的PreparedStatement,ResultSet操作的日期类型均为java.sql.Date,上面需要将java.util.Date转换为java.sql.Date类的实例,下面的类图说明了这个问题:

utilToSqlDate

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

class jdbcOper {
    String driverName = "org.mariadb.jdbc.Driver";
    String url = "jdbc:mysql://localhost:6612/test";
    String username = "root";
    String password = "666666";
    Connection connector = null;

    public jdbcOper() {
        try {
            Class.forName(driverName);
            // DriverManager.registerDriver(new Driver());
            connector = DriverManager.getConnection(url, username, password);
            System.out.println(connector);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        connector.close();
    }

    public void InsertData() {
        String insertString = new StringBuilder(" insert into student ")
                .append(" (name,score,date) ")
                .append(" values(?,?,?) ")
                .toString();
        String names[] = { "xie", "wang", "li" };
        int scores[] = { 30, 40, 60 };

        try {
            PreparedStatement pStatement = connector.prepareStatement(insertString);

            for (int i = 0; i < names.length; i++) {
                pStatement.setString(1, names[i]);
                pStatement.setInt(2, scores[i]);
                pStatement.setDate(3, new java.sql.Date(new java.util.Date().getTime()));
                int count = pStatement.executeUpdate();
                System.out.println("插入行数:"+count);
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public void SelectData() {
        String fieldString="name";
        String valueString="xie";
        String selectString = new StringBuilder(" select count(*) from student ")
                .append(" where "+fieldString+" like ? ")
                .toString();
        PreparedStatement pStatement;
        try {
            pStatement = connector.prepareStatement(selectString);
            
            pStatement.setString(1, "%"+valueString+"%");
            ResultSet rs = pStatement.executeQuery();
            if(rs.next()) {
                System.out.println("符合条件的数量:"+rs.getLong(1));
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        jdbcOper jdbc = new jdbcOper();
        jdbc.InsertData();
        jdbc.SelectData();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

# 数据批处理

JDBC随着JDK每次版本更新也在更新,从JDK 2.0开始提供了更多的功能比如可滚动的结果集,使用结果集更新数据,批处理。下面是使用PreparedStatement执行批处理,它的返回值有3个:

  • 大于0的数字:SQL语句影响的行数。
  • Statement.SUCCESS_NO_INFO(-2):SQL执行成功。
  • Statement.EXECUTE_FAILED(-3):SQL执行失败。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

class jdbcOper {
    String driverName = "org.mariadb.jdbc.Driver";
    String url = "jdbc:mysql://localhost:6612/test";
    String username = "root";
    String password = "666666";
    Connection connector = null;

    public jdbcOper() {
        try {
            Class.forName(driverName);
            // DriverManager.registerDriver(new Driver());
            connector = DriverManager.getConnection(url, username, password);
            System.out.println(connector);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public void insertData() {
        String insertString = new StringBuilder(" insert into student ")
                .append(" (name,score,date) ")
                .append(" values(?,?,?) ")
                .toString();
        String names[] = { "xie", "wang", "li" };
        int scores[] = { 30, 40, 60 };

        try {
            PreparedStatement pStatement = connector.prepareStatement(insertString);

            for (int i = 0; i < names.length; i++) {
                pStatement.setString(1, names[i]);
                pStatement.setInt(2, scores[i]);
                pStatement.setDate(3, new java.sql.Date(new java.util.Date().getTime()));
                pStatement.addBatch();
            }
            pStatement.executeBatch();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        connector.close();
    }

}

public class Main {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        jdbcOper jdbc = new jdbcOper();
        jdbc.insertData();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

# 事务控制

事务指的是一组操作,要么一起成功,要么一起失败。事务具有以下特性(ACID):

  • 原子性:是事务的最小单元,不能再分割,这些操作必须要么一起成功,要么一起失败。A给B转账,如果A转账失败,B就不能收到钱。
  • 一致性:如果数据库操作失败,则其前后是一致的。A给B转账,如果转账失败,A的钱不减少,B的钱不增加。
  • 隔离性:多个事务可同时进行且彼此无法访问,只有事务最后完成时才能看到结果。
  • 持久性:当系统崩溃时,事务依然可以坚持提交,当事务完成后,操作的结果会保存在磁盘中。

JDBC默认开启了事务的自动提交模式。

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

class jdbcOper {
    String driverName = "org.mariadb.jdbc.Driver";
    String url = "jdbc:mysql://localhost:6612/test";
    String username = "root";
    String password = "666666";
    Connection connector = null;
    Statement statement;

    public jdbcOper() {
        try {
            Class.forName(driverName);
            // DriverManager.registerDriver(new Driver());
            connector = DriverManager.getConnection(url, username, password);
            System.out.println(connector);
            statement = connector.createStatement();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public void UseTransaction() {
        try {
            connector.setAutoCommit(false);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            statement.addBatch("insert into student(name,score) values ('testName1',100)");
            statement.addBatch("delete from student where learn_id=h");
            int result[]=statement.executeBatch();
            connector.commit();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            try {
                System.out.println("操作失败进行回滚!");
                connector.rollback();
            } catch (SQLException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
        }
        
    }
    

    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        connector.close();
    }

}

public class Main {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        jdbcOper jdbc = new jdbcOper();
        jdbc.UseTransaction();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

# ORM

ORM为Object Relational Mapping,是一种从面向对象到关系数据的转变,是操作数据库的中间环节,目前的ORM有MyBatis,Hibernate,ADO.NET Entity Framework。

面向对象和关系数据库的概念对应关系是怎样的呢?

面向对象概念 数据库概念
类成员属性 表中字段
对象引用关联 表的外键关联
类的一个实例化对象 表的一行记录
对象数组 表的多行记录