Java为关系数据库定义了一套标准的访问接口:JDBC(Java Database Connectivity),使咱们能够基于JDBC访问数据库,实现数据的增删改查。html
本文将以PostgreSQL为例,展现JDBC的一些基本操做,其中以实操代码为主,关于基本概念的讲解详见参考文献。java
提示:在开始学习JDBC编程以前,务必先学习下SQL基础。mysql
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.18</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.5</version> </dependency>
配置项包括两个jar包:
一、postgresql:PostgreSQL的JDBC驱动
二、HikariCP:JDBC链接池(javax.sql.DataSource)的实现类sql
注意:
postgresql中的scope应该为runtime,由于只在运行期才使用。若是把runtime
改为compile
,虽然也能正常编译,可是在IDE里写程序的时候,会多出来一大堆相似com.mysql.jdbc.Connection
这样的类,很是容易与Java标准库的JDBC接口混淆,因此坚定不要设置为compile
。数据库
import java.sql.*; public class Test { // JDBC链接的URL, 不一样数据库有不一样的格式: static final String URL = "jdbc:postgresql://localhost:3306/test"; static final String USER = "sde"; static final String PASSWORD = "123456"; public static void main(String[] args) { // 获取链接: try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { System.out.println("数据库链接成功!"); }catch(SQLException e){ e.printStackTrace(); } } }
核心代码是DriverManager
提供的静态方法getConnection()
。DriverManager
会自动扫描classpath,找到全部的JDBC驱动,而后根据咱们传入的URL自动挑选一个合适的驱动。由于JDBC链接是一种昂贵的资源,因此使用后要及时释放(经过try-with-resources的方式)。编程
注意:代码中的URL
、USER
、PASSWORD
是数据库的信息,填写你本身的。dom
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class Test { // 不一样数据库有不一样的格式 static final String URL = "jdbc:postgresql://localhost:3306/test"; static final String USER = "sde"; static final String PASSWORD = "123456"; public static void main(String[] args) { // 获取链接: try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { // Statement对象用于执行SQL语句 try (Statement stmt = conn.createStatement()) { String sql = "CREATE TABLE public.javaBuild" + " (id serial PRIMARY KEY NOT NULL," + " name text ," + " age int," + " gender char(1))"; stmt.executeUpdate(sql); } System.out.println("JAVA建表成功!"); }catch(SQLException e){ e.printStackTrace(); } } }
Postgre 中主键设定为serial类型,在后续插入时,主键就会自增。post
import java.sql.*; public class Test { // 不一样数据库有不一样的格式 static final String URL = "jdbc:postgresql://localhost:3306/test"; static final String USER = "sde"; static final String PASSWORD = "123456"; public static void main(String[] args) { // 获取链接: try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { // Statement对象用于执行SQL语句 try (Statement stmt = conn.createStatement()) { // 插入 String sql = "INSERT INTO public.javaBuild (name,age,gender) VALUES ('Tom',12,'M'),('Ming',13,'W'),('Wang',12,'M')"; stmt.executeUpdate(sql); // 删除 String delete = "DELETE FROM public.javaBuild WHERE name='Tom'"; stmt.executeUpdate(delete); // 查询 String query= "SELECT * FROM public.javaBuild"; try (ResultSet rs = stmt.executeQuery(query)) { while (rs.next()) { System.out.println(rs.getInt("id") + rs.getString("name") + rs.getInt("age") + rs.getInt("gender")); } } } }catch(SQLException e){ e.printStackTrace(); } } }
注意:学习
Statment
和ResultSet
都是须要关闭的资源。使用Statement
拼字符串很是容易引起SQL注入的问题,这是由于SQL参数每每是从方法参数传入的(案例详见参考文献2)。所以,使用Java对数据库进行操做时,必须使用PreparedStatement,严禁任何经过参数拼字符串的代码!网站
具体代码以下:
import java.sql.*; public class Test { // JDBC链接的URL, 不一样数据库有不一样的格式: static final String URL = "jdbc:postgresql://localhost:3306/test"; static final String USER = "sde"; static final String PASSWORD = "123456"; public static void main(String[] args) { // 初始化须要插入的数据 Student[] students=new Student[]{new Student("Tom",12,"M"), new Student("Ming",13,"W"),new Student("Wang",12,"M")}; // 获取链接: try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { // 插入,其中?表示占位符 try (PreparedStatement ps = conn.prepareStatement("INSERT INTO public.javaBuild (name,age,gender) VALUES (?,?,?)")) { // 对同一个PreparedStatement反复设置参数并调用addBatch(): for (Student student:students){ // 设置对应占位符的值 ps.setObject(1,student.getName()); ps.setObject(2,student.getAge()); ps.setObject(3,student.getGender()); // 将每一个完整的插入语句放进Batch ps.addBatch(); } // 执行batch: int[] ns = ps.executeBatch(); for (int n : ns) { // batch中每一个SQL执行的结果数量 System.out.println(n + " has been inserted."); } } // 删除 try (PreparedStatement ps = conn.prepareStatement("DELETE FROM public.javaBuild WHERE name='Tom'")) { int n = ps.executeUpdate(); System.out.println(n + " has been deleted."); } // 查询 try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM public.javaBuild")) { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { System.out.println(rs.getInt("id") + rs.getString("name") + rs.getInt("age")+rs.getString("gender")); } } } }catch(SQLException e){ e.printStackTrace(); } } } class Student{ private String name; private int age; private String gender; public Student(String name, int age, String gender) { this.name = name; this.age = age; this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } }
在执行SQL语句的时候,某些业务要求,一系列操做必须所有执行,而不能仅执行一部分。
package com.navinfo.csu.domain.broker.htf.jdbcTest; import scala.xml.Null; import java.sql.*; public class Test { // JDBC链接的URL, 不一样数据库有不一样的格式: static final String URL = "jdbc:postgresql://localhost:3306/test"; static final String USER = "sde"; static final String PASSWORD = "123456"; public static void main(String[] args) throws SQLException { Connection conn = null; Savepoint savepoint =null; try{ // 获取链接 conn = DriverManager.getConnection(URL, USER, PASSWORD); // 设定隔离级别 conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // 关闭自动提交,至关于SQL语句中的BEGAIN conn.setAutoCommit(false); // 插入操做1 try (PreparedStatement ps = conn.prepareStatement("INSERT INTO public.javaBuild (name,age,gender) VALUES ('Huang',12,'M')")) { ps.executeUpdate(); } // 设置保存点 savepoint = conn.setSavepoint("Savepoint"); // 插入操做2 try (PreparedStatement ps = conn.prepareStatement("INSERT INTO public.javaBuild (name,age,gender) VALUES ('Dong',12,11111)")) { ps.executeUpdate(); } // 提交事务 conn.commit(); // 查询 try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM public.javaBuild")) { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { System.out.println(rs.getInt("id") + rs.getString("name") + rs.getInt("age")+rs.getString("gender")); } } } }catch(SQLException e){ e.printStackTrace(); // 回滚事务 if (conn != null){ conn.rollback(savepoint); } }finally { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } } }
默认状况下,咱们获取到Connection
链接后,老是处于“自动提交”模式,也就是每执行一条SQL都是做为事务自动执行的,即默认为“隐式事务”。只要关闭了Connection
的autoCommit
,那么就能够在一个事务中执行多条语句,事务以commit()
方法结束。
MySQL的默认隔离级别是REPEATABLE READ
。
setSavepoint()
能够记录保存点,当出现异常时,能够回滚到指定的保存点。
在执行JDBC的增删改查的操做时,若是每一次操做都来一次打开链接,操做,关闭链接,那么建立和销毁JDBC链接的开销就太大了。为了不频繁地建立和销毁JDBC链接,咱们能够经过链接池(Connection Pool)复用已经建立好的链接。
JDBC链接池有一个标准的接口javax.sql.DataSource
,注意这个类位于Java标准库中,但仅仅是接口。要使用JDBC链接池,咱们必须选择一个JDBC链接池的实现。经常使用的JDBC链接池有:
目前使用最普遍的是HikariCP,代码案例以下:
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.sql.*; public class Test { // JDBC链接的URL, 不一样数据库有不一样的格式: static final String URL = "jdbc:postgresql://localhost:3306/test"; static final String USER = "sde"; static final String PASSWORD = "123456"; static public HikariConfig config = new HikariConfig(); public static void main(String[] args) { // 初始化链接池 config.setJdbcUrl(URL); config.setUsername(USER); config.setPassword(PASSWORD); config.addDataSourceProperty("connectionTimeout", "1000"); // 链接超时:1秒 config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时:60秒 config.addDataSourceProperty("maximumPoolSize", "10"); // 最大链接数:10 DataSource ds = new HikariDataSource(config); // 获取链接,把DriverManage.getConnection()改成ds.getConnection() try (Connection conn = ds.getConnection()) { // 查询 try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM public.javaBuild")) { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { System.out.println(rs.getInt("id") + rs.getString("name") + rs.getInt("age")+rs.getString("gender")); } } } }catch(SQLException e){ e.printStackTrace(); } } }
当咱们调用conn.close()
方法时(在try(resource){...}
结束处),不是真正“关闭”链接,而是释放到链接池中,以便下次获取链接时能直接返回。
所以,链接池内部维护了若干个Connection
实例,若是调用ds.getConnection()
,就选择一个空闲链接,并标记它为“正在使用”而后返回,若是对Connection
调用close()
,那么就把链接再次标记为“空闲”从而等待下次调用。这样一来,咱们就经过链接池维护了少许链接,但能够频繁地执行大量的SQL语句。
【1】菜鸟教程:https://www.runoob.com/postgr...
【2】廖雪峰的官方网站:https://www.liaoxuefeng.com/w...