JAVA EE(五) —— JDBC(JDBC介绍、JDBC预编译、JDBC封装、Properties配置文件、连接池)

一、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(); 	//这里不是在关闭,而是在归还连接
		}
	}
}
发布了104 篇原创文章 · 获赞 58 · 访问量 7511

猜你喜欢

转载自blog.csdn.net/baidu_27414099/article/details/104440927
今日推荐