Java小白修炼手册--第四阶段--JDBC(Java Database Connectivity : Java访问数据库的解决方案 )

目录

JDBC原理

JDBC标准

JDBC是什么

使用JDBC优点

JDBC接 口及数据库厂商实现

​JDBC工作原理

Driver ( 驱动程序)接口及驱动类加载

​Connection( 连接,关联)接口

Statement( 声明)接口

​ResultSet接口

数据库厂商实现

Oracle实现

MySQL实现

JDBC基础

连接管理

通过连接工具类获取连接

读取配置文件

通过属性文件维护连接属性

​从类路径中加载属性文件

连接的关闭

数据库连接池DBCP

连接池技术

为什么要使用连接池

Druid数据库连接池

 通过DataSource获取连接

Apache DBCP数据库连接池

通过DataSource获取连接

连接池参数

异常处理

SQLException简介

处理SQL Exception

JDBC核心API:

Statement

Statement执行查询

Statement执行新增

Statement执行修改

Statement执行删除

PreparedStatement

PreparedStatement原理

SQL注入问题

预编译的SQL执行对象PreparedStatement

提升性能

​SQL Injection简介

​防止SQL Injection (注入)

ResultSet (结果集)

结果集遍历

ResultSetMeta Data(结果集元数据)

JDBC高级编程

事务处理

事务简介

​JDBC事务API

JDBC标准事务编程模式

批量更新

批量更新的优势

批量更新API

防止OutOfMemory

返回自动主键

获取自增主键值:

关联数据插入

​通过自增类型产生主键

JDBC返回自动主键API

DAO

什么是DAO

DAO封装对数据的访问

实体对象

编写DAO 

查询方法

更新方法

异常处理机制


JDBC原理

JDBC标准

JDBC是什么

Java Database Connectivity : Java访问数据库的解决方案
希望用相同的方式访问不同的数据库,以实现与具体数据库无关的Java操作界面,JDBC定义一套标准接口,即访问数据库的通用API ,不同的数据库厂商根据各自数据库的特点去实现这些接口.

​ JavaDataBaseConnectivity:Java数据库连接,是Sun公司提供的一套和数据库进行连接的API(Application Program Interface应用程序编程接口), 作用:通过Java语言和数据库软件进行连接

使用JDBC优点

在工作中Java程序员有可能连接多种不同的数据库,为了避免Java程序员每一种数据库都学习一套新的方法,Sun公司定了一套方法的声明(JDBC),把方法名固定,不管连接的是什么数据库方法名是一样的,各个数据库厂商根据方法名写方法的实现类(驱动),这样Java程序员只需要掌握JDBC中方法的调用,即可访问任何数据库,而且安装JDBC规范所写的代码就算是换数据库代码一行都不用改.


JDBC接 口及数据库厂商实现


JDBC工作原理

  • JDBC定义接口

  • 数据库厂商实现接口

  • 程序员调用接口, 实际调用的是底层数据库厂商的实现部分

JDBC工作过程:

  1. 加载驱动,建立连接
  2. 创建语句对象
  3. 执行SQL语句
  4. 处理结果集
  5. 关闭连接
package cn.tedu;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**
 * 
 * @author Administrator
 *
 */
public class Demo01 {
	public static void main(String[] args) throws Exception {
		//1.注册驱动:高速驱动编译器使用的是什么数据库
		Class.forName("com.mysql.cj.jdbc.Driver");
		//2.获取在数据库连接   导包:java.sql
		//jdbc:mysql://localhost:3306/mysql?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true", "root", "root");
		System.out.println(conn);
		//3.创建执行SQL 语句的对象
		Statement s = conn.createStatement();
		//4.执行SQL
		String sql = "create table jdbct1(id int,name varchar(10))";
		s.execute(sql);
		//5.关闭资源
		conn.close();
		System.out.println("执行完成!");
    }
}

 

Driver ( 驱动程序)接口及驱动类加载


Connection( 连接,关联)接口

Class. forName (" com. mysq1. jdbc . Driver");
Connection conn =
DriverManager . getConnection("jdbc :mysq1 ://1oc alhost :3306/ demo",
root" ,
);
// NOTE:Connection只是接口 !真正的实现是由数据库厂商提供的驱动包完成的
/**根据url连接参数找到与之匹配的
Driver对象,调用其方法获取连接*/

Statement( 声明)接口

执行SQL语句的对象Statement

- execute(sql)  可以执行任意SQL语句,但是推荐执行数据库和表相关的SQL(DDL数据定义语言)(execute 执行 )

- int row = executeUpdate(sql); 此方法执行增insert删delete改update的SQL ,方法的返回值为生效的行数( executeUpdate 执行更新) (int 类型 )

- ResultSet rs = executeQuery(sql); 此方法执行查询的SQL语句,返回值是结果集对象,里面装着查询回来的结果(executeQuery  执行查询)

 


ResultSet接口

执行查询SQL语句后返回的结果集,由ResultSet接口接收,常用处理方式:遍历/判断是否有结果(登录)

String sql =
”
select * from emp";
ResultSet rs = stmt . executeQuery(sq1);
while (rs.next()) {
System . out . println(rs . getInt( " empno")+","
+rs. getstring(" ename"));
}
  •  查询的结果存放在ResultSet对象的一系列行中
  • ResultSet对象的最初位置在行首
  • ResultSet.next(方法用来在行间移动
  • ResultSet.getXXX0方法用来取得字段的内容
public static void main(String[] args) throws Exception {
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true", 
				"root", "root");
		Statement s = conn.createStatement();
		String sql = "select ename,sal from emp";
		//执行查询 得到结果及  集对象
		ResultSet rs = s.executeQuery(sql);
		//遍历结果 集对象 中的查询结果
		long t = System.currentTimeMillis();
		while(rs.next()) {
			//参数为字段名
//			String name = rs.getString("ename");
			//通过参数为字段位置
			String name= rs.getString(1);
//			double sal = rs.getDouble("sal");
			double sal = rs.getDouble(2);
			System.out.println(name+": "+sal);
		}
		long te = System.currentTimeMillis()-t;
		System.out.println(te);
		System.out.println("执行完成!");
    }
}

数据库厂商实现

Oracle实现

下载对应数据库的驱动
一ojdbc6jar / ojdbc14.jar
将驱动类导入到项目中
Maven
加载驱动类
- Class.forName(" orace.jdbc.OracleDriver")

MySQL实现

下载对应数据库的驱动
- mysql-connectorjava-5.0.4-bin.jar
将驱动类导入到项目中
一Maven
加载驱动类
- Class.forName(" com.mysql.jdbc.Driver");

 

JDBC基础

连接管理

通过连接工具类获取连接

  • 在工程中,编写一个访问数据库的工具类,此后所有访问数据库的操作,都从工具类中获取连接
  • 两种方式:
  1. 直接把数据配置写在工具类中
  2. -把数据库配置写在一个properties属性文件里 ,工具类读取属性文件,逐行获取数据库参数(推荐)

读取配置文件

  • 将需要改动的数据从代码中移到*.properties配置文件中,好处: 以后便于修改

  • 如何读取配置文件里面的数据:

db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
db.username=root
db.password=root
db.maxActive=10
db.initialSize=2


通过属性文件维护连接属性


从类路径中加载属性文件

/ /属性文件所在的位置
String path =” com/ tarena/ dms/ daodemo/ v2/ db. properties" ;
//获得当前类的路径,加载指定属性文件
properties . load(DBUtility . class. getClassLoader( )
. getResourceAsstream(path));


连接的关闭

//在工具类中定义公共的关闭连接的方法
//所有访问数据库的应用,共享此方法
public static void closeConnection(Connection con) {
    if(con != nu11) {
        try {
        con.close();
        } catch (SQLException e) {
            e. printstackTrace();
        }
    }
}

 自动关闭

package cn.tedu;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo02 {
	public static void main(String[] args) {
		
		//获取连接
		try (Connection conn = DBUtils.getConn();){
			Statement s = conn.createStatement();
			String sql = "select ename from emp";
			ResultSet rs = s.executeQuery(sql);
			while(rs.next()) {
				String name = rs.getString(1);
				System.out.println(name);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

数据库连接池DBCP

  • DataBaseConnectivityPool(数据库连接池)

  • 为什么使用数据库连接池: 如果没有数据库连接池,每一个业务都至少对应一个数据库连接,如果有1万个业务至少有1万次连接的建立和断开,频繁的开关连接非常浪费资源,使用数据库连接池,可以将连接重用,使用完之后的连接并不是断开而是放回连接池中给其它业务使用,这样就避免了频繁开关链接,从而提高了执行效率

连接池技术

为什么要使用连接池

  • 数据库连接的建立及关闭资源消耗巨大
  • 传统数据库访问方式:一次数据库访问对应一个物理连接每次操作数据库都要打开、关闭该物理连接,系统性能严重受损
  • 解决方案:数据库连接池( Connection Pool )
  • 系统初始运行时,主动建立足够的连接,组成一个池。每次应用程序请求数据库连接时, 无需重新打开连接,而是从池中取出已有的连接,使用完后,不再关闭,而是归还

连接池中连接的释放与使用原则

  • 应用启动时,创建初始化数目的连接
  • 当申请时无连接可用或者达到指定的最小连接数,按增量参数值

创建新的连接
为确保连接池中最小的连接数的策略:

  1. 动态检查:定时检查连接池,一旦发现数量小于最小连接数,则补充相应的新连接,保证连接池正常运转
  2. 静态检查:空闲连接不足时,系统才检测是否达到最小连接数按需分配,用过归还,空闲超时释放,获取超时报错
  • 连接池也只是接口,具体实现由厂商来完成

Druid数据库连接池

利用Maven导入

<!-- 数据库连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

 通过DataSource获取连接

package cn.tedu;

import java.sql.Connection;
import java.sql.SQLException;

import com.alibaba.druid.pool.DruidDataSource;

public class Demo03 {
	public static void main(String[] args) throws SQLException {
		
		//创建数据库连接池对象 
		//如果使用的是DBCPjar包 创建 BasicDataSource 
		DruidDataSource ds = new DruidDataSource();
		//设置数据库连接信息
		ds.setDriverClassName("");
		ds.setUrl("");
		ds.setUsername("");
		ds.setPassword("");
		//设置初始连接数量
		ds.setInitialSize(3);
		//设置最大连接数量
		ds.setMaxActive(5);
		//从连接池中获取连接对象  异常抛出
		Connection conn = ds.getConnection();
		
		
	}
}


Apache DBCP数据库连接池

  • DBCP(DataBase connection pool) :数据库连接池
  • Apache的一个Java连接池开源项目,同时也是Tomcat使用的连接池组件
  • 连接池是创建和管理连接的缓冲池技术,将连接准备好被任何需要它们的应用使用

需要两个jar包文件

  • - commons-dbcp-1.4.jar 连接池的实现
  • - commons-pool-1 5.jar连接池实现的依赖库

利用Maven导入

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>

通过DataSource获取连接

private static BasicDataSource dataSource
= new BasicDataSource );
dataSource . setDr iverClassName ( dri veClas sName) ;
dataSource . setUrl(ur1);
dataSource . setusername( username) ; 
dataSource . setPassword(password);
Connection conn = dataSource . getConnection();


连接池参数

  • 常用参数有:
  1. 初始连接数
  2. 最大连接数
  3. 最小连接数
  4. 每次增加的连接数
  5. 超时时间
  6. 最大空闲连接
  7. 最小空闲连接
  • 根据应用需要,设置合适的值
package cn.tedu;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

import com.alibaba.druid.pool.DruidDataSource;

public class DBUtils {
	private static DruidDataSource ds;
	static {
		//创建读取*.properties配置文件的对象
		Properties p = new Properties();
		//获取文件的输入流 通过反射的方式得到文件的输入流
		//这种写法会自动去src/main/resources目录下查找文件
		InputStream ips = 
				DBUtils.class.getClassLoader()
				.getResourceAsStream("jdbc.properties");
		//让文件和读取配置文件的对象关联
		try {
			p.load(ips); 
		} catch (IOException e) {
			e.printStackTrace();
		} 
		//读取配置文件中的数据
		String url = p.getProperty("db.url");
		String username = p.getProperty("db.username");
		String password = p.getProperty("db.password");
		String driver = p.getProperty("db.driver");
		//创建数据库连接池对象 
		//如果使用的是DBCPjar包 创建 BasicDataSource 
		ds = new DruidDataSource();
		//设置数据库连接信息
		ds.setDriverClassName(driver);
		ds.setUrl(url);
		ds.setUsername(username);
		ds.setPassword(password);
		//设置初始连接数量
		ds.setInitialSize(3);
		//设置最大连接数量
		ds.setMaxActive(5);
	}

	public static Connection getConn() throws SQLException {
		//从连接池中获取连接对象  异常抛出
		Connection conn = ds.getConnection();
		return conn;   
	}
}


异常处理

SQLException简介

  • java.sql.SQL Exception是在处理JDBC时常见的exception对象

  • 用来表示JDBC操作过程中发生的具体错误

  • 一般的SQLException都是因为操作数据库时出错,比如Sq|语句写错,或者数据库中的表或数据出错
  • 常见异常:
  1. 登录被拒绝,可能原因:程序里取键值对信息时的大小写和属性文件中不匹配
  2. 列名无效,可能原因:查找的表和查找的列不匹配
  3. 无效字符,可能原因: sq|语句语法有错,比如语句结尾时不能有分号
  4. 无法转换为内部表示,可能原因:结果集取数据时注意数据类型
  • 表或者视图不存在检查SQL中的表名是否正确
  • 不能将空值插入,检查执行insert操作时,是否表有NOT NULL约束,而没有给出数据
  • 缺少表达式检查SQL语句的语法
  • SQL命令未正确结束,检查SQL语句的语法
  • 无效数值企图将字符串类型的值填入数字型而造成,检查SQL语句
  • 文件找不到,可能原因: db.properties文件路径不正确

处理SQL Exception

SQL Exception属于Checked Exception,必须使用ry..catch或throws明确处理
 

public static synchronized Connection getConnection() throws
    SQLException {
        / /语句
    }
    try {
        //语句
    } catch (SQLException e) {
        e . printstackTrace();//追踪处理
    //throw new Runt imeException(e);//或者抛出
    }

JDBC核心API:

Statement

用于执行静态 SQL 语句并返回它所生成结果的对象。

Statement执行查询

创建Statement的方式:
Connection.createStatement( );
执行INSERT, UPDATE和DELETE :
Statement.executeUpdate( )
执行SELECT :
Statement.executeQuery( )

 

package cn.tedu;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo04 {
	public static void main(String[] args) throws SQLException {
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true","root", "root"); 
		Statement s = conn.createStatement();
		String sql = "select ename,sal from emp";
		//执行查询  得到结果集对象
		ResultSet rs = s.executeQuery(sql);
		//遍历结果集对象中的查询结果
		while(rs.next()) {
			//参数为字段名
			//String name = rs.getString("ename");
			//参数为字段位置
			String name = rs.getString(1);
			double sal = rs.getDouble(2);
			System.out.println(name+":"+sal);
		}
		conn.close();
	}
}


 

Statement执行新增

package cn.tedu;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo02 {

	public static void main(String[] args) throws SQLException {

		//2. 获取数据库连接 导包:java.sql
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true",
				"root", "root"); 
		Statement s = conn.createStatement();
		String sql = "insert into jdbct1 values(1,'刘德华')";
		//执行插入数据的SQL
		s.executeUpdate(sql);
		conn.close();
		System.out.println("执行完成!");
		
		 


	}

}


Statement执行修改
 

package cn.tedu;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo03 {

	public static void main(String[] args) throws SQLException {
		//2. 获取数据库连接 导包:java.sql
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true","root", "root"); 
		Statement s = conn.createStatement();
		String sql = "update jdbct1 set name='张学友' where id=1";
		//执行修改/删除数据的SQL
		s.executeUpdate(sql);
		conn.close();
		System.out.println("执行完成!");
	}

}

Statement执行删除

package cn.tedu;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo03 {

	public static void main(String[] args) throws SQLException {
		//2. 获取数据库连接 导包:java.sql
		Connection conn = DriverManager.getConnection(
				"jdbc:mysql://localhost:3306/newdb3?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true","root", "root"); 
		Statement s = conn.createStatement();
		String sql = "delete from jdbct1 where id=1";
		//执行修改/删除数据的SQL
		s.executeUpdate(sql);
		conn.close();
		System.out.println("执行完成!");
	}

}

PreparedStatement

表示预编译的 SQL 语句的对象。

PreparedStatement原理

  • Statement主要用于执行静态SQL语句,即内容固定不变的SQL语句
  • Statement每执行一次都要对传 入的SQL语句编译次,效率较差
  • 某些情况下, SQL语句只是其中的参数有所不同,其余子句完全相同,适用于PreparedStatement
  • 预防sq|注入攻击
     

预编译的SQL执行对象PreparedStatement

  • SQL注入问题: SQL注入指在传递值得地方传递进去了SQL语句,使原有的SQL业务逻辑发生改变.
  • 使用PreparedStatement可以避免SQL注入,因为在创建对象时就已经将业务逻辑锁死,用户输入的内容不会影响原有逻辑
  • 使用PreparedStatement可以避免进行SQL语句的拼接.
  • 注入内容: ' or '1'='1
  • PreparedStatement实例包含已事先编译的SQL语句
  • SQL语句可有一个或多个IN参数
  1.  IN参数的值在SQL语句创建时未被指定。该语句为每个IN参数保留一个问号(“?")作为占位符
  2. 每个问号的值必须在该语句执行之前,通过适当的setInt或者setString方法提供。
     
  • 由于PreparedStatement对象已预编译过,所以其执行速度要快于Statement对象。因此,多次执行的 SQL语句经常创建PreparedStatement对象,以提高效率。
  • 批量处理
  1. executeUpdate或executeQuery方法每次执行都需要传递一次网络数据, 批量操作可以将多次网络数据传输合并成一次,从而提高执行效率
  2. 代码参考Demo06.java
package cn.tedu;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo06 {
	public static void main(String[] args) {
		
		//获取数据库连接
		try (Connection conn = DBUtils.getConn();) {
//			String sql1 = "insert into user values(null,'刘备','123456')";
//			String sql2 = "insert into user values(null,'关羽','123123')";
//			String sql3 = "insert into user values(null,'张飞','456123')";
//			Statement s = conn.createStatement();
//			//非批量操作
			s.executeUpdate(sql1);
			s.executeUpdate(sql2);
			s.executeUpdate(sql3);
//			//批量操作
//			s.addBatch(sql1);
//			s.addBatch(sql2);
//			s.addBatch(sql3);
//			//执行批量操作
//			s.executeBatch();
//			System.out.println("执行完成!");
			//PreparedStatement的批量操作
			String sql = "insert into user values(null,?,?)";
			PreparedStatement ps = conn.prepareStatement(sql);
			for (int i = 1; i <= 100; i++) {
				ps.setString(1, "name"+i);
				ps.setString(2, "pwd"+i);
				//添加到批量操作
				ps.addBatch();
				//每隔20次执行一次 避免内存溢出 
				if(i%20==0) {
					//执行批量操作
					ps.executeBatch();     
				}
			}
			//执行批量操作  总次数是20的倍数时 这行代码可删除
			ps.executeBatch();
			System.out.println("执行完成!");
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

如果SQL语句中有变量则使用PreparedStatement,没有变量则用Statement

提升性能

  • 数据库具备缓存功能,可以对statement的执行计划进行缓存,以免重复分析
  • 缓存原理:
  1. 使用statement本身作为key并将执行计划存入与statement对应的缓存中
  2. 对曾经执行过的statements , 再运行时执行计划将重用

举例:

  • SELECT a, b FROM t WHEREc= 1 ;
  • 再次发送相同的statement时, 数据库会对先前使用过的执行计划进行重用,降低开销

悲剧:

  • SELECT a,b FROM t WHEREc= 1 ;
  • SELECT a, b FROM t WHEREc= 2 ;
  • 被视作不同的SQL语句,执行计划不可重用
  • 提升性能  SELECT a, b FROM t WHEREc = ?";
  • 被视作同一个的SQL语句执行计划可重用
     


SQL Injection简介

//场景:
String sql = "select * from t where username= '" + name + "'and password='""+ passwd + "'";
//输入参数后,数据库接受到的完整sq|语句将是:
select * from t where username = 'scott' and password ='tiger';


防止SQL Injection (注入)

  • 对JDBC而言, SQL注入攻击只对Statement有效,对PreparedStatement无效,因为PreparedStatement不允许在插入参数时改变SQL语句的逻辑结构
  • 使用预编译的语句对象,用户传入的任何数据不会和原SQL语句发生匹配关系,无需对输入的数据做过滤
  • 如果用户将"or1 = 1"传入赋值给占位符, SQL语句将无法执行
  • select * from t where username = ? and password = ?;
     

小练习:注册登录

注册

package cn.tedu;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;


public class Demo04 {
	public static void main(String[] args) {
		//从控制台获取输入的用户名和密码
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入用户名");
		String username = sc.nextLine();
		System.out.println("请输入密码");
		String password = sc.nextLine();
		sc.close();
		//获取连接
		try (Connection conn = DBUtils.getConn();){
			String sql = 
				"insert into user values(null,'"
						+username+"','"+password+"')";
			Statement s = conn.createStatement();
			s.executeUpdate(sql);
			System.out.println("注册成功!");
		} catch (SQLException e) {
			e.printStackTrace();
		} 
		
	}
}



登录:

package cn.tedu;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class Demo05 {
	public static void main(String[] args) {
		//从控制台获取输入的用户名和密码
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入用户名");
		String username = sc.nextLine();
		System.out.println("请输入密码");
		String password = sc.nextLine();
		sc.close();
		//获取数据库连接
		try (Connection conn = DBUtils.getConn();) {
			//创建登录的SQL语句 把用户输入的内容拼接进去
//			String sql = 
//				"select count(*) from user where username='"
//						+username+"' and password='"+password+"'";
//			System.out.println(sql);
//			Statement s = conn.createStatement();
//			ResultSet rs = s.executeQuery(sql);
			//解决SQL注入写法
			String sql = 
				"select count(*) from user where username=? and password=?";
			//创建预编译的SQL执行对象
			//预编译SQL执行对象好处:在创建对象时就将SQL语句业务逻辑锁死
			//不会被用户输入的内容导致改变
			PreparedStatement ps = conn.prepareStatement(sql);
			//替换SQL语句中的? 参数1:第几个问号 参数2:替换的内容
			ps.setString(1, username);
			ps.setString(2, password);
			//执行时切记不能传递SQL语句
			ResultSet rs = ps.executeQuery();
			//因为查询的结果只有一个数 直接调用next方法
			rs.next();
			//1代表结果集中第一个字段值 
			int count = rs.getInt(1);
			if(count>0) {//登录成功
				System.out.println("登录成功!");
			}else {//登录失败
				System.out.println("登录失败!");
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
	}
}

ResultSet (结果集)

ResultSet,数据库结果集的数据表,通常通过执行查询数据库的语句生成。

结构化查询语言

ResultSet 对象具有指向其当前数据行的指针。最初,指针被置于第一行之前。next 方法将指针移动到下一行;因为该方法在 ResultSet 对象中没有下一行时返回 false,所以可以在 while 循环中使用它来迭代结果集。

默认的 ResultSet 对象不可更新,仅有一个向前移动的指针。因此,只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。可以生成可滚动和/或可更新的 ResultSet 对象。以下代码片段(其中 con 为有效的 Connection 对象)演示了如何生成可滚动且不受其他更新影响的、可更新的结果集。请参阅 ResultSet 字段以了解其他选项。

当生成 ResultSet 对象的 Statement 对象关闭、重新执行或用来从多个结果的序列检索下一个结果时,ResultSet 对象会自动关闭。

结果集遍历

//常用的遍历方式:
string sql = "select empno, ename, sal, hiredate from emp" ;
rs = stmt. executeQuery(sq1);
while ( rs.next() ) {
    int empno = rs. getInt( "empno") ;
    string ename = rs. getstring(" ename") ;
    double sal = rs. getDouble("sal");
    Date hiredate = rs . getDate( "hiredate" );
}
rs.close( );


ResultSetMeta Data(结果集元数据)

  • ResultSetMetaData:数据结果集的元数据
  • 和查询出来的结果集相关,从结果集(ResultSet)中获取
     
package cn.tedu;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

public class Demo07 {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入查询的页数");
		int page = sc.nextInt();
		System.out.println("请输入查询的条数");
		int count = sc.nextInt();
		sc.close();
		//获取数据库连接
		try (Connection conn = DBUtils.getConn();) {
			String sql = "select * from user limit ?,?";
			PreparedStatement ps = conn.prepareStatement(sql);
			//替换? 跳过的条数=(请求页数-1)*每页条数
			ps.setInt(1, (page-1)*count);
			ps.setInt(2, count);
			ResultSet rs = ps.executeQuery();
			//遍历结果集中的多条数据
			while(rs.next()) {
				//表字段 id,username,password
				String name = rs.getString(2);
				System.out.println(name);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		
	}
}


JDBC高级编程

事务处理

事务简介

  • 事务( Transaction ) : 数据库中保证交易可靠的机制
  • JDBC支持数据库中的事务概念
  • 在JDBC中, 事务默认是自动提交的

事务特性ACID :

  • 原子性( Atomicity ) : 事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行
  • 一致性( Consistency ) : 事务在完成时,必须使所有的数据都保持一致状态
  • -隔离性(Isolation ) :由并发事务所作的修改必须与任何其它并发事务所作的修改隔离
  • -持久性( Durability ) :事务完成之后,它对于系统的影响是永久性的
  • 事务是数据库的概念, JDBC支持事务,本质还是在数据库中实现的


JDBC事务API

相关API :

  • Connection.getAutoCommit0):获得当前事务的提交方式,默认为true
  • Connection.setAutoCommit):设置事务的提交属性,参数是 true :自动提交; false :不自动提交
  • Connection.commit():提交事务
  • Connection.rollback0:回滚事务
     

JDBC标准事务编程模式
 

try{ 
    autoCommit = con. getAutoCommit(); // 1. 获得自动提交状态
    con. setAutoCommit(false); // 2.关闭自动提交
    stmt . executeUpdate(sq11); // 3.执行SQL语句
    stmt . executeUpdate(sq12);
    con. commit(); // 4.提交
    con . setAutoCommit( autoCommit); // 5.将自动提交功能恢复到原来的状态
}catch(SQLException e){
    conn. rollback();//异常时回滚
}


批量更新

批量更新的优势

  • 批处理:发送到数据库作为一个单元执行的一组更新语句
  • 批处理降低了应用程序和数据库之间的网络调用
  • 相比单个SQL语句的处理,批处理更为有效

批量更新API

  • addBatch(String sql)--Statement类的方法,可以将多条sq|语句添加Statement对象的SQL语句列表中
  • addBatch( )-- PreparedStatement类的方法, 可以将多条预编译的sq|语句添加到PreparedStatement对象的SQL语句列表中
  • executeBatch( )--把Statement对象或PreparedStatement对象语 句列表中的所有SQL语句发送给数据库进行处理
  • clearBatch( )清空当前SQL语句列表

防止OutOfMemory

  • 如果Preparedstatement对象中的SQL列表包含过多的待处理SQL语句,可能会产生OutOfMemory错误
  • 及时处理SQL语句列表
for(inti=0;i<1000;i++){
    sql =“insert into emp( empno, ename)
    values(emp_ seq. nextval,' name"+i+"'")";
    stmt . addBatch(sq1);将SQL语句加入到Batch中
        if(i%500==0){
            stmt . executeBatch(); //及时处理
            stmt . clearBatch(); //清空列表
         }
}
//最后一次列表不足500条,处理
s tmt . executeBatch( );

批量插入练习

package cn.tedu;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo06 {
	public static void main(String[] args) {
		//获取数据库连接
		try (Connection conn = DBUtils.getConn();) {
//			String sql1 = "insert into user values(null,'刘备','123456')";
//			String sql2 = "insert into user values(null,'关羽','123456')";
//			String sql3 = "insert into user values(null,'张飞','123456')";
//			//如果SQL 语句中没有变量 用Statement 
//			Statement s = conn.createStatement();
//			//非批量操作
			s.executeQuery(sql1);
			s.executeQuery(sql2);
			s.executeQuery(sql3);
//			/*
//			 * executeUpdate 执行更新
//			 * executeQuery  执行查询
//			 * addBatch  添加 批处理
//			 */
//			
//			//批量操作 
//			s.addBatch(sql1);
//			s.addBatch(sql2);
//			s.addBatch(sql3);
//			//执行批量操作
//			s.executeBatch();
//			System.out.println("执行完成!");
			//PreparedStatement 的批量操作
			String sql ="insert into user values(null,?,?)";
			PreparedStatement ps= conn.prepareStatement(sql);
			for (int i = 1; i <= 100; i++) {
				ps.setString(1, "name"+i);
				ps.setString(2, "pew"+i);
				
				//添加到批量操作
				ps.addBatch();
				//没隔20次执行一次  避免内存溢出
				if(i%20==0) {
					ps.executeBatch();
				}
			}
			//执行批量操作    总次数是20的倍数这行代码可以删除 
			ps.executeBatch();
			
			System.out.println("执行完成!");
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		
		
		
	}
}


返回自动主键

获取自增主键值:

关联数据插入


通过自增类型产生主键

CREATE TABLE animals (
    id INT NOT NULL AUTO_ INCREMENT ,
    name CHAR(30) NOT NULL ,
    PRIMARY KEY (id)
);
INSERT INTO animals (name )
    VALUES('dog' ),('cat' ),(' penguin'),('1ax'),( 'whale' ),(' ostrich');
SELECT * FROM animals;


JDBC返回自动主键API
 

package cn.tedu;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Demo08 {
	public static void main(String[] args) {
		
		//获取数据库连接
		try (Connection conn = DBUtils.getConn();) {
			String sql = "insert into user values(null,'刘德华','50')";
			Statement s = conn.createStatement();
			//传递参数 设置获取自增主键值
			s.execute(sql,Statement.RETURN_GENERATED_KEYS);
			System.out.println("执行完成!"); 
			//获取自增主键值
			ResultSet rs = s.getGeneratedKeys();
			//移动游标 
			rs.next();
			//因为获取的结果集中只有一个数据所以写1
			int id = rs.getInt(1);
			System.out.println(id);
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

练习:

package cn.tedu;

import java.lang.Thread.State;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class Demo10 {
	public static void main(String[] args) {
		
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入球队名称");
		String teamName = sc.nextLine();
		System.out.println("请输入球员名称");
		String playerName = sc.nextLine();
		sc.close();
	 
		//获取数据库连接
		try (Connection conn = DBUtils.getConn();) {
			//先查询 没有插入得到自增teamId 有的话获取出teamId
			String sql = "select id from team where name=?";
			PreparedStatement ps = conn.prepareStatement(sql);
			ps.setString(1, teamName);
			ResultSet rs = ps.executeQuery();
			int teamId;
			//判断是否查询到
			if(rs.next()) {//查询到了 之前保存过
				teamId = rs.getInt(1);
				System.out.println("之前保存过球队");
			}else {//之前没有存过
				String sql1 = "insert into team values(null,?)";
				PreparedStatement ps1 = conn.prepareStatement(sql1
						,Statement.RETURN_GENERATED_KEYS);
				ps1.setString(1, teamName);
				ps1.executeUpdate();
				//获取自增主键值
				ResultSet rs1 = ps1.getGeneratedKeys();
				rs1.next();
			    teamId = rs1.getInt(1);
			    System.out.println("球队保存完成!");
			}
			//保存球员
			String sql2 = "insert into player values(null,?,?)";
			PreparedStatement ps2 = conn.prepareStatement(sql2);
			ps2.setString(1, playerName);
			ps2.setInt(2, teamId);
			ps2.executeUpdate();
			System.out.println("保存球员完成");
			
			
			
			
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		
		
	}
}

DAO

什么是DAO

DAO封装对数据的访问

  • DAO (Data Access Object)数据访问对象
  • 建立在数据库和业务层之间,封装所有对数据库的访问
  • 目的:数据访问逻辑和业务逻辑分开
  • 为了建立一个健壮的Java应用,需将所有对数据源的访问操作抽象封装在一个公共API中,包括:
  1. 建立一个接口,接口中定义了应用程序中将会用到的所有事务方法
  2. 建立接口的实现类,实现接口对应的所有方法,和数据库直接交互
  • 在应用程序中,当需要和数据源交互时则使用DAO接口,不涉及任何数据库的具体操作

 

  • DAO通常包括:
  1. 一个DAOIN类;
  2. 一个DAO接口;
  3. 一个实现DAO接口的具体类;
  4. 数据传递对象(实体对象或值对象);

实体对象

  • DAO层需要定义对数据库中表的访问
  • 对象关系映射(ORM : Object/Relation Mapping)描述对象和数据表之间的映射,将Java程序中的对象对应到关系数据库的表中
  1. 表和类对应
  2. 表中的字段和类的属性对应
  3. 记录和对象对应

编写DAO 

查询方法

public Account findById(Integer id) throws SQLException {
    Connection conn = getConnection();
    String sql = SELECT BY_ID; //预先定 义好的SQL查询语句
    Preparedstatement ps = conn. preparestatement(sq1);
    ps.setInt(1, id);//传入参数
    ResultSet rs = ps . executeQuery();
    Account account = null ;
    while(rs .next()){//处理结果集
        account=new Account( );
        account . setId(rs. getInt( "ACCOUNT_ ID"));
        //设置account对象所有的属性,略
    }
    return ac count ;
}


更新方法

public boolean update( Account account) throws SQLException {
    Connection conn = getConnection();
    String sql = UPDATE_STATUS; //预先定义好的SQL语句
    Preparedstatement ps = conn.prepareStatement(sq1);
    ps.setInt(1, account. getId());//传入参数
    ps.setstring(2, account.getstatus() );
    int flag= ps.executeUpdate();
    return (flag>0 ? true : false);
}


异常处理机制


多层系统的异常处理原则:

  1. 谁抛出的异常,谁捕捉处理,因为只有异常抛出者,知道怎样捕捉处理异常
  2. 尽量在当前层中捕捉处理抛出的异常,尽量不要抛出到上层接口
  3. 尽量在每层中封装每层的异常类,这样可准确定位异常抛出的层
  4. 如异常无法捕捉处理,则向上层接口]抛出,直至抛给JVM
  5. 应尽量避免将异常抛给JVM
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值