一、JDBC 概述
1、JDBC 概述
(1)数据的持久化
- 持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以“固化”,而持久化的实现过程大多通过各种关系数据库来完成。
- 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。
(2)JAVA中的数据存储
- 在Java中,数据库存取技术可分为如下几类:
- JDBC直接访问数据库
- JDO (Java Data Object )技术
- 第三方0/R工具,如Hibernate,Mybatis等
JDBC是Java访问数据库的基石,JDO、Hibernate、 MyBatis等只是 更好的封装了JDBC。
(3)JDBC介绍
- JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API) , 定义了用来访问数据库的标准lava类库,( java.sqljavax.sql )使用这些类库可以以一种标准的方法、方便地访问数据库资源。
- JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
- JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
(4)JDBC体系结构
JDBC接口( API)包括两个层次:
- 面向应用的API:Java API ,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
- 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。
二、JDBC 开发步骤
1、JDBC开发步骤图解
2、JDBC开发步骤示例
(1)加载并注册JDBC驱动程序,创建Connection连接
方式一:直接加载
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/test"; //url为数据库的地址,test为数据库
Properties info = new Properties();
info.setProperty("user", "root"); //root为数据库的账户名
info.setProperty("password", "admin"); //admin为数据库的密码
Connection conn = driver.connect(url, info); //获取连接
方式二:使用Class.forName()
推荐使用这种,因为在加载Driver的字节码,字节码里面就有一个DriverManager.registerDriver(new Driver());
Class clazz = Class.forName("com.mysql.jdbc.Driver") ;
Driver driver = (Driver) clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/test";
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "admin");
Connection conn = driver.connect(url, info);
方式三:使用DriverManager
Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver); //注册驱动
//或者
Class clazz = Class.forName("com.mysql.jdbc.Driver") ;
Driver driver = (Driver) clazz.newInstance();
DriverManager.registerDriver(driver); //注册驱动
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "admin";
Connection conn = DriverManager.getConnection(url, user, password);
方式四:读取properties文件
jdbc.properties配置文件内容为:
user=root
password=admin
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
读取配置文件信息
//Test是当前类的类名
InputStream is = Test.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String url = pros.getProperty("url");
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String driverClass = pros.getProperty("driverClass");
//加载驱动
Class.forName(driverClass);
//获取连接
Connection conn = DriverManager.getConnection(url, user, password);
在开发中建议使用这种方式,这种方式实现了数据与代码的解耦合,减少了数据的暴露,也有利于后期的维护和升级。
(2)创建访问和操作数据库的对象
数据库连接被用于向数据库服务器发送命令和SQL语句,并接受数据库服务器返回的结果,其实一个数据库连接就是一个Socket连接。
在java.sql包中有3个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态SQL语句并返回它所生成结果的对象。
- PrepatedStatement:SQL语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行SQL存储过程。
① Statement对象
//创建Statement对象
Statement sta = conn.createStatement();
//定义sql语句:包括增删改查
String sql = "select id,name,age from student"; //查询语句
String sql1 = "insert into student values(5,'周七',18)"; //新增语句
String sql2 = "delete from student where id = 1"; //删除语句
String sql3 = "update student set name = '修改' where id = 3"; //修改语句
//执行SQL
ResultSet set = sta.executeQuery(sql); //查询返回的是一个集合
//增加、删除、修改
int i = sta.executeUpdate(sql); //查询一个修改一个,返回的是执行成功后影响的行数
//执行sql指令,得到结果集
//增删改返回的是执行成功后影响的行数
//增删改统称为:boolean b = sta.execute();
int i = sta.executeUpdate(sql1);
int j = sta.executeUpdate(sql2);
int k = sta.executeUpdate(sql3);
//查询返回的是一个集合
ResultSet set = sta.executeQuery(sql);
//查看查询结果集
while(set.next()) {
System.out.print("id = " + set.getInt("id") + "\t");
System.out.print("name = " + set.getString("name") + "\t");
System.out.println("age = " + set.getInt("age") + "\t");
}
Statement对象的弊端
- 编写SQL时如果有动态参数则需要拼接字符串,比较麻烦;
- 存在SQL注入问题
SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语询段或命令,从而利用系统的SQL引擎完成恶意行为的做法。
为了避免SQL注入,可以使用PrepatedStatement对象。
② PrepatedStatement对象
预编译
在编写sql语句的时候,语句里面条件的值或者需要填充的变量都是用?
代替,然后在使用预编译对?
所代表的的内容进行填充。
预编译步骤
定义预编译的sql语句,其中待填入的参数用 ? 占位。注意,?无关类型,不需要加分号之类。其具体数据类型在下面setXX()时决定。
String sql = "insert into student(id,name,age) values(?,?,?)";
创建预编译Statement,并把sql语句传入,此时sql语句已与此prepareStatement绑定,所以执行语句时无需再把sql语句作为参数传入execute()。
PreparedStatement ps = conn.prepareStatement(sql);
填入具体参数。通过setXX(问号下标,数值)来为sql语句填入具体数据。注意:问号下标从1开始,setXX与数值类型有关,字符串就是setString(index,str).
ps.setInt(1, id);
ps.setString(2, name);
ps.setInt(3, age);
- 执行预处理对象,即执行sql语句。主要有:
- boolean execute()
在此 PreparedStatement 对象中执行 SQL 语句,该语句可以是任何种类的 SQL 语句。 - ResultSet executeQuery()
在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的 ResultSet 对象。 - int executeUpdate()
在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。
boolean b = ps.execute() ;
ResultSet set = ps.executeQuery();
int i ps.executeUpdate();
预编译的好处
- PreparedStatement 比 Statement 更快
使用 PreparedStatement 最重要的一点好处是它拥有更佳的性能优势,SQL语句会预编译在数据库系统中。执行计划同样会被缓存起来,它允许数据库做参数化查询。使用预处理语句比普通的查询更快,因为它做的工作更少(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)。 - PreparedStatement 可以防止SQL注入式攻击
3、中文乱码问题
?useUnicode=true&characterEncoding=utf-8
解决中文乱码问题,添加到JDBC的url后面
String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8";
三、JDBC 的封装
1、封装
- 数据库的数据:Driver url user password 使用properties进行封装
因为properties文件中存放的是连接数据必须的配置文件,因此在加载类的时候,就需要获取文件的内容,另外驱动也需要在类加载的时候就注册,所以一般写在静态代码块中。
2、Properties 类
(1)Properties 概述
Properties 类(Java.util.Properties),主要用于读取Java的配置文件,在Java中,其配置文件常为.properties文件,格式为文本文件,文件的内容的格式是“键=值”的格式,文本注释信息可以用"#"来注释。
(2)Properties 主要方法
getProperty ( String key)
:用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。load ( InputStream inStream)
:从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件)进行装载来获取该文件中的所有键 - 值对。以供 getProperty ( String key) 来搜索。setProperty ( String key, String value)
:调用 Hashtable 的方法put 。他通过调用基类的put方法来设置 键 - 值对。store ( OutputStream out, String comments)
:以适合使用 load 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。clear ()
:清除所有装载的键 - 值对。该方法在基类中提供。
(3)Properties 配置文件步骤
获取properties文件的流
InputStream is = PropertiesDemo.class.getClassLoader().getResourceAsStream("jdbc.properties");
创建propertise对象
Properties prop = new Properties();
加载 properties 文件流
prop.load(is);
3、封装示例
使用JDBC封装和Properties配置文件完成对数据库的增删改成
① jdbc.properties 配置文件
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
user=root
password=admin
② JDBCUtils类:工具包类,创建连接
public class JDBCUtils {
private static String driver = null;
private static String url = null;
private static String user = null;
private static String password = null;
//静态代码块
static{
try {
//获取文件流
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
//创建properties对象
Properties prop = new Properties();
//使用prop对象的load()方法加载流
prop.load(is);
//获取其文件的数据
driver = prop.getProperty("driver");
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
//注册驱动
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
//菜单
public static void menu() throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("*** 1、增 2、删 3、改 4、查 5、退出 ***");
System.out.println("请选择要操作的选项:");
int num = sc.nextInt();
if(num == 1) {
new JDBCOperate().insert();
}else if(num == 2) {
new JDBCOperate().delete();
}else if(num == 3) {
new JDBCOperate().update();
}else if(num == 4) {
new JDBCOperate().read();
}else if(num == 5) {
System.exit(0);
}else {
System.out.println("输入有误,请重新输入:");
JDBCUtils.menu();
}
}
//获取连接方法
public static Connection registJDBC() throws Exception {
return DriverManager.getConnection(url, user, password);
}
//关流
public static void close(ResultSet resultSet, PreparedStatement ps, Connection conn) {
if(resultSet != null) {
try {
resultSet.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(ps != null) {
try {
ps.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
③ JDBCOperate 类:增删改查
public class JDBCOperate {
Scanner sc = new Scanner(System.in);
//增加
public void insert(){
//调用方法获取连接
Connection conn = null;
//创建可以预编译执行sql指令的对象
PreparedStatement ps = null;
try {
conn = JDBCUtils.registJDBC();
//定义sql语句
System.out.println("请输入要新增的id的值:");
int id = sc.nextInt();
System.out.println("请输入要新增的name的值:");
String name = sc.next();
System.out.println("请输入要新增的age的值:");
int age = sc.nextInt();
String sql = "insert into student values(?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.setString(2, name);
ps.setInt(3, age);
//执行sql指令
ps.executeUpdate();
//关流
JDBCUtils.close(null, ps, null);
JDBCUtils.menu();
} catch (Exception e) {
e.printStackTrace();
}
}
//删除
public void delete(){
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtils.registJDBC();
System.out.println("*** 1、id值 2、name值 3、age值 4、退出 ***");
System.out.println("请选择要根据哪种条件删除:");
int num = sc.nextInt();
String sql = null;
//定义sql语句
if(num == 1) {
System.out.println("请输入要删除的id的值:");
int id = sc.nextInt();
sql = "delete from student where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
}else if(num == 2) {
System.out.println("请输入要新增的name的值:");
String name = sc.next();
sql = "delete from student where name = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, name);
}else if(num == 3) {
System.out.println("请输入要新增的age的值:");
int age = sc.nextInt();
sql = "delete from student where age = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, age);
}else if(num == 4){
System.exit(0);
}else {
System.out.println("输入有误,请重新输入:");
read();
}
ps.executeUpdate();
JDBCUtils.close(null, ps, null);
JDBCUtils.menu();
} catch (Exception e) {
e.printStackTrace();
}
}
//修改
public void update() {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtils.registJDBC();
System.out.println("*** 1、id值 2、name值 3、age值 4、退出 ***");
System.out.println("请选择要修改的数据类型:");
int num = sc.nextInt();
String sql = null;
if(num == 1) {
System.out.println("请输入要修改的id的值:");
int oldId = sc.nextInt();
System.out.println("请输入要更新的id的值:");
int newId = sc.nextInt();
sql = "update student set id = ? where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, newId);
ps.setInt(2, oldId);
}else if(num == 2) {
System.out.println("请输入要修改的name的值:");
String oldName = sc.next();
System.out.println("请输入要更新的name的值:");
String newName = sc.next();
sql = "update student set name = ? where name = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, newName);
ps.setString(2, oldName);
}else if(num == 3) {
System.out.println("请输入要修改的age的值:");
int oldAge = sc.nextInt();
System.out.println("请输入要更新的age的值:");
int newAge = sc.nextInt();
sql = "update student set age = ? where age = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, newAge);
ps.setInt(2, oldAge);
}else if(num == 4){
System.exit(0);
}else {
System.out.println("输入有误,请重新输入:");
read();
}
ps.executeUpdate();
JDBCUtils.close(null, ps, null);
JDBCUtils.menu();
} catch (Exception e) {
e.printStackTrace();
}
}
//查询
public void read(){
Connection conn = null;
PreparedStatement ps =null;
ResultSet set = null;
try {
conn = JDBCUtils.registJDBC();
System.out.println("*** 1、id值 2、name值 3、age值 4、全部 5、退出***");
System.out.println("请选择要按照哪种条件查询:");
int num = sc.nextInt();
String sql = null;
if(num == 1) {
System.out.println("请输入要查询的id的值:");
int id = sc.nextInt();
sql = "select id,name,age from student where id = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, id);
}else if(num == 2) {
System.out.println("请输入要查询的name的值:");
String name = sc.next();
sql = "select id,name,age from student where name = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, name);
}else if(num == 3) {
System.out.println("请输入要查询的age的值:");
int age = sc.nextInt();
sql = "select id,name,age from student where age = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, age);
}else if(num == 4) {
sql = "select id,name,age from student";
ps = conn.prepareStatement(sql);
}else if(num == 5){
System.exit(0);
}else {
System.out.println("输入有误,请重新输入:");
read();
}
//得到结果集
set = ps.executeQuery();
while(set.next()) {
System.out.print("id = " + set.getInt("id") + "\t");
System.out.print("name = " + set.getString("name") + "\t");
System.out.println("age = " + set.getInt("age"));
}
JDBCUtils.close(set, ps, null);
JDBCUtils.menu();
} catch (Exception e) {
e.printStackTrace();
}
}
}
④ main 函数类
public class JDBCDemo {
public static void main(String[] args) {
try {
JDBCUtils.menu();
} catch (Exception e) {
e.printStackTrace();
}
}
}
四、连接池
1、连接池概述
连接池是为了避免频繁的创建和关闭而提出的技术
常见的连接池有三类:c3p0,dbcp,ali(德鲁伊)
1、连接池示例
(1)JDBC 连接池示例
public class JDBCUtil {
public static final ThreadLocal<Connection> threadLocal=new ThreadLocal<Connection>();
private static String driver = null;
private static String url = null;
private static String user = null;
private static String password = null;
static Scanner sc = new Scanner(System.in);
//定义一个集合保存连接池对象
private static LinkedList<Connection> pool = new LinkedList<Connection>();
//定义初始化连接数
private static int initNum = 10;
//定义每次自增的连接对象个数
private static int acqNum = 3;
//定义连接池的最大连接个数
private static int maxNum = 30;
//定义已有的连接池数量
private static int curNum = 0;
//静态代码块
static{
try {
//获取文件流
InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("woniushop.properties");
//创建properties对象
Properties prop = new Properties();
//使用prop对象的load()方法加载流
prop.load(is);
//获取其文件的数据
driver = prop.getProperty("river");
url = prop.getProperty("url");
user = prop.getProperty("user");
password = prop.getProperty("password");
//注册驱动
Class.forName(driver);
//初始化连接池
for(int i = 0; i < initNum; i++) {
Connection conn = DriverManager.getConnection(url, user, password);
curNum++;
//创建一个连接后就存入连接池
pool.add(conn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接,需要使用到连接时直接在连接池里面取
public static Connection registJDBC() throws Exception {
synchronized (pool) {
//当连接池里面还有连接对象时,就直接取用,没有了就创建一个连接
if(pool.size() == 0) {
//判断当前连接对象数量是否小于最大连接对象数量
if(curNum < maxNum) {
for(int i = 0; i < (maxNum - curNum < 3?maxNum-curNum:3); i++) {
Connection conn = DriverManager.getConnection(url, user, password);
curNum++;
//创建一个连接后就存入连接池
pool.add(conn);
}
}
}
//取用并移除第一个
return pool.removeFirst();
}
}
//关流
public static void close(ResultSet resultSet, PreparedStatement ps, Connection conn) throws Exception {
if(resultSet != null) {
resultSet.close();
}
if(ps != null) {
ps.close();
}
if(conn != null) {
pool.add(conn);
}
}
}
(2)使用 c3p0 连接池
使用 c3p0 连接池之前需要导入jar包
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.15.jar
public class ConnectionPool {
//定义c3p0连接池对象
private static ComboPooledDataSource pool;
//初始化连接池
static {
pool = new ComboPooledDataSource();
//设置连接池的各项属性
try {
pool.setDriverClass("com.mysql.jdbc.Driver");
pool.setJdbcUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8");
pool.setUser("root");
pool.setPassword("admin");
//设置连接池自身的属性
pool.setInitialPoolSize(30); //初始化连接个数
pool.setAcquireIncrement(5); //设置自增长数量
pool.setMaxPoolSize(50); //设置最大连接数量
pool.setMinPoolSize(30); //设置对消连接数量
pool.setMaxIdleTime(2*60*60); //设置最大空闲时间,超时则销毁这个连接
pool.setCheckoutTimeout(6000); //设置等待连接的最大时间,超时抛出异常
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
//获取连接数量
public static Connection getConnection() throws Exception {
return pool.getConnection();
}
//关闭资源
public static void close(ResultSet resultSet, PreparedStatement ps, Connection conn) throws Exception {
if(resultSet != null) {
resultSet.close();
}
if(ps != null) {
ps.close();
}
if(conn != null) {
conn.close(); //这里不是在关闭,而是在归还连接
}
}
}