JDBC (Java DataBase Connection) 是通过JAVA访问数据库。MySQL-JDBC驱动包下载地址。
所用到的数据库基于该篇博文。
JDBC的Hello World
导入MySQL-JDBC驱动包:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
"123456");
// 3. 创建Statement
statement = connection.createStatement();
// 4. 书写SQL
String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")";
// 5. 执行SQL
statement.execute(sql);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 6. 关闭资源
// 6.1 先关闭Statement
if (statement != null)
try {
statement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 6.2 后关闭Connection
if (connection != null)
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
这个示例代码是新增数据,如果删除数据或更新数据,只需要更改对应的SQL语句即可,下面重点介绍查找数据。
查询数据
- 查询所有:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "123456");
// 3. 创建Statement
statement = connection.createStatement();
// 4. 书写SQL
String sql = "select * from hero";
// 5. 执行SQL
ResultSet rs = statement.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt("id"); // 可以使用字段名
String name = rs.getString(2); // 或使用字段顺序
float hp = rs.getFloat("hp");
int damage = rs.getInt("damage");
System.out.printf("%d\t%s\t%f\t%d\n", id, name, hp, damage);
}
rs.close(); // 这句非必须,因为Statement关闭的时候,ResultSet随之关闭。
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 6. 关闭资源
// 6.1 先关闭Statement
if (statement != null)
try {
statement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 6.2 后关闭Connection
if (connection != null)
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 查询总数:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "123456");
// 3. 创建Statement
statement = connection.createStatement();
// 4. 书写SQL
String sql = "select count(*) from hero";
// 5. 执行SQL
ResultSet rs = statement.executeQuery(sql);
int total = -1;
while (rs.next()) {
total = rs.getInt(1);
}
System.out.printf("total = %d\n", total);
rs.close(); // 这句非必须,因为Statement关闭的时候,ResultSet随之关闭。
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 6. 关闭资源
// 6.1 先关闭Statement
if (statement != null)
try {
statement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 6.2 后关闭Connection
if (connection != null)
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 查询用户名和密码是否正确:
CREATE TABLE user (
id int(11) AUTO_INCREMENT,
name varchar(30) ,
password varchar(30),
PRIMARY KEY (id)
) ;
insert into user values(null,'name1','password1');
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "123456");
// 3. 创建Statement
statement = connection.createStatement();
String name = "name1";
String password = "password1";
// 4. 书写SQL
String sql = "select * from user where name = '"+name+"' and password = '"+password+"'";
// 5. 执行SQL
ResultSet rs = statement.executeQuery(sql);
if(rs.next()) {
System.out.printf("Congratulations!");
} else {
System.out.printf("用户名或密码错误!");
}
rs.close(); // 这句非必须,因为Statement关闭的时候,ResultSet随之关闭。
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 6. 关闭资源
// 6.1 先关闭Statement
if (statement != null)
try {
statement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 6.2 后关闭Connection
if (connection != null)
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
PreparedStatement
PreparedStatement比Statement有很多优点。
- 参数设置方便,不需要像Statement拼接字符串。可读性可维护性好。
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement ps = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "123456");
// 3. 书写SQL
String sql = "insert into hero values(null, ?, ?, ?)";
// 4. 创建PreparedStatement
ps = connection.prepareStatement(sql);
ps.setString(1, "悟空2");
ps.setFloat(2, 20f);
ps.setInt(3, 209);
// 5. 执行SQL
ps.execute();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 6. 关闭资源
// 6.1 先关闭Statement
if (ps != null)
try {
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 6.2 后关闭Connection
if (connection != null)
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
- 有预编译功能,速度快。An object that represents a precompiled SQL statement. A SQL statement is precompiled and stored in a PreparedStatement object. This object can then be used to efficiently execute this statement multiple times.而Statement每次都需要数据库编译SQL语句。
- 防止SQL注入攻击。如下语句拼接字符串后送到数据库再进行编译,就形成了这条语句
select * from hero where name = '盖伦' OR 1=1
,由于1=1恒成立,就相当于select * from hero
,查询所有,如果这个表很大,就会导致数据库负载变的很高,响应很慢。而PreparedStatement设置参数,预编译,形成的SQL语句是select * from hero where name = "'盖伦' OR 1=1"
!!
String name = "'盖伦' OR 1=1";
String sql0 = "select * from hero where name = " + name; //+拼接字符串效率很低
execute() 和 executeUpdate()
都可以进行增删改,前者返回boolean类型(操作是否成功),后者返回int类型(影响的记录数)。而execute还可以查,查的结果可调用getResultSet得到。
插入数据获取自增ID
execute() 和 executeUpdate()进行插入操作后,分别返回boolean类型和int类型,并没有返回对应记录的自增ID,要想得到可以如下做。
PreparedStatement ps = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
// 执行SQL之后
ResultSet rs = ps.getGeneratedKeys();
获取数据库元数据
package jdbc;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "123456");
DatabaseMetaData dbmd = connection.getMetaData();
System.out.println("数据库服务器产品名称:\t"+dbmd.getDatabaseProductName());
System.out.println("数据库产品版本:\t"+dbmd.getDatabaseProductVersion());
System.out.println("数据库和表分隔符:\t"+dbmd.getCatalogSeparator()); // 如test.user
System.out.println("驱动版本:\t"+dbmd.getDriverVersion());
System.out.println("可用的数据库列表:");
ResultSet rs = dbmd.getCatalogs();
while (rs.next()) {
System.out.println(rs.getString(1));
}
rs.close();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
事务
MySQL中InnoDB存储引擎支持事务,MyISAM等不支持事务。
- 不用事务
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "123456");
// 3. 书写SQL
String sql1 = "update hero set hp = hp + 1 where id = 1"; // 执行无误
String sql2 = "updata hero set hp = hp - 1 where id = 1"; // update写错,执行错误
// 4. 创建PreparedStatement
statement = connection.createStatement();
// 5. 执行SQL
statement.execute(sql1);
statement.execute(sql2);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 6. 关闭资源
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
2. 使用事务
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJDBC {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8",
"root", "123456");
connection.setAutoCommit(false);
// 3. 书写SQL
String sql1 = "update hero set hp = hp + 1 where id = 1"; // 执行无误
String sql2 = "updata hero set hp = hp - 1 where id = 1"; // update写错,执行错误
// 4. 创建PreparedStatement
statement = connection.createStatement();
// 5. 执行SQL
statement.execute(sql1);
statement.execute(sql2);
} catch (Exception e) {
e.printStackTrace();
try {
connection.rollback(); // 回滚
} catch (SQLException ex) {
e.printStackTrace();
}
} finally {
try {
connection.commit(); // 提交事务
} catch (SQLException e) {
e.printStackTrace();
}
// 6. 关闭资源
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
数据库连接池
当有多个线程都需要连接数据库执行SQL语句时,都会创建数据库连接,并且在使用完毕后,关闭连接。创建连接和关闭连接的过程是比较消耗时间的,当多线程并发的时候,系统就会变得很卡顿。
连接池在使用之前,就会创建好一定数量的连接。如果有任何线程需要使用连接,就从连接池里面借用,而不是自己重新创建。使用完毕后,又把这个连接归还给连接池供下一次或者其他线程使用。倘若连接池里的连接被借用光了,就会临时等待,直到有连接被归还回来,再继续使用。整个过程,这些连接都不会被关闭,而是不断的被循环使用,从而节约了启动和关闭连接的时间。
ConnectionPool.java
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class ConnectionPool {
private List<Connection> connections = new ArrayList<Connection>();
private int size;
public ConnectionPool(int size) {
this.size = size;
init();
}
public void init() {
try {
Class.forName("com.mysql.jdbc.Driver");
for (int i = 0; i < size; i++) {
Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root", "123456");
connections.add(c);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public synchronized Connection getConnection() {
while (connections.isEmpty()) {
try {
// System.out.println(Thread.currentThread().getName() + " waiting...");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Connection connection = connections.remove(0);
return connection;
}
public synchronized void returnConnection(Connection connection) {
connections.add(connection);
this.notifyAll();
}
}
TestConnectionPool.java
package jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import jdbc.ConnectionPool;
public class TestConnectionPool {
public static void main(String[] args) {
ConnectionPool connectionPool = new ConnectionPool(3);
for (int i = 0; i < 100; i++) {
new WorkingThread("working thread" + i, connectionPool).start();
}
}
}
class WorkingThread extends Thread {
private ConnectionPool connectionPool;
public WorkingThread(String name, ConnectionPool connectionPool) {
super(name);
this.connectionPool = connectionPool;
}
public void run() {
Connection c = connectionPool.getConnection();
System.out.println(this.getName()+ ":\t 获取了一根连接,并开始工作" );
try (Statement st = c.createStatement();){
// 模拟耗时1秒的SQL语句
Thread.sleep(1000);
st.execute("select * from hero");
} catch (SQLException | InterruptedException e) {
e.printStackTrace();
}
connectionPool.returnConnection(c);
}
}
参考资料
[1] How2J
[2] 《疯狂Java讲义(第4版)》 李刚
[3] 《Java核心技术 卷I》
[4] 《Java核心技术 卷II》