JDBC
我们之前学习MYSQL时,为了使用MYSQL服务,我们通常要使用客户端与MYSQL服务进行连接,然后才能输入SQL语句进行数据库的各种操作。客户端有命令行与图形界面2种。
但是在更多的环境下,由我们人直接操作数据是很低效的,比如双11这种业务场景下,一秒中往往要在库中生成上千万甚至几亿条数据,靠人来手工操作是不现实的,只能依赖于程序进行这种高并发的操作。
程序语言有多种,比如Java、Python、C/C++等,程序语言如果想执行SQL语句,也必须要先与数据库进行连接,数据库也有多种,比如MySQL、Oracle、SQL Server等。
不同的程序语言去连接不同的数据库,如果没有一个统一的标准或者规范,肯定是相当混乱的。Java语言对此的解决方案就是JDBC。
JDBC定义了一套规范标准,它对应的是各种接口与抽象类(通常对应java.sql包下面的各种类与接口),具体实现交给各数据库厂商去完成,MySQL的有自己的实现类并打成jar包发布,供程序开发人员使用;Oracle也有自己的实现jar包。
从编码级别上来理解,就是
JDK会定义出接口
public interface Driver {
void connect();
}
各数据库厂商开发自己的实现类
public class MySQLDriver implements Driver {
@Override
public void connect() {
// MySQL服务的连接逻辑
// …
}
}
public class OracleDriver implements Driver {
@Override
public void connect() {
// Oracle服务的连接逻辑
// …
}
}
我们开发人员在使用的时候,要根据连接数据库的不同,去对应的官网上下载对应数据库版本与程序语言的数据库驱动(Java语言对应的是一个jar包)。(比如我们使用MySQL 5.1,就要去MySQL官网下载Java语言对应的jar包)
然后在项目上右键 -> Build Path -> Configure Build Path…,将之加入我们项目的CLASSPATH。
JDBC相关的类与接口
java.sql.Driver接口(MySQL的驱动类com.mysql.jdbc.Driver就实现了此接口)
java.sql.DriverManager类
Connection getConnection(String url, String user, String password)
java.sql.Connection接口
void close()
Statement createStatement()
PreparedStatement prepareStatement(String sql)
java.sql.Statement接口
void close()
boolean execute(String sql)
ResultSet executeQuery(String sql)
int executeUpdate(String sql)
void addBatch(String sql)
int[] executeBatch()
java.sql.PreparedStatement接口extends java.sql.Statement接口
void setXXX(int parameterIndex, xxx value)
ResultSet executeQuery()
int executeUpdate()
java.sql.ResultSet接口
void close()
boolean next()
xxx getXXX(String columnName)
JDBC编程步骤
1.在项目中引入驱动jar包,并配置到classpath中
public static void testConnection() throws Exception{
2. //注册驱动
String className=“com.mysql.jdbc.Driver”; //“com.mysql.jdbc.Driver”;
Class.forName(className); //把指定的类的class文件加载到内存
-
//获取连接对象 String url="jdbc:mysql://127.0.0.1:3306"; //不同的DBMS对应的url写法不同 String user="root"; String password="root"; Connection conn = DriverManager.getConnection(url, user, password); //操作连接对象 System.out.println(conn); //System.out.println(conn.toString());
-
//操作完成后要及时关闭连接,释放资源 conn.close();
}
利用statement进行数据库操作(不安全)
DML–insert update delete
public static void testDML() throws Exception{
String className=“com.mysql.jdbc.Driver”;
Class.forName(className);
String url=“jdbc:mysql://127.0.0.1:3306/test”; //显示在哪个数据库下
String user=“root”;
String password=“root”;
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt =conn.createStatement(); // 用来创建一个语句对象
StringBuffer sql=new StringBuffer();
//DML - insert
// sql.append("insert into www(id,name
,salary) ");
// sql.append(“values(1, ‘小明’, 1.0);”);
//DML - update
// sql.append("update www ");
// sql.append("set salary = salary + 10 ");
// sql.append("where id = 1; ");
//DML - delete
sql.append("delete from www where id=1; ");
//我们通常使用executeUpdate方法来执行DML
int res = stmt.executeUpdate(sql.toString());
stmt.close();
conn.close();
System.out.println("done, 影响了"+res+"行");
}
批量处理
public static void testBatch() throws Exception{
String className=“com.mysql.jdbc.Driver”;
Class.forName(className);
String url=“jdbc:mysql://127.0.0.1:3306/test”;
String user=“root”;
String password=“root”;
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt =conn.createStatement();// 用来创建一个语句对象
for(int i=0;i<10;i++){
StringBuffer sql=new StringBuffer();
sql.append("insert into www(id,`name`,salary) ");
sql.append("values("+i+", '小明', 1.0); ");
stmt.addBatch(sql.toString()); //加入批处理
}
int[] res = stmt.executeBatch();
System.out.println("每条sql语句影响的行数分别是:"+Arrays.toString(res));
stmt.close();
conn.close();
}
利用PreparedStatement进行数据库操作(安全)
DML
public static void testDML() throws Exception{
String className=“com.mysql.jdbc.Driver”;
Class.forName(className);
String url=“jdbc:mysql://127.0.0.1:3306/test”;
String user=“root”;
String password=“root”;
Connection conn=DriverManager.getConnection(url, user, password);
StringBuffer sql=new StringBuffer();
//DML--insert
sql.append("insert into www(id,name,salary) ");
sql.append("values(?,?,?); ");
PreparedStatement stmt=conn.prepareStatement(sql.toString()); //创建预编译语句
stmt.setInt(1, 10);
stmt.setString(2, "小红");
stmt.setDouble(3, 9.0);
//DML--update
// sql.append("update www ");
// sql.append("set salary = salary + 10 ");
// sql.append("where id = ?; ");
// PreparedStatement stmt=conn.prepareStatement(sql.toString()); //创建预编译语句
// stmt.setInt(1, 1);
//DML--delete
// sql.append("delete from www where id=?; ");
// PreparedStatement stmt=conn.prepareStatement(sql.toString()); //创建预编译语句
// stmt.setInt(1, 0);
stmt.executeUpdate();
stmt.close();
conn.close();
System.out.println("done");
}
DQL
public static void testDQL() throws Exception{
String className=“com.mysql.jdbc.Driver”;
Class.forName(className);
String url=“jdbc:mysql://127.0.0.1:3306/test”;
String user=“root”;
String password=“root”;
Connection conn = DriverManager.getConnection(url, user, password);
StringBuffer sql=new StringBuffer();
sql.append("select * ");
sql.append("from www ");
sql.append("where id=?; ");
PreparedStatement stmt=conn.prepareStatement(sql.toString());
stmt.setInt(1, 10);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
double salary = rs.getDouble("salary");
System.out.println(id + "\t" + name + "\t" + salary);
System.out.println("==============================");
}
rs.close();
stmt.close();
conn.close();
}
JDBC模块样板总结优化
下面的代码我们每个方法中都会重复一次,这是没必要的,因为不同的方法其实只是具体执行的SQL语句的内容不同,对于获取连接与释放资源,对应的逻辑是相同的。我们完全可以把这一段逻辑抽取出来,形成独立的类与方法,再在实际应用时调用对应的类和方法就可以了。
主方法========
public class JDBCTemplateTest {
public static void testDQL(){
Connection conn=null;
PreparedStatement stmt=null;
ResultSet rs=null;
try{
conn=JDBCUtil.getConnection();
StringBuffer sql=new StringBuffer();
sql.append("select * ");
sql.append("from www ");
sql.append("where id=?; ");
stmt=conn.prepareStatement(sql.toString());
stmt.setInt(1, 10);
rs = stmt.executeQuery();
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
double salary = rs.getDouble("salary");
System.out.println(id + "\t" + name + "\t" + salary);
System.out.println("==============================");
}
}catch(Exception e){
e.printStackTrace();
}finally{
CloseUtil.close(rs);
CloseUtil.close(stmt);
CloseUtil.close(conn);
}
}
public static void testDML(){
Connection conn=null;
PreparedStatement stmt=null;
try{
conn=JDBCUtil.getConnection();
StringBuffer sql=new StringBuffer();
//DML--insert
sql.append("insert into www(id,name,salary) ");
sql.append("values(?,?,?); ");
stmt=conn.prepareStatement(sql.toString()); //创建预编译语句
stmt.setInt(1, 11);
stmt.setString(2, "小刚");
stmt.setDouble(3, 10.0);
//DML--update
// sql.append("update www ");
// sql.append("set salary = salary + 10 ");
// sql.append("where id = ?; ");
// PreparedStatement stmt=conn.prepareStatement(sql.toString()); //创建预编译语句
// stmt.setInt(1, 1);
//DML--delete
// sql.append("delete from www where id=?; ");
// PreparedStatement stmt=conn.prepareStatement(sql.toString()); //创建预编译语句
// stmt.setInt(1, 0);
stmt.executeUpdate();
System.out.println("done");
}catch(Exception e) {
e.printStackTrace();
}
finally{
CloseUtil.close(stmt);
CloseUtil.close(conn);
}
}
public static void main(String[] args) {
testDML();
//testDQL();
}
}
CloseUtil===
public static void close(AutoCloseable obj) {
if (obj != null) {
try {
obj.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
JDBCUtil========
public class JDBCUtil {
public static Connection getConnection() throws Exception{
Connection conn=null;
String className=“com.mysql.jdbc.Driver”;
Class.forName(className);
String url=“jdbc:mysql://127.0.0.1:3306/test”;
String user=“root”;
String password=“root”;
conn = DriverManager.getConnection(url, user, password);
return conn;
}
}
配置文件
如上,我们项目开发完成测试通过后,通常是要打包发布给别人使用的,如果我们把一些配置信息写死到代码中(这种行为叫硬编码,hardcode),别人就无法修改了。
比如别人使用的MySQL服务器对应的IP是10.0.0.1,端口号是9300,用户名是mysqluser、密码是123456。
那么我们只能改代码再重新打包发布,如果有多个用户,他们对应的配置信息都不同,那么我们要针对不同的用户打包发布多次。
以上显然是没必要的,因为我们开发的是程序,只要业务需求/逻辑不变,我们就无需多次打包发布。对此我们的解决方案是,尽量避免硬编码,将数据与程序分离解耦,将配置信息存储到配置文件中,程序运行时读取配置文件,不同用户只需按自己的实际情况修改配置文件即可。
配置文件目前主要有2种形式:XML、properties文件。XML的解析是一个很大的话题而且需要专门的类库去支持,这里我们以较简单的properties文件为例进行讲解。
在项目目录下的src目录下新建一个文本文件jdbc.properties,内容如下:
(#是注释,中文会显示为unicode编码)
#this is a configuration file(it is a text file actually)
#\u8FD9\u662F\u4E00\u4E2A\u914D\u7F6E\u6587\u4EF6
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.56.101:3306/lhl_test
user=root
pwd=root
保存
Properties文件的解析
涉及到的类是java.util.Properties,
常用方法有
void load(Reader reader)
String getProperty(String key)
String getProperty(String key, String defaultValue)
具体代码:
public class JDBCUtil {
public static Connection getConnection() throws Exception{
Properties p=new Properties();
String src=“cfg”+File.separator+“jdbc.properties”;
p.load(new InputStreamReader(new FileInputStream(src),“utf-8”)); //读配置文件,把对应的key和value存到一个map
Connection conn=null;
String className=p.getProperty(“className”);
Class.forName(className);
String url=p.getProperty(“url”);
String user=p.getProperty(“user”);
String password=p.getProperty(“password”);
conn = DriverManager.getConnection(url, user, password);
return conn;
}
}
连接池
上面我们抽取了样板代码,将配置信息与代码分离解耦。JDBC编程的效率从一定程度上提高了,如果我们只是为了学习或者开发一些小的简单的项目,到这个地步其实就足够了。如果我们要想更上一层楼,支持如下场景:
场景一:电商网站如淘宝、京东每年都会有双11这种活动,同一时刻,会有上亿甚至上十亿的用户访问数据库(因为要生成订单等数据),如果我们还使用上面的思路,显然是捉襟见肘。
场景二:某服务器上除了运行MYSQL服务,还有其他一些服务比如WEB服务。我们知道,数据库连接的创建维持不只消耗我们客户端(个人PC)的系统资源(CPU、内存、IO设备),更消耗服务器的系统资源,而假如我们在周末时并不会去访问数据库,这时候服务器上依然还维持着一条空闲的连接,假设占用了2M内存,现在服务器上内存已经都被分配出去了,WEB服务却要求新申请1M内存,很显然,由于内存不足,WEB服务就无法正常运行了。
以上2个场景,我们上面的代码,是无法支持的。
连接池的引入,则主要解决了以上2类问题:1.能给多用户分配连接或者给一个用户分配多个连接;2.在适当的时候回收空闲连接以节省系统资源。
JDBC连接池有一个对应的接口javax.sql.DataSource。它也是一个标准一个规范,目前实现了这套规范的连接池产品主要有:DBCP(MyBatis通常使用这个连接池)、C3P0(Hibernate通常使用这个连接池)、JNDI(Tomcat的默认连接池)。
我们使用DBCP来讲解。
DBCP内部提供了一个“池子”,程序启动的时候,先创建一些连接对象放到这个池子中,备用,当调用连接池的getConnection()方法时,就从池子取出连接对象分配给多个用户/线程。使用完毕调用close()方法时,DBCP重写了close方法,它并不真正关闭连接,而是返还到池子中,由DBCP自动管理池子中的连接对象,在适当的时候真正关闭这些连接。
因为连接池组件不是JDK自带的,所以要导入相关的jar包。DBCP相关的jar包有3个,如下:
样例代码如下:
public class DBCPTest {
public static void test() throws Exception{
//创建连接池对象
BasicDataSource bds=new BasicDataSource();
Properties p=new Properties();
p.load(new FileReader(“cfg”+File.separator+“jdbc.properties”));
//加载并配置连接信息 className, url, user, password
bds.setDriverClassName(p.getProperty("className"));
bds.setUrl(p.getProperty("url"));
bds.setUsername(p.getProperty("user"));
bds.setPassword(p.getProperty("password"));
Connection conn=bds.getConnection();
System.out.println(conn);
CloseUtil.close(conn);
}
public static void test1() throws Exception{
//创建连接池对象
BasicDataSource bds= new BasicDataSource();
//加载并配置连接信息 className,url,user,password
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUrl("jdbc:mysql://127.0.0.1:3306/test");
bds.setUsername("root");
bds.setPassword("root");
Connection conn=bds.getConnection();
System.out.println(conn);
conn.close();
}
}